在上一章中文,我使用springboot工程整合了mp,以及mp基础使用,代码生成器使用,Druid 数据监控等,但仅限于单数据源 请戳SpringBoot整合Mybatis-plus(一)基本使用与自定义模板代码生成器
因为很多时候,在开发中,并不会只有一个数据库,在保存一条数据的时候,可能需要向几个数据库保存,那么当发生异常时候,就会面临事务问题了,需要将保存的数据进行回滚,所以,本文开始讲解,整合 Mp 多数据源下的druid监控以及事务处理
org.springframework.boot
spring-boot-starter-jta-atomikos
mysql
mysql-connector-java
5.1.47
spring:
datasource:
type: com.alibaba.druid.pool.xa.DruidXADataSource
druid:
#第一个数据源
one:
name: oneDataSource
url: jdbc:mysql://127.0.0.1:3306/mybatis-plus?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=GMT
driver-class-name: com.mysql.jdbc.Driver
username: root
password: root
# 下面为连接池的补充设置,应用到上面所有数据源中
# 初始化大小,最小,最大
initialSize: 5
minIdle: 5
maxActive: 20
# 配置获取连接等待超时的时间
maxWait: 60000
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
timeBetweenEvictionRunsMillis: 60000
# 配置一个连接在池中最小生存的时间,单位是毫秒
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT 1
validationQueryTimeout: 10000
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
# 打开PSCache,并且指定每个连接上PSCache的大小
poolPreparedStatements: true
maxPoolPreparedStatementPerConnectionSize: 20
filters: stat,wall
# 通过connectProperties属性来打开mergeSql功能;慢SQL记录
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
# 合并多个DruidDataSource的监控数据
useGlobalDataSourceStat: true
#第二个数据源
two:
name: twoDataSource
url: jdbc:mysql://127.0.0.1:3306/mybatis-plus2?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=GMT
driver-class-name: com.mysql.jdbc.Driver
username: root
password: root
# 下面为连接池的补充设置,应用到上面所有数据源中
# 初始化大小,最小,最大
initialSize: 5
minIdle: 5
maxActive: 20
# 配置获取连接等待超时的时间
maxWait: 60000
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
timeBetweenEvictionRunsMillis: 60000
# 配置一个连接在池中最小生存的时间,单位是毫秒
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT 1
validationQueryTimeout: 10000
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
# 打开PSCache,并且指定每个连接上PSCache的大小
poolPreparedStatements: true
maxPoolPreparedStatementPerConnectionSize: 20
filters: stat,wall
# 通过connectProperties属性来打开mergeSql功能;慢SQL记录
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
# 合并多个DruidDataSource的监控数据
useGlobalDataSourceStat: true
#第三个数据源
three:
name: threeDataSource
url: jdbc:mysql://127.0.0.1:3306/mybatis-plus3?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=GMT
driver-class-name: com.mysql.jdbc.Driver
username: root
password: root
# 下面为连接池的补充设置,应用到上面所有数据源中
# 初始化大小,最小,最大
initialSize: 5
minIdle: 5
maxActive: 20
# 配置获取连接等待超时的时间
maxWait: 60000
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
timeBetweenEvictionRunsMillis: 60000
# 配置一个连接在池中最小生存的时间,单位是毫秒
minEvictableIdleTimeMillis: 300000
validationQuery: SELECT 1
validationQueryTimeout: 10000
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
# 打开PSCache,并且指定每个连接上PSCache的大小
poolPreparedStatements: true
maxPoolPreparedStatementPerConnectionSize: 20
filters: stat,wall
# 通过connectProperties属性来打开mergeSql功能;慢SQL记录
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
# 合并多个DruidDataSource的监控数据
useGlobalDataSourceStat: true
jta:
atomikos:
properties:
log-base-dir: tx-logs
transaction-manager-id: txManager #默认取计算机的IP地址 需保证生产环境值唯一
由于配置了one two three 三个数据源 已经是改变了springBoot 对Mysql 连接读取的配置路径,所以呢,我们需要自定义数据源的位置,在启动项目时才能成功读取数据库相关信息
无论使用Mybatis 还是mp 在访问数据库时都需要 数据源 , SqlSessionFactory,SqlSessionTemplate
首先需要从配置文件中,获取到数据源信息,以及Druid需要监控的信息
读取我们配置文件中数据库路径 生成对象并配置成Bean 交由spring管理
附上我完整代码
package com.leilei.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;
}
}
在DruidConfig中看到了 每个数据源的Bean 都是 AtomikosDataSourceBean 类型 ,只要数据源是此类型,在多个数据源下,打上Spring自带的事务注解@Transactional 即可
/**
* 数据源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;
}
这里贴上我的第一个数据源的sqlsessionfactory sqlSessionTemplate,无论还有多少数据源,按照我这个配置即可,改改包路径,@Qualifier选择对应数据源 ,改改mpper.xml路径即可
需注意的是,必须有一个数据源的所有相关(datasource,sqlsessionfactory sqlSessionTemplate)要使用**@Primary**注解指定一个主库,否则会启动报错。
package com.leilei.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.leilei.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.leilei.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);
}
}
其他数据源的sqlsessionfactory sqlSessionTemplate 照着我这个配置即可 ,去掉 @Primary注解 注意Bean 名称设为唯一。
其实到这里 多数据源下的事务管理以及Druid监控已经是整合完毕了
在使用代码生成器时,将每个数据源的mapper, entity ,mapper.xml包路径划分开来
一个方法中,调用多个数据源插入数据,并中途制造异常,报错后,查看数据回滚·
body 为我封装的一个对象,因为本文设计三个对象,我准备向三个数据源一起插入数据测试事务
操作数据库
@Override
@Transactional //数据源配置了AtomikosDataSourceBean 再打上此注解即可做到多数据源事务控制
public Map insertAto(BodyVo bodyVo) {
userMapper.insert(bodyVo.getUser());
userRoleMapper.insert(bodyVo.getUserRole());
int a = 1 / 0; //制造异常
roleMapper.insert(bodyVo.getRole());
HashMap map = new HashMap<>();
map.put("user", bodyVo.getUser());
map.put("user_role", bodyVo.getUserRole());
map.put("role", bodyVo.getRole());
return map;
}
未开始测试前,数据库数据
运行测试结果,可以看到是报了异常
那么,此时再查看数据数据,刷新后,发现数据并未改变
那么说明,多数据源下的事务问题已经解决了!!!!!
本文是将一个多对多关系的三张表放在了三个库中
本文中usermapper.MoreDatasourceFindAll 是自定义的方法,为了有分页效果必须构造一个Page对象,将Page对象与查询条件一起作为参数传到UserMapper.interface中,再有mapper.xml操作数据库
IPage MoreDatasourceFindAll(Page page, @Param("id") Long id);
查看Druid监控
发现三个数据源均在此排列
Url监控
结语
到这里,本次项目整合就结束了,附上我的项目源码
SpringBoot整合Mybatis-plus(二) 多数据源Druid监控,Atomikos处理事务,跨库连表查询