IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> 使用注解通过AOP实现Mybatis多数据源动态切换 -> 正文阅读

[Java知识库]使用注解通过AOP实现Mybatis多数据源动态切换

最近有个公共的项目,需要连接多个系统的数据库读取数据生成报表。框架使用的是Springboot+Mybatis。下面展示如何使用注解通过AOP实现多数据源的自动切换。

一、首先需要定义好数据源的类型。

package com.test.db;

public enum DatabaseType {

    DB_ORDER, // 订单库
    DB_MEMBER, // 会员库
    DB_STOCK; // 库存库

    /**
     * 通过名字获取类型
     * @param name
     * @return
     */
    public static DatabaseType getByName(String name) {
        for (DatabaseType dbType : DatabaseType.values()) {
            if (dbType.name().equals(name)) {
                return dbType;
            }
        }
        // 默认返回一个 比如:DB_ORDER
        return DatabaseType.DB_ORDER;
    }

}

二、定义一个容器来保存每个线程当前使用的数据库类型。

package com.test.db;

public class DatabaseContextHolder {

    private static final ThreadLocal<DatabaseType> DATABASE_TYPE_HOLDER = new ThreadLocal<>();

    public static void setDatabaseType(DatabaseType dbType) {
        DATABASE_TYPE_HOLDER.set(dbType);
    }

    public static void clearDatabaseType() {
        DATABASE_TYPE_HOLDER.remove();
    }

    public static DatabaseType getDatabaseType() {
        return DATABASE_TYPE_HOLDER.get();
    }

}

三、自定义动态数据源继承AbstractRoutingDataSource。

AbstractRoutingDataSource里面有个成员变量targetDataSources如下:
@Nullable private Map<Object, Object> targetDataSources;

这个Map就用来保存多个数据源。KEY可以保存类型,VALUE就是数据源对象。

package com.test.db;

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

public class DynamicDataSource extends AbstractRoutingDataSource {

    @Override
    protected Object determineCurrentLookupKey() {
        // 根据返回的key从targetDataSources中获取该key对应的value(即具体的数据源对象)
        return DatabaseContextHolder.getDatabaseType();
    }

}

四、配置数据源,将所有的数据源放置到targetDataSources中。

package com.test.config;

import com.alibaba.druid.pool.DruidDataSourceFactory;
import com.test.db.DatabaseType;
import com.test.db.DynamicDataSource;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Autowired;
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.jdbc.datasource.DataSourceTransactionManager;

import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

/**
 * springboot集成mybatis的基本入口 1)创建数据源(如果采用的是默认的tomcat-jdbc数据源,则不需要)
 * 2)创建SqlSessionFactory 3)配置事务管理器,除非需要使用事务,否则不用配置
 */
@Configuration
@MapperScan("com.test.mapper")
public class DynamicDataSourceConfig {

    private static final String MAPPER_LOCATION = "classpath:mapper/*.xml";

    @Autowired
    private OrderDataSourceProperties orderDataSourceProperties;

    @Autowired
    private MemberDataSourceProperties memberDataSourceProperties;

    @Autowired
    private StockDataSourceProperties stockDataSourceProperties;
   

    @Bean
    public DataSource orderDataSource() throws Exception {
        Properties props = new Properties();
        props.put("driverClassName", orderDataSourceProperties.getDriverClassName());
        props.put("url", orderDataSourceProperties.getJdbcUrl());
        props.put("username", orderDataSourceProperties.getUsername());
        props.put("password", orderDataSourceProperties.getPassword());
        return DruidDataSourceFactory.createDataSource(props);
    }

    @Bean
    public DataSource memberDataSource() throws Exception {
        Properties props = new Properties();
        props.put("driverClassName", memberDataSourceProperties.getDriverClassName());
        props.put("url", memberDataSourceProperties.getJdbcUrl());
        props.put("username", memberDataSourceProperties.getUsername());
        props.put("password", memberDataSourceProperties.getPassword());
        return DruidDataSourceFactory.createDataSource(props);
    }
    

    @Bean
    public DataSource stockDataSource() throws Exception {
        Properties props = new Properties();
        props.put("driverClassName", stockDataSourceProperties.getDriverClassName());
        props.put("url", stockDataSourceProperties.getJdbcUrl());
        props.put("username", stockDataSourceProperties.getUsername());
        props.put("password", stockDataSourceProperties.getPassword());
        return DruidDataSourceFactory.createDataSource(props);
    }

    /**
     * @Primary 该注解表示在同一个接口有多个实现类可以注入的时候,默认选择哪一个,而不是让@autowire注解报错
     * @Qualifier 根据名称进行注入,通常是在具有相同的多个类型的实例的一个注入(例如有多个DataSource类型的实例)
     */
    @Primary
    @Bean("dynamicDataSource")
    public DynamicDataSource dataSource(@Qualifier("orderDataSource") DataSource orderDataSource,
                                        @Qualifier("memberDataSource") DataSource memberDataSource,
                                        @Qualifier("stockDataSource") DataSource stockDataSource) {
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put(DatabaseType.DB_ORDER, orderDataSource);
        targetDataSources.put(DatabaseType.DB_MEMBER, memberDataSource);
        targetDataSources.put(DatabaseType.DB_STOCK, stockDataSource);
        DynamicDataSource dataSource = new DynamicDataSource();
        dataSource.setTargetDataSources(targetDataSources);
        dataSource.setDefaultTargetDataSource(orderDataSource);
        return dataSource;
    }

    /**
     * 根据数据源创建SqlSessionFactory
     */
    @Bean
    public SqlSessionFactory sqlSessionFactory(@Qualifier("dynamicDataSource") DynamicDataSource ds) throws Exception {
        SqlSessionFactoryBean fb = new SqlSessionFactoryBean();
        fb.setDataSource(ds);
        /**
         * !!!这里我不指定的话,会报错:org.apache.ibatis.binding.BindingException: Invalid bound statement (not found)
         */  
        fb.setTypeAliasesPackage("com.test.entity");
        fb.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(DynamicDataSourceConfig.MAPPER_LOCATION));
        return fb.getObject();
    }

    /**
     * 配置事务管理器
     */
    @Bean
    public DataSourceTransactionManager transactionManager(@Qualifier("dynamicDataSource") DynamicDataSource ds) throws Exception {
        return new DataSourceTransactionManager(ds);
    }
}
package com.test.config;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;

@Configuration
@ConfigurationProperties(prefix = "spring.datasource.order")
public class OrderDataSourceProperties {

    private String username;
    private String password;
    private String driverClassName;
    private String jdbcUrl;

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getDriverClassName() {
        return driverClassName;
    }

    public void setDriverClassName(String driverClassName) {
        this.driverClassName = driverClassName;
    }

    public String getJdbcUrl() {
        return jdbcUrl;
    }

    public void setJdbcUrl(String jdbcUrl) {
        this.jdbcUrl = jdbcUrl;
    }
}
package com.test.config;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;

@Configuration
@ConfigurationProperties(prefix = "spring.datasource.member")
public class MemberDataSourceProperties {

    private String username;
    private String password;
    private String driverClassName;
    private String jdbcUrl;

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getDriverClassName() {
        return driverClassName;
    }

    public void setDriverClassName(String driverClassName) {
        this.driverClassName = driverClassName;
    }

    public String getJdbcUrl() {
        return jdbcUrl;
    }

    public void setJdbcUrl(String jdbcUrl) {
        this.jdbcUrl = jdbcUrl;
    }
}
package com.test.config;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;

@Configuration
@ConfigurationProperties(prefix = "spring.datasource.stock")
public class StockDataSourceProperties {

    private String username;
    private String password;
    private String driverClassName;
    private String jdbcUrl;

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getDriverClassName() {
        return driverClassName;
    }

    public void setDriverClassName(String driverClassName) {
        this.driverClassName = driverClassName;
    }

    public String getJdbcUrl() {
        return jdbcUrl;
    }

    public void setJdbcUrl(String jdbcUrl) {
        this.jdbcUrl = jdbcUrl;
    }
}
# Datasource
spring.datasource.order.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.order.username=order
spring.datasource.order.password=111111
spring.datasource.order.driver-class-name=com.microsoft.sqlserver.jdbc.SQLServerDriver
spring.datasource.order.jdbc-url=jdbc:sqlserver://127.0.0.1:1433;database=order;loginTimeout=30;

spring.datasource.member.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.member.username=member
spring.datasource.member.password=111111
spring.datasource.member.driver-class-name=com.microsoft.sqlserver.jdbc.SQLServerDriver
spring.datasource.member.jdbc-url=jdbc:sqlserver://127.0.0.1:1433;database=member;loginTimeout=30;

spring.datasource.stock.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.stock.username=stock
spring.datasource.stock.password=111111
spring.datasource.stock.driver-class-name=com.microsoft.sqlserver.jdbc.SQLServerDriver
spring.datasource.stock.jdbc-url=jdbc:sqlserver://127.0.0.1:1433;database=stock;loginTimeout=30;

# mybatis
mybatis-plus.mapper-locations=classpath:mapper/*.xml
mybatis-plus.type-aliases-package=com.test.entity

五、启动类的配置。

package com.test;

import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;

@SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
@MapperScan(basePackages = "com.test.mapper")
public class DemoTestApplication {

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

}

这里如果不加(exclude = DataSourceAutoConfiguration.class)配置,会报循环依赖的错误。

      ↓
   sqlSessionFactory defined in class
┌─────┐
|  dynamicDataSource defined in class
↑     ↓
|  orderDataSource defined in class
↑     ↓
|  dataSourceInitializer
└─────┘

至此,我们的多数据源就搭建好了。下面开始定义注解,通过AOP实现自动切换。

六、自定义注解。

package com.test.db;

import org.springframework.stereotype.Component;

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

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Component
public @interface DataSource {

    String value() default "";

}

七、通过AOP实现自动切换。

package com.test.db;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

@Aspect
@Component
public class DataSourceAspect {


    @Pointcut("execution(* com.test.mapper..*.*(..))")
    private void anyMethod() {
    }

    @AfterReturning(value = "anyMethod()", returning = "result")
    public void afterReturning(JoinPoint joinPoint, Object result){
        DatabaseContextHolder.clearDatabaseType();
    }

    @Before(value="anyMethod()")
    public void before(JoinPoint joinPoint) throws Throwable {
        MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
        Method method = methodSignature.getMethod();
        // 如果方法体上使用了DataSource注解
        if (method.isAnnotationPresent(DataSource.class)) {
            // 获取该方法上的注解名
            DataSource datasource = method.getAnnotation(DataSource.class);
            // 将方法体上的注解的值赋予给DataSourceHolder数据源持有类
            DatabaseContextHolder.setDatabaseType(DatabaseType.getByName(datasource.value()));
        }else{
            // 默认使用DB_ORDER
            DatabaseContextHolder.setDatabaseType(DatabaseType.DB_ORDER);
        }
    }
}

八、注解的使用。

package com.test.mapper;

import com.test.entity.Member;

public interface MemberMapper {
   
    @DataSource("DB_MEMBER")
    Member selectByPrimaryKey(String memberId);

}
package com.test.service;

import com.test.entity.Member;

public interface IMemberService {

    Member selectByPrimaryKey(String id);

}
package com.test.service.impl;

import com.test.entity.Member;
import com.test.mapper.MemberMapper;
import com.test.service.IMemberService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

@Service
public class MemberServiceImpl implements IMemberService {

    @Autowired
    private MemberMapper memberMapper;

    @Override
    public Member selectByPrimaryKey(String id) {
        return memberMapper.selectByPrimaryKey(id);
    }
}
package com.test.controller;

import com.test.entity.Member;
import com.test.mapper.MemberMapper;
import com.test.service.IMemberService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/test")
public class TestController {

    @Autowired
    private IMemberService memberService;

    @GetMapping("/query")
    public String testQuery(@RequestParam("id") String id) {

        Member member = memberService.selectByPrimaryKey(id);

        return member.toString();
    }

}
package com.test.entity;

public class Member {
    
    private String id;
    
    private String name;
    
    private Integer age;
    
    private String phone;

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public Integer getAge() {
        return age;
    }

    public void setAge(Integer age) {
        this.age = age;
    }

    public String getPhone() {
        return phone;
    }

    public void setPhone(String phone) {
        this.phone = phone;
    }

    @Override
    public String toString() {
        return "Member{" +
                "id='" + id + '\'' +
                ", name='" + name + '\'' +
                ", age=" + age +
                ", phone='" + phone + '\'' +
                '}';
    }
}

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2021-08-10 23:04:10  更:2021-08-10 23:04:37 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 5:11:04-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码