Spring系列第49篇:通过Spring事务实现MQ中的事务消息

@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开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。img

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

学习分享,共勉

这里是小编拿到的学习资源,其中包括“中高级Java开发面试高频考点题笔记300道.pdf”和“Java核心知识体系笔记.pdf”文件分享,内容丰富,囊括了JVM、锁、并发、Java反射、Spring原理、微服务、Zookeeper、数据库、数据结构等大量知识点。同时还有Java进阶学习的知识笔记脑图(内含大量学习笔记)!

资料整理不易,读者朋友可以转发分享下!

Java核心知识体系笔记.pdf

记一次蚂蚁金服Java研发岗的面试经历,分享下我的复习笔记面经

中高级Java开发面试高频考点题笔记300道.pdf

记一次蚂蚁金服Java研发岗的面试经历,分享下我的复习笔记面经

架构进阶面试专题及架构学习笔记脑图

记一次蚂蚁金服Java研发岗的面试经历,分享下我的复习笔记面经

Java架构进阶学习视频分享
《一线大厂Java面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》点击传送门即可获取!
朋友,同时减轻大家的负担。**[外链图片转存中…(img-EPFLE7nI-1711860316442)]

[外链图片转存中…(img-WzZe1SuJ-1711860316442)]

[外链图片转存中…(img-andEygI5-1711860316443)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Java开发知识点,真正体系化!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

如果你觉得这些内容对你有帮助,可以扫码获取!!(备注Java获取)

img

学习分享,共勉

这里是小编拿到的学习资源,其中包括“中高级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面试题解析+核心总结学习笔记+最新讲解视频+实战项目源码》点击传送门即可获取!

  • 16
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值