2017-01-22 173 views
1

我有一个关于@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个不同的线程来运行相同的代码。 线程1和线程2接收连接,线程3没有获得任何连接。 除此之外,看起来thread-1在尝试获取dao级别的连接时变得卡住,与thread-2相同。造成僵局。

我确信通过使用propagation_required这不会发生。 我错过了什么吗? 这是什么建议?有没有一种方法可以在两个层上都有@transactional?如果不是哪个是首选? 感谢 法布里奇奥

+0

如果您的连接不够用,您可能没有正确设置事务,或者自己搞乱连接,而不是让Spring管理事务。只有在没有实现的情况下添加方法签名才能解决您的问题。首先检查你的设置(确保你有正确的tx设置)并检查你的实现。 –

回答

0

由于dao.doSomeStuff预计将调用来自其他交易中,我建议您配置此方法为:

@Transaction(propagation=REQUIRES_NEW) 

多亏了这是调用此方法将交易停止,直到REQUIRES_NEW的那一个完成。

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

+0

感谢您的回答。我确实需要完全相反的。我不想被制止。我只是想知道如果我在服务级别接收到连接,现在我正在'重用'该连接。我为什么卡在那? – fabriziomieli

+0

尝试添加方法的实现。 LEts看看发生了什么 –

+0

嗨,我编辑了我的问题。这可能与我使用2种不同的daos的事实有关? – fabriziomieli

0

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

的首选方法是使用Spring的最高水平的基于模板的 持久化API或使用方法:

一般春季(高级别使用)应该由它转发给底层的ORM层管理资源处理本地ORM API与 事务感知工厂bean或用于管理本地 资源工厂的代理。这些事务感知解决方案在内部处理资源创建和重用,清理,可选事务 资源同步和异常映射。因此,用户数据访问代码不必解决这些任务,但可以将 纯粹集中在非模板化持久性逻辑上。

即使你处理一下你自己(在低级别的API使用)的连接应该被重复使用:

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

...

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

Source

也许你必须找到发生了什么事情在那里。

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

你确定这个“死锁”是由这个嵌套造成的吗?也许这是另一个原因。你有这个例子的测试代码吗?或者一个线程转储或其他东西?

+0

谢谢你的回答。我明白了。但是,如果情况是这样,并且嵌套调用重用了在服务级别创建的事务,那么它是如何影响获得连接的呢?我读到他们正在使用相同的“物理”连接,但有一个不同的“逻辑”连接......但也许我错过了一些东西! – fabriziomieli

+0

编辑我的答案。也许你可以在这里发布你的测试代码。 –

+0

嗨,我编辑了我的问题。这可能与我使用2种不同的daos的事实有关? – fabriziomieli

0

@Transactional通过保持ThreadLocal状态来工作,该状态可由(Spring管理的)代理EntityManager访问。如果您使用的是Propagation.REQUIRED(默认值),并且您有一个调用两个不同DAO(或同一个DAO上的两个事务方法)的非事务性方法,您将获得两个事务,并且两个调用获取一个池连接。您可能会获得相同的连接两次或两次不同的连接,但您应该只使用一次连接。

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

如果遇到死锁,那么有些东西是非常错误的,您可能想要在创建连接和事务时进行调试。通过调用Connection.setAutoCommit(false)开始一个事务,在Hibernate中这发生在org.hibernate.resource.jdbc.internal.AbstractLogicalConnectionImplementor#begin()。连接由延伸org.hibernate.resource.jdbc.internal.AbstractLogicalConnectionImplementor的类进行管理,因此这些是放置断点的好地方,并将调用堆栈追溯回代码,以查看哪些线路创建了连接。