11、SpringBoot-mybatis-plus-druid多源資料事務
系列導航
6、SpringBoot-mybatis分頁實現pagehelper
9、SpringBoot-mybatis-druid多源資料多源資料
10、SpringBoot-mybatis-plus-druid多源資料
11、SpringBoot-mybatis-plus-druid多源資料事務
未完待續
上一篇部落格中因為多源專案的事務沒有解決,本篇部落格介紹一種可以在多源專案中讓事務生效的方法,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的錯誤。
後臺開始也插入了資料,但是會發現資料庫裡並沒有插入資料,說清事務生效了。