生产者实现应用内分布式事务管理

来源:互联网 发布:java简单小游戏源代码 编辑:程序博客网 时间:2024/06/06 02:54
上一章主要是初步实现ActiveMQ与Springboot的集成,本章开始将重点专注在ActiveMQ的事务管理,作为生产者很多的业务场景需要对消息发送进行事务管理(即生产不一定发送),本章主要实践的案例将结合MySQL实现应用内分布式事务管理。

本章概要
1、纯MySQL事务管理
1.1、添加依赖;
1.2、添加待测试用户使用的实体User及其repository定义;(采用Spring-data-jpa实现持久层)
1.3、配置MySQL数据源;
1.4、单元测试验证;
2、加入MQ生产者验证
2.1、设置MQ服务地址;
2.2、自定义实现JmsTemplate支持事务;
2.3、将User保存和消息发送放入同一个事务方法;
2.4、单元测试;

纯MySQL事务管理

1.1、添加依赖:
<properties>
<httpclient.version>4.5.1</httpclient.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.4.0.RELEASE</version>
</parent>

<dependencies>
<!-- Test -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- end -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-devtools</artifactId>
<optional>true</optional>
</dependency>
<!-- end -->
<!-- activeMq support -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-activemq</artifactId>
</dependency>
<!-- activeMq end -->
<!-- 数据库连接池 -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.0.24</version>
</dependency>
<!-- end -->
<!-- jdbc driver 6.0.4 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.39</version>
<!-- <scope>runtime</scope> -->
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- end -->

<!-- atomikos实现分布式事务 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jta-atomikos</artifactId>
</dependency>
<!-- end -->

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<configuration>
<source>1.7</source>
<target>1.7</target>
<fork>true</fork><!-- 如果没有该项配置,devtools不会起作用,即应用不会restart -->
</configuration>
</plugin>
<!-- 打包时跳过test -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<configuration>
<skip>true</skip>
</configuration>
</plugin>

<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<configuration>
<source>1.7</source>
<target>1.7</target>
</configuration>
</plugin>
</plugins>
</build>

特别注意:如果我们的服务启动后立即就shutdown,加入下面的依赖即可亦或是启动一个定时任务均可,否则将作为一个客户端应用程序shutdown。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

