Showing posts with label hibernate. Show all posts
Showing posts with label hibernate. Show all posts

Wednesday, August 21, 2024

Spring Data JPA limitation with LIMIT fetch . . .

In modern Java Spring based applications Spring Data JPA is quite common way to interface with database. Domain/Business objects carry persistable state of the business process. With few JPA annotations, POJOs can be enhanced to persistable domain objects. Unlike Grails framework that underpins HIBERNATE, leverages GORM and elevates and enriches domain objects to the higher level by making them persistence aware, Spring Data JPA keeps the persistence in another abstraction layer called Repository.

With Spring Data JPA, Repository is the central interface and it requires one be familiar with Repository abstractions. Queries can be defined as interface methods and implementation is provided by Spring Data JPA framework by 1) deriving from method naming conventions 2) using manually defined queries with @Query annotation by writing JPQL or native SQL queries. My first choice is interface method naming by following the conventions. Next is JPQL. I avoid native queries unless there is a strong reason for.

JPQL Limitation with LIMIT fetch

One of the limitations I ran into recently with JPQL was limiting query results to limit fetching to limited number of records, say one record from the query results. Typically in native SQL this is done by adding LIMIT clause by specifying LIMIT 1 to limit to the first result to fetch. JPQL lets you specify LIMIT which also works, but under the covers the LIMIT is applied in memory to the results fetched. In other words the LIMIT clause doesn't exist in the generated native SQL. So, the SQL fetches all the results that match the criteria and a collection of entity objects get created and then the LIMIT is applied to get one object. With this the JPQL query does it's job as specified but will incur into expensive query by fetching more than needed records and creating the objects in collection and then returning one object by considering the LIMIT 1.

So, an example Repository method annotated like the following would return one object, but fetches all records that match the criteria into memory and return the first one from the collection.
@Query(""" SELECT msg FROM Message msg WHERE msg.type = :type ORDER BY msg.createdOn DESC LIMIT 1 """) Optional<Message> findLatestByType(MessageType type);

In order to truly fetch the most recent message of a given type the JPQL needs to be optimized to fetch only one record.

With JPQL the query may need to be rewritten something like the following without using LIMIT, assuming id is primary key, and is a sequence. It is more performant with no additional index created than using createdOn auditable column if there is one.
@Query(""" SELECT msg FROM Message msg WHERE msg.id = ( SELECT MAX(m.id) FROM Message m WHERE m.type = :type ) """) Optional<Message> findLatestByType(MessageType type);

The last resort is by writing a native query and using LIMIT 1 to fetch one.

References


Saturday, March 16, 2024

Spring Data JPA - Join Fetch, Entity Graphs - to fetch nested levels of OneToMany related data, and JPQL . . .

One-to-many relationship in Databases is quite common. It is also quite cumbersome in terms of how many aspects that need to be considered for getting it correctly implemented. Just to list few aspects - related JPA annotations, relationship keys specified in annotations, fetch modes, fetch types, joins, query types, performance, N+1 data problem, cartesian product, DISTINCT to eliminate duplicates, indexes etc. With Spring Data, JPA and Hibernate as the default implementation provider there are few JPA annotations, Hibernate specific annotations, JPQL queries, Java collection types, all these will get added to the mix.

Environment: Java 21, Spring Boot 3.2.3, PostgreSQL 15.3, maven 3.9.6 on macOS Catalina 10.15.7

If you have JPA entities related through OneToMany relationships two multiple levels, then some special care is required to fetch data in a performant manner by avoiding the classic N+1 query issue, or even multiple queries. Each query is a roundtrip to Database and it adds up its own baggage.

Let's take a simple example of Country has many States, State has many Cities. We want to represent this relationship in JPA Entities and query using Spring Data JPA Repositories.

The entities with relationships look something like:

