服务层和 dao 层中的事务性
Posted
技术标签:
【中文标题】服务层和 dao 层中的事务性【英文标题】:Transactional in both service and dao layers 【发布时间】:2017-06-07 04:15:07 【问题描述】:我有一个关于@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);
道
@Transactional
public UUID create(StudentEntity entity)
if ( entity == null ) throw new Exception(//…);
getCurrentSession().save(entity);
return entity.getId();
另一个 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);
尽管这不是最优的,但它似乎会导致死锁。 假设我最多有 2 个到数据库的连接。 我正在使用 3 个不同的线程来运行相同的代码。 线程 1 和线程 2 收到连接,线程 3 没有获得任何连接。 不仅如此,似乎线程 1 在尝试获取 dao 级别的连接时卡住了,线程 2 也是如此。导致死锁。
我确信通过使用propagation_required 这不会发生。 我错过了什么吗? 对这样的事情有什么建议?有没有办法可以在两层都有@transactional?如果不是,哪个是首选? 谢谢 法布里奇奥
【问题讨论】:
如果你的连接用完了,你要么没有正确设置事务,要么自己搞乱了连接,而不是让 Spring 管理事情。仅添加没有实现的方法签名将无助于解决您的问题。首先检查您的设置(确保您的 tx 设置正确)并检查您的实现。 【参考方案1】:由于预期会从其他事务中调用 dao.doSomeStuff,我建议您将此方法配置为:
@Transaction(propagation=REQUIRES_NEW)
感谢调用此方法的事务将暂停,直到具有 REQUIRES_NEW 的事务完成。
不确定这是否可以解决您的特定死锁情况,但您的示例适合此特定设置。
【讨论】:
感谢您的回答。我确实需要正好相反。我不想被拦住。我只是想知道如果我在服务级别收到连接,现在我正在“重用”该连接。为什么我会坚持这一点? 尝试添加方法的实现。让我们看看那里发生了什么 嗨,我编辑了我的问题。这可能与我使用 2 个不同的 daos 的事实有关吗?【参考方案2】:你是对的,Propagation.REQUIRED
是默认值。但这也意味着对 dao 的第二次(嵌套)调用加入/重用在服务级别创建的事务。因此无需为嵌套调用创建另一个事务。
一般来说,Spring(在高级使用中)应该通过将资源处理转发到底层 ORM 层来管理资源处理:
首选方法是使用基于 Spring 的***别模板 持久性集成 API 或将本机 ORM API 与 用于管理本机的事务感知工厂 bean 或代理 资源工厂。这些内部交易感知解决方案 处理资源创建和重用、清理、可选事务 资源同步和异常映射。因此用户 数据访问代码不必解决这些任务,但可以 纯粹专注于非样板持久性逻辑。
即使您自己处理它(在低级别 API 使用情况下),也应该重用连接:
当您希望应用程序代码直接处理资源时 本机持久性 API 的类型,您可以使用这些类来确保 获得适当的 Spring Framework 管理的实例, 事务(可选)同步,并且发生异常 在此过程中正确映射到一致的 API。
...
如果现有事务已同步连接 (链接)到它,该实例被返回。否则,方法调用 触发新连接的创建,这是(可选) 同步到任何现有事务,并可用于 在同一事务中的后续重用。
Source
也许你必须找出那里发生的事情。
每个会话/工作单元将绑定到一个线程并在事务结束后释放(连同分配的连接)。当然,当您的线程卡住时,它不会释放连接。
您确定这种“死锁”是由这种嵌套引起的吗?或许这另有原因。你有这个例子的一些测试代码吗?还是线程转储之类的?
【讨论】:
感谢您的回答。我明白了。但是,如果是这种情况,并且嵌套调用重用了在服务级别创建的事务,那么它为什么会停留在获取连接上呢?我读到他们使用相同的“物理”连接,但不同的“逻辑”连接......但也许我错过了一些东西! 编辑了我的答案。也许你可以在这里发布你的测试代码。 嗨,我编辑了我的问题。这可能与我使用 2 个不同的 daos 的事实有关吗?【参考方案3】:@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
的类管理,因此这些是放置断点的好地方,并将调用堆栈跟踪到您的代码以查看哪些行创建了连接。
【讨论】:
以上是关于服务层和 dao 层中的事务性的主要内容,如果未能解决你的问题,请参考以下文章