【Spring技术专题】「实战开发系列」保姆级教你SpringBoot整合Mybatis框架实现多数据源和动态数据源配置落地
Mybatis是什么
Mybatis是一个基于JDBC实现的,支持普通 SQL 查询、存储过程和高级映射的优秀持久层框架,去掉了几乎所有的 JDBC 代码和参数的手工设置以及对结果集的检索封装。
Mybatis主要思想是将程序中大量的 SQL 语句剥离出来,配置在配置文件中,以实现 SQL 的灵活配置。在所有 ORM 框架中都有一个非常重要的媒介——PO(持久化对象),PO 的作用就是完成持久化操作,通过该对象对数据库执行增删改的操作,以面向对象的方式操作数据库。
SpringBoot整合Mybatis框架
配置Maven依赖
使用SpringBoot整合Mybatis需要添加Maven起步依赖,如下所示。
<!-- mybatis -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>x.x.x</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
配置数据源
然后通过 application.yml 中的 spring.datasource.* 前缀
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://localhost:3306/spring_test2?characterEncoding=utf8&useSSL=false
username: root
password: root
构建数据模型
public class ProductInfo implements Serializable {
private Long productId;
private String productName;
private Double productPrice;
private Date gmtCreate;
private Date gmtModified;
//省略getter、setter方法
}
MapperScan配置扫描
在启动类中添加对 mapper 包扫描 @MapperScan:
@SpringBootApplication
@MapperScan("com.example.springboot.mapper")
多数据源
接下来,我们要针对于多数据源和动态数据源进行实现对应的落地方案,希望可以帮助到大家
应用场景
项目需要同时连接两个不同的数据库A, B,并且它们都为主从架构,一台写库,多台读库。
多数据源
首先,要将spring boot自带的DataSourceAutoConfiguration禁掉,因为它会读取application.properties文件的spring.datasource.*属性并自动配置单数据源。
去除DataSourceAutoConfiguration
在@SpringBootApplication注解中添加exclude属性即可:
@SpringBootApplication(exclude = {
DataSourceAutoConfiguration.class
})
public class WebApplication {
public static void main(String[] args) {
SpringApplication.run(WebApplication.class, args);
}
}
然后在application.properties中配置多数据源连接信息:
A库的配置
spring.datasource.a-master.url=jdbc:mysql://X.X.X.X:port/a?characterEncoding=UTF-8
spring.datasource.a-master.username=
spring.datasource.a-master.password=
spring.datasource.a-master.driver-class-name=com.mysql.jdbc.Driver
B库的配置
spring.datasource.b.url=jdbc:mysql://X.X.X.X:port/b?characterEncoding=UTF-8
spring.datasource.b.username=
spring.datasource.b.password=
spring.datasource.b.driver-class-name=com.mysql.jdbc.Driver13
定制化配置对应的数据源
由于我们禁掉了自动数据源配置,因些下一步就需要手动将这些数据源创建出来:
@Configuration
public class DataSourceConfig {
@Bean(name = "aDs")
@ConfigurationProperties(prefix =
"spring.datasource.a-master")
// application.properteis中对应属性的前缀
public DataSource dataSource1() {
return DataSourceBuilder.create().build();
}
@Bean(name = "bDs")
@ConfigurationProperties(prefix = "spring.datasource.b")
// application.properteis中对应属性的前缀
public DataSource dataSource2() {
return DataSourceBuilder.create().build();
}
}
配置对应的SessionFactory
接下来需要配置两个mybatis的SqlSessionFactory分别使用不同的数据源:
@Configuration
@MapperScan(
basePackages = {"a.mapper"},
sqlSessionFactoryRef = "sqlSessionFactory1")
public class MybatisDbAConfig {
@Autowired
@Qualifier("aDs")
private DataSource ds1;
@Bean
public SqlSessionFactory sqlSessionFactory1() throws Exception {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setDataSource(ds1);
// 使用titan数据源, 连接titan库
return factoryBean.getObject();
}
@Bean
public SqlSessionTemplate sqlSessionTemplate1() throws Exception {
SqlSessionTemplate template = new SqlSessionTemplate(sqlSessionFactory1());
// 使用上面配置的Factory
return template;
}
}
经过上面的配置后,a.mapper下的Mapper接口,都会使用a数据源。同理可配第二个SqlSessionFactory:
@Configuration
@MapperScan(basePackages
= {"b.mapper"}, sqlSessionFactoryRef = "sqlSessionFactory2")
public class MybatisDbBConfig {
@Autowired
@Qualifier("bDs")
private DataSource ds2;
@Bean
public SqlSessionFactory sqlSessionFactory2() throws Exception {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setDataSource(ds2);
return factoryBean.getObject();
}
@Bean
public SqlSessionTemplate sqlSessionTemplate2() throws Exception {
SqlSessionTemplate template = new SqlSessionTemplate(sqlSessionFactory2());
return template;
}
}
完成这些配置后,假设有2个Mapper a.mapper.XXXMapper和b.mapper.XXXMapper,使用前者时会自动连接a库,后者连接b库。
动态数据源
使用动态数据源的初衷,是能在应用层做到读写分离,即在程序代码中控制不同的查询方法去连接不同的库。除了这种方法以外,数据库中间件也是个不错的选择,它的优点是数据库集群对应用来说只暴露为单库,不需要切换数据源的代码逻辑。
定义数据源切换上下文
首先定义一个ContextHolder, 用于保存当前线程使用的数据源名:
public class DataSourceContextHolder {
public static final Logger log =
LoggerFactory.getLogger(DataSourceContextHolder.class);
/**
* 默认数据源
*/
public static final String DEFAULT_DS = "a-master";
private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();
// 设置数据源名
public static void setDB(String dbType) {
log.debug("切换到{}数据源", dbType);
contextHolder.set(dbType);
}
// 获取数据源名
public static String getDB() {
return (contextHolder.get());
}
// 清除数据源名
public static void clearDB() {
contextHolder.remove();
}
}
定义动态切换数据源
自定义一个javax.sql.DataSource接口的实现,这里只需要继承Spring为我们预先实现好的父类AbstractRoutingDataSource即可:
public class DynamicDataSource extends AbstractRoutingDataSource {
private static final Logger log = LoggerFactory.getLogger(DynamicDataSource.class);
@Override
protected Object determineCurrentLookupKey() {
log.debug("数据源为{}", DataSourceContextHolder.getDB());
return DataSourceContextHolder.getDB();
}
}
创建动态数据源
/**
* 动态数据源: 通过AOP在不同数据源之间动态切换
* @return
*/
@Bean(name = "dynamicDS1")
public DataSource dataSource() {
DynamicDataSource dynamicDataSource = new DynamicDataSource();
// 默认数据源
dynamicDataSource.setDefaultTargetDataSource(dataSource1());
// 配置多数据源
Map<Object, Object> dsMap = new HashMap(5);
dsMap.put("a-master", dataSource1());
dsMap.put("bDs", dataSource2());
dynamicDataSource.setTargetDataSources(dsMap);
return dynamicDataSource;
}
AOP的方式实现数据源动态切换
通过自定义注解@DS用于在编码时指定方法使用哪个数据源:
@Retention(RetentionPolicy.RUNTIME)
@Target({
ElementType.METHOD
})
public @interface DS {
String value() default "a-master";
}
编写AOP切面,实现切换逻辑:
@Aspect
@Component
public class DynamicDataSourceAspect {
@Before("@annotation(DS)")
public void beforeSwitchDS(JoinPoint point){
//获得当前访问的class
Class<?> className = point.getTarget().getClass();
//获得访问的方法名
String methodName = point.getSignature().getName();
//得到方法的参数的类型
Class[] argClass = ((MethodSignature)point.getSignature()).getParameterTypes();
String dataSource = DataSourceContextHolder.DEFAULT_DS;
try {
// 得到访问的方法对象
Method method = className.getMethod(methodName, argClass);
// 判断是否存在@DS注解
if (method.isAnnotationPresent(DS.class)) {
DS annotation = method.getAnnotation(DS.class);
// 取出注解中的数据源名
dataSource = annotation.value();
}
} catch (Exception e) {
e.printStackTrace();
}
// 切换数据源
DataSourceContextHolder.setDB(dataSource);
}
@After("@annotation(DS)")
public void afterSwitchDS(JoinPoint point){
DataSourceContextHolder.clearDB();
}
}
完成上述配置后,在先前SqlSessionFactory配置中指定使用DynamicDataSource就可以在Service中愉快的切换数据源了。
@Autowired
private CXXMapper userAMapper;
@DS("a-master")
public String ds1() {
return userAMapper.selectByPrimaryKey(1).getName();
}
@DS("bDs")
public String ds2() {
return userAMapper.selectByPrimaryKey(1).getName();
}
总结
在本文中,我们介绍了如何使用SpringBoot整合Mybatis的多数据源和动态数据源。我们需要配置多个数据源,并为每个数据源创建一个SqlSessionFactory。我们还需要创建一个动态数据源来管理多个数据源。最后,我们需要在运行时切换数据源。
转载自:https://juejin.cn/post/7225442687436308536