Spring Transactions如何拆分方法将权限校验移出事务且不新增公共@Transactional方法
最优实现方案
最优方案是使用Spring提供的TransactionTemplate做编程式事务控制,无需新增带@Transactional注解的公共方法,同时可以精准控制事务的开启范围,完全满足你的需求。
改造步骤
- 首先在当前类中注入
TransactionTemplate:
@Autowired private TransactionTemplate transactionTemplate;
- 去掉原有
getById方法上的@Transactional注解,调整逻辑顺序,先执行权限校验再开启事务执行查询:
public Note getById(Long noteId, Collection<Authority> authorities) { // 权限校验逻辑完全在事务外执行 permissionService.actionAllowed(authorities, Permission.NOTE); // 仅数据库查询逻辑包裹在事务中 return transactionTemplate.execute(status -> getNote(noteId)); } private Note getNote(Long noteId) { return repository.findById(noteId) .orElseThrow(() -> new MyException("Note does not exist")); }
方案优势
- 事务范围被精准缩小到仅包含数据库查询操作,最大程度缩短了事务持有数据库连接的时长
- 不需要新增任何公共方法,也不需要调整原有私有方法的逻辑,侵入性极低
- 不需要处理Spring AOP自调用导致的事务不生效问题,代码可读性和可维护性更高
备选声明式事务方案
如果偏好声明式事务实现,也可以通过注入自身代理的方式实现,仅需要新增一个包访问权限的事务方法,不会对外暴露公共接口:
@Service public class NoteService { // 注入自身代理对象,避免自调用时事务不生效 @Autowired @Lazy private NoteService self; public Note getById(Long noteId, Collection<Authority> authorities) { permissionService.actionAllowed(authorities, Permission.NOTE); // 调用代理对象的事务方法 return self.getNoteById(noteId); } // 包访问权限,不会对外暴露为公共方法 @Transactional Note getNoteById(Long noteId) { return repository.findById(noteId) .orElseThrow(() -> new MyException("Note does not exist")); } }
该方案需要额外处理代理注入逻辑,综合来看不如TransactionTemplate方案适配你的需求。
内容的提问来源于stack exchange,提问作者Matexon
相关产品推荐
相关产品推荐

