您的位置:首页 > 编程语言 > Java开发

spring boot + mybatis +aop 注解实现多数据源动态切换

2020-02-29 19:41 531 查看

多数据源配置,在实际开发中,我们一个项目可能会用到多个数据库,通常一个数据库对应一个数据,而且在大数据量的业务下通常都会有多个数据源的。最近在有一个Spring cloud+ sping. Boot + mybatis 框架的多数据源配置切换的业务,所以就研究了一下关于Springboot+ mybatis 多数据源切换,具体没有经过实际业务的测试,只是记录一下实现及学习的心得。请见谅。

不多说了,以下是具体的实现过程:

一 .构建一个 “timer-service “ maven 项目

此功能的实现代码只有common 包下的代码,datasource 包下为数据源加载信息配置,annotanion 和advice 是实现了基于aop的通过注解的方式动态切换数据源。

1.配置pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<groupId>com.hdsxtech</groupId>
<artifactId>timer-service</artifactId>
<version>0.0.1-SNAPSHOT</version>
<packaging>jar</packaging>

<name>timer-service</name>
<description>timer-service</description>

<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.5.18.BUILD-SNAPSHOT</version>
<relativePath /> <!-- lookup parent from repository -->
</parent>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<spring-cloud.version>Edgware.BUILD-SNAPSHOT</spring-cloud.version>
</properties>

<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.oracle</groupId>
<artifactId>ojdbc6</artifactId>
<version>11.2.0.3</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>1.1.10</version>
</dependency>
<!-- 配置log4j 日志,过滤掉spring-boot 默认日志系统 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-eureka</artifactId>
</dependency>
<!-- 健康检查 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.10</version>
</dependency>
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.1</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.4.4</version>
</dependency>
</dependencies>

<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>

<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

<repositories>
<repository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</repository>
<repository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>

<pluginRepositories>
<pluginRepository>
<id>spring-snapshots</id>
<name>Spring Snapshots</name>
<url>https://repo.spring.io/snapshot</url>
<snapshots>
<enabled>true</enabled>
</snapshots>
</pluginRepository>
<pluginRepository>
<id>spring-milestones</id>
<name>Spring Milestones</name>
<url>https://repo.spring.io/milestone</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</pluginRepository>
</pluginRepositories>

</project>

二 .配置文件

pom.xml 添加了项目所需用的jar 包以来,接下来是配置文件信息,在 application.properties 中做以下配置

eureka.client.serviceUrl.defaultZone=http://localhost:8001/eureka/,http://localhost:8001/eureka/
eureka.instance.preferIpAddress=false
eureka.client.register-with-eureka=false
eureka.client.fetch-registry=false
eureka.client.healthcheck.enabled=true
eureka.instance.lease-expiration-duration-in-seconds=3
eureka.instance.lease-renewal-interval-in-seconds=1

spring.application.name=timer-service
server.port=9099

jdbc.type=com.alibaba.druid.pool.DruidDataSource
master.jdbc.driver-class-name=oracle.jdbc.driver.OracleDriver
master.jdbc.url=jdbc:oracle:thin:@localhost:1521:orcl
master.jdbc.username=ss_t1
master.jdbc.password=ss_t1

slave.jdbc.driver-class-name=oracle.jdbc.driver.OracleDriver
slave.jdbc.url=jdbc:oracle:thin:@localhost:1521:orcl
slave.jdbc.username=ss_t2
slave.jdbc.password=ss_t2

# mybatis 配置信息
mybatis.mapper-locations=classpath*:mybatis/*.xml
# sql 日志
logging.level.com.hdsxtech.smsoot_timer.mapper=debug

从第一行开始 到 jdbc.type 这行上部分主要为 sping cloud 所需要的配置信息,可以不用做过多的了解。之后为 配置了两个数据源,master 库和 slave 库,master为主库,也是默认库。

三.上代码,加载并配置数据源

1.DatabaseType

/**
* Copyright (c)  FESCOTech Team
* All rights reserved.
*
* This file DatabaseType.java creation date: [2018年11月15日 上午8:23:08] by 姓名
* http://www.fescoetc.com.cn
*/

package com.hdsxtech.smsoot_timer.common.datasource;

/**
* <descption>列出所有的数据源key</descption>
* 这里的数据源与数据库是一对一的
* @author mawen
* @version 1.0.0
*/

public enum DatabaseType {
MASTER,SLAVE
}