public abstract class BaseEntity implements Serializable { private static final long serialVersionUID = 1L; @Id @GeneratedValue(strategy = GenerationType.IDENTITY) @Column(nullable = false, updatable = false) @ToString.Exclude protected Long id; /** For optimistic locking */ @Version protected Long version; @CreationTimestamp @Column(nullable = false, updatable = false, columnDefinition = "TIMESTAMP WITH TIME ZONE") protected OffsetDateTime dateCreated = OffsetDateTime.now(); @UpdateTimestamp @Column(nullable = false, columnDefinition = "TIMESTAMP WITH TIME ZONE") protected OffsetDateTime lastUpdated = OffsetDateTime.now(); } @Entity public class Country extends BaseEntity { private String name; @OneToMany(mappedBy = "country", cascade = CascadeType.ALL, orphanRemoval = true) @Builder.Default @ToString.Exclude private Set<State> states = new LinkedHashSet<>(); } @Entity public class State extends BaseEntity { private String name; @ManyToOne(fetch = FetchType.LAZY) @JoinColumn(name = "country_id") @ToString.Exclude private Country country; // owning side of the relationship @OneToMany(mappedBy = "state", cascade = CascadeType.ALL, orphanRemoval = true) @Builder.Default @ToString.Exclude private Set<City> cities = new LinkedHashSet<>(); } @Entity public class City extends BaseEntity { private String name; @ManyToOne @JoinColumn(name = "state_id") @ToString.Exclude private State state; }

And a repository interface like:
@Repository public interface CountryRepository extends JpaRepository<Country, Long> { Optional<State> findByName(String name); }

One way to fetch all related data in a single query is by writing JPQL query with JOIN FETCH. This involves making sure to use all @OneToMany annotated properties to use Set and not List, and not using FetchType.EAGER and FetchMode.JOIN., and by writing a JPQL query with @Query annotation as shown below. Make a note of both DISTINCT and JOIN FETCH. This will result into one query which fetches for a Country all States, for each State all Cities data. If it is huge set of records, your best bet is to use @EntityGraph recommended. Lets say that our data is not huge and we want to use JPQL. In this case, the repository method annotatted with JPQL Query looks like:

@Repository public interface CountryRepository extends JpaRepository { @Query(""" SELECT DISTINCT country from Country country JOIN FETCH country.states state JOIN FETCH state.cities city WHERE country.name = :name """) Optional<Country> findByName(String name); }

JPQL

Java Persistencw Query Language (JPQL) is portable query language to query persistent entities irrespective of the mechanism used to store those entities. Typically in a Java application the entities are Java classes. Similar to SQL, it provides select, update, delete statements, join operations, aggregations, subqueries etc. Hibernate supports both JPQL and HQL.

Spring Data JPA offers different ways to define query methods in Repository interface like: 1) Derived queries for which the query is derived/generated from the name of the method by following conventions 2) Declared Queries by annotating query method with @Query annotation 3) Named Queries etc.

JPQL - Fetch Entities
In the above JPQL declared query in CountryRepository using @Query annotation, a typical JPQL to query entity object (Country) is shown. Typically, entity object is mapped to Database table and entity query results into SQL query generated by the underlying JPA implementation like Hibernate. The query result is Database records fetched from table(s) and the raw data is transformed into Entity objects.

JPQL - Fetch Custom objects
JPQL also supports custom objects through JPQL Constructor expressions. A constructor expression can be specified in JPQL to return a custom object instead of Entity object. Below is an example a code snippet in which a light-weight custom Java record is returned instead of Entity object.

@Repository public interface StateRepository extends JpaRepository<State, Long> { /** * JPQL - Query to fetch specific fields of Entity and return a non-entity custom objects * * @param population the population * @return list of light-weight StatePopulation objects */ @Query(""" SELECT new com.giri.countrystatecity.domain.StatePopulation(state.name, state.population) from State state WHERE state.population > :population """) List<StatePopulation> findAllStatesByPopulationGreaterThan(Long population); }
where StatePopulation is simply a record like: public record StatePopulation(String name, Long population) { }

JPQL - Fetch Raw specified column data 
Similar to the custom object, raw column data can also be fetched and then required object csn be constructed from the returned data. Following is code snippet for fetching raw data and constructing a data record object.

/** * JPQL - Query to fetch specific fields of Entity and return Raw data * * @param population the population * @return list of light-weight StatePopulation objects */ @Query(""" SELECT state.name, state.population from State state WHERE state.population > :population """) List<List<Object>> findAllStatesByPopulationGreaterThanJpqlRaw(Long population);
where raw data fetched can be converted into required objects in a service method like shown below:
public List<StatePopulation> getAllByPopulationGreaterThanJpqlRaw(Long population) { List<List<Object>> states = stateRepository.findAllStatesByPopulationGreaterThanJpqlRaw(population); return states.stream() .map(record -> new StatePopulation((String) record.get(0), (Long)record.get(1))) .toList(); }

