Hi,
I'm Thorben Janssen from thoughts-on-java.org with
6 things you should know when you use
Hibernate with a PostgreSQL database.
PostgreSQL is one of the most popular relational databases,
and Hibernate is probably the most popular JPA implementation.
So, it's no surprise that they're often used together
and that you don't have to expect any major issues doing that.
But as so often, there is a difference between
"it works" and "it works great".
Out of the box, Hibernate and PostgreSQL work well together.
But if you want to use both systems to their fullest extent,
you need to know a few things
and avoid some others.
Here is my list of the 6 most important things
you should know when you want to use Hibernate
with PostgreSQL.
First, Generate Primary Keys
Primary keys and the generation of unique values
seem to be basic features.
The JPA specification defines different strategies to generate primary key values
and you can use all of them with PostgreSQL.
But that doesn't mean that you should just pick anyone of them.
The TABLE strategy uses a database table to generate unique primary key values.
This requires pessimistic locking and isn't the most efficient approach.
The IDENTITY strategy forces Hibernate to execute the SQL INSERT statement immediately.
Due to this, Hibernate can't use any of its performance
optimization strategies that require a delayed execution of the statement.
One example of that is JDBC batching.
But it can also affect simple things,
like updating an attribute before the entity gets persisted.
When Hibernate has to execute the INSERT statement immediately,
it has to perform an additional UPDATE statement to
persist the changed value instead of using
that value in the INSERT statement.
The best generation strategy you can use with a PostgreSQL database
is the SEQUENCE strategy.
It uses a simple database sequence and is highly optimized by PostgreSQL.
And Hibernate uses an optimized algorithm by default
to avoid unnecessary statements.
Let's take a quick look at an example that uses a custom database sequence.
Here you can see the required annotations to use a custom database sequence.
I know, it looks like a lot but don't worry, it's not as complicated as it might look.
The @Id annotation tells Hibernate that this is a primary key attribute.
The @GeneratedValue annotation tells Hibernate to
generate a unique value using a database sequence
and to look at the book_generator for more details on how to do that.
The generator gets defined by this annotation.
It tells Hibernate to request a new value from the sequence "book_seqence".
OK, that's all you need to do.
Let's talk about the second thing you need to know
about using Hibernate with a PostgreSQL database.
2. Use Custom Data Types
PostgreSQL supports a set proprietary data types which Hibernate doesn't map by default.
Popular examples for that are the JSON and JSONB data types which allow you to persist
and query JSON documents in a PostgreSQL database.
If you want to use these types with Hibernate,
you need to define the mapping yourself.
That requires additional code, but it's not as complicated as it might sound.
You just need to implement and register a UserType
which tells Hibernate how to map the Java object to a supported JDBC type
and vice versa.
I explained the required implementation in great detail on my blog
You can find a link to it in the video description
You can use the same approach to implement a custom mapping
for all PostgreSQL types that are not supported by Hibernate.
3. Map Read-Only Views
From a mapping point of view,
database tables and views are pretty similar and you can map both of them to an entity class.
The only difference is that some views are read-only.
And it's a good practice to map them to a read-only entity.
By default, Hibernate supports read and write operations for all entities.
If you want to make an entity read-only,
you have to tell Hibernate that it is immutable.
You can do that by annotating the entity class with an @Immutable annotation.
Here you can see the BookView entity which maps the view bookview.
I annotated it with @Entity and @Immutable.
That makes it a read-only entity which you can use
in queries and to define relationships.
This test case, for example, uses the BookView entity in a simple JPQL query
and writes a log message for each entity.
As you can see in the log output,
Hibernate performed a simple SQL query
to select all records from the view bookview.
And the following lines show
a log message for each selected entity.
I annotated the BookView entity with @Immutable
and Hibernate will, therefore, ignore all update operations on it.
Here you can see a simple test case that tries to
change the title of the BookView entity with id 1.
Let's run this test and see how Hibernate handles it.
Here you can see the select statement that
Hibernate used to get the BookView entity with id 1.
This is the only database interaction Hibernate performed.
It ignored the changed title attribute and
didn't execute a SQL UPDATE statement for it.
4. Use PostgreSQL-Specific Query Features
When you're familiar with JPQL and SQL,
you already know that JPQL supports only a small subset of the SQL standard.
But that's not a real issue,
because if JPQL isn't powerful enough to implement your use case
you can use a native SQL query instead.
Native SQL queries allow you to use the full SQL feature set
including all database specific features.
You create them in a similar way as your JPQL queries.
You can define a named native query with a @NamedNativeQuery annotation
or create an ad-hoc native query by calling
the createNativeQuery method on the EntityManager.
Let's take a quick look at an example.
Here you can see a @NamedNativeQuery annotation.
As you can see, it looks very similar to a @NamedQuery annotation.
The main difference is that you need to provide a native SQL
instead of a JPQL statement as the query.
As I said earlier, that allows you to use all query
features that are supported by your database.
A @NamedNativeQuery can be used in the same way as a @NamedQuery.
You just need to call the createNamedQuery method of the EntityManager
with the name of your @NamedNativeQuery.
You then get a Query instance that you can
use to set bind parameter values,
to define pagination
and to execute the query.
That's all about native queries for now.
If you like to learn more about it,
please take a look at my Native Query video.
I will add a link to it to the video description.
5. Call PostgreSQL-Specific Functions
You can, of course, use a native SQL query
to call a PostgreSQL-specific SQL function
in the same way as you use any other proprietary query feature.
But since JPA 2.1, you can also call these functions in your JPQL queries.
The JPQL function, function
allows you to call any SQL function supported by your database.
You just need to provide the name of the function as the first parameter,
followed by an optional list of parameters
that will be used to call the SQL function.
Here you can see a simple example that calls the
SQL function calculate with the parameters 1 and 2.
This is a custom database function that
calculates the sum of the 2 parameters.
So, when I execute this test case,
Hibernate will perform this query
and PostgreSQL will call the function
to calculate the sum of the parameters.
It will then return the Book that has an id equal to the calculation result.
Here you can see the query.
And as expected,
it called the calculate function
and returned the Book with id 3.
6. Call Stored Procedures
PostgreSQL doesn't distinguish between functions and stored procedures.
It only supports very flexible functions which can be used like stored procedures
or as SQL functions.
I already showed you how to call a non-standard SQL function.
So, let's take a look at the stored procedures.
When your PostgreSQL function returns a
REF_CURSOR parameter,
which is a cursor on a result set,
you need to call it like a stored procedure.
Since JPA 2.1, you can do that with a @NamedStoredProcedureQuery
or an ad-hoc StoredProcedureQuery.
Here you can see the example of a @NamedStoredProcedureQuery.
It defines the call of the get_reviews stored procedure.
It tells Hibernate to provide an input parameter of type Long
and to expect an REF_CURSOR as a result.
The resultClass parameter tells Hibernate to map
all records of the REF_CURSOR
to Review objects.
You can call the @NamedStoredProcedureQuery
in a similar way as you would call a @NamedQuery.
You just need to call the createNamedStoredProcedureQuery
method to instantiate the query,
set the input parameters
and retrieve the result.
OK, that's it for today.
If you want to learn more about Hibernate,
you should join the free Thoughts on Java Library.
It gives you free access to a lot of member-only content
like a cheat sheet for this video
and an ebook about the Java 8 support in the Hibernate 5.
I'll add the link to it to the video description below.
And if you like today's video,
please give it a thumbs up and subscribe below.
Bye
No comments:
Post a Comment