我对@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
答案 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。
...
如果现有事务已同步连接 (链接)它,返回该实例。否则,方法调用 触发创建新连接,(可选) 同步到任何现有交易,并可用于 随后在同一交易中重复使用。
也许你必须找出那里发生的事情。
每个会话/工作单元将绑定到一个线程,并在事务结束后释放(与指定的连接一起)。当然,当你的线程卡住时,它不会释放连接。
你确定这个'死锁'是由这个嵌套引起的吗?也许这有另一个原因。你有这个例子的测试代码吗?还是线程转储还是什么?
答案 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
的类管理,因此这些是放置断点的好地方,并将调用堆栈追溯到您的代码以查看哪些行创建了连接。