1.2、添加待测试用户使用的实体User及其repository定义(采用Spring-data-jpa实现持久层):
1.2.1、实体定义:
package com.shf.activemq.user.entity;import java.io.Serializable;import java.util.Date;import javax.persistence.Column;import javax.persistence.Entity;import javax.persistence.GeneratedValue;import javax.persistence.GenerationType;import javax.persistence.Id;import javax.persistence.Table;import javax.persistence.Temporal;import javax.persistence.TemporalType;import org.hibernate.annotations.DynamicInsert;import org.hibernate.annotations.DynamicUpdate;/** * 用户Entity * @author shf */@Entity@Table(name = "t_sys_user")@DynamicInsert@DynamicUpdatepublic class User  implements Serializable{private static final long serialVersionUID = -2485725404415853576L;private int id;private String loginName;// 登录名private String password;// 密码private String name;// 姓名private Date birthday;     //出生年月private String email;// 邮箱public User() {super();}public User(int id) {this();this.id = id;}public User(String loginName,String password) {this();this.loginName = loginName;this.password=password;}@Id@GeneratedValue(strategy=GenerationType.AUTO)public int getId() {return id;}public void setId(int id) {this.id = id;}//此时culumn中name属性不宜用loginName,建议使用loginname,否则在部分连接池应用下会自动被转为login_Name@Column(name="loginname",length=5)public String getLoginName() {return loginName;}public void setLoginName(String loginName) {this.loginName = loginName;}public String getPassword() {return password;}public void setPassword(String password) {this.password = password;}public String getName() {return name;}public void setName(String name) {this.name = name;}public String getEmail() {return email;}@Temporal(TemporalType.DATE)public Date getBirthday() {return birthday;}public void setBirthday(Date birthday) {this.birthday = birthday;}public void setEmail(String email) {this.email = email;}@Overridepublic String toString() {return "User [id=" + id + ", loginName=" + loginName + ", password=" + password + ", name=" + name+ ", birthday=" + birthday + ", email=" + email + "]";}}
1.2.2、集成PagingAndSortingRepository持久层实现:
package com.shf.activemq.user.repository;import java.util.List;import org.springframework.data.jpa.repository.Modifying;import org.springframework.data.jpa.repository.Query;import org.springframework.data.repository.PagingAndSortingRepository;import org.springframework.data.repository.query.Param;import com.shf.activemq.user.entity.User;public interface UserRepository extends PagingAndSortingRepository<User,Integer>{@Modifying@Query("update User u set u.loginName=:loginName where u.id=:id")public int update(@Param("loginName")String loginName,@Param("id")int id);@Query("select t from User t ")    public List<User> getList();}
小结:在实体定义中约定了loginname不能大于5,故在后续的验证中即可通过其长度约束直观测试。

1.3、配置AtomikosJtaPlatform,通过Atomikos支持分布式事务:
package com.shf.activemq.config;import javax.transaction.TransactionManager;import javax.transaction.UserTransaction;import org.hibernate.engine.transaction.jta.platform.internal.AbstractJtaPlatform;/** * 配置AtomikosJtaPlatform,通过Atomikos支持分布式事务 * @author song * */public class AtomikosJtaPlatform extends AbstractJtaPlatform {private static final long serialVersionUID = 1L;static TransactionManager transactionManager;static UserTransaction transaction;@Overrideprotected TransactionManager locateTransactionManager() {return transactionManager;}@Overrideprotected UserTransaction locateUserTransaction() {return transaction;}}
1.4、配置分布式事务管理器:
package com.shf.activemq.config;import javax.transaction.TransactionManager;import javax.transaction.UserTransaction;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.context.annotation.DependsOn;import org.springframework.orm.jpa.JpaVendorAdapter;import org.springframework.orm.jpa.vendor.Database;import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;import org.springframework.transaction.PlatformTransactionManager;import org.springframework.transaction.annotation.EnableTransactionManagement;import org.springframework.transaction.jta.JtaTransactionManager;import com.atomikos.icatch.jta.UserTransactionImp;import com.atomikos.icatch.jta.UserTransactionManager;/** * 配置分布式事务管理器 * @author song */@Configuration@EnableTransactionManagementpublic class JTATransactionConfig {@Bean    public JpaVendorAdapter jpaVendorAdapter() {        HibernateJpaVendorAdapter hibernateJpaVendorAdapter = new HibernateJpaVendorAdapter();        hibernateJpaVendorAdapter.setShowSql(true);        hibernateJpaVendorAdapter.setGenerateDdl(true);        hibernateJpaVendorAdapter.setDatabase(Database.MYSQL);        return hibernateJpaVendorAdapter;    }    @Bean(name = "userTransaction")    public UserTransaction userTransaction() throws Throwable {        UserTransactionImp userTransactionImp = new UserTransactionImp();        userTransactionImp.setTransactionTimeout(10000);        return userTransactionImp;    }    @Bean(name = "atomikosTransactionManager", initMethod = "init", destroyMethod = "close")    public TransactionManager atomikosTransactionManager() throws Throwable {        UserTransactionManager userTransactionManager = new UserTransactionManager();        userTransactionManager.setForceShutdown(false);        AtomikosJtaPlatform.transactionManager = userTransactionManager;        return userTransactionManager;    }    @Bean(name = "transactionManagerJTA")    @DependsOn({ "userTransaction", "atomikosTransactionManager"})    public PlatformTransactionManager transactionManager() throws Throwable {        UserTransaction userTransaction = userTransaction();        AtomikosJtaPlatform.transaction = userTransaction;        TransactionManager atomikosTransactionManager = atomikosTransactionManager();        return new JtaTransactionManager(userTransaction, atomikosTransactionManager);    }}

1.5、配置MySQL数据源:
package com.shf.activemq.config;import java.util.HashMap;import javax.persistence.EntityManager;import javax.sql.DataSource;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.context.annotation.DependsOn;import org.springframework.data.jpa.repository.config.EnableJpaRepositories;import org.springframework.orm.jpa.JpaVendorAdapter;import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;import com.atomikos.jdbc.AtomikosDataSourceBean;import com.mysql.jdbc.jdbc2.optional.MysqlXADataSource;/** * 关系型数据库配置 * @author song * */@Configuration@DependsOn("transactionManagerJTA")//必须加@EnableJpaRepositories(basePackages = {"com.shf.activemq.user.*"}, entityManagerFactoryRef = "entityManagerFactoryJ1", transactionManagerRef = "transactionManagerJTA")public class MySQLDataSourceConfig {@Autowired    private JpaVendorAdapter jpaVendorAdapter;    @Bean(name = "dataSourceJ1", initMethod = "init", destroyMethod = "close")    public DataSource dataSourceJ1() {        MysqlXADataSource mysqlXaDataSource = new MysqlXADataSource();        mysqlXaDataSource.setUrl("jdbc:mysql://localhost:3306/springboot1");        mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true);        mysqlXaDataSource.setPassword("root");        mysqlXaDataSource.setUser("root");        mysqlXaDataSource.setCharacterEncoding("UTF-8");        mysqlXaDataSource.setUseFastDateParsing(true);        mysqlXaDataSource.setUseUnicode(true);                AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean();        xaDataSource.setXaDataSource(mysqlXaDataSource);        xaDataSource.setUniqueResourceName("xads1");        xaDataSource.setBorrowConnectionTimeout(10);        xaDataSource.setMaxPoolSize(20);        xaDataSource.setMinPoolSize(10);        xaDataSource.setMaintenanceInterval(60);        xaDataSource.setTestQuery("SELECT 1");                return xaDataSource;    }    //------------------方式一-----------------------    @Bean(name = "entityManagerFactoryJ1")    public LocalContainerEntityManagerFactoryBean entityManagerFactoryJ1() throws Throwable {    System.out.println("entityManagerFactoryJ1 create");    HashMap<String, Object> properties = new HashMap<String, Object>();properties.put("hibernate.transaction.jta.platform", AtomikosJtaPlatform.class.getName());properties.put("javax.persistence.transactionType", "JTA");        LocalContainerEntityManagerFactoryBean entityManager = new LocalContainerEntityManagerFactoryBean();        entityManager.setDataSource(dataSourceJ1());        entityManager.setJpaVendorAdapter(jpaVendorAdapter);        entityManager.setPackagesToScan("com.shf.activemq.user.*");        entityManager.setPersistenceUnitName("persistenceUnitJ1");        entityManager.setJpaPropertyMap(properties);        return entityManager;    }    @Bean(name = "entityManagerJ1")    public EntityManager entityManagerJ1() throws Throwable {        return entityManagerFactoryJ1().getObject().createEntityManager();    }    }
1.6、在未加入ActiveMQ之前,首先编写如下UserService测试当时保存用户事务是否生效:
package com.shf.activemq.user.service;import java.util.ArrayList;import java.util.List;import javax.jms.Queue;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.jms.core.JmsMessagingTemplate;import org.springframework.stereotype.Service;import org.springframework.transaction.annotation.Propagation;import org.springframework.transaction.annotation.Transactional;import com.shf.activemq.user.entity.User;import com.shf.activemq.user.repository.UserRepository;/** * 用户Service * @author song * */@Service("userService")public class UserService {@Autowiredprivate UserRepository userRepository;@Transactional(value="transactionManagerJTA",propagation=Propagation.REQUIRED,rollbackFor=Exception.class)public List<User> bathSaveJTA(List<User> userList) throws Exception {try{List<User> list=new ArrayList<User>(10);for(User user:userList){list.add(userRepository.save(user));}return list;}catch(Exception e){throw new Exception("出现异常了");}}}
1.7、单元测试验证事务
@RunWith(SpringJUnit4ClassRunner.class)@SpringBootTest(classes={AppProducer.class})//加载启动类public class AppTest {@Autowiredprivate UserService userService;@Testpublic void testUserSave(){try{List<User> list=new ArrayList<User>(10);list.add(new User("111","111"));list.add(new User("223212","222"));list.add(new User("333","333"));userService.bathSaveJTA(list);}catch(Exception e){e.printStackTrace();}}}

小结:通过数据库和控制台可以看到事务生效

加入MQ生产者验证

2.1、设置MQ服务地址:
# ACTIVEMQ
spring.activemq.broker-url=tcp://localhost:61626

2.2、自定义实现JmsTemplate支持事务:
通过阅读源码中实现自动装配的ActiveMQAutoConfiguration类,可以找到JmsAutoConfiguration、ActiveMQXAConnectionFactoryConfiguration(特别注意,在自动装配中其实并没有被执行,其需要@ConditionalOnBean({ XAConnectionFactoryWrapper.class })依赖,默认情况下并没有注册XAConnectionFactoryWrapper的bean实例)等重要配置,在JmsAutoConfiguration中默认的JmsTemplate没有支持事务,故需要实现自定义JmsTemplate的bean实例,实现如下:
package com.shf.activemq.config;import javax.jms.ConnectionFactory;import javax.jms.DeliveryMode;import javax.jms.Session;import org.springframework.beans.factory.ObjectProvider;import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;import org.springframework.boot.autoconfigure.condition.ConditionalOnSingleCandidate;import org.springframework.boot.autoconfigure.jms.JmsProperties;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.jms.core.JmsTemplate;import org.springframework.jms.support.converter.MessageConverter;import org.springframework.jms.support.destination.DestinationResolver;/** * 自定义JmsTemplate,支持事务 * @author song * */@Configurationpublic class JmsTemplateConfiguration {private final JmsProperties properties;private final ObjectProvider<DestinationResolver> destinationResolver;private final ObjectProvider<MessageConverter> messageConverter;public JmsTemplateConfiguration(JmsProperties properties,ObjectProvider<DestinationResolver> destinationResolver,ObjectProvider<MessageConverter> messageConverter) {this.properties = properties;this.destinationResolver = destinationResolver;this.messageConverter = messageConverter;}/** * 配置生产者的JmsTemplate * @param connectionFactory * @return */@Beanpublic JmsTemplate jmsTemplate(ConnectionFactory connectionFactory) {JmsTemplate jmsTemplate = new JmsTemplate(connectionFactory);jmsTemplate.setPubSubDomain(this.properties.isPubSubDomain());DestinationResolver destinationResolver = (DestinationResolver) this.destinationResolver.getIfUnique();if (destinationResolver != null) {jmsTemplate.setDestinationResolver(destinationResolver);}MessageConverter messageConverter = (MessageConverter) this.messageConverter.getIfUnique();if (messageConverter != null) {jmsTemplate.setMessageConverter(messageConverter);}//deliveryMode, priority, timeToLive 的开关,要生效,必须配置为true,默认falsejmsTemplate.setExplicitQosEnabled(true);//DeliveryMode.NON_PERSISTENT=1:非持久 ; DeliveryMode.PERSISTENT=2:持久jmsTemplate.setDeliveryMode(DeliveryMode.PERSISTENT);//默认不开启事务System.out.println("默认是否开启事务:"+jmsTemplate.isSessionTransacted());//如果不启用事务,则会导致XA事务失效;//作为生产者如果需要支持事务,则需要配置SessionTransacted为truejmsTemplate.setSessionTransacted(true);return jmsTemplate;}}
2.3、将User保存和消息发送放入同一个事务方法:
2.3.1、定义一个P2P队列:
import javax.jms.Queue;import org.apache.activemq.command.ActiveMQQueue;import org.springframework.context.annotation.Bean;import org.springframework.context.annotation.Configuration;import org.springframework.jms.annotation.EnableJms;@Configuration@EnableJmspublic class JmsBaseConfiguration {/** * 定义点对点队列 * @return */@Beanpublic Queue queue() {return new ActiveMQQueue("my.queue");}}
2.3.2、优先定义user用户保存和消息发送在一个事务中
@Service("userService")public class UserService {@Autowiredprivate UserRepository userRepository;@Autowiredprivate JmsMessagingTemplate jmsMessagingTemplate;@Autowiredprivate Queue queue;/** * 测试分布式事务 * @param userList * @return */@Transactional(value="transactionManagerJTA",propagation=Propagation.REQUIRED,rollbackFor=Exception.class)public List<User> bathSaveJTA(List<User> userList) throws Exception {try{//发送队列消息this.jmsMessagingTemplate.convertAndSend(this.queue, "生产者辛苦生产的点对点消息成果");System.out.println("生产者:辛苦生产的点对点消息成果");List<User> list=new ArrayList<User>(10);for(User user:userList){list.add(userRepository.save(user));}//System.out.println(1/0);return list;}catch(Exception e){throw new Exception("出现异常了");}}}
2.4、单元测试:
@RunWith(SpringJUnit4ClassRunner.class)@SpringBootTest(classes={AppProducer.class})//加载启动类public class AppTest {@Autowiredprivate UserService userService;@Testpublic void testUserSave(){try{List<User> list=new ArrayList<User>(10);list.add(new User("111","111"));list.add(new User("223212","222"));list.add(new User("333","333"));userService.bathSaveJTA(list);}catch(Exception e){e.printStackTrace();}}}
查看数据库变化:
在springboot1数据库中原始user数据

在activeMQ数据库中原始数据

activeMQ控制台

执行完成单元测试数据库和控制台没有任何数据变化,IDE控制台能够看到提示信息以及事务回滚:

提示信息

继续往上翻看控制台可以看到默认打印出来的JmsTemplate默认是不开启事务的,并且消息已经生产但由于事务回滚并没有发出


2.5、我们不采用新增用户触发异常事务回滚,如果我们的服务层仅仅处理消息发送呢,修改UserService代码如下:
/** * 测试分布式事务 * @param userList * @return */@Transactional(value="transactionManagerJTA",propagation=Propagation.REQUIRED,rollbackFor=Exception.class)public List<User> bathSaveJTA(List<User> userList) throws Exception {try{//发送队列消息this.jmsMessagingTemplate.convertAndSend(this.queue, "生产者辛苦生产的点对点消息成果");System.out.println("生产者:辛苦生产的点对点消息成果");List<User> list=new ArrayList<User>(10);//for(User user:userList){//list.add(userRepository.save(user));//}System.out.println(1/0);return list;}catch(Exception e){throw new Exception("出现异常了");}}
执行单元测试后我们也可以发现事务生效了,并没有消息发出,数据库也没有数据新增。

2.6、调整单元测试代码,提供一个可保存的正常数据,看看是否能够新增:
@Testpublic void testUserSave(){try{List<User> list=new ArrayList<User>(10);list.add(new User("111","111"));list.add(new User("22312","222"));list.add(new User("333","333"));userService.bathSaveJTA(list);}catch(Exception e){e.printStackTrace();}}

activeMQ数据库表新增了一行数据

springboot1数据库中user表新增了三行数据

activeMQ控制台

综上,通过JtaTransactionManager验证完成上一章中待完成的“MQ本地事务、MQ与MySQL单应用分布式事务”内容。



0 0
原创粉丝点击