作用:列举数据源的key.

2.DatabaseContextHolder

/**
* Copyright (c)  FESCOTech Team
* All rights reserved.
*
* This file DatabaseContextHolder.java creation date: [2018年11月15日 上午8:25:15] by 姓名
* http://www.fescoetc.com.cn
*/

package com.hdsxtech.smsoot_timer.common.datasource;

/**
* <descption>保存一个线程安全的DatabaseContextHolder</descption>
* @author maen
* @version 1.0.0
*/

public class DatabaseContextHolder {

// 线程局部变量
private static final ThreadLocal<DatabaseType> contextHolder = new ThreadLocal<DatabaseType>();

// 往线程里边set 数据类型
public static void setDatabaseType(DatabaseType type){
if(type ==null) throw new NullPointerException();
contextHolder.set(type);
}

// 从容器中获取数据类型
public static DatabaseType getDatabaseType(){
return contextHolder.get() ==null ?DatabaseType.MASTER:contextHolder.get();
}

// 清空容器中的数据类型
public static void clear(){
contextHolder.remove();
}
}

作用:构建一个DatabaseType 容器,并提供了向其中设置 ,获取,清除 DatabaseType 的方法。

3.DynamicDataSource

/**
* Copyright (c)  FESCOTech Team
* All rights reserved.
*
* This file DynamicDataSourec.java creation date: [2018年11月15日 上午8:34:37] by 姓名
* http://www.fescoetc.com.cn
*/

package com.hdsxtech.smsoot_timer.common.datasource;

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

/**
* <descption>动态数据源(需要继承AbstractRoutingDataSource)</descption>
* @author mawen
* @version 1.0.0
*/

public class DynamicDataSource extends AbstractRoutingDataSource{

@Override
protected Object determineCurrentLookupKey() {
return DatabaseContextHolder.getDatabaseType();
}

}

作用:使用DatabaseContextHolder 获取当前线程的DatabaseType

4.DataSourceConfiguration

/**
* Copyright (c)  FESCOTech Team
* All rights reserved.
*
* This file DataSourceConfiguration.java creation date: [2018年11月15日 上午8:37:19] by 姓名
* http://www.fescoetc.com.cn
*/

package com.hdsxtech.smsoot_timer.common.datasource;

import java.util.HashMap;
import java.util.Map;

import javax.sql.DataSource;

import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder;
import org.springframework.boot.context.properties.ConfigurationProperties;
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.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;

