springboot+mybatis+jta+atomikos解决多数据源事务问题
一.多数据源环境搭建
项目目录架构如下
- 添加pom依赖
<?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>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.5.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.sgz</groupId>
<artifactId>atomikos</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>atomikos</name>
<description>springboot+jta+atomikos解决多数据源事务问题</description>
<properties>
<java.version>1.8</java.version>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<!--项目使用mysql版本,默认是8.0.15 -->
<mysql.version>5.1.40</mysql.version>
<!--项目使用Tomcat版本 -->
<tomcat.version>8.5.38</tomcat.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-devtools</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.14</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>2.0.0</version>
</dependency>
<!--mysql驱动包-->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<!--jdbc启动器-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!-- 分页插件 -->
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper-spring-boot-starter</artifactId>
<version>1.2.10</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
- 配置文件application.yml
server:
port: 8001
spring:
mvc:
date-format: yyyy-MM-dd HH:mm:ss #修改表单提交到后台的日期格式
#数据源配置
datasource:
db1:
type: com.alibaba.druid.pool.DruidDataSource #指定自定义的数据源类型,如果这里不指定,默认使用的是HikariDataSource
username: root
password: 12345678
url: jdbc:mysql://192.168.5.130:3306/db1
driver-class-name: com.mysql.jdbc.Driver
initialSize: 8 #数据库连接池初始化连接大小
minIdle: 5 #数据库连接池最小连接池数量
maxActive: 20 #数据库连接池最大连接池数量
maxWait: 60000 #获取连接时最大等待时间,单位毫秒
timeBetweenEvictionRunsMillis: 60000 #有两个含义:1) Destroy线程会检测连接的间隔时间,即配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 2) testWhileIdle的判断依据;
minEvictableIdleTimeMillis: 300000 #配置一个连接在池中最小生存的时间,单位是毫秒
validationQuery: SELECT 1 FROM DUAL #用来检测连接是否有效的sql
testWhileIdle: true #建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
testOnBorrow: false #申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。
testOnReturn: false #归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能
poolPreparedStatements: false #是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大,比如说oracle。在mysql下建议关闭,分库分表较多的数据库,建议配置为false。
maxPoolPreparedStatementPerConnectionSize: -1 #要启用PSCache,必须配置大于0,当大于0时,poolPreparedStatements自动触发修改为true。在Druid中,不会存在Oracle下PSCache占用内存过多的问题,可以把这个数值配置大一些,比如说100
#监控配置
filters: stat,wall,slf4j
useGlobalDataSourceStat: true #合并多个DruidDataSource的监控数据,多数据源
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500 #druid.stat.mergeSql=true 合并执行的相同sql,避免因为参数不同而统计多条sql语句, druid.stat.slowSqlMillis=500 用来配置SQL慢的标准,执行时间超过slowSqlMillis的就是慢
db2:
type: com.alibaba.druid.pool.DruidDataSource #指定自定义的数据源类型,如果这里不指定,默认使用的是HikariDataSource
username: root
password: 12345678
url: jdbc:mysql://192.168.5.130:3306/db2
driver-class-name: com.mysql.jdbc.Driver
initialSize: 18 #数据库连接池初始化连接大小
minIdle: 15 #数据库连接池最小连接池数量
maxActive: 120 #数据库连接池最大连接池数量
maxWait: 60000 #获取连接时最大等待时间,单位毫秒
timeBetweenEvictionRunsMillis: 60000 #有两个含义:1) Destroy线程会检测连接的间隔时间,即配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 2) testWhileIdle的判断依据;
minEvictableIdleTimeMillis: 300000 #配置一个连接在池中最小生存的时间,单位是毫秒
validationQuery: SELECT 1 FROM DUAL #用来检测连接是否有效的sql
testWhileIdle: true #建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
testOnBorrow: false #申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。
testOnReturn: false #归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能
poolPreparedStatements: false #是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大,比如说oracle。在mysql下建议关闭,分库分表较多的数据库,建议配置为false。
maxPoolPreparedStatementPerConnectionSize: -1 #要启用PSCache,必须配置大于0,当大于0时,poolPreparedStatements自动触发修改为true。在Druid中,不会存在Oracle下PSCache占用内存过多的问题,可以把这个数值配置大一些,比如说100
#监控配置
filters: stat,wall,slf4j
useGlobalDataSourceStat: true #合并多个DruidDataSource的监控数据,多数据源
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500 #druid.stat.mergeSql=true 合并执行的相同sql,避免因为参数不同而统计多条sql语句, druid.stat.slowSqlMillis=500 用来配置SQL慢的标准,执行时间超过slowSqlMillis的就是慢
#mybatis配置文件
mybatis:
#mapper-locations: classpath:mybatis/mapper/db1/*.xml #mybatis的sql的mapper接口映射文件(单数据源配置使用) 多数据源在对应的配置类中配置使用,这里配置不起作用了(比如DataSource1Config类中)
#type-aliases-package: com.sgz.atomikos.**.entity #对应实体类的路径(可以不用配置)
configuration:
map-underscore-to-camel-case: true #开启驼峰命名转换
#pagehelper分页插件
pagehelper:
helperDialect: mysql
reasonable: true #pageHelper里面自带的一个功能,叫做reasonable分页参数合理化,3.3.0以上版本可用,默认是false。 启用合理化时,如果pageNum<1会查询第一页,如果pageNum>pages(超过总数时)会查询最后一页;禁用合理化时,如果pageNum<1或pageNum>pages会返回空数据
supportMethodsArguments: true
params: count=countSql;
#日志配置
logging:
level:
root: info
com.sgz.atomikos: debug
file: D:/logs/springboot.log
- 数据源配置类(这里有两个数据源,所以就有2个配置类)
package com.sgz.atomikos.config;
import com.alibaba.druid.pool.DruidDataSource;
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.boot.context.properties.ConfigurationProperties;
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 org.springframework.jdbc.datasource.DataSourceTransactionManager;
import javax.sql.DataSource;
/**
* @Auther:shigzhcom.sgz.atomikos.test1下面的mapper访问的是db1数据库
* @Description: 这个是主数据源,所以要加@Primary注解
* @create 2019/2/6 16:55
*/
@Configuration
@MapperScan(basePackages = "com.sgz.atomikos.test1", sqlSessionTemplateRef = "db1SqlSessionTemplate")
public class DataSource1Config {
//绑定数据源配置
@ConfigurationProperties(prefix = "spring.datasource.db1")
@Bean
public DataSource db1DataSource() {
return new DruidDataSource();
}
/**
* 创建Mybatis的连接会话工厂实例
* @param dataSource
* @return
* @throws Exception
*/
@Bean
@Primary
public SqlSessionFactory db1SqlSessionFactory(@Qualifier("db1DataSource") DataSource dataSource) throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
//这里加载对应的mybatis的xml配置文件,application.yml里就不用配置了,即使配置了也不起作用
bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:mybatis/mapper/db1/*.xml"));
return bean.getObject();
}
/**
* 创建该数据源的事务管理
* @param dataSource
* @return
*/
@Bean
@Primary
public DataSourceTransactionManager db1TransactionManager(@Qualifier("db1DataSource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
@Bean
@Primary
public SqlSessionTemplate db1SqlSessionTemplate(@Qualifier("db1SqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
package com.sgz.atomikos.config;
import com.alibaba.druid.pool.DruidDataSource;
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.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import javax.sql.DataSource;
/**
* @Description:com.sgz.atomikos.test2下面的mapper访问的是db2数据库
* @Auther:shigzh
* @create 2019/5/20 17:42
*/
@Configuration
@MapperScan(basePackages = "com.sgz.atomikos.test2", sqlSessionTemplateRef = "db2SqlSessionTemplate")
public class DataSource2Config {
//绑定数据源配置
@ConfigurationProperties(prefix = "spring.datasource.db2")
@Bean
public DataSource db2DataSource() {
return new DruidDataSource();
}
/**
* 创建Mybatis的连接会话工厂实例
* @param dataSource
* @return
* @throws Exception
*/
@Bean
public SqlSessionFactory db2SqlSessionFactory(@Qualifier("db2DataSource") DataSource dataSource) throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
//这里加载对应的mybatis的xml配置文件,application.yml里就不用配置了,即使配置了也不起作用
bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:mybatis/mapper/db2/*.xml"));
return bean.getObject();
}
/**
* 创建该数据源的事务管理
* @param dataSource
* @return
*/
@Bean
public DataSourceTransactionManager db2TransactionManager(@Qualifier("db2DataSource") DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
@Bean
public SqlSessionTemplate db2SqlSessionTemplate(@Qualifier("db2SqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
- druid数据源监控配置类
package com.sgz.atomikos.config;
import com.alibaba.druid.support.http.StatViewServlet;
import com.alibaba.druid.support.http.WebStatFilter;
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 javax.servlet.Filter;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
/**
* @Description: 单独整个配置,配置druid数据源监控
* @Auther:shigzh
* @create 2019/5/21 13:48
*/
@Configuration
public class DruidConfig {
/**
* 配置Druid监控
* 1. 配置一个管理后台的Servlet
* 2. 配置一个监控的filter
*/
@Bean // 1. 配置一个管理后台的Servlet
public ServletRegistrationBean<StatViewServlet> statViewServlet() {
//StatViewServlet是 配置管理后台的servlet
ServletRegistrationBean<StatViewServlet> bean =
new ServletRegistrationBean<>(new StatViewServlet(), "/druid/*");
//配置初始化参数
Map<String, String> initParam = new HashMap<>();
//访问的用户名密码
initParam.put(StatViewServlet.PARAM_NAME_USERNAME, "root");
initParam.put(StatViewServlet.PARAM_NAME_PASSWORD, "123");
//允许访问的ip,默认所有ip访问
initParam.put(StatViewServlet.PARAM_NAME_ALLOW, "");
//禁止访问的ip
initParam.put(StatViewServlet.PARAM_NAME_DENY, "192.168.10.1");
//监控配置界面中 是否能够重置数据(点了重置所有监控数据就没有了)
//initParam.put(StatViewServlet.PARAM_NAME_RESET_ENABLE,"false");
initParam.put(StatViewServlet.PARAM_NAME_RESET_ENABLE,"true");
bean.setInitParameters(initParam);
return bean;
}
//2. 配置一个监控的filter
@Bean
public FilterRegistrationBean<Filter> filter() {
FilterRegistrationBean<Filter> bean = new FilterRegistrationBean<>();
bean.setFilter(new WebStatFilter());
//配置初始化参数
Map<String, String> initParam = new HashMap<>();
//排除请求
initParam.put(WebStatFilter.PARAM_NAME_EXCLUSIONS, "*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/*");
bean.setInitParameters(initParam);
//拦截所有请求
bean.setUrlPatterns(Arrays.asList("/*"));
return bean;
}
}
- test1目录下的mapper访问db1库数据源
package com.sgz.atomikos.test1.entity;
import lombok.Data;
import java.io.Serializable;
import java.util.Date;
/**
* @Description:
* @Auther:shigzh
* @create 2019/5/21 9:43
*/
@Data
public class User1Entity implements Serializable {
//按alt+enter键 生成序列化id
private static final long serialVersionUID = -3932087591641353874L;
private Integer id; //主键
private String userName; //名称
private Date date;
private String dbSource; // 来自哪个数据库,因为微服务架构可以一个服务对应一个数据库,同一个信息被存储到不同数据库
}
package com.sgz.atomikos.test1.mapper;
import com.sgz.atomikos.test1.entity.User1Entity;
public interface User1Mapper {
boolean addUser(User1Entity user1Entity);
}
import com.sgz.atomikos.test1.entity.User1Entity;
import com.sgz.atomikos.test1.mapper.User1Mapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
* @Description:
* @Auther:shigzh
* @create 2019/5/21 9:52
*/
@Transactional
@Service
public class User1Service {
@Autowired
private User1Mapper user1Mapper;
public void testAdd(){
User1Entity user1Entity = new User1Entity();
user1Entity.setUserName("user1");
user1Mapper.addUser(user1Entity);
}
}
<?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.sgz.atomikos.test1.mapper.User1Mapper">
<insert id="addUser" parameterType="com.sgz.atomikos.test1.entity.User1Entity">
INSERT INTO user1(userName, dbSource,date) VALUES(#{userName}, DATABASE(),now());
</insert>
</mapper>
- test2目录下的mapper访问db2库数据源
package com.sgz.atomikos.test2.entity;
import lombok.Data;
import java.util.Date;
/**
* @Description:
* @Auther:shigzh
* @create 2019/5/21 9:46
*/
@Data
public class User2Entity {
//按alt+enter键 生成序列化id
private static final long serialVersionUID = -3932017591641353874L;
private Integer id; //主键
private String userName; //名称
private Date date;
private String dbSource; // 来自哪个数据库,因为微服务架构可以一个服务对应一个数据库,同一个信息被存储到不同数据库
}
package com.sgz.atomikos.test2.mapper;
import com.sgz.atomikos.test2.entity.User2Entity;
public interface User2Mapper {
boolean addUser(User2Entity user2Entity);
}
package com.sgz.atomikos.test2.service;
import com.sgz.atomikos.test2.entity.User2Entity;
import com.sgz.atomikos.test2.mapper.User2Mapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
* @Description:
* @Auther:shigzh
* @create 2019/5/21 9:52
*/
@Transactional
@Service
public class User2Service {
@Autowired
private User2Mapper user2Mapper;
public void testAdd(){
User2Entity user2Entity = new User2Entity();
user2Entity.setUserName("user2");
user2Mapper.addUser(user2Entity);
}
}
<?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.sgz.atomikos.test2.mapper.User2Mapper">
<insert id="addUser" parameterType="com.sgz.atomikos.test2.entity.User2Entity">
INSERT INTO user2(userName, dbSource,date) VALUES(#{userName}, DATABASE(),now());
</insert>
</mapper>
- 启动类
package com.sgz.atomikos;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
//扫描mybatis使用的mapper接口
//@MapperScan(value="com.sgz.**.mapper") //扫描mybatis的mapper文件,多数据源就不在这里配置扫描mapper了
@SpringBootApplication
public class AtomikosApplication {
public static void main(String[] args) {
SpringApplication.run(AtomikosApplication.class, args);
}
}
- controller类
package com.sgz.atomikos.controller;
import com.sgz.atomikos.service.TestService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
* @Description:
* @Auther:shigzh
* @create 2019/5/20 17:16
*/
@RestController
public class TestController {
@Autowired
private TestService testService;
@RequestMapping("/testAdd")
public void testAdd(){
testService.testAdd();
}
}
package com.sgz.atomikos.service;
import com.sgz.atomikos.test1.service.User1Service;
import com.sgz.atomikos.test2.service.User2Service;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
/**
* @Description:
* @Auther:shigzh
* @create 2019/5/20 17:16
*/
@Transactional
@Service
public class TestService {
@Autowired
private User1Service user1Service;
@Autowired
private User2Service user2Service;
public void testAdd(){
user1Service.testAdd();
user2Service.testAdd();
}
}
- 测试访问http://192.168.12.4:8001/testAdd 两个库中能够正常插入两条数据,此时说明多数据源配置完成。
-
检查druid数据库连接池配置文件参数是否生效
如上图如果dataSource里的属性与application.yml中的配置一致,说明生效
initialSize: 8 #数据库连接池初始化连接大小
minIdle: 5 #数据库连接池最小连接池数量
maxActive: 20 #数据库连接池最大连接池数量
maxWait: 60000 #获取连接时最大等待时间,单位毫秒
timeBetweenEvictionRunsMillis: 60000 #有两个含义:1) Destroy线程会检测连接的间隔时间,即配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 2) testWhileIdle的判断依据;
minEvictableIdleTimeMillis: 300000
二.多数据源事务回滚测试
@Transactional
@Service
public class TestService {
@Autowired
private User1Service user1Service;
@Autowired
private User2Service user2Service;
public void testAdd(){
user1Service.testAdd();
user2Service.testAdd();
int a =1/0;
}
}
结果:db1发生回滚,db2没有发生回滚
原因:@Transactional默认使用的是带有@Primary的事务管理器db1TransactionManager
这里注解如果修改成@Transactional(value=“db2TransactionManager”)
@Transactional(value="db2TransactionManager")
@Service
public class TestService {
@Autowired
private User1Service user1Service;
@Autowired
private User2Service user2Service;
public void testAdd(){
user1Service.testAdd();
user2Service.testAdd();
int a =1/0;
}
}
结果:db2发生回滚,db1没有发生回滚
三.多数据源事务问题解决
这里使用springboot+jta+atomikos 来解决多数据源事务管理问题
修改DataSource1Config
package com.sgz.atomikos.config;
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 org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import javax.sql.DataSource;
/**
* @Auther:shigzh
* @Description: com.sgz.atomikos.test1下面的mapper访问的是db1数据库
* 这个是主数据源,所以要加@Primary注解
* @create 2019/2/6 16:55
*/
@Configuration
@MapperScan(basePackages = "com.sgz.atomikos.test1", sqlSessionTemplateRef = "db1SqlSessionTemplate")
public class DataSource1Config {
/**
* 创建Mybatis的连接会话工厂实例
* @param dataSource
* @return
* @throws Exception
*/
@Bean
@Primary
public SqlSessionFactory db1SqlSessionFactory(@Qualifier("db1DataSource") DataSource dataSource) throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
//这里加载对应的mybatis的xml配置文件,application.yml里就不用配置了,即使配置了也不起作用
bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:mybatis/mapper/db1/*.xml"));
return bean.getObject();
}
@Bean
@Primary
public SqlSessionTemplate db1SqlSessionTemplate(@Qualifier("db1SqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
修改DataSource2Config
package com.sgz.atomikos.config;
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.core.io.support.PathMatchingResourcePatternResolver;
import javax.sql.DataSource;
/**
* @Description:com.sgz.atomikos.test2下面的mapper访问的是db2数据库
* @Auther:shigzh
* @create 2019/5/20 17:42
*/
@Configuration
@MapperScan(basePackages = "com.sgz.atomikos.test2", sqlSessionTemplateRef = "db2SqlSessionTemplate")
public class DataSource2Config {
/**
* 创建Mybatis的连接会话工厂实例
* @param dataSource
* @return
* @throws Exception
*/
@Bean
public SqlSessionFactory db2SqlSessionFactory(@Qualifier("db2DataSource") DataSource dataSource) throws Exception {
SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
bean.setDataSource(dataSource);
//这里加载对应的mybatis的xml配置文件,application.yml里就不用配置了,即使配置了也不起作用
bean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath*:mybatis/mapper/db2/*.xml"));
return bean.getObject();
}
@Bean
public SqlSessionTemplate db2SqlSessionTemplate(@Qualifier("db2SqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
修改DruidConfig
package com.sgz.atomikos.config;
import com.alibaba.druid.support.http.StatViewServlet;
import com.alibaba.druid.support.http.WebStatFilter;
import com.atomikos.icatch.jta.UserTransactionImp;
import com.atomikos.icatch.jta.UserTransactionManager;
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 org.springframework.transaction.jta.JtaTransactionManager;
import javax.servlet.Filter;
import javax.sql.DataSource;
import javax.transaction.UserTransaction;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
/**
* 网上查找都说 atomikos这套方案性能太差了,做性能测试的时候并发完全压不上去,高并发的系统千万不要用。简单了解学习使用
*
* XA 是一个分布式事务协议,由Tuxedo 提出,所以分布式事务也称为XA 事务
* @Description: 单独整个配置,配置druid数据源监控
* @Auther:shigzh
* @create 2019/5/21 13:48
*/
@Configuration
public class DruidConfig {
@Autowired
private Environment env;
//绑定数据源配置
@Primary
@Bean
public DataSource db1DataSource() {
AtomikosDataSourceBean dataSourceBean = new AtomikosDataSourceBean();//分布式数据源(AtomikosNonXADataSourceBean 非分布式数据源)
Properties prop = build("spring.datasource.db1.");
dataSourceBean.setXaDataSourceClassName("com.alibaba.druid.pool.xa.DruidXADataSource");
dataSourceBean.setUniqueResourceName("db1");//该值要唯一
dataSourceBean.setPoolSize(5);
dataSourceBean.setXaProperties(prop);
return dataSourceBean;
}
@Bean
public DataSource db2DataSource() {
AtomikosDataSourceBean dataSourceBean = new AtomikosDataSourceBean();//分布式数据源
Properties prop = build("spring.datasource.db2.");
dataSourceBean.setXaDataSourceClassName("com.alibaba.druid.pool.xa.DruidXADataSource");
dataSourceBean.setUniqueResourceName("db2"); //该值要唯一
dataSourceBean.setPoolSize(5);
dataSourceBean.setXaProperties(prop);
return dataSourceBean;
}
private Properties build(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 + "driver-class-name", ""));
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;
}
/**
* 注入事物管理器
* 由于我们只使用一个事务管理器:DataSource1Config.DataSource2Config.java中配置的事务管理器了
* @return
*/
@Bean
public JtaTransactionManager regTransactionManager () {
UserTransactionManager userTransactionManager = new UserTransactionManager();
UserTransaction userTransaction = new UserTransactionImp();
return new JtaTransactionManager(userTransaction, userTransactionManager);
}
/**
* 配置Druid监控
* 1. 配置一个管理后台的Servlet
* 2. 配置一个监控的filter
*/
@Bean // 1. 配置一个管理后台的Servlet
public ServletRegistrationBean<StatViewServlet> statViewServlet() {
//StatViewServlet是 配置管理后台的servlet
ServletRegistrationBean<StatViewServlet> bean =
new ServletRegistrationBean<>(new StatViewServlet(), "/druid/*");
//配置初始化参数
Map<String, String> initParam = new HashMap<>();
//访问的用户名密码
initParam.put(StatViewServlet.PARAM_NAME_USERNAME, "root");
initParam.put(StatViewServlet.PARAM_NAME_PASSWORD, "123");
//允许访问的ip,默认所有ip访问
initParam.put(StatViewServlet.PARAM_NAME_ALLOW, "");
//禁止访问的ip
initParam.put(StatViewServlet.PARAM_NAME_DENY, "192.168.10.1");
//监控配置界面中 是否能够重置数据(点了重置所有监控数据就没有了)
//initParam.put(StatViewServlet.PARAM_NAME_RESET_ENABLE,"false");
initParam.put(StatViewServlet.PARAM_NAME_RESET_ENABLE,"true");
bean.setInitParameters(initParam);
return bean;
}
//2. 配置一个监控的filter
@Bean
public FilterRegistrationBean<Filter> filter() {
FilterRegistrationBean<Filter> bean = new FilterRegistrationBean<>();
bean.setFilter(new WebStatFilter());
//配置初始化参数
Map<String, String> initParam = new HashMap<>();
//排除请求
initParam.put(WebStatFilter.PARAM_NAME_EXCLUSIONS, "*.js,*.gif,*.jpg,*.bmp,*.png,*.css,*.ico,/druid/*");
bean.setInitParameters(initParam);
//拦截所有请求
bean.setUrlPatterns(Arrays.asList("/*"));
return bean;
}
}
修改application.yml
server:
port: 8001
spring:
mvc:
date-format: yyyy-MM-dd HH:mm:ss #修改表单提交到后台的日期格式
#数据源配置
datasource:
db1:
username: root
password: 12345678
url: jdbc:mysql://192.168.5.130:3306/db1
driver-class-name: com.mysql.jdbc.Driver
initialSize: 8 #数据库连接池初始化连接大小
minIdle: 5 #数据库连接池最小连接池数量
maxActive: 20 #数据库连接池最大连接池数量
maxWait: 60000 #获取连接时最大等待时间,单位毫秒
timeBetweenEvictionRunsMillis: 60000 #有两个含义:1) Destroy线程会检测连接的间隔时间,即配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 2) testWhileIdle的判断依据;
minEvictableIdleTimeMillis: 300000 #配置一个连接在池中最小生存的时间,单位是毫秒
validationQuery: SELECT 1 FROM DUAL #用来检测连接是否有效的sql
testWhileIdle: true #建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
testOnBorrow: false #申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。
testOnReturn: false #归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能
poolPreparedStatements: false #是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大,比如说oracle。在mysql下建议关闭,分库分表较多的数据库,建议配置为false。
maxPoolPreparedStatementPerConnectionSize: -1 #要启用PSCache,必须配置大于0,当大于0时,poolPreparedStatements自动触发修改为true。在Druid中,不会存在Oracle下PSCache占用内存过多的问题,可以把这个数值配置大一些,比如说100
#监控配置
filters: stat,wall,slf4j
useGlobalDataSourceStat: true #合并多个DruidDataSource的监控数据,多数据源
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500 #druid.stat.mergeSql=true 合并执行的相同sql,避免因为参数不同而统计多条sql语句, druid.stat.slowSqlMillis=500 用来配置SQL慢的标准,执行时间超过slowSqlMillis的就是慢
db2:
username: root
password: 12345678
url: jdbc:mysql://192.168.5.130:3306/db2
driver-class-name: com.mysql.jdbc.Driver
initialSize: 18 #数据库连接池初始化连接大小
minIdle: 15 #数据库连接池最小连接池数量
maxActive: 120 #数据库连接池最大连接池数量
maxWait: 60000 #获取连接时最大等待时间,单位毫秒
timeBetweenEvictionRunsMillis: 60000 #有两个含义:1) Destroy线程会检测连接的间隔时间,即配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒 2) testWhileIdle的判断依据;
minEvictableIdleTimeMillis: 300000 #配置一个连接在池中最小生存的时间,单位是毫秒
validationQuery: SELECT 1 FROM DUAL #用来检测连接是否有效的sql
testWhileIdle: true #建议配置为true,不影响性能,并且保证安全性。申请连接的时候检测,如果空闲时间大于timeBetweenEvictionRunsMillis,执行validationQuery检测连接是否有效。
testOnBorrow: false #申请连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能。
testOnReturn: false #归还连接时执行validationQuery检测连接是否有效,做了这个配置会降低性能
poolPreparedStatements: false #是否缓存preparedStatement,也就是PSCache。PSCache对支持游标的数据库性能提升巨大,比如说oracle。在mysql下建议关闭,分库分表较多的数据库,建议配置为false。
maxPoolPreparedStatementPerConnectionSize: -1 #要启用PSCache,必须配置大于0,当大于0时,poolPreparedStatements自动触发修改为true。在Druid中,不会存在Oracle下PSCache占用内存过多的问题,可以把这个数值配置大一些,比如说100
#监控配置
filters: stat,wall,slf4j
useGlobalDataSourceStat: true #合并多个DruidDataSource的监控数据,多数据源
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=500 #druid.stat.mergeSql=true 合并执行的相同sql,避免因为参数不同而统计多条sql语句, druid.stat.slowSqlMillis=500 用来配置SQL慢的标准,执行时间超过slowSqlMillis的就是慢
#mybatis配置文件
mybatis:
#mapper-locations: classpath:mybatis/mapper/db1/*.xml #mybatis的sql的mapper接口映射文件(单数据源配置使用) 多数据源在对应的配置类中配置使用,这里配置不起作用了(比如DataSource1Config类中)
#type-aliases-package: com.sgz.atomikos.**.entity #对应实体类的路径(可以不用配置)
configuration:
map-underscore-to-camel-case: true #开启驼峰命名转换
#pagehelper分页插件
pagehelper:
helperDialect: mysql
reasonable: true #pageHelper里面自带的一个功能,叫做reasonable分页参数合理化,3.3.0以上版本可用,默认是false。 启用合理化时,如果pageNum<1会查询第一页,如果pageNum>pages(超过总数时)会查询最后一页;禁用合理化时,如果pageNum<1或pageNum>pages会返回空数据
supportMethodsArguments: true
params: count=countSql;
#日志配置
logging:
level:
root: info
com.sgz.atomikos: debug
file: D:/logs/springboot.log