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

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中添加代码
先自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Java工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!

因此收集整理了一份《Java开发全套学习资料》送给大家,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

img

img

img

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

如果你觉得这些内容对你有帮助,可以扫码领取!

img

那么如何才能正确的掌握Redis呢?

为了让大家能够在Redis上能够加深,所以这次给大家准备了一些Redis的学习资料,还有一些大厂的面试题,包括以下这些面试题

  • 并发编程面试题汇总

  • JVM面试题汇总

  • Netty常被问到的那些面试题汇总

  • Tomcat面试题整理汇总

  • Mysql面试题汇总

  • Spring源码深度解析

  • Mybatis常见面试题汇总

  • Nginx那些面试题汇总

  • Zookeeper面试题汇总

  • RabbitMQ常见面试题汇总

JVM常频面试:

Redis高频面试笔记:基础+缓存雪崩+哨兵+集群+Reids场景设计

Mysql面试题汇总(一)

Redis高频面试笔记:基础+缓存雪崩+哨兵+集群+Reids场景设计

Mysql面试题汇总(二)

Redis高频面试笔记:基础+缓存雪崩+哨兵+集群+Reids场景设计

Redis常见面试题汇总(300+题)

Redis高频面试笔记:基础+缓存雪崩+哨兵+集群+Reids场景设计
DHc-1711460053027)]

[外链图片转存中…(img-xO1ScXYD-1711460053028)]

[外链图片转存中…(img-vqPGygmk-1711460053028)]

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

如果你觉得这些内容对你有帮助,可以扫码领取!

img

那么如何才能正确的掌握Redis呢?

为了让大家能够在Redis上能够加深,所以这次给大家准备了一些Redis的学习资料,还有一些大厂的面试题,包括以下这些面试题

  • 并发编程面试题汇总

  • JVM面试题汇总

  • Netty常被问到的那些面试题汇总

  • Tomcat面试题整理汇总

  • Mysql面试题汇总

  • Spring源码深度解析

  • Mybatis常见面试题汇总

  • Nginx那些面试题汇总

  • Zookeeper面试题汇总

  • RabbitMQ常见面试题汇总

JVM常频面试:

[外链图片转存中…(img-54M7CgTB-1711460053029)]

Mysql面试题汇总(一)

[外链图片转存中…(img-PtlW2Rrn-1711460053029)]

Mysql面试题汇总(二)

[外链图片转存中…(img-4Ro2Hb8e-1711460053029)]

Redis常见面试题汇总(300+题)

[外链图片转存中…(img-r5ErWGeg-1711460053029)]
需要更多Java资料的小伙伴可以帮忙点赞+关注,点击传送门,即可免费领取!

  • 13
    点赞
  • 22
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值