/**
* <descption>配置并创建数据源</descption>
* @author mawen
* @version 1.0.0
*/
@Configuration
public class DataSourceConfiguration {

private static final Logger LOGGER = LoggerFactory.getLogger(DataSourceConfiguration.class) ;

@Autowired
private Environment env;

@Value("${jdbc.type}")
private Class<? extends DataSource> dataSourceType;

@Bean(name="masterDataSource")
@ConfigurationProperties(prefix="master.jdbc")
@Primary
public  DataSource masterDataSource(){
DataSource masterDataSource = DataSourceBuilder.create().type(dataSourceType).build();
LOGGER.info("========= MASTER :{} =======",masterDataSource);
return masterDataSource;
}

@Bean(name="slaveDataSource")
@ConfigurationProperties(prefix="slave.jdbc")
public DataSource slaveDataSource(){
DataSource slaveDataSource = DataSourceBuilder.create().type(dataSourceType).build();
LOGGER.info("========= SLAVE :{} ========",slaveDataSource);
return slaveDataSource;
}

/**
* @Primary 给注解表示在同一个接口有多个实现类可以注入的时候,默认选择那个,而不是让@Autowied 注解报错
* @Qualifier 根据名称进行注入,通常是在具有相同的多个类型的实力的一个注入(列如有多个DataSource 的类型)
*/
@Bean
public DynamicDataSource dataSource(@Qualifier("masterDataSource") DataSource masterDataSource,
@Qualifier("slaveDataSource") DataSource slaveDataSource){
Map<Object,Object> targetDataSources = new HashMap<>();
targetDataSources.put(DatabaseType.MASTER, masterDataSource);
targetDataSources.put(DatabaseType.SLAVE, slaveDataSource);

DynamicDataSource dataSource = new DynamicDataSource();
dataSource.setTargetDataSources(targetDataSources); // 给方法是AbstractRoutingDataSource 的方法
dataSource.setDefaultTargetDataSource(masterDataSource); // 默认的datasource 设置为  masterDataSource
return dataSource;
}

/**
*
* <descption> 根据数据源创建SqlSessionFactory </descption>
*
* @param
* @return
* @throws Exception
*/
@Bean
public SqlSessionFactory seqSessionFactory(DynamicDataSource ds) throws Exception{
SqlSessionFactoryBean fb = new SqlSessionFactoryBean();
fb.setDataSource(ds);  // 指定数据源(这个必须有,否则报错)

// 下边这两句仅仅用于*.xml文件,如果这个持久层操作不需要使用的xml 的话(只用注解就可以搞定) ,则不加
fb.setTypeAliasesPackage("mybatis.typeAliasesPackage");  // 指定基包
fb.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(env.getProperty("myb
19f08
atis.mapper-locations")));
return fb.getObject();
}

/**
*
* <descption> 配置事务管理 </descption>
*
* @param
* @return
*/
@Bean
public DataSourceTransactionManager tracsactionManager(DynamicDataSource ds){
return new DataSourceTransactionManager(ds);
}
}

作用:

  • 3.1 通过读取application.properties 文件中的两个数据源(master,slave )
  • 3.2 使用以上生成的两个数据源构造动态数据源dataSource
  1. @Primary:指定在同一个接口有多个实现类可以注入的时候,默认选择哪一个,而不是 @Autowire注解报错(一般用于多数据源的情况下)
  2. @Qualifier:指定名称的注入,当一个接口有多个实现类的时候使用(在本例中,有两个DataSource类型的实例,需要指定名称注入)
  3. @Bean:生成的bean实例的名称是方法名(例如上边的@Qualifier注解中使用的名称是前边两个数据源的方法名,而这两个数据源也是使用@Bean注解进行注入的)
  • 3.3 通过动态数据源构造SqlSessionFactory和事务管理器(如果不需要事务,后者可以去掉)

四.注解

1.Write 写注解

/**
* Copyright (c)  FESCOTech Team
* All rights reserved.
*
* This file Write.java creation date: [2018年11月15日 上午9:42:32] by 姓名
* http://www.fescoetc.com.cn
*/

package com.hdsxtech.smsoot_timer.common.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* <descption>写</descption>
* @author mawen
* @version 1.0.0
*/
@Target({ElementType.METHOD,ElementType.TYPE}) // 该注解用在方法上
@Retention(RetentionPolicy.RUNTIME) // 在运行时加载
public @interface Write {

}

作用:此注解标识 主数据库

2.Read 读注解

/**
* Copyright (c)  FESCOTech Team
* All rights reserved.
*
* This file ReadOnlyConn.java creation date: [2018年11月15日 上午9:39:51] by 姓名
* http://www.fescoetc.com.cn
*/

package com.hdsxtech.smsoot_timer.common.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
* <descption>只读</descption>
* @author mawen
* @version 1.0.0
*/
@Target({ElementType.METHOD,ElementType.TYPE}) // 该注解用在方法上
@Retention(RetentionPolicy.RUNTIME) // 在运行时加载
public @interface Read {

}

作用:此注解表示为从库

五.加载注解

1. WriteAdvice // 加载Write 注解

/**
* Copyright (c)  FESCOTech Team
* All rights reserved.
*
* This file WriteAdvice.java creation date: [2018年11月15日 上午9:58:27] by 姓名
* http://www.fescoetc.com.cn
*/

package com.hdsxtech.smsoot_timer.common.advice;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;

import com.hdsxtech.smsoot_timer.common.annotation.Write;
import com.hdsxtech.smsoot_timer.common.datasource.DatabaseContextHolder;
import com.hdsxtech.smsoot_timer.common.datasource.DatabaseType;

/**
* <descption>执行写注解</descption>
* @author mawen
* @version 1.0.0
*/
@Aspect
@Component
@EnableAspectJAutoProxy(exposeProxy=true,proxyTargetClass=true)
public class WriteAdvice implements Ordered {

public static final Logger LOGGER = LoggerFactory.getLogger(WriteAdvice.class);

@Around("@annotation(write)") // 在注解上加入切人点语法,实现方法
public Object proceed(ProceedingJoinPoint proceedingJoinPoint,Write write) throws Throwable{

try {
LOGGER.info("-----------------set database connection write only---------------------");
DatabaseContextHolder.setDatabaseType(DatabaseType.MASTER);
return proceedingJoinPoint.proceed();  // 让这个方法执行完毕
}finally{
DatabaseContextHolder.clear();
LOGGER.info("---------------clear database connection---------------");
}
}

@Override
public int getOrder() {
return 1;
}

}

2.ReadAdvice // 加载read 注解

/**
* Copyright (c)  FESCOTech Team
* All rights reserved.
*
* This file ReadAdvice.java creation date: [2018年11月15日 上午9:43:46] by 姓名
* http://www.fescoetc.com.cn
*/

package com.hdsxtech.smsoot_timer.common.advice;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;

import com.hdsxtech.smsoot_timer.common.annotation.Read;
import com.hdsxtech.smsoot_timer.common.datasource.DatabaseContextHolder;
import com.hdsxtech.smsoot_timer.common.datasource.DatabaseType;

/**
* <descption>执行只读注解</descption>
* @author maen
* @version 1.0.0
*/
@Aspect
@Component
@EnableAspectJAutoProxy(exposeProxy=true,proxyTargetClass=true)
public class ReadAdvice implements Ordered{

public static final Logger LOGGER = LoggerFactory.getLogger(ReadAdvice.class);

@Around("@annotation(read)") // 在注解上加入切人点语法,实现方法
public Object proceed(ProceedingJoinPoint proceedingJoinPoint,Read read) throws Throwable{
try {
LOGGER.info("----------------set database connection read only----------------------");
DatabaseContextHolder.setDatabaseType(DatabaseType.SLAVE);
return proceedingJoinPoint.proceed(); // 让这个方法执行完
}finally{
DatabaseContextHolder.clear();
LOGGER.info("-----------------clear database connection --------------------");
}
}

@Override
public int getOrder() {
return 1;
}

}

以上的代码是用来实现 多数据源配置加载,以及通过注解来实现数据源的动态切换功能。

六.使用

1.启动类 TimerApplication

package com.hdsxtech.smsoot_timer;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.cloud.client.discovery.EnableDiscoveryClient;

@SpringBootApplication
@EnableDiscoveryClient
@MapperScan("com.hdsxtech.*.mapper")
public class TimerApplication {

public static void main(String[] args) {
SpringApplication.run(TimerApplication.class, args);
}
}

2. TestBMapper

package com.hdsxtech.smsoot_timer.mapper;

import java.util.List;

import org.apache.ibatis.annotations.Mapper;

import com.hdsxtech.smsoot_timer.entity.TestB;

@Mapper
public interface TestBMapper {

List<TestB> queryAll();

void save(TestB testB);

void update(TestB testB);

}

3.TestBServiceImpl

package com.hdsxtech.smsoot_timer.service.impl;

import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import com.hdsxtech.smsoot_timer.common.annotation.Read;
import com.hdsxtech.smsoot_timer.common.annotation.Write;
import com.hdsxtech.smsoot_timer.entity.TestB;
import com.hdsxtech.smsoot_timer.mapper.TestBMapper;
import com.hdsxtech.smsoot_timer.service.TestBService;

@Service
@Transactional
public class TestBServiceImpl implements TestBService {

@Autowired
TestBMapper testBMapper;

@Read
@Override
public List<TestB> queryAll() {
return testBMapper.queryAll();
}

@Read
@Override
public void save(List<TestB> testbList) {
for (TestB testB : testbList) {
testBMapper.save(testB);
}

}

@Read
@Override
public void update(List<TestB> testbList) {

for (TestB testB : testbList) {
testBMapper.update(testB);
}
}

}

注意:在调用mapper 接口的方法上,需要通过注解来注入对应的数据源,否则就会已默认的 master 数据源操作数据。

4.TestBService

package com.hdsxtech.smsoot_timer.service;

import java.util.List;

import com.hdsxtech.smsoot_timer.entity.TestB;

public interface TestBService {

List<TestB> queryAll();

void save(List<TestB> testbList);

void update(List<TestB> testbList);
}

文档写的不是太好,请大家多多包含。

源代码

码云:https://gitee.com/sugnup/timer-service.git

个人博客:http://121.42.10.167

转载于:https://my.oschina.net/hen/blog/2985334

  • 点赞
  • 收藏
  • 分享
  • 文章举报
chuqi2604 发布了0 篇原创文章 · 获赞 0 · 访问量 2 私信 关注
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: