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

上一章主要是初步实现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@DynamicUpdate
public 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;
	}

	@Override
	public 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;

	@Override
	protected TransactionManager locateTransactionManager() {
		return transactionManager;
	}

	@Override
	protected 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
@EnableTransactionManagement
public 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 {
	@Autowired
	private 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 
{
	@Autowired
	private UserService userService;
	
	@Test
	public 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
 *
 */
@Configuration
public 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
	 */
	@Bean
	public 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,默认false
		jmsTemplate.setExplicitQosEnabled(true);
		//DeliveryMode.NON_PERSISTENT=1:非持久 ; DeliveryMode.PERSISTENT=2:持久
		jmsTemplate.setDeliveryMode(DeliveryMode.PERSISTENT);
		//默认不开启事务
		System.out.println("默认是否开启事务:"+jmsTemplate.isSessionTransacted());
		//如果不启用事务,则会导致XA事务失效;
		//作为生产者如果需要支持事务,则需要配置SessionTransacted为true
		jmsTemplate.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
@EnableJms
public class JmsBaseConfiguration {
	/**
	 * 定义点对点队列
	 * @return
	 */
	@Bean
	public Queue queue() {
		return new ActiveMQQueue("my.queue");
	}
	
}
2.3.2、优先定义user用户保存和消息发送在一个事务中
@Service("userService")
public class UserService {
	@Autowired
	private UserRepository userRepository;
	@Autowired
	private JmsMessagingTemplate jmsMessagingTemplate;
	@Autowired
	private 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 
{
	@Autowired
	private UserService userService;
	
	@Test
	public 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、调整单元测试代码,提供一个可保存的正常数据,看看是否能够新增:
@Test
	public 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单应用分布式事务”内容。



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值