public interface Service{
//业务方法
doBusiness();
//数据库更新方法
doBusinessTX();
}
@Service
public class ServiceImpl implements Service,ApplicationContextAware{
//自己注入自己由IOC Container解决循环依赖
@Autowired
private Service ServiceProxy;
//或者使用Aware接口获取BeanFactory获取当前对象事务代理对象 Aware接口动作逻辑由IOC扩展点接口BeanPostProcessor来执行
@Override
public void setApplicationContext(ApplicationContext ctx) throws BeansException{
this.Service = ctx.getBean(Service.class);
}
@Transactional(rollback=RuntimeException.class)//默认RuntimeException和虚拟机Error回滚
public void doBusinessTX(){
数据库更新 可抛出RuntimeException 自动触发数据库回滚
return;
}
public void operateRedis(){
//redisTemplate.eval 涉及多个KEY操作使用LUA脚本
}
public void operateRedisRollback(){
//redisTemplate.eval 执行LUA脚本 多个KEY同时操作 原子执行 回退redis修改
}
public void operateMQ() throws MQException{
//发送MQ 捕捉RuntimeException 转换为MQException再抛出 其他异常转换为MQException
}
@Override
public void doBusiness(){
try{
this.operateRedis();//redis逻辑 脚本串行化执行立即生效 脚本中需要相应校验条件成功然后执行并返回相应的状态码
this.ServiceProxy.doBusinessTX();//数据库逻辑 需判断上步redis返回的状态码 并由代理对象调用声明事务方法 方法调用结束数据库事务生效
this.operateMQ();//MQ逻辑 最后发送 此时redis和数据库已生效 如MQ失败可能需重新发送 (retry) 如重发仍失败可则抛出MQException 而对可能产生的RuntimeException需将之转换为MQException
}catch(RuntimeException e){
//处理运行时异常 数据库回滚异常 此时数据库已自动回滚需显式回滚redis 仍使用脚本串行化执行回退立即生效
this.operateRedisRollback();
}catch(MQException me){
//MQException 最后发送MQ 如果是先发送MQ 则可能涉及redis或者数据库逻辑失败 需要再发送撤销MQ 这可能导致已发出MQ的关联业务回退 增加交互复杂性
//如先发送MQ等待redis和数据库业务成功后确认 则redis和数据逻辑务期间占用MQ链接而不commit 效果同最后发送MQ
//如最后发送MQ重试多次仍失败 一般可将业务状态置为异常 需后续冲正或异步任务处理或者延迟发送
//如果还有类似文件服务的操作 一般不将耗时或耗费资源IO操作放在数据库事务中 可异步调起文件服务操作 并可使用类似信号量作相应限制
}finally{
}
}
}