Spring Boot + Mybatis + Druid 动态切换多数据源
在大型应用程序中,配置主从数据库并使用读写分离是常见的设计模式。
在Spring应用程序中,要实现读写分离,最好不要对现有代码进行改动,而是在底层透明地支持。
这样,就需要我们再一个项目中,配置两个,乃至多个数据源。
今天,小编先来介绍一下自己配置动态多数据源的步骤
项目简介:
编译器:IDEA
JDK:1.8
框架:Spring Boot 2.1.0.RELEASES + Mybatis + Druid
一、配置数据库连接数据
因为项目使用的是Spring Boot 框架,该框架会自动配置数据源,自动从application.properties中读取数据源信息,如果没有配置,启动时会报错,因此我们再配置自定义的数据源的时候,需要禁掉数据源的自动配置。
但是小编在启动项目的时候,还是报错了,可是由于jdbcTemplate重复了,框架自动帮我们定义了一个jdbcTemplate,而小编自己又自定义了一个,因此,也要将这个自动配置禁止掉
启动类方法如下:
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class,JdbcTemplateAutoConfiguration.class})
@MapperScan(sqlSessionTemplateRef = "jdbcTemplate")
public class DynamicDatasourseApplication { public static void main(String[] args) {
SpringApplication.run(DynamicDatasourseApplication.class, args);
}
}
下面开始配置自定义的数据源。
新建jdbc.properties文件,配置数据库的连接,数据源1为写库,数据源2为读库
jdbc.driverClassName.db=com.mysql.jdbc.Driver
#主数据源
jdbc.w.url=jdbc:mysql://localhost:3306/learning?characterEncoding=UTF-8&&serverTimezone=UTC
jdbc.w.user=root
jdbc.w.password=
#从数据源
jdbc.r.url=jdbc:mysql://localhost:3306/slave?characterEncoding=UTF-8&&serverTimezone=UTC
jdbc.r.user=root
jdbc.r.password=
#连接池配置
druid.initialSize=
druid.minIdle=
druid.maxActive=
druid.maxWait=
druid.timeBetweenEvictionRunsMillis=
druid.minEvictableIdleTimeMillis=
druid.validationQuery=SELECT 'x'
druid.testWhileIdle=true
druid.testOnBorrow=false
druid.testOnReturn=false
druid.poolPreparedStatements=true
druid.maxPoolPreparedStatementPerConnectionSize=
druid.filters=wall,stat
建表语句:
#数据库learning
CREATE TABLE `a` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`age` int(11) NOT NULL,
`gender` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`psw` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`seq` int(11) NOT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 8 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic; INSERT INTO `a` VALUES (1, 'zsan', 30, 'f', '', 3);
INSERT INTO `a` VALUES (2, 'lisi', 31, 'f', '', 5);
INSERT INTO `a` VALUES (3, 'wangwu', 32, 'm', '', 1);
INSERT INTO `a` VALUES (4, 'zhaoliu', 33, 'm', '', 4);
INSERT INTO `a` VALUES (5, 'baiqi', 34, 'm', '', 6);
INSERT INTO `a` VALUES (6, 'hongba', 35, 'f', '', 2);
INSERT INTO `a` VALUES (7, 'zhuyl', 30, 'f', '', 7); #数据库slave
CREATE TABLE `b` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`age` int(11) NOT NULL,
`gender` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`psw` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NOT NULL,
`seq` int(11) NOT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 8 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic; INSERT INTO `b` VALUES (1, 'szsan', 30, 'f', '', 3);
INSERT INTO `b` VALUES (2, 'slisi', 31, 'f', '', 5);
INSERT INTO `b` VALUES (3, 'swangwu', 32, 'm', '', 1);
INSERT INTO `b` VALUES (4, 'szhaoliu', 33, 'm', '', 4);
INSERT INTO `b` VALUES (5, 'sbaiqi', 34, 'm', '', 6);
INSERT INTO `b` VALUES (6, 'shongba', 35, 'f', '', 2);
INSERT INTO `b` VALUES (7, 'szhuyl', 30, 'f', '', 7);
建表语句
二、配置mybatis的属性
在application.properties中配置mybatis的属性
mybatis.type-aliases-package:实体类的位置,如果将实体类放到Application.java文件的同级包或者下级包时,这个属性可以不配置
mybatis.mapper-locations:mapper.xml的位置
mybatis.config-location:mybatis配置文件的位置,无则不填
mybatis.type-aliases-package=cn.com.exercise.dynamicDatasourse.module.condition
mybatis.mapper-locations=/mappers/**.xml
mybatis.config-location=/config/sqlmap-config.xml
三、使用Java文件读取资源数据
1)配置主数据源(写库)
@Bean(name = '写库名字')
@Primary
public DataSource master(){
DruidDataSource source = new DruidDataSource();
//使用source.setXxx(Yyy);进行配置
//数据库基本属性driverClassName url、user、password配置
//连接池基本属性配置
return source;
}
@Primary表示优先为注入的Bean,此处用来标识住数据源
2)配置从数据源(读库),配置内容和主数据源相同
@Bean(name = '读库名字')
public DataSource master(){
DruidDataSource source = new DruidDataSource();
//使用source.setXxx(Yyy);进行配置
//数据库基本属性driverClassName url、user、password配置
//连接池基本属性配置
return source;
}
3)数据源支持,配置默认数据源
@Bean(name = "dynamicDataSource")
public DataSource dynamicDataSource(){
DynamicDataSource dynamicRoutingDataSource = new DynamicDataSource();
//配置多数据源
Map<Object, Object> dataSourceMap = new HashMap<>(2);
dataSourceMap.put("写库名字", master());
dataSourceMap.put("读库名字", slave());
// 将 master 数据源作为默认指定的数据源
dynamicRoutingDataSource.setDefaultTargetDataSource(master());
// 将 master 和 slave 数据源作为指定的数据源
dynamicRoutingDataSource.setTargetDataSources(dataSourceMap);
return dynamicRoutingDataSource;
}
4)配置sqlSessionFactory和jdbcTemplate
在sqlSessionFactory中,配置mybatis相关的三个内容:typeAliasesPackage,configLocation和mapperLocation,分别对应了application.properties中的三个内容,有则配置,无则省略。
@Bean(name = "sqlSessionFactory")
public SqlSessionFactoryBean sqlSessionFactoryBean() throws Exception{
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
sqlSessionFactoryBean.setVfs(SpringBootVFS.class);
sqlSessionFactoryBean.setTypeAliasesPackage(typeAlias);
sqlSessionFactoryBean.setConfigLocation( new ClassPathResource(sqlmapConfigPath));
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
String packageSearchPath = PathMatchingResourcePatternResolver.CLASSPATH_ALL_URL_PREFIX+mapperLocation;
sqlSessionFactoryBean.setMapperLocations(resolver.getResources(packageSearchPath));
sqlSessionFactoryBean.setDataSource(dynamicDataSource());
return sqlSessionFactoryBean;
} @Bean(name = "jdbcTemplate")
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory){
return new SqlSessionTemplate(sqlSessionFactory);
}
5)配置事务传播相关内容
@Bean(name = "transactionManager")
public PlatformTransactionManager transactionManager() {
DataSourceTransactionManager manager = new DataSourceTransactionManager(dynamicDataSource());
return manager;
} /**
* 配置事务的传播特性
*/
@Bean(name = "txAdvice")
public TransactionInterceptor txAdvice(){
TransactionInterceptor interceptor = new TransactionInterceptor();
interceptor.setTransactionManager(transactionManager());
Properties transactionAttributes = new Properties();
//使用transactionAttributes.setProperty()配置传播特性
interceptor.setTransactionAttributes(transactionAttributes);
return interceptor;
} @Bean(name = "txAdviceAdvisor")
public Advisor txAdviceAdvisor() {
AspectJExpressionPointcut pointcut = new AspectJExpressionPointcut();
String transactionExecution = "execution(* cn.com.hiveview.springboot.demoapi..service.*.*(..))";
pointcut.setExpression(transactionExecution);
return new DefaultPointcutAdvisor(pointcut, txAdvice());
}
四、动态数据源支持
在上面配置动态数据源支持的时候,我们使用了一个类“DynamicDataSource.java”。
这个类是自定义的类,继承了抽象类AbstractRoutingDataSource,正是通过这个抽象类来实现动态数据源的选择的。
来看下这个抽象类的成员变量:
private Map<Object, Object> targetDataSources;
private Object defaultTargetDataSource;
以下介绍可以参考上一节(3)的内容。
【1】targetDataSources:保存了key和数据库连接的映射关系
【2】defaultTargetDataSource:表示默认的数据库连接
接下来就是根据这个类,实现我们自己的类DynamicDataSource.java
public class DynamicDataSource extends AbstractRoutingDataSource { @Autowired
private DBHelper helper; @Override
protected Object determineCurrentLookupKey() {
return helper.getDBType();
}
}
determineCurrentLookUpKey():决定需要使用哪个数据库,这个方法需要我们自己实现
先看一下在抽象类中,是如何使用这个方法的
protected DataSource determineTargetDataSource() {
Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
Object lookupKey = this.determineCurrentLookupKey();
DataSource dataSource = (DataSource)this.resolvedDataSources.get(lookupKey);
if (dataSource == null && (this.lenientFallback || lookupKey == null)) {
dataSource = this.resolvedDefaultDataSource;
} if (dataSource == null) {
throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
} else {
return dataSource;
}
}
因此我们只需在determineCurrentLookUpKey方法中,返回数据库的标志即可。
DBHelper类也是自定义的类,数据源持有类,存放了读、写库名字,以及设置数据源类型、获取数据源类型、清除数据源类型的方法
@Component
public class DBHelper {
/**
* 线程独立
*/
private ThreadLocal<String> contextHolder = new ThreadLocal<String>(); public static final String DB_TYPE_RW = "dataSource_db01";
public static final String DB_TYPE_R = "dataSource_db02"; public String getDBType() {
String db = contextHolder.get();
if (db == null) {
db = DB_TYPE_RW;
// 默认是读写库
}
return db;
} public void setDBType(String str) {
contextHolder.set(str);
} public void clearDBType() {
contextHolder.remove();
}
}
五、动态切换
1)配置注解 DS.java
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE,ElementType.METHOD})
public @interface DS {
String value() default "主库名字";
}
2)使用AOP切换
@Aspect
@Component
@Order(0)
public class DynamicDataSourceAspect {
private static final Logger logger = LoggerFactory.getLogger(DynamicDataSourceAspect.class.getName()); @Autowired
DBHelper dbHelper; /**
* 在Mapper层添加注解,实现切换数据源
*/
@Pointcut("execution(* cn.com.exercise.dynamicDatasourse.module..mapper.*.*(..))")
public void dataSourcePointCut(){
} @Before("dataSourcePointCut()")
public void before(JoinPoint joinPoint) {
Object target = joinPoint.getTarget();
String method = joinPoint.getSignature().getName();
Class<?>[] clazz = target.getClass().getInterfaces();
Class<?>[] parameterTypes = ((MethodSignature) joinPoint.getSignature()).getMethod().getParameterTypes();
try {
Method m = clazz[0].getMethod(method, parameterTypes);
//如果方法上存在切换数据源的注解,则根据注解内容进行数据源切换
if (m != null && m.isAnnotationPresent(DS.class)) {
DS data = m.getAnnotation(DS.class);
String dataSourceName = data.value();
dbHelper.setDBType(dataSourceName);
logger.debug("current thread " + Thread.currentThread().getName() + " add " + dataSourceName + " to ThreadLocal");
} else {
logger.debug("switch datasource fail,use default");
}
} catch (Exception e) {
logger.error("current thread " + Thread.currentThread().getName() + " add data to ThreadLocal error", e);
}
} @After("dataSourcePointCut()")
public void after(JoinPoint joinPoint){
dbHelper.clearDBType();
}
}
完成以上内容后,就可以在mapper层的方法上,添加@DS注解,来实现数据源的切换了。
六、使用
mapper层代码
@Mapper
public interface DynamicMapper {
List<DynamicCondition> getListFromSource1(); @DS(DBHelper.DB_TYPE_R)
List<DynamicCondition> getListFromSource2();
}
由于写库是默认数据源,因此当不使用@DS配置数据源,以及使用@DS(“写库名字”)时,使用的都是写库。
依次访问地址:
http://localhost:8082/dynamic/source1,
http://localhost:8082/dynamic/source2
运行结果如下:
按照以上步骤,就可以完成动态切换数据源了,下面附上完整代码连接
Spring Boot + Mybatis + Druid 动态切换多数据源的更多相关文章
- spring boot + mybatis + druid配置实践
最近开始搭建spring boot工程,将自身实践分享出来,本文将讲述spring boot + mybatis + druid的配置方案. pom.xml需要引入mybatis 启动依赖: < ...
- Spring Boot + MyBatis + Druid + Redis + Thymeleaf 整合小结
Spring Boot + MyBatis + Druid + Redis + Thymeleaf 整合小结 这两天闲着没事想利用**Spring Boot**加上阿里的开源数据连接池**Druid* ...
- Spring Boot + Mybatis 实现动态数据源
动态数据源 在很多具体应用场景的时候,我们需要用到动态数据源的情况,比如多租户的场景,系统登录时需要根据用户信息切换到用户对应的数据库.又比如业务A要访问A数据库,业务B要访问B数据库等,都可以使用动 ...
- spring boot + mybatis + druid
因为在用到spring boot + mybatis的项目时候,经常发生访问接口卡,服务器项目用了几天就很卡的甚至不能访问的情况,而我们的项目和数据库都是好了,考虑到可能时数据库连接的问题,所以我打算 ...
- spring boot +mybatis+druid 多数据源配置
因为我的工程需要在两个数据库中操作数据,所以要配置两个数据库,我这里没有数据源没有什么主从之分,只是配合多数据源必须要指定一个主数据源,所以我就把 操作相对要对的那个数据库设置为主数据(dataBas ...
- spring boot + mybatis + druid + redis
接上篇,使用redis做缓存 新建spring boot 工程,添加pom引用 <dependency> <groupId>org.springframework.boot&l ...
- Spring动态切换多数据源解决方案
Spring动态配置多数据源,即在大型应用中对数据进行切分,并且采用多个数据库实例进行管理,这样可以有效提高系统的水平伸缩性.而这样的方案就会不同于常见的单一数据实例的方案,这就要程序在运行时根据当时 ...
- Spring Boot下Druid连接池+mybatis
目前Spring Boot中默认支持的连接池有dbcp,dbcp2, hikari三种连接池. 引言: 在Spring Boot下默认提供了若干种可用的连接池,Druid来自于阿里系的一个开源连 ...
- Spring Boot + Mybatis 配置多数据源
Spring Boot + Mybatis 配置多数据源 Mybatis拦截器,字段名大写转小写 package com.sgcc.tysj.s.common.mybatis; import java ...
随机推荐
- 【BZOJ1122】[POI2008] 账本BBB
→传送门← 正解: 贪心加单调队列优化 先粘贴一张别人写的被老师发下来给我们的题解(就是看着这张题解才写出来的) 下面是自己的话(一些具体操作过程): 把环拆成一条2*n的链,然后用优先队列来求出每一 ...
- bzoj1538 [NWERC2017]High Score
网上的题解都很奇怪.. 经过相当长时间的思考,有了一个有效(自认为)的解法 设某一种合法分配方案完成后三个数分别变为a,b,c,其中a>=c,b>=c 此时如果让c减1,让a或b加1(设让 ...
- SpirngMVC-JSON
Springmvc默认用MappingJacksonHttpMessageConverter对json数据进行转换,需要加入jackson的包,如下: 配置json转换器 在注解适配器中加入messa ...
- 097 Interleaving String 交错字符串
给定三个字符串 s1, s2, s3, 验证 s3 是否是由 s1 和 s2 交错组成的.例如,给定:s1 = "aabcc",s2 = "dbbca",当 s ...
- Retrofit实现Delete请求
//设置取消关注 @Headers("Content-Type:application/x-www-form-urlencoded") @HTTP(method = "D ...
- [转] boost:lexical_cast用法
转载地址:http://www.habadog.com/2011/05/07/boost-lexical_cast-intro/ 一.lexical_cast的作用lexical_cast使用统一的接 ...
- C#的弱引用
关于C#中的弱引用 一:什么是弱引用 了解弱引用之前,先了解一下什么是强引用 例如 : Object obj=new Object(); 就是一个强引用,内存分配一份空间给用以存储Object ...
- Unity Shader入门精要学习笔记 - 第9章 更复杂的光照
转载自 冯乐乐的<Unity Shader入门精要> Unity 的渲染路径 在Unity里,渲染路径决定了光照是如何应该到Unity Shader 中的.因此,如果要和光源打交道,我们需 ...
- json_encode 中文处理
在 php 中使用 json_encode() 内置函数(php > 5.2)可以使用得 php 中数据可以与其它语言很好的传递并且使用它. 这个函数的功能是将数值转换成json数据存储格式. ...
- Enum 枚举类
目录 Enum 枚举类 基础 定义与用途 基本方法 示例 进阶 实现原理 枚举与Class对象 自定义枚举类和构造方法及toString() Enum中使用抽象方法来实现枚举实例的多态性 Enum与接 ...