2
votes

I have a question regarding @Transactional annotation. Nothing special defined, so as I understand is PROPAGATION_REQUIRED Let’s say I have a transactional annotation which on both service and dao layer.

Service

@Transactional
public long createStudentInDB(Student student) {
    final long id = addStudentToDB (student);
    addStudentToCourses (id, student.getCourseIds());
    return id;
}

private long addStudentToDB (Student student) {
    StudentEntity entity = new StudentEntity ();
    convertToEntity(student, entity);
    try {
        final id = dao.create(entity);
     } catch (Exception e){
        //   
      }
    return id;
}

private void addStudentToCourses (long studentId, List<String> coursesIds){
    //add user to group
    if(coursesIds!= null){
        List<StudentCourseEntity> studentCourses = new ArrayList<>();
        for(String coursesId: coursesIds){
            StudentCourseEntity entity = new StudentCourseEntity ();
            entity.setCourseId(coursesId);
            entity.setStudentId(userId);
            studentCourses.add(studentId);
        }
        anotherDao.saveAll(studentCourses);
    }
}

DAO

@Transactional
public UUID create(StudentEntity entity) {

   if ( entity == null ) { throw new Exception(//…); }

   getCurrentSession().save(entity);
   return entity.getId();
}

ANOTHER DAO:

@Transactional
public void saveAll(Collection< StudentCourseEntity > studentCourses) {
    List< StudentCourseEntity > result = new ArrayList<>();
    if(studentCourses!= null) {
        for (StudentCourseEntity studentCourse : studentCourses) {
            if (studentCourse!= null) {
                save(studentCourse);
            }
        }
    }

}

Despite the fact that’s not optimal, it seems it causing deadlocks. Let’s say I have max 2 connections to the database. And I am using 3 different threads to run the same code. Thread-1 and thread-2 receive a connection, thread-3 is not getting any connection. More than that, it seems that thread-1 become stuck when trying to get a connection in dao level, same for thread-2. Causing a deadlock.

I was sure that by using propagation_required this would not happen. Am I missing something? What’s the recommendation for something like that? Is there a way I can have @transactional on both layers? If not which is preferred? Thanks Fabrizio

3
If you are running out of connections you either don't have transactions setup correctly or are messing around with connections yourself instead of letting Spring manage things. Only adding method signatures without an implementation will not help in solving your issue. First check your setup (make sure you have tx setup correctly) and check your implementations as well.M. Deinum

3 Answers

0
votes

As the dao.doSomeStuff is expected to be invoked from within other transactions I would suggest you to configure this method as:

@Transaction(propagation=REQUIRES_NEW)

Thanks to that the transaction which is invoking this method will halted until the one with REQUIRES_NEW will be finished.

Not sure if this is the fix for your particular deadlock case but your example fits this particular set-up.

0
votes

You are right, Propagation.REQUIRED is the default. But that also means that the second (nested) invocation on dao joins / reuses the transaction created on service level. So there is no need to create another transaction for the nested call.

In general Spring (on high level usage) should manage resource handling by forwarding it to the underlying ORM layer:

The preferred approach is to use Spring's highest level template based persistence integration APIs or to use native ORM APIs with transaction- aware factory beans or proxies for managing the native resource factories. These transaction-aware solutions internally handle resource creation and reuse, cleanup, optional transaction synchronization of the resources, and exception mapping. Thus user data access code does not have to address these tasks, but can be focused purely on non-boilerplate persistence logic.

Even if you handle it on your own (on low level API usage) the connections should be reused:

When you want the application code to deal directly with the resource types of the native persistence APIs, you use these classes to ensure that proper Spring Framework-managed instances are obtained, transactions are (optionally) synchronized, and exceptions that occur in the process are properly mapped to a consistent API.

...

If an existing transaction already has a connection synchronized (linked) to it, that instance is returned. Otherwise, the method call triggers the creation of a new connection, which is (optionally) synchronized to any existing transaction, and made available for subsequent reuse in that same transaction.

Source

Maybe you have to find out what is happening down there.

Each Session / Unit of Work will be bound to a thread and released (together with the assigned connection) after the transaction has ended. Of course when your thread gets stuck it won't release the connection.

Are you sure that this 'deadlock' is caused by this nesting? Maybe that has another reason. Do you have some test code for this example? Or a thread dump or something?

0
votes

@Transactional works by keeping ThreadLocal state, which can be accessed by the (Spring managed) Proxy EntityManager. If you are using Propagation.REQUIRED (the default), and you have a non-transactional method which calls two different DAOs (or two Transactional methods on the same DAO), you will get two transactions, and two calls to acquire a pooled connection. You may get the same connection twice or two different connections, but you should only use one connection at the time.

If you call two DAOs from a @Transactional method, there will only be one transaction, as the DAO will find and join the existing transaction found in the ThreadLocal state, again you only need one connection from the pool.

If you get a deadlock then something is very wrong, and you may want to debug when your connections and transaction are created. A transaction is started by calling Connection.setAutoCommit(false), in Hibernate this happens in org.hibernate.resource.jdbc.internal.AbstractLogicalConnectionImplementor#begin(). Connections are managed by a class extending org.hibernate.resource.jdbc.internal.AbstractLogicalConnectionImplementor so these are some good places to put a break point, and trace the call-stack back to your code to see which lines created connections.