- 项目中经常会出现需要同时连接两个数据源的情况,这里基于MyBatis来配置两个数据源,并演示如何切换不同的数据源。
- 通过自定义注解+AOP的方式,来简化这种数据源的切换操作。
- 项目名称 文章地址
springboot-thymeleaf 集成Thymeleaf构建Web应用
springboot-mybatis 集成MyBatis
springboot-hibernate 集成Hibernate
springboot-mongodb 集成MongoDB
springboot-restful 实现RESTful接口
springboot-resttemplate 使用RestTemplate
springboot-shiro 集成Shiro权限管理
springboot-swagger2 集成Swagger2自动生成API文档
springboot-jwt 集成JWT实现接口权限认证
springboot-multisource 多数据源配置
springboot-schedule 定时任务
springboot-cxf cxf实现WebService
springboot-websocket 使用WebScoket实时通信
springboot-socketio 集成SocketIO实时通信
springboot-async 异步线程池
springboot-starter 教你自己写starter
springboot-aop 使用AOP
springboot-transaction 声明式事务
springboot-cache 使用缓存
springboot-redis Redis数据库
springboot-batch 批处理
springboot-rabbitmq 使用消息队列RabbitMQ
springboot-echarts 集成Echarts导出图片
package com.programb.pos;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
package com.programb.pos.common.annotion;
import java.lang.annotation.*;
@Inherited
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface DataSource {
String name() default "";
}
package com.programb.pos.common.aop;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
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.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;
import com.programb.pos.common.annotion.DataSource;
import com.programb.pos.common.mutidatesource.DSEnum;
import com.programb.pos.common.mutidatesource.DataSourceContextHolder;
import java.lang.reflect.Method;
@Aspect
@Component
@ConditionalOnProperty(prefix = "programb", name = "muti-datasource-open", havingValue = "true")
public class MultiSourceExAop implements Ordered {
private Logger log = LoggerFactory.getLogger(this.getClass());
@Pointcut(value = "@annotation(com.programb.pos.common.annotion.DataSource)")
private void cut() {
}
@Around("cut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
Signature signature = point.getSignature();
MethodSignature methodSignature = null;
if (!(signature instanceof MethodSignature)) {
throw new IllegalArgumentException("该注解只能用于方法");
}
methodSignature = (MethodSignature) signature;
Object target = point.getTarget();
Method currentMethod = target.getClass().getMethod(methodSignature.getName(), methodSignature.getParameterTypes());
DataSource datasource = currentMethod.getAnnotation(DataSource.class);
if (datasource != null) {
DataSourceContextHolder.setDataSourceType(datasource.name());
log.debug("设置数据源为:" + datasource.name());
} else {
DataSourceContextHolder.setDataSourceType(DSEnum.DATA_SOURCE_CORE);
log.debug("设置数据源为:dataSourceCore");
}
try {
return point.proceed();
} finally {
log.debug("清空数据源信息!");
DataSourceContextHolder.clearDataSourceType();
}
}
@Override
public int getOrder() {
return 1;
}
}
package com.programb.pos.common.dao.entity;
import java.util.Date;
import com.baomidou.mybatisplus.annotations.TableName;
import com.baomidou.mybatisplus.enums.IdType;
import com.baomidou.mybatisplus.annotations.TableId;
import com.baomidou.mybatisplus.activerecord.Model;
import java.io.Serializable;
@TableName(value = "t_user")
public class User extends Model<User> {
private static final long serialVersionUID = 1L;
@TableId(value="id", type= IdType.AUTO)
private Integer id;
private String username;
private String name;
private String password;
private String salt;
private String phone;
private String tips;
private Integer state;
private Date createdTime;
private Date updatedTime;
public Integer getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getSalt() {
return salt;
}
public void setSalt(String salt) {
this.salt = salt;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public String getTips() {
return tips;
}
public void setTips(String tips) {
this.tips = tips;
}
public Integer getState() {
return state;
}
public void setState(Integer state) {
this.state = state;
}
public Date getCreatedTime() {
return createdTime;
}
public void setCreatedTime(Date createdTime) {
this.createdTime = createdTime;
}
public Date getUpdatedTime() {
return updatedTime;
}
public void setUpdatedTime(Date updatedTime) {
this.updatedTime = updatedTime;
}
@Override
protected Serializable pkVal() {
return this.id;
}
}
package com.programb.pos.common.dao.repository;
import com.baomidou.mybatisplus.mapper.BaseMapper;
import com.programb.pos.common.dao.entity.User;
public interface UserMapper extends BaseMapper<User> {
}
package com.programb.pos.common.mutidatesource;
public class DataSourceContextHolder {
private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();
public static void setDataSourceType(String dataSourceType) {
contextHolder.set(dataSourceType);
}
public static String getDataSourceType() {
return contextHolder.get();
}
public static void clearDataSourceType() {
contextHolder.remove();
}
}
package com.programb.pos.common.mutidatesource;
public interface DSEnum {
String DATA_SOURCE_CORE = "dataSourceCore"; //核心数据源
String DATA_SOURCE_BIZ = "dataSourceBiz"; //其他业务的数据源
}
package com.programb.pos.common.mutidatesource;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
return DataSourceContextHolder.getDataSourceType();
}
}
package com.programb.pos.config;
import com.alibaba.druid.pool.DruidDataSource;
import com.baomidou.mybatisplus.plugins.PaginationInterceptor;
import com.programb.pos.common.mutidatesource.DSEnum;
import com.programb.pos.common.mutidatesource.DynamicDataSource;
import com.programb.pos.config.properties.DruidProperties;
import com.programb.pos.config.properties.MutiDataSourceProperties;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import java.sql.SQLException;
import java.util.HashMap;
@Configuration
@EnableTransactionManagement(order = 2)
@MapperScan(basePackages = {"com.programb.pos.common.dao.repository"})
public class MybatisPlusConfig {
@Autowired
DruidProperties druidProperties;
@Autowired
MutiDataSourceProperties mutiDataSourceProperties;
private DruidDataSource coreDataSource() {
DruidDataSource dataSource = new DruidDataSource();
druidProperties.config(dataSource);
return dataSource;
}
private DruidDataSource bizDataSource() {
DruidDataSource dataSource = new DruidDataSource();
druidProperties.config(dataSource);
mutiDataSourceProperties.config(dataSource);
return dataSource;
}
@Bean
@ConditionalOnProperty(prefix = "programb", name = "muti-datasource-open", havingValue = "false")
public DruidDataSource singleDatasource() {
return coreDataSource();
}
@Bean
@ConditionalOnProperty(prefix = "programb", name = "muti-datasource-open", havingValue = "true")
public DynamicDataSource mutiDataSource() {
DruidDataSource coreDataSource = coreDataSource();
DruidDataSource bizDataSource = bizDataSource();
try {
coreDataSource.init();
bizDataSource.init();
} catch (SQLException sql) {
sql.printStackTrace();
}
DynamicDataSource dynamicDataSource = new DynamicDataSource();
HashMap<Object, Object> hashMap = new HashMap<>();
hashMap.put(DSEnum.DATA_SOURCE_CORE, coreDataSource);
hashMap.put(DSEnum.DATA_SOURCE_BIZ, bizDataSource);
dynamicDataSource.setTargetDataSources(hashMap);
dynamicDataSource.setDefaultTargetDataSource(coreDataSource);
return dynamicDataSource;
}
@Bean
public PaginationInterceptor paginationInterceptor() {
return new PaginationInterceptor();
}
}
package com.programb.pos.config.properties;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.util.JdbcConstants;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
import java.sql.SQLException;
@Component
@ConfigurationProperties(prefix = "spring.datasource")
public class DruidProperties {
private String url;
private String username;
private String password;
private String driverClassName = "com.mysql.cj.jdbc.Driver";
private Integer initialSize = 10;
private Integer minIdle = 3;
private Integer maxActive = 60;
private Integer maxWait = 60000;
private Boolean removeAbandoned = true;
private Integer removeAbandonedTimeout = 180;
private Integer timeBetweenEvictionRunsMillis = 60000;
private Integer minEvictableIdleTimeMillis = 300000;
private String validationQuery = "SELECT 'x'";
private Boolean testWhileIdle = true;
private Boolean testOnBorrow = false;
private Boolean testOnReturn = false;
private Boolean poolPreparedStatements = true;
private Integer maxPoolPreparedStatementPerConnectionSize = 50;
private String filters = "stat";
public void config(DruidDataSource dataSource) {
dataSource.setDbType(JdbcConstants.MYSQL);
dataSource.setUrl(url);
dataSource.setUsername(username);
dataSource.setPassword(password);
dataSource.setDriverClassName(driverClassName);
dataSource.setInitialSize(initialSize); // 定义初始连接数
dataSource.setMinIdle(minIdle); // 最小空闲
dataSource.setMaxActive(maxActive); // 定义最大连接数
dataSource.setMaxWait(maxWait); // 获取连接等待超时的时间
dataSource.setRemoveAbandoned(removeAbandoned); // 超过时间限制是否回收
dataSource.setRemoveAbandonedTimeout(removeAbandonedTimeout); // 超过时间限制多长
// 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
dataSource.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
// 配置一个连接在池中最小生存的时间,单位是毫秒
dataSource.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
// 用来检测连接是否有效的sql,要求是一个查询语句
dataSource.setValidationQuery(validationQuery);
// 申请连接的时候检测
dataSource.setTestWhileIdle(testWhileIdle);
// 申请连接时执行validationQuery检测连接是否有效,配置为true会降低性能
dataSource.setTestOnBorrow(testOnBorrow);
// 归还连接时执行validationQuery检测连接是否有效,配置为true会降低性能
dataSource.setTestOnReturn(testOnReturn);
// 打开PSCache,并且指定每个连接上PSCache的大小
dataSource.setPoolPreparedStatements(poolPreparedStatements);
dataSource.setMaxPoolPreparedStatementPerConnectionSize(maxPoolPreparedStatementPerConnectionSize);
// 属性类型是字符串,通过别名的方式配置扩展插件,常用的插件有:
// 监控统计用的filter:stat
// 日志用的filter:log4j
// 防御SQL注入的filter:wall
try {
dataSource.setFilters(filters);
} catch (SQLException e) {
e.printStackTrace();
}
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getDriverClassName() {
return driverClassName;
}
public void setDriverClassName(String driverClassName) {
this.driverClassName = driverClassName;
}
public Integer getInitialSize() {
return initialSize;
}
public void setInitialSize(Integer initialSize) {
this.initialSize = initialSize;
}
public Integer getMinIdle() {
return minIdle;
}
public void setMinIdle(Integer minIdle) {
this.minIdle = minIdle;
}
public Integer getMaxActive() {
return maxActive;
}
public void setMaxActive(Integer maxActive) {
this.maxActive = maxActive;
}
public Integer getMaxWait() {
return maxWait;
}
public void setMaxWait(Integer maxWait) {
this.maxWait = maxWait;
}
public Integer getTimeBetweenEvictionRunsMillis() {
return timeBetweenEvictionRunsMillis;
}
public void setTimeBetweenEvictionRunsMillis(Integer timeBetweenEvictionRunsMillis) {
this.timeBetweenEvictionRunsMillis = timeBetweenEvictionRunsMillis;
}
public Integer getMinEvictableIdleTimeMillis() {
return minEvictableIdleTimeMillis;
}
public void setMinEvictableIdleTimeMillis(Integer minEvictableIdleTimeMillis) {
this.minEvictableIdleTimeMillis = minEvictableIdleTimeMillis;
}
public String getValidationQuery() {
return validationQuery;
}
public void setValidationQuery(String validationQuery) {
this.validationQuery = validationQuery;
}
public Boolean getTestWhileIdle() {
return testWhileIdle;
}
public void setTestWhileIdle(Boolean testWhileIdle) {
this.testWhileIdle = testWhileIdle;
}
public Boolean getTestOnBorrow() {
return testOnBorrow;
}
public void setTestOnBorrow(Boolean testOnBorrow) {
this.testOnBorrow = testOnBorrow;
}
public Boolean getTestOnReturn() {
return testOnReturn;
}
public void setTestOnReturn(Boolean testOnReturn) {
this.testOnReturn = testOnReturn;
}
public Boolean getPoolPreparedStatements() {
return poolPreparedStatements;
}
public void setPoolPreparedStatements(Boolean poolPreparedStatements) {
this.poolPreparedStatements = poolPreparedStatements;
}
public Integer getMaxPoolPreparedStatementPerConnectionSize() {
return maxPoolPreparedStatementPerConnectionSize;
}
public void setMaxPoolPreparedStatementPerConnectionSize(Integer maxPoolPreparedStatementPerConnectionSize) {
this.maxPoolPreparedStatementPerConnectionSize = maxPoolPreparedStatementPerConnectionSize;
}
public String getFilters() {
return filters;
}
public void setFilters(String filters) {
this.filters = filters;
}
public Boolean getRemoveAbandoned() {
return removeAbandoned;
}
public void setRemoveAbandoned(Boolean removeAbandoned) {
this.removeAbandoned = removeAbandoned;
}
public Integer getRemoveAbandonedTimeout() {
return removeAbandonedTimeout;
}
public void setRemoveAbandonedTimeout(Integer removeAbandonedTimeout) {
this.removeAbandonedTimeout = removeAbandonedTimeout;
}
}
package com.programb.pos.config.properties;
import com.alibaba.druid.pool.DruidDataSource;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.stereotype.Component;
@Component
@ConfigurationProperties(prefix = "biz.datasource")
public class MutiDataSourceProperties {
private String url;
private String username;
private String password;
public void config(DruidDataSource dataSource) {
dataSource.setUrl(url);
dataSource.setUsername(username);
dataSource.setPassword(password);
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
package com.programb.pos.service;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import com.programb.pos.common.annotion.DataSource;
import com.programb.pos.common.dao.entity.User;
import com.programb.pos.common.dao.repository.UserMapper;
import com.programb.pos.common.mutidatesource.DSEnum;
import javax.annotation.Resource;
@Service
@Transactional
public class UserService {
@Resource
private UserMapper userMapper;
public User findById(Integer id) {
return userMapper.selectById(id);
}
@DataSource(name = DSEnum.DATA_SOURCE_BIZ)
public User findById1(Integer id) {
return userMapper.selectById(id);
}
public void insertUser(User user) {
userMapper.insert(user);
}
public void updateUser(User user) {
userMapper.updateById(user);
}
public void deleteUser(Integer id) {
userMapper.deleteById(id);
}
}
programb:
muti-datasource-open: true #是否开启多数据源(true/false)
spring:
profiles:
active: dev
mybatis-plus:
mapper-locations: classpath*:com/programb/pos/common/dao/repository/mapping/*.xml
typeAliasesPackage: >
com.xncoding.pos.common.dao.entity
global-config:
id-type: 0 # 0:数据库ID自增 1:用户输入id 2:全局唯一id(IdWorker) 3:全局唯一ID(uuid)
db-column-underline: false
refresh-mapper: true
configuration:
map-underscore-to-camel-case: true
cache-enabled: true #配置的缓存的全局开关
lazyLoadingEnabled: true #延时加载的开关
multipleResultSetsEnabled: true #开启的话,延时加载一个属性时会加载该对象全部属性,否则按需加载属性
logging:
level:
org.springframework.web.servlet: ERROR
spring:
profiles: dev
datasource:
url: jdbc:mysql://127.0.0.1:3306/pos?serverTimezone=UTC&useSSL=false&autoReconnect=true&tinyInt1isBit=false&useUnicode=true&characterEncoding=utf8
username: root
password: 123456
biz:
datasource:
url: jdbc:mysql://127.0.0.1:3306/biz?serverTimezone=UTC&useSSL=false&autoReconnect=true&tinyInt1isBit=false&useUnicode=true&characterEncoding=utf8
username: root
password: 123456
logging:
level:
ROOT: INFO
com:
xncoding: DEBUG
file: D:/programb/logs/app.log
<?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.programb</groupId>
<artifactId>springboot-multisource</artifactId>
<version>1.0.0-SNAPSHOT</version>
<packaging>jar</packaging>
<name>springboot-multisource</name>
<description>SpringBoot</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.0.4.RELEASE</version>
</parent>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<druid.version>1.1.2</druid.version>
<mysql-connector.version>8.0.7-dmr</mysql-connector.version>
<mybatis-plus.version>2.1.8</mybatis-plus.version>
<mybatisplus-spring-boot-starter.version>1.0.5</mybatisplus-spring-boot-starter.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>${mysql-connector.version}</version>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid</artifactId>
<version>${druid.version}</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus</artifactId>
<version>${mybatis-plus.version}</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatisplus-spring-boot-starter</artifactId>
<version>${mybatisplus-spring-boot-starter.version}</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.hamcrest</groupId>
<artifactId>hamcrest-all</artifactId>
<version>1.3</version>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-compiler-plugin</artifactId>
<version>3.6.1</version>
<configuration>
<!--<proc>none</proc>-->
<source>1.8</source>
<target>1.8</target>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.20</version>
<configuration>
<skip>true</skip>
</configuration>
</plugin>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
<executions>
</executions>
</plugin>
</plugins>
<resources>
<resource>
<directory>src/main/resources</directory>
</resource>
<resource>
<directory>src/main/java</directory>
<includes>
<include>**/*.xml</include>
</includes>
</resource>
</resources>
</build>
</project>