环境
eclipse 4.7
jdk 1.8
Spring Boot 1.5.2
一、springboot整合事务
事务分类:编程事务、声明事务(XML、注解),推荐使用注解方式,springboot默认集成事物,只主要在方法上加上@Transactional即可
1、controller
package com.wjy.controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import com.wjy.test1.service.UserServiceTest1; @RestController public class UserController { @Autowired public UserServiceTest1 userServiceTest1; @RequestMapping("/insertTest1ByService") public String insertTest1ByService(String name,Integer age) { userServiceTest1.insertuser1(name, age); return "success"; } }
2、service
/** * */ package com.wjy.test1.service; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import com.wjy.test1.dao.UserMapperTest1; /** * @Desc * @author wangjy15 */ @Service public class UserServiceTest1 { @Autowired private UserMapperTest1 userMapperTest1; /** * @Description: 如果没有事务控制 那么报错之后 插入到库里的数据不会回滚 加上 注解@Transactional 就可以回滚 */ @Transactional public String insertuser1(String name,Integer age) { userMapperTest1.insert(name, age); int i =1/0; return "success"; } }
3、mapper
/** * */ package com.wjy.test1.dao; import org.apache.ibatis.annotations.Insert; import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Select; import com.wjy.entity.User; /** * @Desc * @author wangjy15 */ public interface UserMapperTest1 { @Select("SELECT * FROM users WHERE NAME = #{name}") User findByName(@Param("name") String name); @Insert("insert into users (name,age) values(#{name},#{age})") int insert(@Param("name") String name,@Param("age") Integer age); }
4、APP
package com.wjy; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class APP { public static void main(String[] args) { SpringApplication.run(APP.class, args); } }
5、测试验证
http://localhost:8080/insertTest1ByService?name=wangsan0010&age=1000
二、SpringBoot分布式事务管理
使用springboot+jta+atomikos分布式事务管理,service层有事务控制,dao层没有,一般情况下都需要调用其他数据源的dao层,这就需要进行分布式事务管理。
将多个数据源注册到atomikos进行管理,进行2PC(Two-phaseCommit)二阶段提交。
理解一下分布式事务:
对于上面一中示例做一下修改,再引入一个数据源test2,修改一下service:
/** * */ package com.wjy.test1.service; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import com.wjy.test1.dao.UserMapperTest1; import com.wjy.test2.dao.UserMapperTest2; import com.wjy.test2.service.UserServiceTest2; /** * @Desc * @author wangjy15 */ @Service public class UserServiceTest1 { @Autowired private UserMapperTest1 userMapperTest1; @Autowired private UserMapperTest2 userMapperTest2; @Autowired private UserServiceTest2 userServiceTest1; /** * @Desc: 如果没有事务控制 那么报错之后 插入到库里的数据不会回滚 加上 注解@Transactional 就可以回滚 */ @Transactional public String insertuser1(String name,Integer age) { userMapperTest1.insert(name, age); userServiceTest1.insertuser2(name, age);//有事务控制 可以回滚 int i =1/0; return "success"; } }
/** * */ package com.wjy.test2.service; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.transaction.annotation.Transactional; import com.wjy.test2.dao.UserMapperTest2; /** * @Desc * @author wangjy15 */ public class UserServiceTest2 { @Autowired private UserMapperTest2 userMapperTest2; /** * @Desc: 如果没有事务控制 那么报错之后 插入到库里的数据不会回滚 加上 注解@Transactional 就可以回滚 */ @Transactional public String insertuser2(String name,Integer age) { userMapperTest2.insert(name, age); return "success"; } }
这时test1数据库里没有插入数据,test2数据库也没有插入数据库,因为test2在service层也有事务控制。
再修改一下test1 service:
/** * */ package com.wjy.test1.service; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import com.wjy.test1.dao.UserMapperTest1; import com.wjy.test2.dao.UserMapperTest2; import com.wjy.test2.service.UserServiceTest2; /** * @Desc * @author wangjy15 */ @Service public class UserServiceTest1 { @Autowired private UserMapperTest1 userMapperTest1; @Autowired private UserMapperTest2 userMapperTest2; /** * @Desc: 如果没有事务控制 那么报错之后 插入到库里的数据不会回滚 加上 注解@Transactional 就可以回滚 */ @Transactional public String insertuser1(String name,Integer age) { userMapperTest1.insert(name, age); userMapperTest2.insert(name, age);//没有事务控制 不可以回滚 int i =1/0; return "success"; } }
这时test1数据库里没有插入数据,test2数据库有数据插入到数据库 没有回滚,因为test2在mapper层也没有事务控制。
下面引入atomikos 和springboot整合:
1、数据源配置信息
引入依赖:
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jta-atomikos</artifactId> </dependency>
# Mysql 1 mysql.datasource.test1.url = jdbc:mysql://192.168.118.102:3306/springboot?useSSL=false&autoReconnect=true&useUnicode=true&characterEncoding=utf-8 mysql.datasource.test1.username = root mysql.datasource.test1.password = 123456 mysql.datasource.test1.minPoolSize = 3 mysql.datasource.test1.maxPoolSize = 25 mysql.datasource.test1.maxLifetime = 20000 mysql.datasource.test1.borrowConnectionTimeout = 30 mysql.datasource.test1.loginTimeout = 30 mysql.datasource.test1.maintenanceInterval = 60 mysql.datasource.test1.maxIdleTime = 60 mysql.datasource.test1.testQuery = select 1 # Mysql 2 mysql.datasource.test2.url =jdbc:mysql://192.168.118.102:3306/springboot2?useSSL=false&autoReconnect=true&useUnicode=true&characterEncoding=utf-8 mysql.datasource.test2.username =root mysql.datasource.test2.password =123456 mysql.datasource.test2.minPoolSize = 3 mysql.datasource.test2.maxPoolSize = 25 mysql.datasource.test2.maxLifetime = 20000 mysql.datasource.test2.borrowConnectionTimeout = 30 mysql.datasource.test2.loginTimeout = 30 mysql.datasource.test2.maintenanceInterval = 60 mysql.datasource.test2.maxIdleTime = 60 mysql.datasource.test2.testQuery = select 1
2、数据源配置类
package com.wjy.datasource; import org.springframework.boot.context.properties.ConfigurationProperties; @ConfigurationProperties(prefix="mysql.datasource.test1") public class DBConfig1 { 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; } }
package com.wjy.datasource; import org.springframework.boot.context.properties.ConfigurationProperties; @ConfigurationProperties(prefix="mysql.datasource.test2") public class DBConfig2 { 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; } }
package com.wjy.datasource; import java.sql.SQLException; import javax.sql.DataSource; 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 com.atomikos.jdbc.AtomikosDataSourceBean; import com.mysql.jdbc.jdbc2.optional.MysqlXADataSource; ////注册到springboot容器中 @Configuration ////@MapperScan可以指定要扫描的Mapper类的包的路径 sqlSessionFactoryRef 表示定义了 key ,表示一个唯一 SqlSessionFactory 实例 @MapperScan(basePackages="com.wjy.test1",sqlSessionFactoryRef="sqlSessionFactory1") public class TestMyBatisConfig1 { @Bean(name="dataSource1") @Primary public DataSource dataSource1(DBConfig1 dbConfig1) throws SQLException { MysqlXADataSource mysqlXaDataSource = new MysqlXADataSource(); mysqlXaDataSource.setUrl(dbConfig1.getUrl()); mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true); mysqlXaDataSource.setPassword(dbConfig1.getPassword()); mysqlXaDataSource.setUser(dbConfig1.getUsername()); mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true); AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean(); xaDataSource.setXaDataSource(mysqlXaDataSource); xaDataSource.setUniqueResourceName("dataSource1"); xaDataSource.setMinPoolSize(dbConfig1.getMinPoolSize()); xaDataSource.setMaxPoolSize(dbConfig1.getMaxPoolSize()); xaDataSource.setMaxLifetime(dbConfig1.getMaxLifetime()); xaDataSource.setBorrowConnectionTimeout(dbConfig1.getBorrowConnectionTimeout()); xaDataSource.setLoginTimeout(dbConfig1.getLoginTimeout()); xaDataSource.setMaintenanceInterval(dbConfig1.getMaintenanceInterval()); xaDataSource.setMaxIdleTime(dbConfig1.getMaxIdleTime()); xaDataSource.setTestQuery(dbConfig1.getTestQuery()); return xaDataSource; } //注意 这里没有事务管理类 因为事务全部交给AtomikosDataSourceBean来管理 @Primary @Bean(name = "sqlSessionFactory1") public SqlSessionFactory sqlSessionFactory1(@Qualifier("dataSource1") DataSource dataSource) throws Exception { SqlSessionFactoryBean bean = new SqlSessionFactoryBean(); bean.setDataSource(dataSource); return bean.getObject(); } @Primary @Bean(name = "sqlSessionTemplate1") public SqlSessionTemplate sqlSessionTemplate1( @Qualifier("sqlSessionFactory1") SqlSessionFactory sqlSessionFactory) throws Exception { return new SqlSessionTemplate(sqlSessionFactory); } }
package com.wjy.datasource; import java.sql.SQLException; import javax.sql.DataSource; 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 com.atomikos.jdbc.AtomikosDataSourceBean; import com.mysql.jdbc.jdbc2.optional.MysqlXADataSource; @Configuration @MapperScan(basePackages="com.wjy.test2",sqlSessionFactoryRef="sqlSessionFactory2") public class TestMyBatisConfig2 { @Bean(name="dataSource2") public DataSource dataSource2(DBConfig2 dbConfig2) throws SQLException { MysqlXADataSource mysqlXaDataSource = new MysqlXADataSource(); mysqlXaDataSource.setUrl(dbConfig2.getUrl()); mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true); mysqlXaDataSource.setPassword(dbConfig2.getPassword()); mysqlXaDataSource.setUser(dbConfig2.getUsername()); mysqlXaDataSource.setPinGlobalTxToPhysicalConnection(true); AtomikosDataSourceBean xaDataSource = new AtomikosDataSourceBean(); xaDataSource.setXaDataSource(mysqlXaDataSource); xaDataSource.setUniqueResourceName("dataSource2"); xaDataSource.setMinPoolSize(dbConfig2.getMinPoolSize()); xaDataSource.setMaxPoolSize(dbConfig2.getMaxPoolSize()); xaDataSource.setMaxLifetime(dbConfig2.getMaxLifetime()); xaDataSource.setBorrowConnectionTimeout(dbConfig2.getBorrowConnectionTimeout()); xaDataSource.setLoginTimeout(dbConfig2.getLoginTimeout()); xaDataSource.setMaintenanceInterval(dbConfig2.getMaintenanceInterval()); xaDataSource.setMaxIdleTime(dbConfig2.getMaxIdleTime()); xaDataSource.setTestQuery(dbConfig2.getTestQuery()); return xaDataSource; } //注意 这里没有事务管理类 因为事务全部交给AtomikosDataSourceBean来管理 @Bean(name = "sqlSessionFactory2") public SqlSessionFactory sqlSessionFactory2(@Qualifier("dataSource2") DataSource dataSource) throws Exception { SqlSessionFactoryBean bean = new SqlSessionFactoryBean(); bean.setDataSource(dataSource); return bean.getObject(); } @Bean(name = "sqlSessionTemplate2") public SqlSessionTemplate sqlSessionTemplate2( @Qualifier("sqlSessionFactory2") SqlSessionFactory sqlSessionFactory) throws Exception { return new SqlSessionTemplate(sqlSessionFactory); } }
3、controller
package com.wjy.controller; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; import com.wjy.test1.service.UserServiceTest1; @RestController public class UserController { @Autowired public UserServiceTest1 userServiceTest1; @RequestMapping("/insertTest1ByService") public String insertTest1ByService(String name,Integer age) { userServiceTest1.insertuser1(name, age); return "success"; } }
4、service
/** * */ package com.wjy.test1.service; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import com.wjy.test1.dao.UserMapperTest1; import com.wjy.test2.dao.UserMapperTest2; /** * @Desc * @author wangjy15 */ @Service public class UserServiceTest1 { @Autowired private UserMapperTest1 userMapperTest1; @Autowired private UserMapperTest2 userMapperTest2; /** * @Desc: 如果没有事务控制 那么报错之后 插入到库里的数据不会回滚 加上 注解@Transactional 就可以回滚 */ @Transactional public String insertuser1(String name,Integer age) { userMapperTest1.insert(name, age); userMapperTest2.insert(name, age);//没有事务控制 不可以回滚 int i =1/0; return "success"; } }
5、APP
package com.wjy; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.context.properties.EnableConfigurationProperties; import com.wjy.datasource.DBConfig1; import com.wjy.datasource.DBConfig2; @SpringBootApplication @EnableConfigurationProperties(value = { DBConfig1.class, DBConfig2.class }) //启动的时候读取两个类对应的配置信息 public class APP { public static void main(String[] args) { SpringApplication.run(APP.class, args); } }
6、测试验证
http://localhost:8080/insertTest1ByService?name=wangsan&age=10
执行后 两个数据库里都没有数据