JPA (Jakarta Persistence API, formerly Java Persistence API) specifies how Java objects are mapped to stored data and managed as persistent entities. It defines the contract; a provider such as Hibernate performs the work. This separates application code from a particular ORM implementation without eliminating the need to understand SQL, relational modeling, or the SQL a provider generates. section 1.3 section 1.0
A small JPA application has four practical parts:
- Map a persistent class to stored data. JPA mapping metadata commonly uses annotations on domain classes; XML mappings are also possible. section 1.3
- Configure a persistence unit, create an EntityManagerFactory, and obtain an EntityManager to store or load objects. section 2.8
- Begin and commit a transaction around database work. A changed property of a managed object can be detected and written back without an explicit update call. section 2.3.3
- Query entities with JPQL. A class name in JPQL denotes the persistent class, not its database table, so changing the table mapping need not change that query. section 2.3.3
Store and change a message
Create an EntityManager from an EntityManagerFactory, begin a transaction, construct a Message, call persist(message), and commit. When a loaded message’s text changes inside a transaction, dirty checking causes Hibernate to update the stored record. Inspect the generated SQL as well as the Java result: the provider’s queries still have performance consequences. section 2.3.3 section 1.3
Listing 2.4 The HelloWorldJPATest class
Path: Ch02/helloworld/src/test/java/com/manning/javapersistence/ch02 /HelloWorldJPATest.java public class HelloWorldJPATest { @Test public void storeLoadMessage() { EntityManagerFactory emf = #1 Persistence.createEntityManagerFactory("ch02"); #1 try { EntityManager em = emf.createEntityManager(); #2 em.getTransaction().begin(); #3 Message message = new Message(); #4 message.setText("Hello World!"); #4 em.persist(message); #5 em.getTransaction().commit(); #6 //INSERT into MESSAGE (ID, TEXT) values (1, 'Hello World!') em.getTransaction().begin(); #7 List<Message> messages = #8 em.createQuery("select m from Message m", Message.class) #8 .getResultList(); #8 //SELECT * from MESSAGE #8 messages.get(messages.size() - 1). #9 setText("Hello World from JPA!"); #9 em.getTransaction().commit(); #10 //UPDATE MESSAGE set TEXT = 'Hello World from JPA!' where ID = 1 assertAll( #11 () -> assertEquals(1, messages.size()), #11 () -> assertEquals("Hello World from JPA!", #12 messages.get(0).getText()) #12 ); em.close(); #13 } finally { emf.close(); #14 } } } #1 First we need an <kbd>EntityManagerFactory</kbd> to talk to the database. This API represents the persistence unit, and most applications have one <kbd>EntityManager-Factory</kbd> for one configured persistence unit. Once it starts, the application should create the <kbd>EntityManagerFactory</kbd>; the factory is thread-safe, and all code in the application that accesses the database should share it. #2 Begin a new session with the database by creating an <kbd>EntityManager</kbd>. This is the context for all persistence operations. #3 Get access to the standard transaction API, and begin a transaction on this thread of execution. #4 Create a new instance of the mapped domain model class <kbd>Message</kbd>, and set its <kbd>text</kbd> property. #5 Enlist the transient instance with the persistence context; we make it persistent. Hibernate now knows that we wish to store that data, but it doesn't necessarily call the database immediately. #6 Commit the transaction. Hibernate automatically checks the persistence context and executes the necessary SQL <kbd>INSERT</kbd> statement. To help you understand how Hibernate works, we show the automatically generated and executed SQL statements in source code comments when they occur. Hibernate inserts a row in the <kbd>MESSAGE</kbd> table, with an automatically generated value for the <kbd>ID</kbd> primary key column, and the <kbd>TEXT</kbd> value. #7 Every interaction with the database should occur within transaction boundaries, even if we’re only reading data, so we start a new transaction. Any potential failure appearing from now on will not affect the previously committed transaction. #8 Execute a query to retrieve all instances of <kbd>Message</kbd> from the database. #9 We can change the value of a property. Hibernate detects this automatically because the loaded <kbd>Message</kbd> is still attached to the persistence context it was loaded in. #10 On commit, Hibernate checks the persistence context for dirty state, and it executes the SQL <kbd>UPDATE</kbd> automatically to synchronize in-memory objects with the database state. #11 Check the size of the list of messages retrieved from the database. #12 Check that the message we persisted is in the database. We use the JUnit 5 <kbd>assertAll</kbd> method, which always checks all the assertions that are passed to it, even if some of them fail. The two assertions that we verify are conceptually related. #13 We created an <kbd>EntityManager</kbd>, so we must close it. #14 We created an <kbd>EntityManagerFactory</kbd>, so we must close it.
The test listing shows the JPA bootstrap and transaction sequence used to persist a Message. section 2.3.3
JPA, native Hibernate, and Spring Data JPA offer different entry points. Native Hibernate has its own SessionFactory and Session APIs; Spring Data JPA sits above a JPA provider and can generate queries from repository method names. Compare them by portability, dependencies, code volume, and execution speed rather than assuming one is always best. With Hibernate OGM and changed configuration, JPA operations and JPQL can also be used against MongoDB; that example depends on the provider, not on JPA alone. section 2.8 section 1.3 section 18.2.3
