java自定义注解实现多数据源切换

文章目录

应用场景

项目需要从自己的数据库上读取和管理数据外,还有一部分业务涉及到其他多个数据库。

为了能够灵活地指定具体的数据库,本文基于注解和AOP的方法实现多数据源自动切换。在使用过程中,只需要添加注解就可以使用,简单方便。

入门代码

废话不多说,开整

工程目录如下
在这里插入图片描述

sql文件
test1库goods表,test2库order_form表

/*
 Navicat MySQL Data Transfer

 Source Server         : localhost
 Source Server Type    : MySQL
 Source Server Version : 50712
 Source Host           : localhost:3306
 Source Schema         : test1

 Target Server Type    : MySQL
 Target Server Version : 50712
 File Encoding         : 65001

 Date: 09/01/2020 14:52:01
*/

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for goods
-- ----------------------------
DROP TABLE IF EXISTS `goods`;
CREATE TABLE `goods`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `goods_id` int(10) NULL DEFAULT NULL,
  `stock` int(10) NULL DEFAULT NULL,
  `group_id` varchar(20) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT NULL,
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 2 CHARACTER SET = utf8 COLLATE = utf8_unicode_ci ROW_FORMAT = Dynamic;

-- ----------------------------
-- Records of goods
-- ----------------------------
INSERT INTO `goods` VALUES (1, 31, 100, '');

SET FOREIGN_KEY_CHECKS = 1;


/*
 Navicat MySQL Data Transfer

 Source Server         : localhost
 Source Server Type    : MySQL
 Source Server Version : 50712
 Source Host           : localhost:3306
 Source Schema         : test2

 Target Server Type    : MySQL
 Target Server Version : 50712
 File Encoding         : 65001

 Date: 09/01/2020 13:53:07
*/

SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;

-- ----------------------------
-- Table structure for order_form
-- ----------------------------
DROP TABLE IF EXISTS `order_form`;
CREATE TABLE `order_form`  (
  `id` int(11) NOT NULL AUTO_INCREMENT,
  `goods_id` int(20) NULL DEFAULT NULL,
  `goods_name` varchar(20) CHARACTER SET utf8 COLLATE utf8_unicode_ci NULL DEFAULT NULL,
  `user_id` int(10) NULL DEFAULT NULL,
  `create_time` timestamp(0) NULL DEFAULT NULL ON UPDATE CURRENT_TIMESTAMP(0),
  PRIMARY KEY (`id`) USING BTREE
) ENGINE = InnoDB AUTO_INCREMENT = 46 CHARACTER SET = utf8 COLLATE = utf8_unicode_ci ROW_FORMAT = Dynamic;

SET FOREIGN_KEY_CHECKS = 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.wangtao</groupId>
    <artifactId>datasource</artifactId>
    <version>1.0-SNAPSHOT</version>
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.0.1.RELEASE</version>
        <relativePath/>
    </parent>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
        <!-- springboot-aop包,AOP切面注解,Aspectd等相关注解 -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <!-- https://mvnrepository.com/artifact/org.mybatis.spring.boot/mybatis-spring-boot-starter -->
        <dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.1.0</version>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
    </dependencies>
</project>

application.yml

server:
  port: 8080
spring:
  datasource:
    test1:
      driver-class-name: com.mysql.jdbc.Driver
      initial-size: 5
      max-idle: 10
      max-wait: 10000
      min-idle: 5
      name: test1
      password: 123456
      test-on-borrow: false
      test-while-idle: true
      time-between-eviction-runs-millis: 18800
      jdbc-url: jdbc:mysql://localhost:3306/test1?useUnicode=true&characterEncoding=utf-8&useSSL=false&allowMultiQueries=true
      username: root
      validation-query: SELECT 1
    test2:
      driver-class-name: com.mysql.jdbc.Driver
      initial-size: 5
      max-idle: 10
      max-wait: 10000
      min-idle: 5
      name: test2
      password: 123456
      test-on-borrow: fa
      test-while-idle: true
      time-between-eviction-runs-millis: 18800
      jdbc-url: jdbc:mysql://localhost:3306/test2?useUnicode=true&characterEncoding=utf-8&useSSL=false&allowMultiQueries=true
      username: root
      validation-query: SELECT 1

自定义注解DS

package com.wangtao.annotation;

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

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface DS {
    String value() default "";
}

数据源配置类
DataSourceConfig.java

package com.wangtao.config.dataSource;

import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;

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


@Configuration
public class DataSourceConfig {

    //test1数据源
    @Bean(name = "test1")
    @ConfigurationProperties(prefix = "spring.datasource.test1") // application.properteis中对应属性的前缀
    public DataSource test1() {
        return DataSourceBuilder.create().build();
    }

    //test2数据源
    @Bean(name = "test2")
    @ConfigurationProperties(prefix = "spring.datasource.test2") // application.properteis中对应属性的前缀
    public DataSource test2() {
        return DataSourceBuilder.create().build();
    }

    /**
     * 动态数据源: 通过AOP在不同数据源之间动态切换
     *
     * @return
     */
    @Primary
    @Bean(name = "dynamicDataSource")
    public DataSource dynamicDataSource() {
        DynamicDataSource dynamicDataSource = new DynamicDataSource();
        // 默认数据源
        dynamicDataSource.setDefaultTargetDataSource(test1());
        // 配置多数据源
        Map<Object, Object> dsMap = new HashMap();
        dsMap.put("test1", test1());
        dsMap.put("test2", test2());
        dynamicDataSource.setTargetDataSources(dsMap);
        return dynamicDataSource;
    }

