Manage connections to the database correctly using hibernate

1

What would be the best default for connections, I thought of two modes:

Mode 1:

Each function creates and closes its own connection

Function 'insert'

        EntityManagerFactory factory = Persistence.createEntityManagerFactory("Tarefa");
        EntityManager manager = factory.createEntityManager();
        manager.getTransaction().begin();
        manager.persist(t);
        manager.getTransaction().commit();
        manager.close();
        factory.close();

Mode 2:

A static connection to the class, if the connection is closed, a method is used to open it.

private static EntityManagerFactory factoryClasse = Persistence.createEntityManagerFactory("Tarefa");
private static EntityManager managerClasse = factoryClasse.createEntityManager();

private static void conectar()
{
    factoryClasse = Persistence.createEntityManagerFactory("Tarefa");
    managerClasse = factoryClasse.createEntityManager();
}

public static Boolean inserir(Tarefa t)
{
    try
    {
        if(managerClasse.isOpen() == false || factoryClasse.isOpen() == false)
            conectar();
        managerClasse.getTransaction().begin();
        managerClasse.persist(t);
        managerClasse.getTransaction().commit();
    }
    catch(Exception e)
    {
        e.printStackTrace();
        return false;
    }
    return true;
}

In terms of speed and / or security, which is the most appropriate?

    
asked by anonymous 06.04.2018 / 23:42

1 answer

2

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

    
08.04.2018 / 17:27