Gotcha

  • If you use List instead of Set, you might bump into infamous HIBERNATE concept called bag and an exception like - MultipleBagFetchException: cannot simultaneously fetch multiple bags which will force you to read a whole lot of text in finding the information you need, digging through googled links and StackOverflow without much luck, and eventually breaking your head ;)
  • There are also other ways to tackle this N+1 query problem. Writing native query in the @Query annotation is another way. I wouldn't go that route as I don't want to get sucked by databases. I am sure if you take that route, you will have someone around you ready to argue in favor of Sub Selects, Stored Procedures etc. ;). My personal preference is to stay away from diving deep into database,  avoid abyss ;)

Sample Spring Boot Application - Country-State-City

Here is the link to a sample Spring Boot 3.2.3 GraphQL application which has both @Query JPQL way and @EntityGraph way of getting the single generated query that is performant in fetching all related data in one roundtrip.

References

Sunday, March 03, 2024

Enums - all the way to persistence (revisited and revised for today's tech stack) . . .

About two years ago I blogged on this combination: Enums - all the way to persistence. Technology is moving at faster pace than ever before. Java's release cadence is moving at rapid 6 months cycle, every March and September. Spring boot catches Java and other technologies and moves along at the same pace as Java, every 6 months in May and November. Of course, the PostgreSQL database, Hibernate and even Maven build system keep moving as well, at their own pace.

The challenge for Java developer is to keep up with all the moving technologies. As software development requires talented developers with years of experience and knowledge, debates go on Artificial Intelligence (AI). Some who have moved away from coding strongly feel that the generative AI which currently is capable of generating even code will replace software developers. I don't believe in that, at least at this time. The add-on 'at least at this time' is only a cautious extension to that non-generative human statement). I have tried CodeGPT lately at work, a couple of times when I was stuck with things not working together as described in documentations and blog posts, asking it's generative trained intelligence to see if it would be able to be my copilot in those development situations. It couldn't really stand up the hype in anyway, and I had to go and figure out myself all those situations.

Enums persistence - is one such problem again I did hit roadblocks lately after two years. The only change is newer versions of all of these technologies. It required additional exploration of few things before arriving at a solution that worked eventually.

Environment: Java 21, Spring Boot 3.2.3, PostgreSQL 15.3, maven 3.9.6 on macOS Catalina 10.15.7

Spring boot 3.2.3 data jpa brings in Hibernate 6.4.4 dependency.

The same persistent model described in my earlier blog post: Enums - all the way to persistence would need the following changes for enums to work.

The DDL script requires an extra PostgreSQL casting as shown below for enums:

-- create enum type genders CREATE TYPE genders AS ENUM( 'MALE', 'FEMALE' ); CREATE CAST (varchar AS genders) WITH INOUT AS IMPLICIT;

In maven pom.xml, the spring boot version is 3.2.3 and the hibernate-types-55 dependency is not needed.

Changes to domain object Person.java are shown below (the @TypeDef annotation is not required):

... import jakarta.persistence.EnumType; import jakarta.persistence.Enumerated; import org.hibernate.annotations.JdbcTypeCode; import org.hibernate.type.SqlTypes; ... @NotNull @Enumerated(EnumType.STRING) @JdbcTypeCode(SqlTypes.NAMED_ENUM) Gender gender; ... }

Changes look simple after figuring out and making things to work, but finding things that work required a bit of exploration ;)

References

Thursday, February 08, 2024

Spring Boot - log database query and binding parameters . . .

Environment: Java 21, Spring Boot 3.2.2, PostgreSQL 16, maven 3.9.6 on macOS Catalina 10.15.7

In a Spring Boot, Spring Data JPA application with Hibernate as the default JPA implementation, to log generated SQLs along with the values bound to parameters configure the following properties in relevant env related application.properties or application.yml file.
E.g. application-test.yml

logging: level: org.hibernate.SQL: debug org.hibernate.orm.jdbc.bind: trace
With the above Hibernate log levels, generated SQL query gets logged followed by binding parameter values.

To have the generated SQL query formatted, add the following JPA property:
spring: jpa: properties: hibernate: format_sql: true

Gotcha

Setting the property spring.jpa.show-sql to true is another way to see generated SQL, but it only gets outputted to the standard out, not to logs.