應用場景
項目需要從自己的數據庫上讀取和管理數據外,還有一部分業務涉及到其他多個數據庫。
爲了能夠靈活地指定具體的數據庫,本文基於註解和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
可以看到頁面輸出
控制檯輸出
就這樣完成了多數據源切換,完畢…