本文讲解:SpringBoot + Druid 连接池 + Transcation(事务),基于注解方式,来完成多数据源的切换。该Demo只需要修改一下.yml文件中的MySQL部分,即可运行。
结构图如下:
demo-config模块
1.自定义注解
package com.example.demo02.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();
//String value() default "ds1"; //此处可默认某个数据源
}
2.切面编写
package com.example.demo02.aspect;
import com.example.demo02.annotation.Ds;
import com.example.demo02.config.DataSourceContextHolder;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
@Aspect
@Component
public class DataSourceAspect {
@Pointcut("@annotation(com.example.demo02.annotation.Ds)")
public void pointcutConfig(){
}
@Before("pointcutConfig()")
public void before(JoinPoint joinPoint){
//获得当前访问的class
Class<?> className = joinPoint.getTarget().getClass();
//获得访问的方法名
String methodName = joinPoint.getSignature().getName();
//得到方法的参数的类型
Class[] argClass = ((MethodSignature)joinPoint.getSignature()).getParameterTypes();
String dataSource = null;
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.setDataSource(dataSource);
}
@After("pointcutConfig()")
public void after(JoinPoint joinPoint){
DataSourceContextHolder.clearDataSource();
}
}
3.config详细配置
1.Druid连接池配置 DruidConfig
package com.example.demo02.config;
import com.alibaba.druid.support.http.StatViewServlet;
import com.alibaba.druid.support.http.WebStatFilter;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.boot.web.servlet.ServletRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
/**
* Druid连接池配置文件
*/
@Configuration
public class DruidConfig {
//配置 Druid 的监控
/**
* 1.配置一个管理后台的Servlet
*/
@Bean
public ServletRegistrationBean startViewServlet(){
ServletRegistrationBean servletRegistrationBean = new ServletRegistrationBean(new StatViewServlet(),"/druid/*");
// IP白名单
servletRegistrationBean.addInitParameter("allow","127.0.0.1");
// IP黑名单(共同存在时,deny优先于allow)
servletRegistrationBean.addInitParameter("deny","127.0.0.1");
//控制台管理用户
servletRegistrationBean.addInitParameter("loginUsername","admin");
servletRegistrationBean.addInitParameter("loginPassword","123456");
//是否能够重置数据
servletRegistrationBean.addInitParameter("resetEnable","false");
return servletRegistrationBean;
}
/**
* 配置一个 Web 监控的 Filter
* @return
*/
@Bean
public FilterRegistrationBean statFilter(){
FilterRegistrationBean filterRegistrationBean = new FilterRegistrationBean(new WebStatFilter());
//添加过滤规则
filterRegistrationBean.addUrlPatterns("/*");
//忽略过滤的格式
filterRegistrationBean.addInitParameter("exclusions","*.js,*.gif,*.jpg,*.png,*.css,*.ico,/druid/*");
return filterRegistrationBean;
}
}
2.DynamicDataSource类
package com.example.demo02.config;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DataSourceContextHolder.getDataSource();
}
}
3.DataSourceContextHolder类
package com.example.demo02.config;
import org.apache.log4j.Logger;
public class DataSourceContextHolder {
private static Logger LOG = Logger.getLogger(DataSourceContextHolder.class);
private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();
//设置数据源名称
public static void setDataSource(String dataSource){
LOG.info("切换到{"+dataSource+"}数据源");
contextHolder.set(dataSource);
}
public static String getDataSource(){
return contextHolder.get();
}
//清除数据源
public static void clearDataSource(){
contextHolder.remove();
}
}
4.多数据源配置 DataSourceConfig类
package com.example.demo02.config;
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import org.springframework.boot.context.properties.ConfigurationProperties;
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 {
/**
* 数据源1
*/
@Bean(name = "ds1")
@ConfigurationProperties(prefix = "spring.datasource.druid.ds1")
public DataSource Ds1(){
return DruidDataSourceBuilder.create().build();
}
/**
* 数据源2
*/
@Bean(name = "ds2")
@ConfigurationProperties(prefix = "spring.datasource.druid.ds2")
public DataSource Ds2(){
return DruidDataSourceBuilder.create().build();
}
/**
* 数据源切换: 通过AOP在不同数据源之间动态切换
*/
@Primary
@Bean
public DataSource dynamicDataSource(){
DynamicDataSource dynamicDataSource = new DynamicDataSource();
//设置默认数据源
dynamicDataSource.setDefaultTargetDataSource(Ds1());
//配置多数据源
Map<Object,Object> dsMap = new HashMap<>();
dsMap.put("ds1",Ds1());
dsMap.put("ds2",Ds2());
dynamicDataSource.setTargetDataSources(dsMap);
return dynamicDataSource;
}
/**
* 配置@Transactional注解事务
* @return
*/
@Bean
public PlatformTransactionManager transactionManager() {
return new DataSourceTransactionManager(dynamicDataSource());
}
}
4.demo-config模块 pom依赖
<dependencies>
<!-- springboot-aop -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<!-- springboot-jdbc -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<!--springboot整合log4j-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j</artifactId>
<version>1.3.8.RELEASE</version>
</dependency>
<!-- druid -->
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.20</version>
</dependency>
</dependencies>
demo-web模块
1.Controller层
package com.example.demo02.controller;
import com.example.demo02.entity.User;
import com.example.demo02.service.IUserService;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.annotation.Resource;
import java.util.List;
@Controller
@RequestMapping("user")
public class UserController {
@Resource(name = "userService")
public IUserService userService;
@RequestMapping("findDs1AllUser")
@ResponseBody
public List<User> findDs1AllUser(){
List<User> user = this.userService.findDs1AllUser();
return user;
}
@RequestMapping("findDs2AllUser")
@ResponseBody
public List<User> findDs2AllUser(){
List<User> user = this.userService.findDs2AllUser();
return user;
}
}
2.Service层
package com.example.demo02.service.impl;
import com.example.demo02.annotation.Ds;
import com.example.demo02.dao.UserDao;
import com.example.demo02.entity.User;
import com.example.demo02.service.IUserService;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import javax.annotation.Resource;
import java.util.List;
@Service("userService")
public class UserServiceImpl implements IUserService {
@Resource(name = "userDao")
public UserDao userDao;
@Override
@Ds("ds1")
@Transactional
public List<User> findDs1AllUser() {
User user1 = new User();
user1.setId("123");
user1.setName("B");
user1.setAge("56");
user1.setAddress("北京市");
userDao.insertUserToDs1(user1);
List<User> users = userDao.findDs1AllUser();
return users;
}
@Override
@Ds("ds2")
public List<User> findDs2AllUser() {
List<User> user = userDao.findDs2AllUser();
return user;
}
}
3.dao层
package com.example.demo02.dao;
import com.example.demo02.entity.User;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository("userDao")
public interface UserDao{
List<User> findDs1AllUser();
List<User> findDs2AllUser();
void insertUserToDs1(User user);
}
4.entity实体类
package com.example.demo02.entity;
public class User {
private String id;
private String name;
private String age;
private String address;
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 String getAge() {
return age;
}
public void setAge(String age) {
this.age = age;
}
public String getAddress() {
return address;
}
public void setAddress(String address) {
this.address = address;
}
}
5.SpringBoot入口启动类
package com.example.demo02;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.transaction.annotation.EnableTransactionManagement;
/**
* springboot入口类,此类需要在所有用到的package上层
* exclude = {DataSourceAutoConfiguration.class}
* 禁用springboot默认加载的application.properties单数据源配置
*/
@MapperScan("com.example.demo02.dao")
@EnableTransactionManagement //开启事务
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class Demo02Application {
public static void main(String[] args) {
SpringApplication.run(Demo02Application.class, args);
}
}
6.MyBatis映射SQL文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" >
<mapper namespace="com.example.demo02.dao.UserDao">
<insert id="insertUserToDs1" parameterType="com.example.demo02.entity.User">
insert into test_user(id,name,age,address)
value (#{id},#{name},#{age},#{address})
</insert>
<!-- 查询DS1所有user -->
<select id="findDs1AllUser" resultType="com.example.demo02.entity.User">
select
id,
name,
age,
address
from
m_user
</select>
<!-- 查询DS2所有user -->
<select id="findDs2AllUser" resultType="com.example.demo02.entity.User">
select
id,
name,
age,
address
from
d_user
</select>
</mapper>
7.application.yml 配置文件
server:
port: 8088
spring:
datasource:
name: druidDataSource
type: com.alibaba.druid.pool.DruidDataSource
druid:
ds1:
url: jdbc:mysql://192.168.xxx.xxx:3306/test?useUnicode=true&characterEncoding=UTF-8
username: root
password: 123456
driver-class-name: com.mysql.jdbc.Driver
filters: stat,wall #配置监控统计拦截的filters,去掉后监控界面SQL无法进行统计,'wall'用于防火墙
initial-size: 1 #初始化大小
min-idle: 1 #最小连接数
max-active: 20 #最大连接数
max-wait: 60000 #获取连接等待超时时间
time-between-eviction-runs-millis: 60000 #间隔多久才进行一次检测,检测需要关闭的空闲连接,单位毫秒
min-evictable-idle-time-millis: 30000 #一个连接在池中最小生存的时间,单位是毫秒
validation-query: SELECT 'x' #测试语句是否执行正确
test-while-idle: true #指明连接是否被空闲连接回收器(如果有)进行检验.如果检测失败,则连接将被从池中去除
test-on-borrow: false #借出连接时不要测试,否则很影响性能
test-on-return: false
pool-prepared-statements: false #打开PSCache,并指定每个连接上PSCache的大小。oracle设为true,mysql设为false。分库分表较多推荐设置为false
max-pool-prepared-statement-per-connection-size: 20 #与Oracle数据库PSCache有关,在druid下可以设置的比较高
ds2:
url: jdbc:mysql://192.168.xxx.xxx:3306/test?useUnicode=true&characterEncoding=UTF-8
username: root
password: 123456
driver-class-name: com.mysql.jdbc.Driver
filters: stat,wall #配置监控统计拦截的filters,去掉后监控界面SQL无法进行统计,'wall'用于防火墙
initial-size: 1 #初始化大小
min-idle: 1 #最小连接数
max-active: 20 #最大连接数
max-wait: 60000 #获取连接等待超时时间
time-between-eviction-runs-millis: 60000 #间隔多久才进行一次检测,检测需要关闭的空闲连接,单位毫秒
min-evictable-idle-time-millis: 30000 #一个连接在池中最小生存的时间,单位是毫秒
validation-query: SELECT 'x' #测试语句是否执行正确
test-while-idle: true #指明连接是否被空闲连接回收器(如果有)进行检验.如果检测失败,则连接将被从池中去除
test-on-borrow: false #借出连接时不要测试,否则很影响性能
test-on-return: false
pool-prepared-statements: false #打开PSCache,并指定每个连接上PSCache的大小。oracle设为true,mysql设为false。分库分表较多推荐设置为false
max-pool-prepared-statement-per-connection-size: 20 #与Oracle数据库PSCache有关,在druid下可以设置的比较高
mybatis:
mapper-locations: classpath:sqlmaps/*.xml
type-aliases-package: com.example.demo02.entity
logging:
level:
com.example.demo02.dao: debug
8.log4j.properties 配置文件
### #配置根Logger ###
log4j.rootLogger=info,stdout
### 输出到控制台 ###
log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%d{yyy-MM-dd HH\:mm\:ss} %5p - %m%n
log4j.logger.com.report.dao=debug
9.demo-web模块 pom依赖
<dependencies>
<dependency>
<groupId>com.example</groupId>
<artifactId>demo-config</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<!-- mysql驱动 -->
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.34</version>
</dependency>
<!-- mybatis -->
<dependency>
<groupId>org.mybatis.spring.boot</groupId>
<artifactId>mybatis-spring-boot-starter</artifactId>
<version>1.3.2</version>
</dependency>
</dependencies>
10.最外层 pom 依赖
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
<exclusions>
<exclusion>
<groupId>org.junit.vintage</groupId>
<artifactId>junit-vintage-engine</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
测试
分别使用链接 http://localhost:8088/user/findDs2AllUser 和 http://localhost:8088/user/findDs1AllUser 访问,通过日志可以看到数据源已经成功切换
SpringBoot 基于注解,实现多数据源切换,介绍到此为止
如果本文对你有所帮助,那就给我点个赞呗 ^_^
End