    /**
     * 配置@Transactional注解事物
     *
     * @return
     */
    @Bean
    public PlatformTransactionManager transactionManager() {
        return new DataSourceTransactionManager(dynamicDataSource());
    }

}

DataSourceContextHolder.java

package com.wangtao.config.dataSource;

import lombok.extern.slf4j.Slf4j;

@Slf4j
public class DataSourceContextHolder {

    //    默认数据源
    public static final String DEFAULT_DS = "test1";

    private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();

    /**
     * 设置数据源名
     *
     * @param dbType
     */
    public static void setDB(String dbType) {
        log.info("切换到{" + dbType + "}数据源");
        contextHolder.set(dbType);
    }

    /**
     * 获取数据源名
     *
     * @return
     */
    public static String getDB() {
        return (contextHolder.get());
    }

    /**
     * 清除数据源名
     */
    public static void clearDB() {
        contextHolder.remove();
    }

}

DynamicDataSource.java

package com.wangtao.config.dataSource;

import lombok.extern.slf4j.Slf4j;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;


@Slf4j
public class DynamicDataSource extends AbstractRoutingDataSource {

    @Override
    protected Object determineCurrentLookupKey() {
        log.info("数据源为" + DataSourceContextHolder.getDB());
        return DataSourceContextHolder.getDB();
    }


}

数据源切面
DynamicDataSourceAspect.java

package com.wangtao.config.dataSource;

import com.wangtao.annotation.DS;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.After;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

@Aspect
@Component
@Order(1)
public class DynamicDataSourceAspect {
    @Before("@annotation(com.wangtao.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(com.wangtao.annotation.DS)")
    public void afterSwitchDS(JoinPoint point) {
        DataSourceContextHolder.clearDB();
    }

}

controller层
DataSourceController.java

package com.wangtao.controller;

import com.wangtao.enity.Goods;
import com.wangtao.enity.Order;
import com.wangtao.service.DataSourceService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;

import java.util.Arrays;
import java.util.List;

@Controller
public class DataSourceController {
    
    @Autowired
    private DataSourceService dataSourceService;

    @RequestMapping("/test")
    @ResponseBody
    public List test() {
        List<Order> orderlist = dataSourceService.findOrderForm();
        List<Goods> goodslist = dataSourceService.findGoods();
        return Arrays.asList(orderlist,goodslist);
    }
}

entity
Goods.java

package com.wangtao.enity;

import lombok.Data;

import javax.persistence.*;

@Entity
@Data
@Table(name = "goods")
public class Goods {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY) // 自增类型的主键
    private int id;

    @Column(name = "goods_id")
    private int goodsId;

    private int stock;

    @Column(name = "group_id")
    private String groupId;


}

Order.java

package com.wangtao.enity;

import lombok.Data;

import javax.persistence.*;
import java.util.Date;

@Data
@Entity
@Table(name = "order_form")
public class Order {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY) // 自增类型的主键
    private int id;

    @Column(name = "goods_id")
    private int goodsId;

    @Column(name = "goods_name")
    private String goodsName;

    @Column(name="user_id")
    private int userId;

    @Column(name = "create_time")
    private Date createTime;

}

mapper文件
DataSourceMapper.java

package com.wangtao.mapper;

import com.wangtao.enity.Goods;
import com.wangtao.enity.Order;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;

import java.util.List;

@Mapper
public interface DataSourceMapper {

	@Select(" select * from goods ")
	List<Goods> findGoods();

	@Select(" select * from order_form ")
	List<Order> findOrderForm();

}

service层
DataSourceService.java

package com.wangtao.service;

import com.wangtao.enity.Goods;
import com.wangtao.enity.Order;

import java.util.List;

public interface DataSourceService {
    List<Order> findOrderForm();

    List<Goods> findGoods();
}

DataSourceServiceImpl.java

package com.wangtao.service.impl;

import com.wangtao.annotation.DS;
import com.wangtao.mapper.DataSourceMapper;
import com.wangtao.service.DataSourceService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;

import java.util.List;

@Service
public class DataSourceServiceImpl implements DataSourceService {

    @Autowired
    private DataSourceMapper dataSourceMapper;

    @DS("test2")
    @Override
    public List findOrderForm() {
        return dataSourceMapper.findOrderForm();
    }

    @DS("test1")
    @Override
    public List findGoods() {
        return dataSourceMapper.findGoods();
    }
}

启动类
Application.java

package com.wangtao;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
@ComponentScan(value = "com.wangtao")
@EnableTransactionManagement(order = 10)
public class Application {
    public static void main(String[] args) {
        SpringApplication.run(Application.class,args);
    }
}

这里注意一下@EnableTransactionManagement这个注解很重要,我就在遇到过在配了shiroconfig的地方切换不了数据源,加上这个就好使了,原因是shiro底层也使用了aop,把优先级设置一下,就好使了。

启动项目,访问http://localhost:8080/test
可以看到页面输出在这里插入图片描述
控制台输出
在这里插入图片描述
就这样完成了多数据源切换,完毕…

发布了15 篇原创文章 · 获赞 17 · 访问量 5235

猜你喜欢

转载自blog.csdn.net/qq_35818427/article/details/103946112