To make the best use of JPA, we should be familiar with the concepts of Persistence Unit, Persistence Context and EntityManager.
What is a Persistence Unit
A Persistence Unit defines the set of all Java classes managed by JPA's EntityManager. All entities of a Persistence Unit
represent data within a database. So, if we wanted to map two databases in JPA, we would have to define two Persistence Units.
Persistence Units are configured within the persistence.xml file.
Persistence Units classes can be packaged as part of a JAR, WAR or EJB archive or as a JAR file that can then be included in a WAR or EAR. Specifically:
- If you package in an EJB JAR file, the persistence.xml file should be placed in the META-INF directory of the EJB JAR.
- If you package in a WAR file, the persistence.xml should be in the directory WEB-INF/classes/META-INF of the WAR file.
- If you pack in a JAR file that will be included in a WAR or EAR file, the JAR file should be there:
- in the WEB-INF/lib folder of the WAR
- in the EAR library folder
What is a Persistence Context and an EntityManager?
A Persistence Context is a Session that encapsulates a set of entities managed at a specific time by the EntityManager, which controls their lifecycle. In addition, the EntityManager has APIs that are used to create and remove entities, search for them by their primary key, and making queries on them. When a Persistence Context (Session) terminates, previously managed entities become detached.
An EntityManager instance can be obtained either through the Inject of a Container (such as a JEE Container or Spring) or through the EntityManagerFactory interface (usually in JSE applications). The key thing to know is that the behavior of the EntityManager changes depending on whether it is injected from the container or created by the application.
EntityManager managed by the container (declaratively Transaction)
@PersistenceContext
private EntityManager entityManager;
@Transactional
@Override
public T save(T entity) {
if(getterId(entity) == null) {
entityManager.persist(entity);
}
else {
entityManager.merge(entity);
}
return entity;
}
The container, before performing an operation on the Entity, checks if there is a Persistence Context connected to the transaction; if not present, it creates a new Persistence Context (session) and connects it. In practice, an EntityManager is automatically created for each new transaction. Operations such as commits and rollbacks are handled automatically by the Container.
EntityManager managed by the application (programmatically Transaction)
protected static EntityManagerFactory entityManagerFactory;
static {
entityManagerFactory = Persistence.createEntityManagerFactory("TEST_UNIT");
}
@Override
public T save(T entity) {
EntityManager entityManager = entityManagerFactory.createEntityManager();
try {
entityManager.getTransaction().begin();
if(getterId(entity) == null) {
entityManager.persist(entity);
}
else {
entityManager.merge(entity);
}
entityManager.getTransaction().commit();
}
catch (Exception e) {
entityManager.getTransaction().rollback();
}
entityManager.close();
return entity;
}
Here the developer creates each time the EntityManager from the EntityManagerFactory. He has more control over the flow, but also more responsibilities (e.g. he has to remember to close the EntityManager, he has to explicitly call commit and rollback operations).
Now that we understand these basic concepts, let's create an application with DAO classes. We'll use the entities of the post JPA Reletions with an H2 in-memory database.
We'll look at using the EntityManager managed by the application, and then compare it to the one managed by the Container.
Step 1: create the JpaDao interface
This interface will contain the general methods of findById, save, etc...
public interface JpaDao<T extends JpaEntity, ID> {
T findById(ID id);
Collection<T> findAll();
T save(T entity);
void delete(T entity);
void clear();
}
We create the UserDao interface that simply extends JpaDao:
public interface UserDao extends JpaDao<UserEntity, Long> {
}
Step 2: Create abstract class JpaDaoImpl
public abstract class JpaDaoImpl<T extends JpaEntity, ID> implements JpaDao<T, ID> {
protected static EntityManagerFactory entityManagerFactory;
private Class<T> persistentClass;
private final String FIND_ALL;
static {
entityManagerFactory = Persistence.createEntityManagerFactory("TEST_UNIT");
}
public JpaDaoImpl() {
this.persistentClass = (Class<T>) ((ParameterizedType) getClass()
.getGenericSuperclass()).getActualTypeArguments()[0];
FIND_ALL = "select e from " + persistentClass.getSimpleName() + " e";
}
@Override
public T findById(ID id) {
EntityManager entityManager = entityManagerFactory.createEntityManager();
T entity = entityManager.find(persistentClass, id);
entityManager.close();
return entity;
}
@Override
public Collection<T> findAll() {
EntityManager entityManager = entityManagerFactory.createEntityManager();
Collection<T> entities = entityManager.createQuery(FIND_ALL, persistentClass).getResultList();
entityManager.close();
return entities;
}
@Override
public T save(T entity) {
EntityManager entityManager = entityManagerFactory.createEntityManager();
try {
entityManager.getTransaction().begin();
if(getterId(entity) == null) {
entityManager.persist(entity);
}
else {
entityManager.merge(entity);
}
entityManager.getTransaction().commit();
}
catch (Exception e) {
entityManager.getTransaction().rollback();
}
entityManager.close();
return entity;
}
@Override
public void delete(T entity) {
EntityManager entityManager = entityManagerFactory.createEntityManager();
try {
entityManager.getTransaction().begin();
entity = entityManager.merge(entity);
entityManager.remove(entity);
entityManager.getTransaction().commit();
}
catch (Exception e) {
entityManager.getTransaction().rollback();
}
entityManager.close();
}
//use a forEach loop and not a delete query because JPA not use the cascade in the query
@Override
public void clear() {
EntityManager entityManager = entityManagerFactory.createEntityManager();
try {
entityManager.getTransaction().begin();
Collection<T> all = findAll();
all.stream().map(entityManager::merge)
.forEach(entityManager::remove);
entityManager.getTransaction().commit();
}
catch (Exception e) {
entityManager.getTransaction().rollback();
}
entityManager.close();
}
//get the value of the field annotated with @Id
private ID getterId(T entity) {
try {
Field id = Arrays.stream(persistentClass.getDeclaredFields())
.filter(field -> field.isAnnotationPresent(Id.class))
.findAny()
.orElse(null);
id.setAccessible(true);
return (ID) id.get(entity);
}
catch (Exception e) {
throw new RuntimeException(e);
}
}
}
Let's analyze the code:
- The entityManagerFactory field is initialized at app startup. It reads the persistence unit within the persistence.xml file.
- The persistentClass field is valued with the Entity's concrete class.
- Whenever a method is invoked, we create the EntityManager from the EntityManagerFactory.
- For write operations to the database, we manually create and manage transactions.
- The delete method, before calling the EntityManager remove, calls the merge that transforms a detached entity into a managed entity (operations can only be done on managed entities).
- The getterId method, via reflection, takes the value of the field annotated with @Id from JPA.
We have created a general DAO class that can be used by all Entities! Let's create the concrete class UserDaoImpl:
public class UserDaoImpl extends JpaDaoImpl<UserEntity, Long> implements UserDao {
private static UserDaoImpl userDao;
private UserDaoImpl() {}
public static UserDao getInstance() {
if(userDao == null) {
userDao = new UserDaoImpl();
}
return userDao;
}
}
Note that UserDaoImpl is a simple singleton that contains no methods. It already contains all findAll, findById, save, etc, since it extends JpaDaoImpl.
Step 3: create the persistence.xml file
Within resources/META-INF, we create the persistence.xml:
<?xml version="1.0" encoding="UTF-8" ?>
<persistence xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd"
version="2.0" xmlns="http://java.sun.com/xml/ns/persistence">
<persistence-unit name="TEST_UNIT" transaction-type="RESOURCE_LOCAL">
<class>com.vincenzoracca.jpaproject.entities.UserEntity</class>
<class>com.vincenzoracca.jpaproject.entities.CarEntity</class>
<class>com.vincenzoracca.jpaproject.entities.ContactEntity</class>
<class>com.vincenzoracca.jpaproject.entities.ArticleEntity</class>
<properties>
<property name="javax.persistence.jdbc.driver" value="org.h2.Driver" />
<property name="javax.persistence.jdbc.url" value="jdbc:h2:mem:testdb" />
<property name="hibernate.dialect" value="org.hibernate.dialect.H2Dialect"/>
<property name="javax.persistence.jdbc.user" value="sa" />
<property name="javax.persistence.jdbc.password" value="sa" />
<!-- Hibernate create the database schema automatically -->
<property name="hibernate.hbm2ddl.auto" value="create-drop"/>
<property name="hibernate.show_sql" value="true" />
</properties>
</persistence-unit>
</persistence>
Let's analyze the file:
- In the persistence-unit section, we declare the unit name and type, which can be RESOURCE_LOCAL or JTA.. JTA is a type of transaction managed only by JEE containers, maybe I'll write an article about it: it's enough to know that if we have to deploy the application inside an application server, it can be useful to use this kind of transaction.
- The class sections, indicate the various Java classes mapped as entities.
- The properties section indicates the various JPA and, in this case, Hibernate properties that we can set.
Step 4: Test the DAOs
We create jUnits to test DAOs:
public class JpaRelationsTest {
private UserDao userDao;
private CarDao carDao;
private ArticleDao articleDao;
@Before
public void init() {
userDao = UserDaoImpl.getInstance();
carDao = CarDaoImpl.getInstance();
articleDao = ArticleDaoImpl.getInstance();
System.out.println("\n*************************************************");
}
@After
public void destroy(){
System.out.println("*************************************************\n");
System.out.println("BEGIN destroy");
articleDao.clear();
carDao.clear();
userDao.clear();
System.out.println("END destroy\n");
}
@Test
public void oneToOneTest() {
System.out.println("BEGIN oneToOneTest");
assertEquals(0, userDao.findAll().size());
UserEntity userEntity = createUser();
ContactEntity contactEntity = createContact(userEntity);
userDao.save(userEntity);
UserEntity retrieved = userDao.findById(userEntity.getId());
System.out.println(retrieved);
assertEquals(userEntity, retrieved);
assertEquals(contactEntity, userEntity.getContactEntity());
System.out.println("END oneToOneTest");
}
private UserEntity createUser(){
UserEntity userEntity = new UserEntity();
userEntity.setCode("1");
userEntity.setName("Vincenzo");
userEntity.setSurname("Racca");
return userEntity;
}
private ContactEntity createContact(UserEntity userEntity) {
ContactEntity contactEntity = new ContactEntity();
contactEntity.setCity("Naples");
contactEntity.setTelephoneNumber("333333333");
contactEntity.setUserEntity(userEntity);
userEntity.setContactEntity(contactEntity);
return contactEntity;
}
}
Let's analyze the oneToOneTest method:
- We make sure with assertEquals(0, userDao.findAll().size()) that the USERS table is empty.
- We create a UserEntity, its own ContactEntity, and then save the UserEntity. Remember that UserEntity, having the cascade on ContactEntity, when it will be persisted, it will be saved to cascade also the eventual ContactEntity.
- We recover then the userEntity from the db and verify that he and its contactEntity are equal to those inserted a moment before.
If we enable the logs on the Transaction, we see that actually the insertions on USERS and CONTACTS happened within the same transaction. When in fact we explicitly commit the transaction, JPA also inserts contactEntity:
22:12:10.602 [main] DEBUG o.h.e.t.internal.TransactionImpl - begin
Hibernate: insert into USERS (user_id, code, name, surname) values (null, ?, ?, ?)
22:12:17.066 [main] DEBUG o.h.e.t.internal.TransactionImpl - committing
Hibernate: insert into CONTACTS (city, telephone_number, user_id) values (?, ?, ?)
Container managed vs Application managed
But what if a business logic method had multiple DAO methods and we wanted to call them within the same transaction?
Currently, with the methods we wrote, you would still create a transaction for each DAO, so we wouldn't have a single transaction.
A possible solution, still talking about EntityManager managed by the application, is to use Java's CDI (Context and Dependecy Injection) and inject
the EntityManager.
As for the EntityManager managed by Containers, we won't have this problem, since in the methods annotated with @Transactional, it checks if there is already a transaction running. If yes, it uses that one, otherwise it creates a new one (this is the default behavior, but you can change it).
Let's look at the JpaDaoImpl class with EntityManager managed by the Container:
public abstract class JpaDaoImpl<T extends JpaEntity, ID> implements JpaDao<T, ID> {
private Class<T> persistentClass;
private final String FIND_ALL;
@PersistenceContext
private EntityManager entityManager;
public JpaDaoImpl() {
this.persistentClass = (Class<T>) ((ParameterizedType) getClass()
.getGenericSuperclass()).getActualTypeArguments()[0];
FIND_ALL = "select e from " + persistentClass.getSimpleName() + " e";
}
@Override
public T findById(ID id) {
T entity = entityManager.find(persistentClass, id);
return entity;
}
@Override
public Collection<T> findAll() {
Collection<T> entities = entityManager.createQuery(FIND_ALL, persistentClass).getResultList();
return entities;
}
@Transactional
@Override
public T save(T entity) {
if(getterId(entity) == null) {
entityManager.persist(entity);
}
else {
entityManager.merge(entity);
}
return entity;
}
@Transactional
@Override
public void delete(T entity) {
entity = entityManager.merge(entity);
entityManager.remove(entity);
}
//use a forEach loop and not a delete query because JPA not use the cascade in the query
@Transactional
@Override
public void clear() {
Collection<T> all = findAll();
all.forEach(this::delete);
}
//get the value of the field annotated with @Id
private ID getterId(T entity) {
try {
Field id = Arrays.stream(persistentClass.getDeclaredFields())
.filter(field -> field.isAnnotationPresent(Id.class))
.findAny()
.orElse(null);
id.setAccessible(true);
return (ID) id.get(entity);
}
catch (Exception e) {
throw new RuntimeException(e);
}
}
}
As we can see, transactions are handled automatically and the EntityManager is not created by the developer but by the Container.
We notice also that the clear method calls the delete of the same class. This is possible because the Container will create only one transaction, in the clear method. When it will pass to the delete method, since there is already a transaction, it will use that one, and so the commit will be done only at the end of the of the clear method.
Conclusions
In this JPA article, we saw what is an EntityManager, how to use it, both with manged applications and managed containers.
You can find the full project on my github at this link: JPA Project.
You will find one module dedicated to application managed and one to container managed, using Spring 5.
Articles about JPA: JPA