java自定义注解实现多数据源切换
程序员文章站
2022-05-17 08:55:20
...
应用场景
项目需要从自己的数据库上读取和管理数据外,还有一部分业务涉及到其他多个数据库。
为了能够灵活地指定具体的数据库,本文基于注解和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
可以看到页面输出
控制台输出
就这样完成了多数据源切换,完毕…