This will depend on the application: whether it will support a user or multiple users simultaneously; whether it will use one or multiple threads; application architecture; if any framework that will manage transactions and inject the EntityManager
, etc..
To identify which solution to adopt, it is important that you understand the main JPA classes: EntityManagerFactory
and EntityManager
.
Entitymanagerfactory
A EntityManagerFactory
is an immutable class and thread safe which represents the mapping of the domain model to the database. It keeps all services used by the JPA implementation used, such as cache second-tier, Connection pool, etc. Its creation is extremely costly, so it should be created only once for each database used by the application.
Entitymanager
A EntityManager
, in turn, it is not thread safe, should therefore be used by a single thread at a time. It manages the life cycle of objects that are part of your context through a cache first level. Unlike the EntityManagerFactory
, the EntityManager
is a short and inexpensive life object to be created.
It is important to note that the EntityManagerFactory
and the EntityManager
nay represent a connection to the database, they utilize connections. The form the connection is obtained and when this occurs is in charge of the JPA implementation. The management of these connections is in charge of the Connection pool (use is not recommended Connection pool standard used by Hibernate in production. Specific libraries such as DBCP and c3p0 should be used).
Hibernate, for example, gets the connection of a Connection pool only when the first SQL statement is executed, and, depending on the release_mode
used, the connection will be released after the execution of the SQL statement (after_statement
) or when a commit or rollback (after_transaction
).
With that in mind, we can evaluate your two solutions.
#1
As we have seen, the creation of a EntityManagerFactory
It’s an extremely costly operation. Thus, creating such an object every time it is necessary to execute a method, in addition to unnecessary, can bring serious performance problems, which makes such implementation unfeasible.
#2
As we have seen, a EntityManager
nay is thread safe, therefore, keep it as a static variable that can be used by several threads, is something dangerous.
In addition, you are linking your transaction to the insert operation. How would you do if you needed to insert multiple records into a single transaction? Would you implement another method that received multiple tasks? What if it was necessary for a transaction to modify multiple object types (maybe interact with multiple objects Daos, if you use this standard), would you implement a method that would receive all of them? Such a solution could be feasible in small applications, but in larger applications, this could lead to problems. In addition, there would be a huge repetition of code to manage transactions.
A possible solution:
With all these problems in mind, we can begin to envision a solution.
Create a class responsible for managing transactions:
Transactional:
/**
*
* Representa operação que deve ser realizada de forma atômica.
*
* @param <T> retorno da transação, caso haja um
*/
public interface Transactional<T> {
public T execute();
}
Transactionmanager:
/**
* Gerencia as transações
*
*/
public interface TransactionManager {
public <T> T doInTransaction(Transactional<T> transaction);
}
Jpatransactionmanager:
/**
* Implementação de um gerenciador de transações para a Java Persistence API
*
*/
public final class JPATransactionManager implements TransactionManager {
private final EntityManagerFactory emf;
private final ThreadLocal<EntityManager> threadLocal;
public JPATransactionManager(EntityManagerFactory emf, ThreadLocal<EntityManager> threadLocal) {
this.emf = emf;
this.threadLocal = threadLocal;
}
@Override
public final <T> T doInTransaction(Transactional<T> transaction) {
EntityManager em = null;
T result = null;
try {
em = emf.createEntityManager();
threadLocal.set(em);
em.getTransaction().begin();
result = transaction.execute();
em.getTransaction().commit();
} catch(RuntimeException e) {
if(em != null && em.getTransaction().isActive()) {
em.getTransaction().rollback();
}
throw e;
} finally {
if(em != null) {
em.close();
}
threadLocal.remove();
}
return result;
}
}
Examples of entities:
Person:
@Entity
public class Person {
@Id
private int id;
@OneToMany(orphanRemoval = true, fetch=FetchType.EAGER)
private Set<Car> cars;
protected Person() {}
public Person(int id, Set<Car> cars) {
this.id = id;
this.cars = cars;
}
public int getId() {
return id;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + id;
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (!(obj instanceof Person))
return false;
Person other = (Person) obj;
if (id != other.id)
return false;
return true;
}
@Override
public String toString() {
return "Person [id=" + id + ", cars=" + cars + "]";
}
}
Car:
@Entity
public class Car {
@Id
private int id;
@Column
private String model;
protected Car() {}
public Car(int id, String model) {
this.id = id;
this.model = model;
}
public int getId() {
return id;
}
@Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime * result + id;
return result;
}
@Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (!(obj instanceof Car))
return false;
Car other = (Car) obj;
if (id != other.id)
return false;
return true;
}
@Override
public String toString() {
return "Car [id=" + id + ", model=" + model + "]";
}
}
Daos
Personhood:
public interface PersonDAO {
public void save(Person person);
public List<Person> getAll();
}
Jpapersondao:
public final class JPAPersonDAO implements PersonDAO {
private final ThreadLocal<EntityManager> threadLocal;
public JPAPersonDAO(ThreadLocal<EntityManager> threadLocal) {
this.threadLocal = threadLocal;
}
public final void save(Person pessoa) {
getEntityManager().persist(pessoa);
}
public final List<Person> getAll() {
return getEntityManager()
.createQuery("SELECT p FROM Person p", Person.class)
.getResultList();
}
private final EntityManager getEntityManager() {
EntityManager em = threadLocal.get();
if(em == null || !em.getTransaction().isActive()) {
throw new TransactionRequiredException();
}
return em;
}
}
Cardao:
public interface CarDAO {
public void save(Car car);
}
Jpacardao:
public class JPACarDAO implements CarDAO {
private final ThreadLocal<EntityManager> threadLocal;
public JPACarDAO(ThreadLocal<EntityManager> threadLocal) {
this.threadLocal = threadLocal;
}
@Override
public final void save(Car car) {
getEntityManager().persist(car);
}
private final EntityManager getEntityManager() {
EntityManager em = threadLocal.get();
if(em == null || !em.getTransaction().isActive()) {
throw new TransactionRequiredException();
}
return em;
}
}
Finally, examples of this model working in environments with a single thread and in multiple environments threads:
One thread:
public class SingleThreadPersistenceJPA {
private static final List<String> CAR_MODELS = Arrays.asList(
"Gol", "Siena", "Civic", "Celta", "Sandero", "Tucson"
);
public static void main(String[] args) {
EntityManagerFactory emf = null;
try {
emf = Persistence.createEntityManagerFactory("seu-persistence-context");
final ThreadLocal<EntityManager> threadLocal = new ThreadLocal<>();
final TransactionManager tm = new JPATransactionManager(emf, threadLocal);
final PersonDAO personDao = new JPAPersonDAO(threadLocal);
final CarDAO carDao = new JPACarDAO(threadLocal);
tm.doInTransaction(() -> {
Car car1 = new Car(1, CAR_MODELS.get(0));
carDao.save(car1);
Car car2 = new Car(2, CAR_MODELS.get(3));
carDao.save(car2);
Set<Car> cars = Stream.of(car1, car2).collect(Collectors.toSet());
Person person1 = new Person(1, cars);
personDao.save(person1);
Car car3 = new Car(3, CAR_MODELS.get(1));
carDao.save(car3);
Car car4 = new Car(4, CAR_MODELS.get(2));
carDao.save(car4);
cars = Stream.of(car3, car4).collect(Collectors.toSet());
Person person2 = new Person(2, cars);
personDao.save(person2);
return null;
});
tm.doInTransaction(personDao::getAll).forEach(System.out::println);
} finally {
if(emf != null && emf.isOpen()) {
emf.close();
}
}
}
}
And in Environment with multiple threads:
public class MultipleThreadPersistenceThread {
private static final List<String> CAR_MODELS = Arrays.asList(
"Gol", "Siena", "Civic", "Celta", "Sandero", "Tucson"
);
private static final AtomicInteger PERSON_ID = new AtomicInteger(0);
private static final AtomicInteger CAR_ID = new AtomicInteger(0);
public static void main(String[] args) {
EntityManagerFactory emf = null;
try {
emf = Persistence.createEntityManagerFactory("seu-persistence-context");
final ThreadLocal<EntityManager> threadLocal = new ThreadLocal<>();
final TransactionManager tm = new JPATransactionManager(emf, threadLocal);
final PersonDAO personDao = new JPAPersonDAO(threadLocal);
final CarDAO carDao = new JPACarDAO(threadLocal);
ExecutorService es = Executors.newFixedThreadPool(10);
final Random random = new Random();
for(int i = 0; i < 50; i++) {
es.submit(() -> {
tm.doInTransaction(() -> {
Car car1 = new Car(CAR_ID.incrementAndGet(), CAR_MODELS.get(random.nextInt(CAR_MODELS.size() - 1)));
Car car2 = new Car(CAR_ID.incrementAndGet(), CAR_MODELS.get(random.nextInt(CAR_MODELS.size() - 1)));
carDao.save(car1);
carDao.save(car2);
Set<Car> cars = Stream.of(car1, car2).collect(Collectors.toSet());
Person person = new Person(PERSON_ID.incrementAndGet(), cars);
System.out.println("Saving person: " + person.getId());
personDao.save(person);
return null;
});
});
}
es.shutdown();
while(!es.isTerminated()) {}
tm.doInTransaction(personDao::getAll).forEach(System.out::println);
} finally {
if(emf != null && emf.isOpen()) {
emf.close();
}
}
}
}
To deepen:
http://docs.jboss.org/hibernate/orm/5.2/userguide/html_single/Hibernate_User_Guide.html#Architecture-Overview
https://vladmihalcea.com/hibernate-aggressive-connection-release/
Incredible solution..
– Marlysson