之前写的springboot+mybatis实现多数据源存在事务管理问题,这次用springboot+jta+atomikos+mybatis 分布式事物管理来解决只有主事务生效的问题。
项目结构
pom.xml配置
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.ldg</groupId>
<artifactId>springboot_02</artifactId>
<version>1.0-SNAPSHOT</version>
<packaging>war</packaging>
<name>springboot_02 Maven Webapp</name>
<!-- FIXME change it to the project's website -->
<url>http://www.example.com</url>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.7</maven.compiler.source>
<maven.compiler.target>1.7</maven.compiler.target>
</properties>
<!--父工程-->
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.9.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.11</version>
<scope>test</scope>
</dependency>
<!-- SpringBoot 核心组件 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-tomcat</artifactId>
</dependency>
<dependency>
<groupId>org.apache.tomcat.embed</groupId>
<artifactId>tomcat-embed-jasper</artifactId>
</dependency>
<!-- JDBC -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!-- 数据库驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<!-- mybaties -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.1.1</version>
</dependency>
<!--使用springboot+jta+atomikos 分布式事物管理解决方案-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jta-atomikos</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
<build>
<finalName>springboot_02</finalName>
<pluginManagement><!-- lock down plugins versions to avoid using Maven defaults (may be moved to parent pom) -->
<plugins>
<plugin>
<artifactId>maven-clean-plugin</artifactId>
<version>3.1.0</version>
</plugin>
<!-- see http://maven.apache.org/ref/current/maven-core/default-bindings.html#Plugin_bindings_for_war_packaging -->
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<version>3.0.2</version>
</plugin>
<plugin>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.8.0</version>
</plugin>
<plugin>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.22.1</version>
</plugin>
<plugin>
<artifactId>maven-war-plugin</artifactId>
<version>3.2.2</version>
</plugin>
<plugin>
<artifactId>maven-install-plugin</artifactId>
<version>2.5.2</version>
</plugin>
<plugin>
<artifactId>maven-deploy-plugin</artifactId>
<version>2.8.2</version>
</plugin>
</plugins>
</pluginManagement>
<!--扫描java下的所有.xml文件-->
<resources>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
</resource>
</resources>
</build>
</project>
application.properties配置
# springboot数据库
mysql.datasource.springboot.url = jdbc:mysql:///springboot?useUnicode=true&characterEncoding=utf-8
mysql.datasource.springboot.username = ldg
mysql.datasource.springboot.password = 714838871
mysql.datasource.springboot.minPoolSize = 3
mysql.datasource.springboot.maxPoolSize = 25
mysql.datasource.springboot.maxLifetime = 20000
mysql.datasource.springboot.borrowConnectionTimeout = 30
mysql.datasource.springboot.loginTimeout = 30
mysql.datasource.springboot.maintenanceInterval = 60
mysql.datasource.springboot.maxIdleTime = 60
mysql.datasource.springboot.testQuery = select 1
#springboottest01
mysql.datasource.springboottest01.url =jdbc:mysql:///springboottest01?useUnicode=true&characterEncoding=utf-8
mysql.datasource.springboottest01.username =ldg
mysql.datasource.springboottest01.password =714838871
mysql.datasource.springboottest01.minPoolSize = 3
mysql.datasource.springboottest01.maxPoolSize = 25
mysql.datasource.springboottest01.maxLifetime = 20000
mysql.datasource.springboottest01.borrowConnectionTimeout = 30
mysql.datasource.springboottest01.loginTimeout = 30
mysql.datasource.springboottest01.maintenanceInterval = 60
mysql.datasource.springboottest01.maxIdleTime = 60
mysql.datasource.springboottest01.testQuery = select 1
由于两个数据源有很多相同代码 以下都只给出主数据源的代码
其中一个Dbcofig配置
package com.ldg.dbconfig;
import org.springframework.boot.context.properties.ConfigurationProperties;
//从applicationconfig.xml中读取对应的数据封装到模型中
@ConfigurationProperties("mysql.datasource.springboot")
public class Dbconfig {
private String url;
private String username;
private String password;
private int minPoolSize;
private int maxPoolSize;
private int maxLifetime;
private int borrowConnectionTimeout;
private int loginTimeout;
private int maintenanceInterval;
private int maxIdleTime;
private String testQuery;
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public int getMinPoolSize() {
return minPoolSize;
}
public void setMinPoolSize(int minPoolSize) {
this.minPoolSize = minPoolSize;
}
public int getMaxPoolSize() {
return maxPoolSize;
}
public void setMaxPoolSize(int maxPoolSize) {
this.maxPoolSize = maxPoolSize;
}
public int getMaxLifetime() {
return maxLifetime;
}
public void setMaxLifetime(int maxLifetime) {
this.maxLifetime = maxLifetime;
}
public int getBorrowConnectionTimeout() {
return borrowConnectionTimeout;
}
public void setBorrowConnectionTimeout(int borrowConnectionTimeout) {
this.borrowConnectionTimeout = borrowConnectionTimeout;
}
public int getLoginTimeout() {
return loginTimeout;
}
public void setLoginTimeout(int loginTimeout) {
this.loginTimeout = loginTimeout;
}
public int getMaintenanceInterval() {
return maintenanceInterval;
}
public void setMaintenanceInterval(int maintenanceInterval) {
this.maintenanceInterval = maintenanceInterval;
}
public int getMaxIdleTime() {
return maxIdleTime;
}
public void setMaxIdleTime(int maxIdleTime) {
this.maxIdleTime = maxIdleTime;
}
public String getTestQuery() {
return testQuery;
}
public void setTestQuery(String testQuery) {
this.testQuery = testQuery;
}
}
IUserMapper接口
package com.ldg.mapper;
import com.ldg.model.User;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;
public interface IUserMapper {
//注解配置
//会自动与注解生成代理
// @Insert("insert test values(#{username},#{password})")
// public void save(@Param("username") String username, @Param("password") String password);
//
// @Select("select * from test where username=#{a}")
// public User foundUser(@Param("a") String username);
public void save(String username, String password);
public User foundUser(String username);
}
IUserMapper.xml
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.ldg.mapper.IUserMapper" >
<insert id="save">
insert into test (username,password) VALUES(#{0},#{1})
</insert>
<select id="foundUser" resultType="com.ldg.model.User" parameterType="string">
select * from test where username = #{username}
</select>
</mapper>
Datasource 主数据源
package com.ldg.datasource;
import com.atomikos.jdbc.AtomikosDataSourceBean;
import com.ldg.dbconfig.Dbconfig;
import com.mysql.jdbc.jdbc2.optional.MysqlXADataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import javax.sql.DataSource;
import java.sql.SQLException;
//springboot的数据源配置
// 注解到springboot容器中 会将被@bean注解的方法生成对应bean 放入springboot容器中
@Configuration
@MapperScan(basePackages="com.ldg.mapper",sqlSessionFactoryRef="test1SqlSessionFactory")
public class DataSource01 {
// 配置数据源 且在其中生成了事务管理
// 配置主数据源
@Primary
@Bean(name = "test1DataSource")
public DataSource testDataSource(Dbconfig testConfig) throws SQLException {
MysqlXADataSource mysqlXaDataSource = new MysqlXADataSource();
mysqlXaDataSource.setUrl(testConfig.getUrl());
mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true);
mysqlXaDataSource.setPassword(testConfig.getPassword());
mysqlXaDataSource.setUser(testConfig.getUsername());
mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true);
AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean();
xaDataSource.setXaDataSource(mysqlXaDataSource);
xaDataSource.setUniqueResourceName("test1DataSource");
xaDataSource.setMinPoolSize(testConfig.getMinPoolSize());
xaDataSource.setMaxPoolSize(testConfig.getMaxPoolSize());
xaDataSource.setMaxLifetime(testConfig.getMaxLifetime());
xaDataSource.setBorrowConnectionTimeout(testConfig.getBorrowConnectionTimeout());
xaDataSource.setLoginTimeout(testConfig.getLoginTimeout());
xaDataSource.setMaintenanceInterval(testConfig.getMaintenanceInterval());
xaDataSource.setMaxIdleTime(testConfig.getMaxIdleTime());
xaDataSource.setTestQuery(testConfig.getTestQuery());
return xaDataSource;
}
//生成
@Bean(name = "test1SqlSessionFactory")
public SqlSessionFactory testSqlSessionFactory(@Qualifier("test1DataSource") DataSource dataSource)
throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
return bean.getObject();
}
//sqlsessionTemplate会话模板
@Bean(name = "test1SqlSessionTemplate")
public SqlSessionTemplate testSqlSessionTemplate(
@Qualifier("test1SqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
UserserviceImp02配置
package com.ldg.service.imp;
import com.ldg.mapper.IUserMapper;
import com.ldg.model.User;
import com.ldg.service.IUserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
//操作springboot数据库的服务
@Service("test01")
@Transactional
public class UserServiceImp2 implements IUserService {
//会自动注入生成的代理对象 关联springboot
@Autowired
private IUserMapper iUserMapper;
@Override
public void save(String username, String password) {
iUserMapper.save(username, password);
}
@Override
public User foundUser(String username) {
return iUserMapper.foundUser(username);
}
}
UserController配置
package com.ldg.web.controller;
import com.ldg.model.User;
import com.ldg.service.IUserService;
import org.apache.ibatis.annotations.Param;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
@RestController
@RequestMapping("/test")
public class UserController {
@Autowired
@Qualifier("test01")
private IUserService iUserService;
@RequestMapping("/one")
public String test() {
return "one";
}
@RequestMapping("/save/{username}/{password}")
public String save(@PathVariable String username, @PathVariable String password) {
iUserService.save(username, password);
return "success";
}
@RequestMapping("/get/{username}")
@ResponseBody
public User get(@PathVariable String username) {
return iUserService.foundUser(username);
}
@RequestMapping("/savetwo/{username}/{password}")
public String saveTwo(@PathVariable String username, @PathVariable String password) {
iUserService.save(username, password);
return "success";
}
@RequestMapping("/gettwo/{username}")
@ResponseBody
public User getTwo(@PathVariable String username) {
return iUserService.foundUser(username);
}
}
App配置
import com.ldg.dbconfig.Dbconfig;
import com.ldg.dbconfig.Dbconfig01;
import com.ldg.web.controller.UserController;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.ComponentScan;
//@MapperScan(basePackages = "com.ldg.mapper")
@ComponentScan(basePackages = {"com.ldg.dbconfig","com.ldg.service", "com.ldg.web","com.ldg.datasource"})
//@EnableAutoConfiguration可以帮助SpringBoot应用将所有符合条件的@Configuration配置都加载到当前SpringBoot创建并使用的IoC容器。
@EnableAutoConfiguration()
//@EnableConfigurationProperties注解的作用是:使使用 @ConfigurationProperties 注解的类生效。该注解在Dbconfig上
@EnableConfigurationProperties(value = {Dbconfig.class, Dbconfig01.class})
public class App {
public static void main(String[] args) {
SpringApplication.run(App.class, args);
}
}
jta+atomikos 在配置数据源的时候就自动生成了事务管理且且这些事务管理都能运用到相应的数据源中,不存在设置主键:只有主事务生效的情况.