@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
MsgModel
package com.javacode2018.tx.demo11;
import lombok.*;
/**
* 公众号:路人甲Java,工作10年的前阿里P7分享Java、算法、数据库方面的技术干货!
* 个人博客
*/
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
@ToString
public class MsgModel {
private Long id;
//消息内容
private String msg;
//消息订单id
private Long msg_order_id;
//消息状态,0:待投递,1:已发送,2:取消发送
private Integer status;
}
MsgOrderModel
package com.javacode2018.tx.demo11;
import lombok.*;
/**
* 公众号:路人甲Java,工作10年的前阿里P7分享Java、算法、数据库方面的技术干货!
* 个人博客
*/
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class MsgOrderModel {
private Long id;
//关联业务类型
private Integer ref_type;
//关联业务id(ref_type & ref_id 唯一)
private String ref_id;
}
MsgOrderService
提供了对t_msg_order表的一些操作,2个方法,一个用来插入数据,一个用来查询。
package com.javacode2018.tx.demo11;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.Objects;
/**
* 公众号:路人甲Java,工作10年的前阿里P7分享Java、算法、数据库方面的技术干货!
* 个人博客
*/
@Component
public class MsgOrderService {
@Autowired
private JdbcTemplate jdbcTemplate;
/**
* 插入消息订单
* @param ref_type
* @param ref_id
* @return
*/
@Transactional
public MsgOrderModel insert(Integer ref_type, String ref_id) {
MsgOrderModel msgOrderModel = MsgOrderModel.builder().ref_type(ref_type).ref_id(ref_id).build();
//插入消息
this.jdbcTemplate.update(“insert into t_msg_order (ref_type,ref_id) values (?,?)”,
ref_type,
ref_id
);
//获取消息订单id
msgOrderModel.setId(this.jdbcTemplate.queryForObject(“SELECT LAST_INSERT_ID()”, Long.class));
return msgOrderModel;
}
/**
* 根据消息id获取消息
* @param id
* @return
*/
public MsgOrderModel getById(Long id) {
List list = this.jdbcTemplate.query(“select * from t_msg_order where id = ? limit 1”, new BeanPropertyRowMapper(MsgOrderModel.class), id);
return Objects.nonNull(list) && !list.isEmpty() ? list.get(0) : null;
}
}
MsgService
消息服务,提供了对t_msg表的一些操作以及消息投递的一些方法
| 方法 | 说明 |
| — | — |
| addMsg | 添加消息,消息会落库,处于待发送状态 |
| confirmSendMsg | 确定投递消息,事务成功后可以调用 |
| cancelSendMsg | 取消投递消息,事务回滚可以调用 |
代码:
package com.javacode2018.tx.demo11;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import java.util.List;
import java.util.Objects;
/**
* 公众号:路人甲Java,工作10年的前阿里P7分享Java、算法、数据库方面的技术干货!
* 个人博客
*/
@Component
public class MsgService {
//添加一条消息(独立的事务中执行)
@Transactional(propagation = Propagation.REQUIRES_NEW)
public Long addMsg(String msg, Long msg_order_id, boolean isSend) {
MsgModel msgModel = MsgModel.builder().msg(msg).msg_order_id(msg_order_id).status(0).build();
//先插入消息
Long msg_id = this.insert(msgModel).getId();
if (isSend) {
//如果需要投递,则调用投递的方法
this.confirmSendMsg(msg_id);
}
return msg_id;
}
/**
* 确认消息投递(不需要事务)
* @param msg_id 消息id
*/
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void confirmSendMsg(Long msg_id) {
MsgModel msgModel = this.getById(msg_id);
//向mq中投递消息
System.out.println(String.format(“投递消息:%s”, msgModel));
//将消息状态置为已投递
this.updateStatus(msg_id, 1);
}
/**
* 取消消息投递(不需要事务)
* @param msg_id 消息id
*/
@Transactional(propagation = Propagation.NOT_SUPPORTED)
public void cancelSendMsg(Long msg_id) {
MsgModel msgModel = this.getById(msg_id);
System.out.println(String.format(“取消投递消息:%s”, msgModel));
//将消息状态置为取消投递
this.updateStatus(msg_id, 2);
}
@Autowired
private JdbcTemplate jdbcTemplate;
/**
* 插入消息
* @param msgModel
* @return
*/
private MsgModel insert(MsgModel msgModel) {
//插入消息
this.jdbcTemplate.update(“insert into t_msg (msg,msg_order_id,status) values (?,?,?)”,
msgModel.getMsg(),
msgModel.getMsg_order_id(),
msgModel.getStatus());
//获取消息id
msgModel.setId(this.jdbcTemplate.queryForObject(“SELECT LAST_INSERT_ID()”, Long.class));
System.out.println(“插入消息:” + msgModel);
return msgModel;
}
/**
* 根据消息id获取消息
* @param id
* @return
*/
private MsgModel getById(Long id) {
List list = this.jdbcTemplate.query(“select * from t_msg where id = ? limit 1”, new BeanPropertyRowMapper(MsgModel.class), id);
return Objects.nonNull(list) && !list.isEmpty() ? list.get(0) : null;
}
/**
* 更新消息状态
* @param id
* @param status
*/
private void updateStatus(long id, int status) {
this.jdbcTemplate.update(“update t_msg set status = ? where id = ?”, status, id);
}
}
消息投递器MsgSender
消息投递器,给业务方使用,内部只有一个方法,用来发送消息。
若上下文没有事务,则消息落地之后立即投递;若存在事务,则消息投递分为2步走:消息先落地,事务执行完毕之后再确定是否投递,用到了事务扩展点:TransactionSynchronization,事务执行完毕之后会回调TransactionSynchronization接口中的afterCompletion方法,在这个方法中确定是否投递消息。对事务扩展点TransactionSynchronization不熟悉的建议先看一下这篇文章:Spring系列第47篇:spring事务源码解析
package com.javacode2018.tx.demo11;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.support.TransactionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationAdapter;
import org.springframework.transaction.support.TransactionSynchronizationManager;
/**
* 公众号:路人甲Java,工作10年的前阿里P7分享Java、算法、数据库方面的技术干货!
* 个人博客
* 消息发送器,所有使用者调用send方法发送消息
*/
@Component
public class MsgSender {
@Autowired
private MsgOrderService msgOrderService;
@Autowired
private MsgService msgService;
//发送消息
public void send(String msg, int ref_type, String ref_id) {
MsgOrderModel msgOrderModel = this.msgOrderService.insert(ref_type, ref_id);
Long msg_order_id = msgOrderModel.getId();
//TransactionSynchronizationManager.isSynchronizationActive 可以用来判断事务同步是否开启了
boolean isSynchronizationActive = TransactionSynchronizationManager.isSynchronizationActive();
/**
* 若事务同步开启了,那么可以在事务同步中添加事务扩展点,则先插入消息,暂不发送,则在事务扩展点中添加回调
* 事务结束之后会自动回调扩展点TransactionSynchronizationAdapter的afterCompletion()方法
* 咱们在这个方法中确定是否投递消息
*/
if (isSynchronizationActive) {
final Long msg_id = this.msgService.addMsg(msg, msg_order_id, false);
TransactionSynchronizationManager.registerSynchronization(new TransactionSynchronizationAdapter() {
@Override
public void afterCompletion(int status) {
//代码走到这里时,事务已经完成了(可能是回滚了、或者是提交了)
//看一下消息关联的订单是否存在,如果存在,说明事务是成功的,业务是执行成功的,那么投递消息
if (msgOrderService.getById(msg_order_id) != null) {
System.out.println(String.format(“准备投递消息,{msg_id:%s}”, msg_id));
//事务成功:投递消息
msgService.confirmSendMsg(msg_id);
} else {
System.out.println(String.format(“准备取消投递消息,{msg_id:%s}”, msg_id));
//事务是不:取消投递消息
msgService.cancelSendMsg(msg_id);
}
}
});
} else {
//无事务的,直接插入并投递消息
this.msgService.addMsg(msg, msg_order_id, true);
}
}
}
3.3、测试(3种场景)
3.3.1、场景1:业务成功,消息投递成功
UserService
下面的register方法是有事务的,内部会插入一条用户信息,然后会投递一条消息
package com.javacode2018.tx.demo11;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
/**
* 公众号:路人甲Java,工作10年的前阿里P7分享Java、算法、数据库方面的技术干货!
* 个人博客
*/
@Component
public class UserService {
@Autowired
private JdbcTemplate jdbcTemplate;
//消息投递器
@Autowired
private MsgSender msgSender;
/**
* 模拟用户注册成功,顺便发送消息
*/
@Transactional
public void register(Long user_id, String user_name) {
//先插入用户
this.jdbcTemplate.update(“insert into t_user(id,name) VALUES (?,?)”, user_id, user_name);
System.out.println(String.format(“用户注册:[user_id:%s,user_name:%s]”, user_id, user_name));
//发送消息
String msg = String.format(“[user_id:%s,user_name:%s]”, user_id, user_name);
//调用投递器的send方法投递消息
this.msgSender.send(msg, 1, user_id.toString());
}
}
测试类
package com.javacode2018.tx.demo11;
import org.junit.Before;
import org.junit.Test;
import org.springframework.context.annotation.AnnotationConfigApplicationContext;
import org.springframework.jdbc.core.JdbcTemplate;
/**
* 公众号:路人甲Java,工作10年的前阿里P7分享Java、算法、数据库方面的技术干货!
* 个人博客
*/
public class Demo11Test {
private AnnotationConfigApplicationContext context;
private UserService userService;
private JdbcTemplate jdbcTemplate;
@Before
public void before() {
this.context = new AnnotationConfigApplicationContext(MainConfig11.class);
userService = context.getBean(UserService.class);
this.jdbcTemplate = context.getBean(“jdbcTemplate”, JdbcTemplate.class);
jdbcTemplate.update(“truncate table t_user”);
jdbcTemplate.update(“truncate table t_msg”);
jdbcTemplate.update(“truncate table t_msg_order”);
}
@Test
public void test1() {
this.userService.register(1L, “路人”);
}
}
运行输出
用户注册:[user_id:1,user_name:路人]
插入消息:MsgModel(id=1, msg=[user_id:1,user_name:路人], msg_order_id=1, status=0)
准备投递消息,{msg_id:1}
投递消息:MsgModel(id=1, msg=[user_id:1,user_name:路人], msg_order_id=1, status=0)
3.3.2、场景2:业务失败,消息取消投递
UserService中添加代码
手动抛出异常,让事务回滚。
/**
* 模拟用户注册失败,咱们通过弹出异常让事务回滚,结果也会导致消息发送被取消
* @param user_id
* @param user_name
*/
@Transactional
public void registerFail(Long user_id, String user_name) {
this.register(user_id, user_name);
throw new RuntimeException(“故意失败!”);
}
Demo11Test添加用例
@Test
public void test2() {
this.userService.registerFail(1L, “张三”);
}
运行输出
弹出了异常,信息比较多,我们截了关键的部分,如下,可以看出事务被回滚了,消息被取消投递了。
用户注册:[user_id:1,user_name:张三]
插入消息:MsgModel(id=1, msg=[user_id:1,user_name:张三], msg_order_id=1, status=0)
准备取消投递消息,{msg_id:1}
取消投递消息:MsgModel(id=1, msg=[user_id:1,user_name:张三], msg_order_id=1, status=0)
java.lang.RuntimeException: 故意失败!
at com.javacode2018.tx.demo11.UserService.registerFail(UserService.java:44)
at com.javacode2018.tx.demo11.UserService F a s t C l a s s B y S p r i n g C G L I B FastClassBySpringCGLIB FastClassBySpringCGLIB5dd21f5c.invoke()
3.3.3、嵌套事务
事务发送是跟随当前所在的事务的,当前事务提交了,消息一定会被投递出去,当前事务是不,消息会被取消投递。
下面看嵌套事务的代码
UserService中添加代码
注意下面方法的事务传播行为是:REQUIRES_NEW,当前如果有事务,会重启一个事务。
//事务传播属性是REQUIRES_NEW,会在独立的事务中运行
@Transactional(propagation = Propagation.REQUIRES_NEW)
public void registerRequiresNew(Long user_id, String user_name) {
this.register(user_id, user_name);
}
添加一个类UserService1
package com.javacode2018.tx.demo11;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.transaction.annotation.Transactional;
/**
* 公众号:路人甲Java,工作10年的前阿里P7分享Java、算法、数据库方面的技术干货!
* 个人博客
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则几千的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Java开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)
![img](https://i-blog.csdnimg.cn/blog_migrate/b071dc7df91ce927b34fe034f0488894.jpeg)
学习分享,共勉
这里是小编拿到的学习资源,其中包括“中高级Java开发面试高频考点题笔记300道.pdf”和“Java核心知识体系笔记.pdf”文件分享,内容丰富,囊括了JVM、锁、并发、Java反射、Spring原理、微服务、Zookeeper、数据库、数据结构等大量知识点。同时还有Java进阶学习的知识笔记脑图(内含大量学习笔记)!
资料整理不易,读者朋友可以转发分享下!
Java核心知识体系笔记.pdf
中高级Java开发面试高频考点题笔记300道.pdf
架构进阶面试专题及架构学习笔记脑图
Java架构进阶学习视频分享
《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》,点击传送门即可获取!
朋友,同时减轻大家的负担。**[外链图片转存中…(img-EPFLE7nI-1711860316442)]
[外链图片转存中…(img-WzZe1SuJ-1711860316442)]
[外链图片转存中…(img-andEygI5-1711860316443)]
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)
![img](https://i-blog.csdnimg.cn/blog_migrate/b071dc7df91ce927b34fe034f0488894.jpeg)
学习分享,共勉
这里是小编拿到的学习资源,其中包括“中高级Java开发面试高频考点题笔记300道.pdf”和“Java核心知识体系笔记.pdf”文件分享,内容丰富,囊括了JVM、锁、并发、Java反射、Spring原理、微服务、Zookeeper、数据库、数据结构等大量知识点。同时还有Java进阶学习的知识笔记脑图(内含大量学习笔记)!
资料整理不易,读者朋友可以转发分享下!
Java核心知识体系笔记.pdf
[外链图片转存中…(img-fx3oBXyY-1711860316443)]
中高级Java开发面试高频考点题笔记300道.pdf
[外链图片转存中…(img-ECxzimyZ-1711860316444)]
架构进阶面试专题及架构学习笔记脑图
[外链图片转存中…(img-3YYJOuSp-1711860316444)]
Java架构进阶学习视频分享
《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》,点击传送门即可获取!