mysql
spring boot
配置依赖
配置文件
# 数据源配置
spring:
datasource:
type: com.alibaba.druid.pool.DruidDataSource
driverClassName: com.mysql.cj.jdbc.Driver
ds:
# 主库数据源
master:
url: jdbc:mysql://192.168.10.10:3306/test08?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B8
username: root
password: root
# 从库数据源
slave:
# 从数据源开关/默认关闭
url: jdbc:mysql://192.168.10.10:3306/test09?useUnicode=true&characterEncoding=utf8&zeroDateTimeBehavior=convertToNull&useSSL=false&serverTimezone=GMT%2B8
username: root
password: root
# 初始连接数
initialSize: 5
# 最小连接池数量
minIdle: 10
# 最大连接池数量
maxActive: 20
# 配置获取连接等待超时的时间
maxWait: 60000
# 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
timeBetweenEvictionRunsMillis: 60000
# 配置一个连接在池中最小生存的时间,单位是毫秒
minEvictableIdleTimeMillis: 300000
# 配置一个连接在池中最大生存的时间,单位是毫秒
maxEvictableIdleTimeMillis: 900000
# 配置检测连接是否有效
validationQuery: SELECT 1 FROM DUAL
testWhileIdle: true
testOnBorrow: false
testOnReturn: false
webStatFilter:
enabled: true
statViewServlet:
enabled: true
# 设置白名单,不填则允许所有访问
allow:
url-pattern: /druid/*
# 控制台管理用户名和密码
login-username: tienchin
login-password: 123456
filter:
stat:
enabled: true
# 慢SQL记录
log-slow-sql: true
slow-sql-millis: 1000
merge-sql: true
wall:
config:
multi-statement-allow: true
自定义一个注解 @DataSource
,将来可能加在 service
方法或者是该类上,表示方法或者类上的所有方法都是用某一个数据源。
对于第一步,如果某个方法上面有 @DataSource
注解,那么就将该方法需要使用的数据源名称存入到 ThreadLocal
当中。
自定义切面,在切面中去解析 @DataSource
注解,当一个方法或者类上面有 @DataSource
注解的时候,将 @DataSource
注解所标记的数据源存入 ThreadLocal
中。
最后,当 Mapper
执行的时候,需要 DataSource
,他会自动去 AbstractRoutingDataSource
类中去查找需要的数据源,我们只需要在 AbstractRoutingDataSource
中返回 ThreadLocal
中的值即可。
定义注解:@DataSource
package org.javaboy.dd;
import org.javaboy.dd.dataSource.DataSourceType;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* @author: yueLQ
* @date: 2022-08-14 21:14
* 这个注解,将来可也加载到某一个 service 类或者方法上,通过 value 属性来指定类或者方法应该使用那个数据源
*/
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD,ElementType.TYPE})
public @interface DataSource {
/**
* 如果一个方法上,加了 @DataSource 注解,但是没有指定数据源的名称,那么默认就是用 master 数据源
* @return
*/
String value() default DataSourceType.DEFAULT_DS_NAME;
}
定义一个 contextHolder
来存储当前线程使用的数据源:
package org.javaboy.dd.dataSource;
/**
* @author: yueLQ
* @date: 2022-08-14 21:20
*
* 这个类用来存储当前线程所使用的的数据源名称
*/
public class DynamicDataSourceContextHolder {
private static ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal();
public static void setDataSourceType(String dsType){
CONTEXT_HOLDER.set(dsType);
}
public static String getDataSourceType(){
return CONTEXT_HOLDER.get();
}
/**
* 清除
*/
public static void clearDataSourceType(){
CONTEXT_HOLDER.remove();
}
}
定义一个切面,动态的读取业务层注解中的值:
package org.javaboy.dd.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.javaboy.dd.DataSource;
import org.javaboy.dd.dataSource.DynamicDataSourceContextHolder;
import org.springframework.core.annotation.AnnotationUtils;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import org.springframework.util.ObjectUtils;
/**
* @author: yueLQ
* @date: 2022-08-14 21:26
*/
@Component
@Aspect
@Order(11)
public class DataSourceAspect {
/**
* @annotation(org.javaboy.dd.DatadSource) 表示方法上有 datasource 将方法拦截下来
* @within(org.javaboy.dd.DatadSource) 表示类上有 datasource 将类中方法拦截下来
*/
@Pointcut("@annotation(org.javaboy.dd.DataSource) || @within(org.javaboy.dd.DataSource)")
public void pc(){
}
@Around("pc()")
public Object around(ProceedingJoinPoint point){
DataSource dataSource = getDatadSource(point);
if (dataSource !=null){
// 获取直接中数据源的名称
String value = dataSource.value();
DynamicDataSourceContextHolder.setDataSourceType(value);
}
try {
return point.proceed();
}catch (Throwable e){
e.printStackTrace();
}finally {
DynamicDataSourceContextHolder.clearDataSourceType();
}
return null;
}
private DataSource getDatadSource(ProceedingJoinPoint point) {
// 获取方法上的有效注解
MethodSignature signature = (MethodSignature) point.getSignature();
// 查找方法上的注解
DataSource annotation = AnnotationUtils.findAnnotation(signature.getMethod(), DataSource.class);
// 如果不为空,则方法上有该注解
if (!ObjectUtils.isEmpty(annotation)) {
return annotation;
}
return AnnotationUtils.findAnnotation(signature.getDeclaringType(), DataSource.class);
}
}
读取配置文件的数据源的值:
package org.javaboy.dd.dataSource;
import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.boot.context.properties.ConfigurationProperties;
import javax.sql.DataSource;
import java.util.Map;
/**
* @author: yueLQ
* @date: 2022-08-15 19:07
*/
@ConfigurationProperties(prefix = "spring.datasource")
public class DruidProperties {
private String type;
private String driverClassName;
private Map<String, Map<String, String>> ds;
private Integer initialSize;
private Integer minIdle;
private Integer maxActive;
private Integer maxWait;
/**
* 一会在外部构造好一个 dataSource 对象,但是对象只包含三个核心属性,url,username,password
* 在这个方法中,给这个对象设置公共的属性
*
* @param dataSource
* @return
*/
public DataSource dataSource(DruidDataSource dataSource) {
dataSource.setInitialSize(initialSize);
dataSource.setMinIdle(minIdle);
dataSource.setMaxActive(maxActive);
dataSource.setMaxWait(maxWait);
return dataSource;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getDriverClassName() {
return driverClassName;
}
public void setDriverClassName(String driverClassName) {
this.driverClassName = driverClassName;
}
public Map<String, Map<String, String>> getDs() {
return ds;
}
public void setDs(Map<String, Map<String, String>> ds) {
this.ds = ds;
}
public Integer getInitialSize() {
return initialSize;
}
public void setInitialSize(Integer initialSize) {
this.initialSize = initialSize;
}
public Integer getMaxActive() {
return maxActive;
}
public void setMaxActive(Integer maxActive) {
this.maxActive = maxActive;
}
public Integer getMinIdle() {
return minIdle;
}
public void setMinIdle(Integer minIdle) {
this.minIdle = minIdle;
}
public Integer getMaxWait() {
return maxWait;
}
public void setMaxWait(Integer maxWait) {
this.maxWait = maxWait;
}
}
加载数据源的配置:
package org.javaboy.dd.dataSource;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.pool.DruidDataSourceFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.stereotype.Component;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
/**
* @author: yueLQ
* @date: 2022-08-15 19:20
* 加载数据源的配置
*/
@Component
@EnableConfigurationProperties(DruidProperties.class)
public class LoadDataSource {
@Autowired
private DruidProperties druidProperties;
/**
* 将数据源数据存入 map 集合中
*/
public Map<String, DataSource> loadAllDataSource() {
HashMap<String, DataSource> map = new HashMap<>();
Map<String, Map<String, String>> ds = druidProperties.getDs();
Set<String> keySet = ds.keySet();
try {
for (String key : keySet) {
map.put(key, druidProperties.dataSource((DruidDataSource) DruidDataSourceFactory.createDataSource(ds.get(key))));
}
} catch (Exception e) {
e.printStackTrace();
}
return map;
}
}
继承 AbstractRoutingDataSource
抽象类,重写方法:
package org.javaboy.dd.dataSource;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import org.springframework.stereotype.Component;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
/**
* @author: yueLQ
* @date: 2022-08-15 19:29
*/
@Component
public class DynamicDataSource extends AbstractRoutingDataSource {
/**
* 拿到数据源之后,我们要告诉该类去找 LoadDataSource 中的 loadAllDataSource() 方法,查找信息
*/
public DynamicDataSource(LoadDataSource loadDataSource) {
// 1. 设置所有的数据源
Map<String, DataSource> allDs = loadDataSource.loadAllDataSource();
super.setTargetDataSources(new HashMap<>(allDs));
// 2,设置默认的数据源
// 将来,并不是所有的方法上都有 @dataSource 的注解,对于那些没有 @DataSource 注解的方法,该使用那个数据源
super.setDefaultTargetDataSource(allDs.get(DataSourceType.DEFAULT_DS_NAME));
// 3. 调用父类中的方法
super.afterPropertiesSet();
}
/**
* 这个方法用来返回数据源名称,当系统需要获取数据源的时候,会自动调用该方法获取数据源的名称
* @return
*/
@Override
protected Object determineCurrentLookupKey() {
return DynamicDataSourceContextHolder.getDataSourceType();
}
}
定义一个常量类:
package org.javaboy.dd.dataSource;
/**
* @author: yueLQ
* @date: 2022-08-15 19:40
*/
public interface DataSourceType {
/**
* 默认的数据源
*/
String DEFAULT_DS_NAME = "master";
/**
* session key
*/
String DS_SESSION_KEY = "ds_session_key";
}
创建数据库,model
mapper
service
controller
/*
Navicat Premium Data Transfer
Source Server : TienChin
Source Server Type : MariaDB
Source Server Version : 100311
Source Host : 192.168.10.10:3306
Source Schema : test08
Target Server Type : MariaDB
Target Server Version : 100311
File Encoding : 65001
Date: 16/08/2022 20:19:17
*/
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` int(32) DEFAULT NULL,
`name` varchar(64) CHARACTER SET latin1 COLLATE latin1_swedish_ci DEFAULT NULL,
`age` int(10) DEFAULT NULL
) ENGINE = InnoDB CHARACTER SET = latin1 COLLATE = latin1_swedish_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES (1, 'zhangsan', 18);
SET FOREIGN_KEY_CHECKS = 1;
--------------------------------
/*
Navicat Premium Data Transfer
Source Server : TienChin
Source Server Type : MariaDB
Source Server Version : 100311
Source Host : 192.168.10.10:3306
Source Schema : test09
Target Server Type : MariaDB
Target Server Version : 100311
File Encoding : 65001
Date: 16/08/2022 20:20:30
*/
SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0;
-- ----------------------------
-- Table structure for user
-- ----------------------------
DROP TABLE IF EXISTS `user`;
CREATE TABLE `user` (
`id` int(32) DEFAULT NULL,
`name` varchar(64) CHARACTER SET latin1 COLLATE latin1_swedish_ci DEFAULT NULL,
`age` int(10) DEFAULT NULL
) ENGINE = InnoDB CHARACTER SET = latin1 COLLATE = latin1_swedish_ci ROW_FORMAT = Dynamic;
-- ----------------------------
-- Records of user
-- ----------------------------
INSERT INTO `user` VALUES (2, 'lisi', 99);
SET FOREIGN_KEY_CHECKS = 1;
package org.javaboy.dd.model;
/**
* @author: yueLQ
* @date: 2022-08-15 19:56
*/
public class User {
private Integer id;
private String name;
private Integer age;
@Override
public String toString() {
return "User{" +
"id=" + id +
", name='" + name + '\'' +
", age=" + age +
'}';
}
public Integer getId() {
return id;
}
public void setId(Integer 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;
}
}
package org.javaboy.dd.mapper;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Select;
import org.javaboy.dd.model.User;
import java.util.List;
/**
* @author: yueLQ
* @date: 2022-08-15 19:59
*/
@Mapper
public interface UserMapper {
@Select("select * from user")
List<User> getAllUsers();
}
package org.javaboy.dd.service;
import org.javaboy.dd.DataSource;
import org.javaboy.dd.mapper.UserMapper;
import org.javaboy.dd.model.User;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import java.util.List;
/**
* @author: yueLQ
* @date: 2022-08-15 20:01
*/
@Service
@DataSource()
public class UserService {
@Autowired
private UserMapper userMapper;
public List<User> getAllUser(){
return userMapper.getAllUsers();
}
}
package org.javaboy.dd.controller;
import org.javaboy.dd.model.User;
import org.javaboy.dd.service.UserService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
* @author: yueLQ
* @date: 2022-08-15 20:03
*/
@RestController
public class UserController {
@Autowired
private UserService userService;
@GetMapping("hello")
public List<User> hello(){
return userService.getAllUser();
}
}
定义接口,传入参数,切换数据源:
package org.javaboy.dd.controller;
import org.javaboy.dd.dataSource.DataSourceType;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpSession;
/**
* @author: yueLQ
* @date: 2022-08-16 19:05
*/
@RestController
public class DataSourceController {
private static final Logger logger = LoggerFactory.getLogger(DataSourceController.class);
/**
* 修改数据源的接口
* 数据源的修改就存储到 httpSession 中去, 跟当前回话绑定
* @param dsType
*/
@PostMapping("/dsType")
public void setDsType(String dsType, HttpSession session) {
session.setAttribute(DataSourceType.DS_SESSION_KEY,dsType);
logger.info("数据源切换为:{}",dsType);
}
}
定义全局切面,切换数据源:
package org.javaboy.dd.aspect;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.javaboy.dd.dataSource.DataSourceType;
import org.javaboy.dd.dataSource.DynamicDataSourceContextHolder;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpSession;
/**
* @author: yueLQ
* @date: 2022-08-16 19:13
*
* 该切面去管理,我们传入的数据源,接收到数据源之后,全局所有的 service 方法,使用该数据源
*
* 当我们配置了全局的数据源切换和注解的数据源 @DataSource() 切换时
* 有 @DataSource 注解,则使用注解,没有注解的使用全局数据源,使用 @Order() 注解
* 业务上优先级高,代码上的优先级要低,让带有 @dataSource() 的注解后执行
*/
@Component
@Aspect
@Order(10)
public class GloalDataSourceAspect {
@Autowired
private HttpSession httpSession;
/**
* 所有的 service 方法都切入
*/
@Pointcut("execution(* org.javaboy.dd.service.*.*(..))")
public void pc() {
}
/**
* 环绕通知
*
* @param point
*/
@Around("pc()")
public Object aroud(ProceedingJoinPoint point) {
// 从 session 中,把我们的数据源的名称读出来。出入到 contextHolder 中去
DynamicDataSourceContextHolder.setDataSourceType((String) httpSession.getAttribute(DataSourceType.DS_SESSION_KEY));
try {
return point.proceed();
} catch (Throwable throwable) {
throwable.printStackTrace();
} finally {
// 清空
DynamicDataSourceContextHolder.clearDataSourceType();
}
return null;
}
}
定义前台页面,引入 jquery
:
DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>Titletitle>
<script src="jquery-3.4.1.js">script>
head>
<body>
<div>
请选择数据源:
<select name="" id="" onchange="dsChange(this.options[this.options.selectedIndex].value)">
<option value="请选择">请选择option>
<option value="master">masteroption>
<option value="slave">slaveoption>
select>
div>
<div id="result">
div>
<button onclick="loadData()">加载数据button>
<script>
function loadData(){
$.get("/hello",function (data){
$("#result").html(JSON.stringify(data))
})
}
function dsChange(val){
$.post("/dsType",{dsType:val})
}
script>
body>
html>