SpringBoot+Atomikos多数据源事务配置,以及日志打印控制。
程序员文章站
2022-05-23 12:26:59
...
前言:公司业务需求用到了多数据源,为了实现事务一致性所以选择使用了Atomikos。结合网上的资源做了一下配置,只保证了业务的实现具体原理没多做研究。
1·添加依赖
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jta-atomikos</artifactId>
</dependency>
2·数据源配置文件
spring:
datasource:
# 数据库访问配置, 使用druid数据源
type: com.alibaba.druid.pool.DruidDataSource
systemData:
#type: com.alibaba.druid.pool.xa.DruidXADataSource
#type: com.alibaba.druid.pool.DruidDataSource
#driverClassName: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://192.168.13.xxx:3306/oa_salary_test?allowMultiQueries=true&useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8&useSSL=false&autoReconnect=true&failOverReadOnly=false
username: root
password: 123456
initialSize: 5
minIdle: 5
maxActive: 20
maxWait: 30000
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQuery: select 1
validationQueryTimeout: 10000
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: true
maxPoolPreparedStatementPerConnectionSize: 20
filters: stat,wall
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
#物料专用数据库
materialData:
#driverClassName: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://192.168.13.xxx:3306/cloud_rd?allowMultiQueries=true&useUnicode=true&characterEncoding=utf8&serverTimezone=GMT%2B8&useSSL=false&autoReconnect=true&failOverReadOnly=false
username: root
password: 123456
initialSize: 5
minIdle: 5
maxActive: 20
maxWait: 30000
timeBetweenEvictionRunsMillis: 60000
minEvictableIdleTimeMillis: 300000
validationQuery: select 1
validationQueryTimeout: 10000
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
poolPreparedStatements: true
maxPoolPreparedStatementPerConnectionSize: 20
filters: stat,wall
connectionProperties: druid.stat.mergeSql=true;druid.stat.slowSqlMillis=5000
3·数据源配置
package com.dls.dynamicDataSourceConfig;
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 com.atomikos.icatch.jta.UserTransactionImp;
import com.atomikos.icatch.jta.UserTransactionManager;
import java.util.Properties;
import javax.sql.DataSource;
import javax.transaction.UserTransaction;
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;
/**
* Created with IntelliJ IDEA. Created by yutang 2021/3/26 10:43 Description:配置所有数据源
*/
@Configuration
public class DataSourceConfig {
/**
* 公共模块的数据源
* @param env
* @return
*/
@Bean(name = "systemDataSource")
@Primary
public DataSource systemDataSource(Environment env) {
AtomikosDataSourceBean ds = new AtomikosDataSourceBean();
Properties prop = build(env, "spring.datasource.systemData.");
ds.setXaDataSourceClassName("com.alibaba.druid.pool.xa.DruidXADataSource");
ds.setUniqueResourceName("systemDb");
ds.setPoolSize(5);
ds.setXaProperties(prop);
return ds;
}
/**
* 物料模块数据源
* @param env
* @return
*/
@Autowired
@Bean(name = "materialDataSource")
public DataSource materialDataSource(Environment env) {
AtomikosDataSourceBean ds = new AtomikosDataSourceBean();
Properties prop = build(env, "spring.datasource.materialData.");
ds.setXaDataSourceClassName("com.alibaba.druid.pool.xa.DruidXADataSource");
ds.setUniqueResourceName("materialDb");
ds.setPoolSize(5);
ds.setXaProperties(prop);
return ds;
}
/**
* 统一的事务管理器
* @return
*/
@Bean
@Primary//覆盖公共模块里的事物管理器
public JtaTransactionManager jtaTransactionManager () {
UserTransactionManager userTransactionManager = new UserTransactionManager();
UserTransaction userTransaction = new UserTransactionImp();
return new JtaTransactionManager(userTransaction, userTransactionManager);
}
/**
* @Description 配置读取通用的方法
* @param env 环境
* @param prefix 前缀
* @return java.util.Properties
* @throws
*/
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("initialSize", env.getProperty(prefix + "initialSize", Integer.class));
prop.put("minIdle", env.getProperty(prefix + "minIdle", Integer.class));
prop.put("maxActive", env.getProperty(prefix + "maxActive", Integer.class));
prop.put("maxWait", env.getProperty(prefix + "maxWait", Integer.class));
prop.put("timeBetweenEvictionRunsMillis",env.getProperty(prefix + "timeBetweenEvictionRunsMillis", Integer.class));
prop.put("minEvictableIdleTimeMillis", env.getProperty(prefix + "minEvictableIdleTimeMillis", Integer.class));
prop.put("validationQuery", env.getProperty(prefix + "validationQuery"));
prop.put("validationQueryTimeout", env.getProperty(prefix + "validationQueryTimeout", Integer.class));
prop.put("testWhileIdle", env.getProperty(prefix + "testWhileIdle", Boolean.class));
prop.put("testOnBorrow", env.getProperty(prefix + "testOnBorrow", Boolean.class));
prop.put("testOnReturn", env.getProperty(prefix + "testOnReturn", Boolean.class));
prop.put("poolPreparedStatements", env.getProperty(prefix + "poolPreparedStatements", Boolean.class));
prop.put("maxPoolPreparedStatementPerConnectionSize", env.getProperty(prefix + "maxPoolPreparedStatementPerConnectionSize", Integer.class));
prop.put("filters", env.getProperty(prefix + "filters"));
prop.put("connectionProperties", env.getProperty(prefix + "connectionProperties"));
return prop;
}
/**
* @Description 添加对druid的安全访问
* @param
* @return org.springframework.boot.web.servlet.ServletRegistrationBean
* @throws
*/
@Bean
public ServletRegistrationBean druidServlet() {
ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(new StatViewServlet(), "/druid/*");
//控制台管理用户,加入下面2行 进入druid后台就需要登录
//servletRegistrationBean.addInitParameter("loginUsername", "admin");
//servletRegistrationBean.addInitParameter("loginPassword", "admin");
return servletRegistrationBean;
}
/**
* @Description
* @param
* @return org.springframework.boot.web.servlet.FilterRegistrationBean
* @throws
*/
@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();
statFilter.setLogSlowSql(true); //slowSqlMillis用来配置SQL慢的标准,执行时间超过slowSqlMillis的就是慢。
statFilter.setMergeSql(true); //SQL合并配置
statFilter.setSlowSqlMillis(1000);//slowSqlMillis的缺省值为3000,也就是3秒。
return statFilter;
}
@Bean
public WallFilter wallFilter(){
WallFilter wallFilter = new WallFilter();
//允许执行多条SQL
WallConfig config = new WallConfig();
config.setMultiStatementAllow(true);
wallFilter.setConfig(config);
return wallFilter;
}
}
4·配置数据源包相关
package com.dls.dynamicDataSourceConfig;
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.sql.DataSource;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Autowired;
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.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
/**
* Created with IntelliJ IDEA. Created by yutang 2021/3/25 13:58 Description:公共模块数据源
*/
@Configuration
@MapperScan(basePackages = {"com.dls.common.dao", "com.dls.job.dao",
"com.dls.production.**.dao", "com.dls.sale.**.dao",
"com.dls.sysm.android.dao", "com.dls.sysm.bankCard.dao",
"com.dls.sysm.dataDictionary.dao", "com.dls.sysm.log.dao",
"com.dls.sysm.logAndSafety.dao", "com.dls.sysm.permission.dao",
"com.dls.sysm.productPrice.dao",
"com.dls.sysm.pushMsg.dao"}, sqlSessionFactoryRef = "systemSqlSessionFactory")
public class SystemDataSourceConfig {
private String[] mapperLocation = {"classpath*:com/dls/job/dao/xml/*.xml",
"classpath*:com/dls/production/**/dao/xml/*.xml",
"classpath*:com/dls/sale/**/dao/xml/*.xml",
"classpath*:com/dls/sysm/android/dao/xml/*.xml",
"classpath*:com/dls/sysm/bankCard/dao/xml/*.xml",
"classpath*:com/dls/sysm/dataDictionary/dao/xml/*.xml",
"classpath*:com/dls/sysm/log/dao/xml/*.xml",
"classpath*:com/dls/sysm/logAndSafety/dao/xml/*.xml",
"classpath*:com/dls/sysm/permission/dao/xml/*.xml",
"classpath*:com/dls/sysm/productPrice/dao/xml/*.xml",
"classpath*:com/dls/sysm/pushMsg/dao/xml/*.xml"};
private String domainPackage = "com.dls.**.entity.**,com.dls.**.vo.**";
@Autowired
@Qualifier("systemDataSource")
private DataSource systemDataSource;
@Bean(name = "systemSqlSessionFactory")
@Primary
public SqlSessionFactory systemSqlSessionFactory(
@Qualifier("systemPaginationInterceptor") PaginationInterceptor paginationInterceptor)
throws Exception {
//注意,这里引入的是MP的工厂,而不是mybatis的工厂SqlSessionFactoryBean
MybatisSqlSessionFactoryBean bean = new MybatisSqlSessionFactoryBean();
bean.setDataSource(systemDataSource);
//引入Mapper.xml文件的位置
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
List<Resource> resources = new ArrayList<>();
if (mapperLocation.length > 0) {
for (int i = 0; i < mapperLocation.length; i++) {
Resource[] resource = resolver.getResources(mapperLocation[i]);
resources.addAll(Arrays.asList(resource));
}
}
bean.setMapperLocations(resources.toArray(new Resource[resources.size()]));
//bean.getObject().getConfiguration().setMapUnderscoreToCamelCase(true);
bean.setTypeAliasesPackage(domainPackage);
Interceptor[] plugins = new Interceptor[]{paginationInterceptor};
bean.setPlugins(plugins);
return bean.getObject();
}
/**
* 分页插件
*/
@Bean("systemPaginationInterceptor")
public PaginationInterceptor paginationInterceptor() {
return new PaginationInterceptor();
}
@Bean(name = "systemSqlSessionTemplate")
@Primary
public SqlSessionTemplate systemSqlSessionTemplate(
@Qualifier("systemSqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
由于是老项目之前没有很好的区分模块,所以扫描包很麻烦。
@MapperScan里的basePackages不知道为什么不能接收数组,只好全写在里面了。多个mapperLocation需要循环写入,这2个配置不知道有没有更好的方式,目前先这样。
package com.dls.dynamicDataSourceConfig;
import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import javax.sql.DataSource;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionTemplate;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.io.Resource;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
/**
* Created with IntelliJ IDEA. Created by yutang 2021/3/25 13:58 Description:物料数据源
*/
@Configuration
@MapperScan(basePackages = {"com.dls.material.dao", "com.dls.sysm.activity.dao",
"com.dls.common.dao"}, sqlSessionFactoryRef = "materialSqlSessionFactory")
public class MaterialDataSourceConfig {
private String[] mapperLocation = {"classpath*:com/dls/material/dao/xml/*.xml",
"classpath*:com/dls/sysm/activity/dao/xml/*.xml",
"classpath*:com/dls/common/dao/xml/*.xml"};
//private static final String DOMAIN_PACKAGE = "com.dls.job.material.entity.**,com.dls.material.vo.**,"
// + "com.dls.common.entity.**,com.dls.sysm.activity.entity.**,com.dls.sysm.activity.vo.**";
@Autowired
@Qualifier("materialDataSource")
private DataSource materialDataSource;
@Bean(name = "materialSqlSessionFactory")
public SqlSessionFactory materialSqlSessionFactory(
@Qualifier("materialPaginationInterceptor") PaginationInterceptor paginationInterceptor)
throws Exception {
MybatisSqlSessionFactoryBean bean = new MybatisSqlSessionFactoryBean();
bean.setDataSource(materialDataSource);
//引入Mapper.xml文件的位置
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
List<Resource> resources = new ArrayList<>();
if (mapperLocation.length > 0) {
for (int i = 0; i < mapperLocation.length; i++) {
Resource[] resource = resolver.getResources(mapperLocation[i]);
resources.addAll(Arrays.asList(resource));
}
}
bean.setMapperLocations(resources.toArray(new Resource[resources.size()]));
//bean.getObject().getConfiguration().setMapUnderscoreToCamelCase(true);
//bean.setTypeAliasesPackage(DOMAIN_PACKAGE);
Interceptor[] plugins = new Interceptor[]{paginationInterceptor};
bean.setPlugins(plugins);
return bean.getObject();
}
/**
* 分页插件
*/
@Bean("materialPaginationInterceptor")
public PaginationInterceptor paginationInterceptor() {
return new PaginationInterceptor();
}
@Bean(name = "materialSqlSessionTemplate")
public SqlSessionTemplate materialSqlSessionTemplate(
@Qualifier("materialSqlSessionFactory") SqlSessionFactory sqlSessionFactory)
throws Exception {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
以上基本都配置完成了,具体使用和平时一样,在需要用事务的方法加上@Transactional就可以了。
日志打印的处理:
配置好之后控制台会默认一直打印atomikos的info日志很烦,网上找了半天配置也没弄好,后来在spring日志里给配置好了。
在配置文件里做如下配置就好了。
#日志级别TRACE < DEBUG < INFO < WARN < ERROR < FATAL。
logging:
level:
com.atomikos: WARN
至此已经全部搞定了。项目并发量不高,目前还没考虑到优化。有不懂的可以留言咨询。
上一篇: 光武帝刘秀开挂人生,仅用三年时间登上帝位