11、SpringBoot-mybatis-plus-druid多源数据事务
系列导航
6、SpringBoot-mybatis分页实现pagehelper
9、SpringBoot-mybatis-druid多源数据多源数据
10、SpringBoot-mybatis-plus-druid多源数据
11、SpringBoot-mybatis-plus-druid多源数据事务
12、SpringBoot-mybatis-plus-ehcache
未完待续
上一篇博客中因为多源项目的事务没有解决,本篇博客介绍一种可以在多源项目中让事务生效的方法,mybatis-plus配置多源数据还要使的事务生效,需要引入新的依赖atomikos
1数据库中创建表
zy数据库:
CREATE TABLE TEST_BLOCK_T ( BLOCK_ID VARCHAR2(10 BYTE) PRIMARY KEY, --编码 BLOCK_NAME VARCHAR2(200 BYTE) --资源名称 ); Insert into TEST_BLOCK_T (BLOCK_ID, BLOCK_NAME) Values ('1', 'java'); COMMIT;
yc数据库:
CREATE TABLE TEST_USER_T ( USER_ID VARCHAR2(10 BYTE) PRIMARY KEY, NAME VARCHAR2(200 BYTE) ); Insert into TEST_USER_T (USER_ID, NAME) Values ('1', '张三'); COMMIT;
2、pom.xml
<properties> <java.version>1.8</java.version> <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding> <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding> <spring-boot.version>2.1.17.RELEASE</spring-boot.version> </properties> <dependencies> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-test</artifactId> <scope>test</scope> </dependency> <!--阿里数据库连接池 --> <dependency> <groupId>com.alibaba</groupId> <artifactId>druid-spring-boot-starter</artifactId> <version>1.1.20</version> </dependency> <!--mybatis-plus--> <dependency> <groupId>com.baomidou</groupId> <artifactId>mybatis-plus-boot-starter</artifactId> <version>3.3.1</version> </dependency> <!--分布式事务--> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-jta-atomikos</artifactId> </dependency> <!-- oracle驱动 --> <dependency> <groupId>com.oracle</groupId> <artifactId>ojdbc6</artifactId> <version>11.2.0.3</version> </dependency> <!-- 省略get/set等方法 日志打印 --> <dependency> <groupId>org.projectlombok</groupId> <artifactId>lombok</artifactId> <optional>true</optional> </dependency> </dependencies>
3、 application.properties配置
# 应用名称
spring.application.name=demo
# 应用服务 WEB 访问端口
server.port=8080
spring.datasource.type=com.alibaba.druid.pool.xa.DruidXADataSource
spring.datasource.druid.one.name=oneDataSource
spring.datasource.druid.one.url=jdbc:oracle:thin:@192.168.0.100:1521:orcl
spring.datasource.druid.one.driver-class-name=oracle.jdbc.OracleDriver
spring.datasource.druid.one.username=zy
spring.datasource.druid.one.password=1
spring.datasource.druid.one.initialSize=5
spring.datasource.druid.one.minIdle=5
spring.datasource.druid.one.maxActive=20
spring.datasource.druid.one.maxWait=60000
spring.datasource.druid.one.timeBetweenEvictionRunsMillis=60000
spring.datasource.druid.one.minEvictableIdleTimeMillis=300000
spring.datasource.druid.one.validationQuery= SELECT 1 from dual
spring.datasource.druid.one.validationQueryTimeout=10000
spring.datasource.druid.one.testWhileIdle=true
spring.datasource.druid.one.testOnBorrow=false
spring.datasource.druid.one.testOnReturn=false
spring.datasource.druid.one.poolPreparedStatements=true
spring.datasource.druid.one.maxPoolPreparedStatementPerConnectionSize=20
spring.datasource.druid.one.filters=stat,wall
spring.datasource.druid.one.connectionProperties=druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
spring.datasource.druid.one.useGlobalDataSourceStat=true
spring.datasource.druid.two.name=twoDataSource
spring.datasource.druid.two.url=jdbc:oracle:thin:@192.168.0.100:1521:orcl
spring.datasource.druid.two.driver-class-name=oracle.jdbc.OracleDriver
spring.datasource.druid.two.username=yc
spring.datasource.druid.two.password=1
spring.datasource.druid.two.initialSize=5
spring.datasource.druid.two.minIdle=5
spring.datasource.druid.two.maxActive=20
spring.datasource.druid.two.maxWait=60000
spring.datasource.druid.two.timeBetweenEvictionRunsMillis=60000
spring.datasource.druid.two.minEvictableIdleTimeMillis=300000
spring.datasource.druid.two.validationQuery=SELECT 1 from dual
spring.datasource.druid.two.validationQueryTimeout=10000
spring.datasource.druid.two.testWhileIdle=true
spring.datasource.druid.two.testOnBorrow=false
spring.datasource.druid.two.testOnReturn=false
spring.datasource.druid.two.poolPreparedStatements=true
spring.datasource.druid.two.maxPoolPreparedStatementPerConnectionSize=20
spring.datasource.druid.two.filters=stat,wall
spring.datasource.druid.two.connectionProperties=druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
spring.datasource.druid.two.useGlobalDataSourceStat=true
spring.jta.atomikos.properties.log-base-dir=tx-logs
spring.jta.transaction-manager-id=txManager
4、文件目录
5、源码
package com.example.demo; import org.springframework.boot.SpringApplication; import org.springframework.boot.autoconfigure.SpringBootApplication; @SpringBootApplication public class DemoApplication { private org.springframework.dao.support.DaoSupport dao ; public static void main(String[] args) { SpringApplication.run(DemoApplication.class, args); } }
package com.example.demo.config; import com.alibaba.druid.filter.stat.StatFilter; import com.alibaba.druid.support.http.StatViewServlet; import com.alibaba.druid.support.http.WebStatFilter; import com.alibaba.druid.wall.WallConfig; import com.alibaba.druid.wall.WallFilter; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.jta.atomikos.AtomikosDataSourceBean; import org.springframework.boot.web.servlet.FilterRegistrationBean; import org.springframework.boot.web.servlet.ServletRegistrationBean; import org.springframework.context.annotation.Bean; import org.springframework.context.annotation.Configuration; import org.springframework.context.annotation.Primary; import org.springframework.core.env.Environment; import java.util.Properties; /** * 多数据源和Druid配置 * * @author leilei */ @Configuration public class DruidConfig { /** * 数据源1配置 使用AtomikosDataSourceBean 支持多数据源事务 * * @param env * @return Primary 指定主库 (必须指定一个主库 否则会报错) */ @Bean(name = "MybatisPlusOneDataSource") @Primary @Autowired public AtomikosDataSourceBean oneDataSource(Environment env) { AtomikosDataSourceBean ds = new AtomikosDataSourceBean(); Properties prop = build(env, "spring.datasource.druid.one."); ds.setXaDataSourceClassName("com.alibaba.druid.pool.xa.DruidXADataSource"); ds.setUniqueResourceName("oneDataSource"); ds.setPoolSize(5); ds.setXaProperties(prop); return ds; } /** * 数据源2配置 使用AtomikosDataSourceBean 支持多数据源事务 * * @param env * @return */ @Autowired @Bean(name = "MybatisPlusTwoDataSource") public AtomikosDataSourceBean twoDataSource(Environment env) { AtomikosDataSourceBean ds = new AtomikosDataSourceBean(); Properties prop = build(env, "spring.datasource.druid.two."); ds.setXaDataSourceClassName("com.alibaba.druid.pool.xa.DruidXADataSource"); ds.setUniqueResourceName("twoDataSource"); ds.setPoolSize(5); ds.setXaProperties(prop); return ds; } // @Autowired // @Bean(name = "MybatisPlusThreeDataSource") // public AtomikosDataSourceBean threeDataSource(Environment env) { // AtomikosDataSourceBean ds = new AtomikosDataSourceBean(); // Properties prop = build(env, "spring.datasource.druid.three."); // ds.setXaDataSourceClassName("com.alibaba.druid.pool.xa.DruidXADataSource"); // ds.setUniqueResourceName("threeDataSource"); // ds.setPoolSize(5); // ds.setXaProperties(prop); // return ds; // } // /** // * 注入事物管理器 // * @return // */ // @Bean(name = "leijta") // public JtaTransactionManager regTransactionManager () { // UserTransactionManager userTransactionManager = new UserTransactionManager(); // UserTransaction userTransaction = new UserTransactionImp(); // return new JtaTransactionManager(userTransaction, userTransactionManager); // } /** * 从配置文件中加载数据源信息 * * @param env * @param prefix * @return */ private Properties build(Environment env, String prefix) { Properties prop = new Properties(); prop.put("url", env.getProperty(prefix + "url")); prop.put("username", env.getProperty(prefix + "username")); prop.put("password", env.getProperty(prefix + "password")); prop.put("driverClassName", env.getProperty(prefix + "driverClassName", "")); prop.put("initialSize", env.getProperty(prefix + "initialSize", Integer.class)); prop.put("maxActive", env.getProperty(prefix + "maxActive", Integer.class)); prop.put("minIdle", env.getProperty(prefix + "minIdle", Integer.class)); prop.put("maxWait", env.getProperty(prefix + "maxWait", Integer.class)); prop.put("poolPreparedStatements", env.getProperty(prefix + "poolPreparedStatements", Boolean.class)); prop.put("maxPoolPreparedStatementPerConnectionSize", env.getProperty(prefix + "maxPoolPreparedStatementPerConnectionSize", Integer.class)); prop.put("maxPoolPreparedStatementPerConnectionSize", env.getProperty(prefix + "maxPoolPreparedStatementPerConnectionSize", Integer.class)); prop.put("validationQuery", env.getProperty(prefix + "validationQuery")); prop.put("validationQueryTimeout", env.getProperty(prefix + "validationQueryTimeout", Integer.class)); prop.put("testOnBorrow", env.getProperty(prefix + "testOnBorrow", Boolean.class)); prop.put("testOnReturn", env.getProperty(prefix + "testOnReturn", Boolean.class)); prop.put("testWhileIdle", env.getProperty(prefix + "testWhileIdle", Boolean.class)); prop.put("timeBetweenEvictionRunsMillis", env.getProperty(prefix + "timeBetweenEvictionRunsMillis", Integer.class)); prop.put("minEvictableIdleTimeMillis", env.getProperty(prefix + "minEvictableIdleTimeMillis", Integer.class)); prop.put("filters", env.getProperty(prefix + "filters")); return prop; } /** * druid访问配置 * * @return */ @Bean public ServletRegistrationBean druidServlet() { ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(new StatViewServlet(), "/druid/*"); //控制台管理用户,加入下面2行 进入druid后台就需要登录 servletRegistrationBean.addInitParameter("loginUsername", "leilei"); servletRegistrationBean.addInitParameter("loginPassword", "123456"); return servletRegistrationBean; } @Bean public FilterRegistrationBean filterRegistrationBean() { FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(); filterRegistrationBean.setFilter(new WebStatFilter()); filterRegistrationBean.addUrlPatterns("/*"); filterRegistrationBean.addInitParameter("exclusions", "*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*"); filterRegistrationBean.addInitParameter("profileEnable", "true"); return filterRegistrationBean; } @Bean public StatFilter statFilter() { StatFilter statFilter = new StatFilter(); //slowSqlMillis用来配置SQL慢的标准,执行时间超过slowSqlMillis的就是慢。 statFilter.setLogSlowSql(true); //SQL合并配置 statFilter.setMergeSql(true); //slowSqlMillis的缺省值为3000,也就是3秒。 statFilter.setSlowSqlMillis(1000); return statFilter; } @Bean public WallFilter wallFilter() { WallFilter wallFilter = new WallFilter(); //允许执行多条SQL WallConfig config = new WallConfig(); config.setMultiStatementAllow(true); wallFilter.setConfig(config); return wallFilter; } }
package com.example.demo.config; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; import javax.servlet.http.HttpServletRequest; import java.util.HashMap; import java.util.Map; /** * @author : leilei * @date : 14:20 2020/3/5 * @desc : 自定义异常响应 */ @RestControllerAdvice public class ExceptionHadler { @ExceptionHandler(value = Exception.class) public Map<String, Object> exceptionHandler(HttpServletRequest req, Exception e) { HashMap<String, Object> map = new HashMap<>(4); map.put("请求状态", "False"); map.put("请求路径", req.getRequestURI()); map.put("请求方式", req.getMethod()); map.put("错误信息", e.getMessage()); return map; } }
配置数据源1
package com.example.demo.config; import com.baomidou.mybatisplus.core.MybatisConfiguration; import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor; import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean; import org.apache.ibatis.logging.stdout.StdOutImpl; import org.apache.ibatis.session.SqlSessionFactory; 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 org.springframework.core.io.support.PathMatchingResourcePatternResolver; import javax.sql.DataSource; /** * @author leilei */ @Configuration @MapperScan(basePackages = "com.example.demo.mapper.one", sqlSessionFactoryRef = "oneSqlSessionFactory") public class OneDataSourceConfig { @Primary @Bean(name = "oneSqlSessionFactory") public SqlSessionFactory sqlSessionFactory(@Qualifier("MybatisPlusOneDataSource") DataSource dataSource) throws Exception { //配置myabtisSqlSession MybatisSqlSessionFactoryBean sessionFactoryBean = new MybatisSqlSessionFactoryBean(); // 指明mapper.xml位置(配置文件中指明的xml位置会失效用此方式代替,具体原因未知) //sessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:com/leilei/mapper/one/*/*Mapper.xml")); // 指明实体扫描(多个package用逗号或者分号分隔) sessionFactoryBean.setTypeAliasesPackage("com.example.demo.entity.one"); MybatisConfiguration mybatisConfiguration = new MybatisConfiguration(); // mybatisConfiguration.setJdbcTypeForNull(JdbcType.NULL); //驼峰 mybatisConfiguration.setMapUnderscoreToCamelCase(true); //是否开启缓存 mybatisConfiguration.setCacheEnabled(false); //多数据源下分页模式 mybatisConfiguration.addInterceptor(new PaginationInterceptor()); // 配置打印sql语句 mybatisConfiguration.setLogImpl(StdOutImpl.class); sessionFactoryBean.setConfiguration(mybatisConfiguration); //数据源注入 sessionFactoryBean.setDataSource(dataSource); return sessionFactoryBean.getObject(); } @Primary @Bean(name = "oneSqlSessionTemplate") public SqlSessionTemplate sqlSessionTemplate(@Qualifier("oneSqlSessionFactory") SqlSessionFactory sqlSessionFactory) { return new SqlSessionTemplate(sqlSessionFactory); } }
配置数据源2 需要几个就配置几个
package com.example.demo.config; import com.baomidou.mybatisplus.core.MybatisConfiguration; import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor; import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean; import org.apache.ibatis.logging.stdout.StdOutImpl; import org.apache.ibatis.session.SqlSessionFactory; 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.core.io.support.PathMatchingResourcePatternResolver; import javax.sql.DataSource; /** * @author leilei */ @Configuration @MapperScan(basePackages = "com.example.demo.mapper.two", sqlSessionFactoryRef = "twoSqlSessionFactory") public class TwoDataSourceConfig { @Bean(name = "twoSqlSessionFactory") public SqlSessionFactory sqlSessionFactory(@Qualifier("MybatisPlusTwoDataSource") DataSource dataSource) throws Exception { //配置myabtisSqlSession MybatisSqlSessionFactoryBean sessionFactoryBean = new MybatisSqlSessionFactoryBean(); // 指明mapper.xml位置(配置文件中指明的xml位置会失效用此方式代替,具体原因未知) //sessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:com/leilei/mapper/two/*/*Mapper.xml")); // 指明实体扫描(多个package用逗号或者分号分隔) sessionFactoryBean.setTypeAliasesPackage("com.example.demo.entity.two"); MybatisConfiguration mybatisConfiguration = new MybatisConfiguration(); //mybatisConfiguration.setJdbcTypeForNull(JdbcType.NULL); //驼峰 mybatisConfiguration.setMapUnderscoreToCamelCase(true); //是否开启缓存 mybatisConfiguration.setCacheEnabled(false); //多数据源下分页模式 mybatisConfiguration.addInterceptor(new PaginationInterceptor()); // 配置打印sql语句 mybatisConfiguration.setLogImpl(StdOutImpl.class); sessionFactoryBean.setConfiguration(mybatisConfiguration); //数据源注入 sessionFactoryBean.setDataSource(dataSource); return sessionFactoryBean.getObject(); } @Bean(name = "twoSqlSessionTemplate") public SqlSessionTemplate sqlSessionTemplate(@Qualifier("twoSqlSessionFactory") SqlSessionFactory sqlSessionFactory) { return new SqlSessionTemplate(sqlSessionFactory); } }
package com.example.demo.controller; import com.example.demo.service.ManySourceService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; @RestController @RequestMapping("/hello") public class ManySourceController { @Autowired ManySourceService manySourceService; @GetMapping("/getZyBlock") @ResponseBody public String test1() { return manySourceService.getZyBlock(); } @GetMapping("/getYcUser") @ResponseBody public String test2() { return manySourceService.getYcUser(); } @PostMapping("/insertZyBlock") @ResponseBody public String test3() { return manySourceService.insertZyBlock(); } @PostMapping("/insertYcUser") @ResponseBody public String test4() { return manySourceService.insertYcUser(); } @PostMapping("/insertMany") @ResponseBody public String test5() { return manySourceService.insertMany(); } }
package com.example.demo.service; import com.example.demo.entity.one.Block; import com.example.demo.entity.two.User; import com.example.demo.mapper.one.BlockMapper; import com.example.demo.mapper.two.UserMapper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; @Service public class ManySourceService { //SpringBoot在使用事物Transactional的时候,要在main方法上加上 @EnableTransactionManagement // 注解开发事物声明,在使用的service层的公共方法加上 @Transactional (spring)注解。 @Autowired BlockMapper blockMapper; @Autowired UserMapper userMapper; //获取zy库中的block中的数据 public String getZyBlock() { return blockMapper.selectById("1").toString(); } //获取yc库中的user中的数据 public String getYcUser() { return userMapper.selectById("2").toString() ; } public String insertZyBlock() { Block block = new Block(); block.setBlockId("99999"); block.setBlockName("PHP"); return blockMapper.insert(block)+""; } public String insertYcUser() { User user = new User(); user.setUserId("2"); user.setName("李四"); return userMapper.insert(user)+""; } @Transactional public String insertMany() { Block block = new Block(); block.setBlockId("99999"); block.setBlockName("PHP"); blockMapper.insert(block) ; int a = 100/0; User user = new User(); user.setUserId("2"); user.setName("李四"); userMapper.insert(user) ; return "1"; } }
package com.example.demo.entity.one; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; /** * <p> * 。 * </p> * * @author yc * @since 2021-09-18 */ @TableName(value = "TEST_BLOCK_T") public class Block { private static final long serialVersionUID = 1L; @TableId private String blockId; /** * $field.comment。 */ private String blockName; public String getBlockId() { return blockId; } public void setBlockId(String blockId) { this.blockId = blockId; } public String getBlockName() { return blockName; } public void setBlockName(String blockName) { this.blockName = blockName; } @Override public String toString() { return "XyDicBlockT{" + "blockId='" + blockId + '\'' + ", blockName='" + blockName + '\'' + '}'; } }
package com.example.demo.entity.two; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; /** * <p> * 。 * </p> * * @author yc * @since 2021-09-18 */ @TableName(value = "TEST_USER_T") public class User { private static final long serialVersionUID = 1L; @TableId private String userId; /** * $field.comment。 */ private String name; public String getUserId() { return userId; } public void setUserId(String userId) { this.userId = userId; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String toString() { return "User{" + "userId='" + userId + '\'' + ", name='" + name + '\'' + '}'; } }
package com.example.demo.entity.two; import com.baomidou.mybatisplus.annotation.TableId; import com.baomidou.mybatisplus.annotation.TableName; /** * <p> * 。 * </p> * * @author yc * @since 2021-09-18 */ @TableName(value = "TEST_USER_T") public class User { private static final long serialVersionUID = 1L; @TableId private String userId; /** * $field.comment。 */ private String name; public String getUserId() { return userId; } public void setUserId(String userId) { this.userId = userId; } public String getName() { return name; } public void setName(String name) { this.name = name; } @Override public String toString() { return "User{" + "userId='" + userId + '\'' + ", name='" + name + '\'' + '}'; } }
package com.example.demo.mapper.two; import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.example.demo.entity.two.User; public interface UserMapper extends BaseMapper<User> { }
6、启动项目访问项目
清空yc库里的表TEST_USER_T 和 zy库里的表TEST_BLOCK_T
(1)访问http://localhost:8080/hello/insertZyBlock 成功插入数据到TEST_BLOCK_T
(2)访问http://localhost:8080/hello/insertYcUser成功插入数据到TEST_USER_T
说明分别向不同数据源的数据库的插入没有问题。
(3)访问http://localhost:8080/hello/getZyBlock
(4)访问http://localhost:8080/hello/getYcUser
说明分别从不同数据源的数据库的查询没有问题。
再次清空yc库里的表TEST_USER_T 和 zy库里的表TEST_BLOCK_T
重头戏来了
(5)访问http://localhost:8080/hello/insertMany
这里添加了事务,并且在插入TEST_BLOCK_T后制造了一个除数为0的错误,如果事务生效,前面插入的数据就应该回滚。
下面可以看到前台返回的消息报错了说了除数为0的错误。
后台开始也插入了数据,但是会发现数据库里并没有插入数据,说清事务生效了。
资源丰富的的网盘资源:网盘资源大全! 推荐一个适合零基础学习SQL的网站:不用安装数据库,在线轻松学习SQL!