This will depend on the application: whether it will support one user or multiple users simultaneously; if it will use one or multiple threads ; application architecture; if it will use some framework that will manage transactions and inject EntityManager
, etc.
For you 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 that represents the mapping of the domain model to the database. It keeps all services used by the JPA implementation used, such as second level cache , 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, is not thread safe , so it should be used by a single thread at a time. It manages the life cycle of objects that are part of its context through a first level cache . Unlike EntityManagerFactory
, EntityManager
is a short and cheap life object to be created.
It's important to note that EntityManagerFactory
and EntityManager
do not represent a connection to the database, they use connections. The way the connection is obtained and when this occurs is up to the JPA implementation. However, the management of these connections is handled by the connection pool (the standard ) used by Hibernate in production is not recommended. Specific libraries such as DBCP and c3p0 ).
Hibernate, for example, gets a connection pool connection only when the first SQL statement is run, and depending on the release_mode
used, the connection will be released upon execution of the SQL statement ( after_statement
) or when a commit or rollback occurs. >
With that in mind, we can evaluate your two solutions.
# 1
As we have seen, the creation of a after_transaction
is an extremely costly operation. Therefore, to create such an object every time it is necessary to execute a method, besides being unnecessary, can cause serious performance problems, which makes such implementation impractical.
# 2
As we have seen, a% not is thread safe , so keep it as a static variable that can be used by several threads , is something dangerous.
Additionally, you are linking your transaction to the insert operation. How would you do if multiple records were required in a single transaction? Would you implement another method that receives multiple tasks? And if it were necessary for a transaction to modify multiple object types (perhaps interacting with multiple DAOs , if you use this pattern), 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 generate problems. In addition, there would be a huge repetition of code to manage the transactions.
One possible solution:
With all these issues 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;
}
}
Entity Examples:
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
PersonDAO :
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 single-threaded environments and multi-threaded environments:
A 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 multi-threaded environment :
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 dig deeper:
link
link