服务和dao层中的事务

时间:2017-01-22 15:58:01

标签: spring hibernate jpa

我对@Transactional注释有疑问。 没有特别定义,所以据我所知是PROPAGATION_REQUIRED 假设我在服务和dao层都有一个事务性注释。

服务

@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();
}

ANOTHERDAO

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

}

尽管事实并非最佳,但它似乎导致死锁。 假设我与数据库有最多2个连接。 我使用3个不同的线程来运行相同的代码。 Thread-1和thread-2接收连接,thread-3没有得到任何连接。 更重要的是,似乎线程1在尝试获取dao级别的连接时会卡住,对于线程2也是如此。造成僵局。

我确信通过使用propagation_required,这不会发生。 我错过了什么吗? 这样的建议是什么?有没有办法在两个图层上都有@transactional?如果没有哪个是首选? 谢谢 的Fabrizio

3 个答案:

答案 0 :(得分:1)

由于dao.doSomeStuff应该从其他事务中调用,我建议你将此方法配置为:

@Transaction(propagation=REQUIRES_NEW)

由于调用此方法的事务将暂停,直到具有REQUIRES_NEW的事务将完成。

不确定这是否是针对您的特定死锁情况的修复,但您的示例适合此特定设置。

答案 1 :(得分:0)

你是对的,Propagation.REQUIRED是默认值。但这也意味着dao上的第二个(嵌套)调用加入/重用在服务级别创建的事务。因此,无需为嵌套调用创建另一个事务。

一般情况下,Spring(在高级别使用时)应该通过将资源处理转发到底层ORM层来管理资源处理:

  

首选方法是使用Spring的最高级别模板   持久性集成API或使用本机ORM API   用于管理本机的事务感知工厂bean或代理   资源工厂。这些内部事务感知解决方案   处理资源创建和重用,清理,可选事务   资源同步和异常映射。这样用户   数据访问代码不必解决这些任务,但可以   完全专注于非样板持久性逻辑。

即使你自己处理它(在低级API使用上),也应该重用连接:

  

当您希望应用程序代码直接处理资源时   您可以使用这些类来确保本机持久性API的类型   获得了正确的Spring Framework托管实例,   (可选)同步事务和发生的异常   在此过程中正确映射到一致的API。

...

  

如果现有事务已同步连接   (链接)它,返回该实例。否则,方法调用   触发创建新连接,(可选)   同步到任何现有交易,并可用于   随后在同一交易中重复使用。

Source

也许你必须找出那里发生的事情。

每个会话/工作单元将绑定到一个线程,并在事务结束后释放(与指定的连接一起)。当然,当你的线程卡住时,它不会释放连接。

你确定这个'死锁'是由这个嵌套引起的吗?也许这有另一个原因。你有这个例子的测试代码吗?还是线程转储还是什么?

答案 2 :(得分:0)

@Transactional通过保持ThreadLocal状态来工作,该状态可以由(Spring托管的)代理实体管理器访问。如果您正在使用Propagation.REQUIRED(默认值),并且您有一个非事务性方法,它调用两个不同的DAO(或同一个DAO上的两个Transactional方法),您将获得两个事务,并且两次调用以获取汇集连接。您可以获得相同的连接两次或两次不同的连接,但您当时应该只使用一个连接。

如果从@Transactional方法调用两个DAO,则只有一个事务,因为DAO将找到并加入在ThreadLocal状态中找到的现有事务,再次只需要池中的一个连接。

如果遇到死锁,那么就会出现问题,并且您可能希望在创建连接和事务时进行调试。通过调用Connection.setAutoCommit(false)启动事务,在Hibernate中,这发生在org.hibernate.resource.jdbc.internal.AbstractLogicalConnectionImplementor#begin()。连接由扩展org.hibernate.resource.jdbc.internal.AbstractLogicalConnectionImplementor的类管理,因此这些是放置断点的好地方,并将调用堆栈追溯到您的代码以查看哪些行创建了连接。