详解SpringBoot+Mybatis实现动态数据源
动态数据源
在很多具体应⽤场景的时候,我们需要⽤到动态数据源的情况,⽐如多租户的场景,系统登录时需要根据⽤户信息切换到⽤户对应的数据库。⼜⽐如业务A要访问A数据库,业务B要访问B数据库等,都可以使⽤动态数据源⽅案进⾏解决。接下来,我们就来讲解如何实现动态数据源,以及在过程中剖析动态数据源背后的实现原理。
实现案例
本教程案例基于 Spring Boot + Mybatis + MySQL 实现。
数据库设计
⾸先需要安装好MySQL数据库,新建数据库 example,创建example表,⽤来测试数据源,SQL脚本如下:
CREATE TABLE `example` (
`pk` bigint(20) unsigned NOT NULL AUTO_INCREMENT COMMENT '主键',
`message` varchar(100) NOT NULL,
`create_time` datetime NOT NULL COMMENT '创建时间',
`modify_time` datetime DEFAULT NULL COMMENT '⽣效时间',
PRIMARY KEY (`pk`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8 ROW_FORMAT=COMPACT COMMENT='测试⽤例表'
添加依赖
添加Spring Boot,Spring Aop,Mybatis,MySQL相关依赖。
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId&batis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.1</version>
</dependency>
<!-- spring aop -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.8</version>
</dependency>
⾃定义配置⽂件
新建⾃定义配置⽂件resource/config/mysql/db.properties,添加数据源:
#数据库设置
ample.jdbc-url=jdbc:mysql://localhost:3306/example?characterEncoding=UTF-8
ample.username=root
ample.password=123456
ample.sql.jdbc.Driver
启动类
启动类添加 exclude = {DataSourceAutoConfiguration.class},以禁⽤数据源默认⾃动配置。
数据源默认⾃动配置会读取 spring.datasource.* 的属性创建数据源,所以要禁⽤以进⾏定制。
DynamicDatasourceApplication.java:
package ample.dynamic.datasource;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
@SpringBootApplication(exclude = {
DataSourceAutoConfiguration.class
})
public class DynamicDatasourceApplication {
public static void main(String[] args) {
SpringApplication.run(DynamicDatasourceApplication.class, args);
}
}
数据源配置类
创建⼀个数据源配置类,主要做以下⼏件事情:
1. 配置 dao,model(bean),xml mapper⽂件的扫描路径。
2. 注⼊数据源配置属性,创建数据源。
3. 创建⼀个动态数据源,装⼊数据源。
4. 将动态数据源设置到SQL会话⼯⼚和事务管理器。
spring aop应用场景如此,当进⾏数据库操作时,就会通过我们创建的动态数据源去获取要操作的数据源了。DbSourceConfig.java:
package fig.dao;
import amplemon.DataEnum;
import amplemon.DynamicDataSource;
batis.spring.SqlSessionFactoryBean;
import org.t.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import t.annotation.Bean;
import t.annotation.Configuration;
import t.annotation.PropertySource;
import io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import ansaction.PlatformTransactionManager;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
/
/数据库配置统⼀在config/mysql/db.properties中
@Configuration
@PropertySource(value = "classpath:config/mysql/db.properties")
public class DbSourceConfig {
private String typeAliasesPackage = "ample.bean.**.*";
@Bean(name = "exampleDataSource")
@ConfigurationProperties(prefix = "ample")
public DataSource exampleDataSource() {
ate().build();
}
/*
* 动态数据源
* dbMap中存放数据源名称与数据源实例,数据源名称存于DataEnum.DbSource中
* setDefaultTargetDataSource⽅法设置默认数据源
*/
@Bean(name = "dynamicDataSource")
public DataSource dynamicDataSource() {
DynamicDataSource dynamicDataSource = new DynamicDataSource();
//配置多数据源
Map<Object, Object> dbMap = new HashMap();
dbMap.put(Name(), exampleDataSource());
dynamicDataSource.setTargetDataSources(dbMap);
/
/ 设置默认数据源
dynamicDataSource.setDefaultTargetDataSource(exampleDataSource());
return dynamicDataSource;
}
/*
* 数据库连接会话⼯⼚
* 将动态数据源赋给⼯⼚
* mapper存于resources/mapper⽬录下
* 默认bean存于ample.bean包或⼦包下,也可直接在mapper中指定
*/
@Bean(name = "sqlSessionFactory")
public SqlSessionFactoryBean sqlSessionFactory() throws Exception {
SqlSessionFactoryBean sqlSessionFactory = new SqlSessionFactoryBean();
sqlSessionFactory.setDataSource(dynamicDataSource());
sqlSessionFactory.setTypeAliasesPackage(typeAliasesPackage); //扫描bean
PathMatchingResourcePatternResolver resolver = new PathMatchingResourcePatternResolver();
sqlSessionFactory.Resources("classpath*:mapper/*.xml")); // 扫描映射⽂件
return sqlSessionFactory;
}
@Bean
public PlatformTransactionManager transactionManager() {
// 配置事务管理, 使⽤事务时在⽅法头部添加@Transactional注解即可
return new DataSourceTransactionManager(dynamicDataSource());
}
}
动态数据源类
我们上⼀步把这个动态数据源设置到了SQL会话⼯⼚和事务管理器,这样在操作数据库时就会通过动态数据源类来获取要操作的数据源了。
动态数据源类集成了Spring提供的AbstractRoutingDataSource类,AbstractRoutingDataSource 中获取数据源的⽅法就是determineTargetDataSource,⽽此⽅法⼜通过 determineCurrentLookupKey ⽅法获取查询数据源的key。
所以如果我们需要动态切换数据源,就可以通过以下两种⽅式定制:
1. 覆写 determineCurrentLookupKey ⽅法
通过覆写 determineCurrentLookupKey ⽅法,从⼀个⾃定义的 DbSource() 获取
数据源key值,这样在我们想动态切换数据源的时候,只要通过 DbSourceContext.setDbSource(key) 的⽅式就可以动态改变数据源了。这种⽅式要求在获取数据源之前,要先初始化各个数据源到 DbSourceContext 中,我们案例就是采⽤这种⽅式实现的,所以要将数据源都事先初始化到DynamicDataSource 中。
2. 可以通过覆写 determineTargetDataSource,因为数据源就是在这个⽅法创建并返回的,所以这种⽅式就⽐较⾃由了,⽀持到任何你希望的地⽅读取数据源信息,只要最终返回⼀个 DataSource 的实现类即可。⽐如你可以到数据库、本地⽂件、⽹络接⼝等⽅式读取到数据源信息然后返回相应的数据源对象就可以了。
DynamicDataSource.java:
package amplemon;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
DbSource();
}
}
数据源上下⽂
动态数据源的切换主要是通过调⽤这个类的⽅法来完成的。在任何想要进⾏切换数据源的时候都可以通过调⽤这个类的⽅法实现切换。⽐如系统登录时,根据⽤户信息调⽤这个类的数据源切换⽅法切换到⽤户对应的数据库。完整代码如下:
DbSourceContext.java:
package amplemon;
import org.apache.log4j.Logger;
public class DbSourceContext {
private static Logger logger = Logger(DbSourceContext.class);
private static final ThreadLocal<String> dbContext = new ThreadLocal<String>();
public static void setDbSource(String source) {
logger.debug("set source ====>" + source);
dbContext.set(source);
}
public static String getDbSource() {
logger.debug("get source ====>" + ());
();
}
public static void clearDbSource() {
}
}
注解式数据源
到这⾥,在任何想要动态切换数据源的时候,只要调⽤DbSourceContext.setDbSource(key) 就可以完成了。
接下来我们实现通过注解的⽅式来进⾏数据源的切换,原理就是添加注解(如@DbSource(value="example")),然后实现注解切⾯进⾏数据源切换。
创建⼀个动态数据源注解,拥有⼀个value值,⽤于标识要切换的数据源的key。
DbSource.java:
package fig.dao;
import java.lang.annotation.*;
/**
* 动态数据源注解
* @author
* @date April 12, 2019
*/
@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface DbSource {
/**
* 数据源key值
* @return
*/
String value();
}
创建⼀个AOP切⾯,拦截带 @DataSource 注解的⽅法,在⽅法执⾏前切换⾄⽬标数据源,执⾏完成后恢复到默认数据源。
DynamicDataSourceAspect.java:
package fig.dao;
import amplemon.DbSourceContext;
import org.apache.log4j.Logger;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import annotation.Order;
import org.springframework.stereotype.Component;
/**
* 动态数据源切换处理器
* @author linzhibao
* @date April 12, 2019
*/
@Aspect
@Order(-1) // 该切⾯应当先于 @Transactional 执⾏
@Component
public class DynamicDataSourceAspect {
private static Logger logger = Logger(DynamicDataSourceAspect.class);
/**
* 切换数据源
* @param point
* @param dbSource
*/
//@Before("@annotation(dbSource)") 注解在对应⽅法,拦截有@DbSource的⽅法
//注解在类对象,拦截有@DbSource类下所有的⽅法
@Before("@within(dbSource)")
public void switchDataSource(JoinPoint point, DbSource dbSource) {
/
/ 切换数据源
DbSourceContext.setDbSource(dbSource.value());
}
/**
* 重置数据源
* @param point
* @param dbSource
*/
//注解在类对象,拦截有@DbSource类下所有的⽅法
@After("@within(dbSource)")
public void restoreDataSource(JoinPoint point, DbSource dbSource) {
/
/ 将数据源置为默认数据源
DbSourceContext.clearDbSource();
}
}
到这⾥,动态数据源相关的处理代码就完成了。
编写⽤户业务代码
接下来编写⽤户查询业务代码,⽤来进⾏测试,Dao层只需添加⼀个查询接⼝即可。ExampleDao.java:
package ample.dao;
import amplemon.DataEnum;
import fig.dao.DbSource;
import t.annotation.Bean;
import org.springframework.stereotype.Component;
import javax.annotation.Resource;
import java.util.List;
@Component("exampleDao")
//切换数据源注解,以DataEnum.DbSource中的值为准
@DbSource("example")
public class ExampleDao extends DaoBase {
private static final String MAPPER_NAME_SPACE = "ample.dao.ExampleMapper";
public List<String> selectAllMessages() {
return selectList(MAPPER_NAME_SPACE, "selectAllMessages");
}
}
Controler代码:
TestExampleDao.java:
package ample.dao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.ArrayList;
import java.util.List;
@RestController
public class TestExampleDao {
@Autowired
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论