前言
本篇文章详细介绍了基于阿里巴巴的Druid连接池的多数据源以及Spring 的自定义事务管理器实现方式。
技术栈:
JDK:8
Spring Boot:2.5.5
Mybatis:2.2.0
Druid:1.1.10 (建议使用1.2.8,我受限于Nexus只能用1.1.10)
文章目录
Yml配置
spring:
datasource:
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
url: jdbc:mysql://localhost:3306/db1?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true&tinyInt1isBit=false
username: root
password: root
datasource2:
driver-class-name: com.mysql.cj.jdbc.Driver
type: com.alibaba.druid.pool.DruidDataSource
url: jdbc:mysql://localhost:3306/db2?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true&tinyInt1isBit=false
username: root
password: root
这两个数据源可以是异构数据库(未实测),注意调整相关配置
Druid连接池配置类
package com.xxx.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 lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
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 javax.sql.DataSource;
/**
* Description:
* 阿里巴巴数据库库连接池配置类
* <p>
* date: 2021/11/26 15:20
*
* @author Arvin Lee
* @version 1.0
*/
@Configuration
@Slf4j
public class DruidConfig {
@Value("${spring.datasource.url:#{null}}")
private String dbUrl;
@Value("${spring.datasource.username: #{null}}")
private String username;
@Value("${spring.datasource.password:#{null}}")
private String password;
@Value("${spring.datasource.driverClassName:#{null}}")
private String driverClassName;
@Value("${spring.datasource2.url:#{null}}")
private String dbUrl2;
@Value("${spring.datasource2.username: #{null}}")
private String username2;
@Value("${spring.datasource2.password:#{null}}")
private String password2;
@Value("${spring.datasource2.driverClassName:#{null}}")
private String driverClassName2;
@Bean
@Primary
public DataSource dataSource() {
return CommonFunctionInConfig.getDataSource(dbUrl,
username,
password,
driverClassName,
statFilter(), wallFilter());
}
@Bean
public DataSource dataSource2() {
return CommonFunctionInConfig.getDataSource(dbUrl2, username2, password2, driverClassName2,
statFilter(), wallFilter());
}
@Bean
public ServletRegistrationBean druidServlet() {
ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(new StatViewServlet(), "/druid/*");
//控制台管理用户,加入下面2行 进入druid后台就需要登录
servletRegistrationBean.addInitParameter("loginUsername", "admin");
servletRegistrationBean.addInitParameter("loginPassword", "admin");
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.xxx.config;
import com.alibaba.druid.filter.Filter;
import com.alibaba.druid.filter.stat.StatFilter;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.wall.WallFilter;
import java.util.ArrayList;
import java.util.List;
/**
* Description:
* 配置包下的通用方法
* <p>
* date: 2021/11/29 15:06
*
* @author Arvin Lee
* @version 1.0
*/
public class CommonFunctionInConfig {
/**
* getDataSource
* 获取数据源
*
* @param dbUrl 数据库连接地址
* @param username 用户名
* @param password 密码
* @param driverClassName 驱动名
* @param statFilter -
* @param wallFilter -
* @return com.alibaba.druid.pool.DruidDataSource
* @author Arvin Lee
* @date 2021/12/7 9:28
**/
protected static DruidDataSource getDataSource(String dbUrl,
String username,
String password,
String driverClassName,
StatFilter statFilter,
WallFilter wallFilter) {
DruidDataSource datasource = new DruidDataSource();
datasource.setUrl(dbUrl);
datasource.setUsername(username);
datasource.setPassword(password);
datasource.setDriverClassName(driverClassName);
List<Filter> filters = new ArrayList<>();
filters.add(statFilter);
filters.add(wallFilter);
datasource.setProxyFilters(filters);
return datasource;
}
}
连接池其他配置参数可以在这里统一调整
第一个数据源(datasource)配置类
package com.xxx.config;
import com.xxx.BaseMapper;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
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 org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import tk.mybatis.spring.annotation.MapperScan;
import javax.annotation.Resource;
import javax.sql.DataSource;
/**
* Description:
* mybatis数据源配置
* <p>
* date: 2021/11/26 15:39
*
* @author Arvin Lee
* @version 1.0
*/
@Configuration
@MapperScan(basePackages = "com.xxx.dao.datasource1", markerInterface = BaseMapper.class, sqlSessionFactoryRef = "sqlSessionFactory")
public class DataSourceConfig {
@Resource
@Qualifier("dataSource")
private DataSource ds;
@Bean
public SqlSessionFactory sqlSessionFactory() throws Exception {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setDataSource(ds);
//指定mapper xml目录
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
factoryBean.setMapperLocations(resolver.getResources("classpath:mapper/datasource1/*.xml"));
return factoryBean.getObject();
}
@Bean
public SqlSessionTemplate sqlSessionTemplate() throws Exception {
// 使用上面配置的Factory
return new SqlSessionTemplate(sqlSessionFactory());
}
/**
* masterTransactionManager
* 关于事务管理器,不管是JPA还是JDBC等都实现自接口 PlatformTransactionManager
* 如果你添加的是 spring-boot-starter-jdbc 依赖,框架会默认注入 DataSourceTransactionManager 实例。
* 在Spring容器中,我们手工注解@Bean 将被优先加载,框架不会重新实例化其他的 PlatformTransactionManager 实现类。
*
* @return org.springframework.jdbc.datasource.DataSourceTransactionManager
* @author Arvin Lee
* @date 2021/11/26 15:42
**/
@Bean(name = "transactionManager")
@Primary
public DataSourceTransactionManager masterTransactionManager() {
//MyBatis自动参与到spring事务管理中,无需额外配置,只要org.mybatis.spring.SqlSessionFactoryBean引用的数据源
// 与DataSourceTransactionManager引用的数据源一致即可,否则事务管理会不起作用。
return new DataSourceTransactionManager(ds);
}
}
- 此处需要注意一下,代码经过脱敏,所以MapperScan 注解的 basePackages 路径com.xxx.dao.datasource1 大意如下
–com.xxx
------dao (存放dao层接口)
----------datasource1(与第一个数据源映射的dao)
----------datasource2(与第二个数据源映射的dao)- markerInterface 对应的BaseMapper.class是我自定义的Mybatis的BaseMapper,如果没有可以不写。
- sqlSessionFactoryRef 参考的sql会话工厂,这个与返回SqlSessionFactory的方法的方法名对应
- @Qualifier(“dataSource”) 这是给DataSource起别名,因为是多数据源,命名没有特殊要求。
- classpath:mapper/datasource1/*.xml 这个是mapper.xml映射路径,路径格式参考dao
示例:
–reosurce
------mapper
----------datasource1
----------datasource2
第二个数据源(datasource2)配置类
package com.xxx.config;
import com.xxx.common.BaseMapper;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.SqlSessionTemplate;
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 org.springframework.core.io.support.ResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import tk.mybatis.spring.annotation.MapperScan;
import javax.annotation.Resource;
import javax.sql.DataSource;
/**
* Description:
* mybatis数据源2配置
* <p>
* date: 2021/11/26 16:02
*
* @author Arvin Lee
* @version 1.0
*/
@Configuration
@MapperScan(basePackages = "com.xxx.dao.datasource2", markerInterface = BaseMapper.class, sqlSessionFactoryRef = "sqlSessionFactory2")
public class DataSource2Config {
@Resource
@Qualifier("dataSource2")
private DataSource ds;
@Bean
public SqlSessionFactory sqlSessionFactory2() throws Exception {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setDataSource(ds);
//指定mapper xml目录
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
factoryBean.setMapperLocations(resolver.getResources("classpath:mapper/datasource2/*.xml"));
return factoryBean.getObject();
}
@Bean
public SqlSessionTemplate sqlSessionTemplate2() throws Exception {
// 使用上面配置的Factory
return new SqlSessionTemplate(sqlSessionFactory2());
}
/**
* masterTransactionManager
* 关于事务管理器,不管是JPA还是JDBC等都实现自接口 PlatformTransactionManager
* 如果你添加的是 spring-boot-starter-jdbc 依赖,框架会默认注入 DataSourceTransactionManager 实例。
* 在Spring容器中,我们手工注解@Bean 将被优先加载,框架不会重新实例化其他的 PlatformTransactionManager 实现类。
*
* @return org.springframework.jdbc.datasource.DataSourceTransactionManager
* @author Arvin Lee
* @date 2021/11/26 15:42
**/
@Bean(name = "transactionManager2")
@Primary
public DataSourceTransactionManager masterTransactionManager() {
//MyBatis自动参与到spring事务管理中,无需额外配置,只要org.mybatis.spring.SqlSessionFactoryBean引用的数据源
// 与DataSourceTransactionManager引用的数据源一致即可,否则事务管理会不起作用。
return new DataSourceTransactionManager(ds);
}
}
- 格式与第一个一致,只不过dao、mapper.xml、datasource、SQLSessionFactory不同,需要加个2
- 如果3个数据源,以此类推,再加个DataSourceConfig 同时也要修改DruidConfig
自定义事务管理器通用方法类
package com.xxx.interceptor;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.TransactionManager;
import org.springframework.transaction.interceptor.*;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
/**
* Description:
* <p>
* date: 2021/11/29 10:11
*
* @author Arvin Lee
* @version 1.0
*/
public class OtherFunction {
private static final int TX_METHOD_TIMEOUT = 50000;
protected static TransactionInterceptor getInterceptor(TransactionManager transactionManager) {
NameMatchTransactionAttributeSource source = new NameMatchTransactionAttributeSource();
/*只读事务,不做更新操作*/
RuleBasedTransactionAttribute readOnlyTx = new RuleBasedTransactionAttribute();
readOnlyTx.setReadOnly(true);
readOnlyTx.setPropagationBehavior(TransactionDefinition.PROPAGATION_NOT_SUPPORTED);
/*当前存在事务就使用当前事务,当前不存在事务就创建一个新的事务*/
RuleBasedTransactionAttribute requiredTx = new RuleBasedTransactionAttribute();
requiredTx.setRollbackRules(Collections.singletonList(new RollbackRuleAttribute(Exception.class)));
requiredTx.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
requiredTx.setTimeout(TX_METHOD_TIMEOUT);
Map<String, TransactionAttribute> txMap = new HashMap<>(8);
txMap.put("add*", requiredTx);
txMap.put("save*", requiredTx);
txMap.put("insert*", requiredTx);
txMap.put("update*", requiredTx);
txMap.put("delete*", requiredTx);
txMap.put("get*", readOnlyTx);
txMap.put("select*", readOnlyTx);
txMap.put("query*", readOnlyTx);
source.setNameMap(txMap);
return new TransactionInterceptor(transactionManager, source);
}
}
这个是下面事务管理器的基础
自定义第一个数据源的事务管理器
package com.xxx.interceptor;
import com.xxx.config.DataSourceConfig;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.aop.Advisor;
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.TransactionManager;
import org.springframework.transaction.interceptor.TransactionInterceptor;
import javax.annotation.Resource;
/**
* Description:
* 使用 DataSourceConfig
* 生成的事务管理器,通过AOP为指定包下的service方法提供事务支持
* 并根据不同的方法提供不同的事务隔离级别
*
* @author Arvin Lee
* @version 1.0
* @see DataSourceConfig
*
* <p>
* date: 2021/11/26 15:44
*/
@Aspect
@Configuration
public class TxAdviceInterceptor {
/**
* 单位秒
*/
private static final String AOP_POINTCUT_EXPRESSION = "execution (* com.xxx.service.datasource1.*.*(..))";
@Resource
@Qualifier("transactionManager")
private TransactionManager transactionManager;
@Bean
public TransactionInterceptor txAdvice() {
return OtherFunction.getInterceptor(transactionManager);
}
@Bean
public Advisor txAdviceAdvisor() {
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression(AOP_POINTCUT_EXPRESSION);
return new DefaultPointcutAdvisor(pointcut, txAdvice());
}
}
service映射路径规则与dao一致,不做赘述
自定义第二个数据源的事务管理器
package com.xxx.interceptor;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.aop.Advisor;
import org.springframework.aop.aspectj.AspectJExpressionPointcut;
import org.springframework.aop.support.DefaultPointcutAdvisor;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.TransactionManager;
import org.springframework.transaction.interceptor.TransactionInterceptor;
import javax.annotation.Resource;
/**
* Description:
* <p>
* date: 2021/11/26 16:05
*
* @author Arvin Lee
* @version 1.0
*/
@Aspect
@Configuration
public class TxAdvice2Interceptor {
/**
* 单位秒
*/
private static final String AOP_POINTCUT_EXPRESSION = "execution (* com.xxx.service.datasource2.*.*(..))";
@Resource
@Qualifier("transactionManager2")
private TransactionManager transactionManager;
@Bean
public TransactionInterceptor txAdvice2() {
return OtherFunction.getInterceptor(transactionManager);
}
@Bean
public Advisor txAdviceAdvisor2() {
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
pointcut.setExpression(AOP_POINTCUT_EXPRESSION);
return new DefaultPointcutAdvisor(pointcut, txAdvice2());
}
}
同上
结语
- 实现以上代码以后就可以实现多数据源以及多数据源的自定义事务了。如果由异常或其他问题可以留言讨论
- 我的实现是为了解决业务上的不同库问题,当然,这个也可以用来做读写分离
- 关于Mybatis的自定义Mapper 自定义Service我会再另一篇文章中介绍
- 其实很多人都不知道,druid是有可视化界面的,相信细心的童鞋已经通过DruidConfig这个类发现类,可以配置账号密码,访问地址就是 https://localhost:port/druid 这个UI界面详细展示了连接池的相关信息,并对应用的SQL进行监控,基于这个数据,也可以拓展个系统SQL性能监控功能模块,详情请见官网