Spring Boot 动态数据源示例(多数据源自动切换)

网友投稿 329 2023-06-08


Spring Boot 动态数据源示例(多数据源自动切换)

本文实现案例场景:

某系统除了需要从自己的主要数据库上读取和管理数据外,还有一部分业务涉及到其他多个数据库,要求可以在任何方法上可以灵活指定具体要操作的数据库。

为了在开发中以最简单的方法使用,本文基于注解和AOP的方法实现,在spring boot框架的项目中,添加本文实现的代码类后,只需要配置好数据源就可以直接通过注解使用,简单方便。

一配置二使用

1. 启动类注册动态数据源

2. 配置文件中配置多个数据源

3. 在需要的方法上使用注解指定数据源

1、在启动类添加 @Import({DynamicDataSourceRegister.class, MProxyTransactionManagementConfiguration.class})

@SpringBootApplication

@Import({DynamicDataSourceRegister.class}) // 注册动态多数据源

public class SpringBootSampleApplication {

// 省略其他代码

}

2、配置文件配置内容为: (不包括项目中的其他配置,这里只是数据源相关的)

# 主数据源,默认的

spring.datasource.driver-class-name=com.mysql.jdbc.Driver

spring.datasource.url=jdbc:mysql://localhost:3306/test

spring.datasource.username=root

spring.datasource.password=123456

# 更多数据源

custom.datasource.names=ds1,ds2

custom.datasource.ds1.driver-class-name=com.mysql.jdbc.Driver

custom.datasource.ds1.url=jdbc:mysql://localhost:3306/test1

custom.datasource.ds1.username=root

custom.datasource.ds1.password=123456

custom.datasource.ds2.driver-class-name=com.mysql.jdbc.Driver

custom.datasource.ds2.url=jdbc:mysql://localhost:3306/test2

custom.datasource.ds2.username=root

custom.datasource.ds2.password=123456

3、使用方法

package org.springboot.sample.service;

import java.sql.ResultSet;

import java.sql.SQLException;

import java.util.List;

import org.springboot.sample.datasource.TargetDataSource;

import org.springboot.sample.entity.Student;

import org.springboot.sample.mapper.StudentMapper;

import org.springframework.beans.factory.annotation.Autowired;

import org.springframework.jdbc.core.JdbcTemplate;

import org.springframework.jdbc.core.RowMapper;

import org.springframework.stereotype.Service;

/**

* Student Service

*

* @author 单红宇(365384722)

* @myblog http://blog.csdn.net/catoop/

* @create 2016年1月12日

*/

@Service

public class StudentService {

@Autowired

private JdbcTemplate jdbcTemplate;

// MyBatis的Mapper方法定义接口

@Autowired

private StudentMapper studentMapper;

@TargetDataSource(name="ds2")

public List likeName(String name){

return studentMapper.likeName(name);

}

public List likeNameByDefaultDataSource(String name){

return studentMapper.likeName(name);

}

/**

* 不指定数据源使用默认数据源

*

* @return

* @author SHANHY

* @create 2016年1月24日

*/

public List getList(){

String sql = "SELECT ID,NAME,SCORE_SUM,SCORE_AVG, AGE FROM STUDENT";

return (List) jdbcTemplate.query(sql, new RowMapper(){

@Override

public Student mapRow(ResultSet rs, int rowNum) throws SQLException {

Student stu = new Student();

stu.setId(rs.getInt("ID"));

stu.setAge(rs.getInt("AGE"));

stu.setName(rs.getString("NAME"));

stu.setSumScore(rs.getString("SCORE_SUM"));

stu.setAvgScore(rs.getString("SCORE_AVG"));

return stu;

}

});

}

/**

* 指定数据源

*

* @return

* @author SHANHY

* @create 2016年1月24日

*/

@TargetDataSource(name="ds1")

public List getListByDs1(){

String sql = "SELECT ID,NAME,SCORE_SUM,SCORE_AVG, AGE FROM STUDENT";

return (List) jdbcTemplate.query(sql, new RowMapper(){

@Override

public Student mapRow(ResultSet rs, int rowNum) throws SQLException {

Student stu = new Student();

stu.setId(rs.getInt("ID"));

stu.setAge(rs.getInt("AGE"));

stu.setName(rs.getString("NAME"));

stu.setSumScore(rs.getString("SCORE_SUM"));

stu.setAvgScore(rs.getString("SCORE_AVG"));

return stu;

}

});

}

/**

* 指定数据源

*

* @return

* @author SHANHY

* @create 2016年1月24日

*/

@TargetDataSource(name="ds2")

public List getListByDs2(){

String sql = "SELECT ID,NAME,SCORE_SUM,SCORE_AVG, AGE FROM STUDENT";

return (List) jdbcTemplate.query(sql, new RowMapper(){

@Override

public Student mapRow(ResultSet rs, int rowNum) throws SQLException {

Student stu = new Student();

stu.setId(rs.getInt("ID"));

stu.setAge(rs.getInt("AGE"));

stu.setName(rs.getString("NAME"));

stu.setSumScore(rs.getString("SCORE_SUM"));

stu.setAvgScore(rs.getString("SCORE_AVG"));

return stu;

}

});

}

}

要注意的是,在使用MyBatis时,注解@TargetDataSource 不能直接在接口类Mapper上使用。

按上面的代码中StudentMapper为接口,代码如下:

package org.springboot.sample.mapper;

import java.util.List;

import org.springboot.sample.entity.Student;

/**

* StudentMapper,映射SQL语句的接口,无逻辑实现

*

* @author 单红宇(365384722)

* @myblog http://blog.csdn.net/catoop/

* @create 2016年1月20日

*/

public interface StudentMapper {

// 注解 @TargetDataSource 不可以在这里使用

List likeName(String name);

Student getById(int id);

String getNameById(int id);

}

请将下面几个类放到Spring Boot项目中。

DynamicDataSource.Java

DynamicDataSourceAspect.java

DynamicDataSourceContextHolder.java

DynamicDataSourceRegister.java

TargetDataSource.java

package org.springboot.sample.datasource;

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

/**

* 动态数据源

*

* @author 单红宇(365384722)

* @create 2016年1月22日

*/

public class DynamicDataSource extends AbstractRoutingDataSource {

@Override

protected Object determineCurrentLookupKey() {

return DynamicDataSourceContextHolder.getDataSourceType();

}

}

package org.springboot.sample.datasource;

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.slf4j.Logger;

import org.slf4j.LoggerFactory;

import org.springframework.stereotype.Component;

/**

* 切换数据源Advice

*

* @author 单红宇(365384722)

* @create 2016年1月23日

*/

@Aspect

@Order(-1)// 保证该AOP在@Transactional之前执行

@Component

public class DynamicDataSourceAspect {

private static final Logger logger = LoggerFactory.getLogger(DynamicDataSourceAspect.class);

@Before("@annotation(ds)")

public void changeDataSource(JoinPoint point, TargetDataSource ds) throws Throwable {

String dsId = ds.name();

if (!DynamicDataSourceContextHolder.containsDataSource(dsId)) {

logger.error("数据源[{}]不存在,使用默认数据源 > {}", ds.name(), point.getSignature());

} else {

logger.debug("Use DataSource : {} > {}", ds.name(), point.getSignature());

DynamicDataSourceContextHolder.setDataSourceType(ds.name());

}

}

@After("@annotation(ds)")

public void restoreDataSource(JoinPoint point, TargetDataSource ds) {

logger.debug("Revert DataSource : {} > {}", ds.name(), point.getSignature());

DynamicDataSourceContextHolder.clearDataSourceType();

}

}

package org.springboot.sample.datasource;

import java.util.ArrayList;

import java.util.List;

public class DynamicDataSourceContextHolder {

private static final ThreadLocal contextHolder = new ThreadLocal();

public static List dataSourceIds = new ArrayList<>();

public static void setDataSourceType(String dataSourceType) {

contextHolder.set(dataSourceType);

}

public static String getDataSourceType() {

return contextHolder.get();

}

public static void clearDataSourceType() {

contextHolder.remove();

}

/**

* 判断指定DataSrouce当前是否存在

*

* @param dataSourceId

* @return

* @author SHANHY

* @create 2016年1月24日

*/

public static boolean containsDataSource(String dataSourceId){

return dataSourceIds.contains(dataSourceId);

}

}

package org.springboot.sample.datasource;

import java.util.HashMap;

import java.util.Map;

import javax.sql.DataSource;

import org.slf4j.Logger;

import org.slf4j.LoggerFactory;

import org.springframework.beans.MutablePropertyValues;

import org.springframework.beans.PropertyValues;

import org.springframework.beans.factory.support.BeanDefinitionRegistry;

import org.springframework.beans.factory.support.GenericBeanDefinition;

import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder;

import org.springframework.boot.bind.RelaxedDataBinder;

import org.springframework.boot.bind.RelaxedPropertyResolver;

import org.springframework.context.EnvironmentAware;

import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;

import org.springframework.core.convert.ConversionService;

import org.springframework.core.convert.support.DefaultConversionService;

import org.springframework.core.env.Environment;

import org.springframework.core.type.AnnotationMetadata;

/**

* 动态数据源注册

* 启动动态数据源请在启动类中(如SpringBootSampleApplication)

* 添加 @Import(DynamicDataSourceRegister.class)

*

* @author 单红宇(365384722)

* @create 2016年1月24日

*/

public class DynamicDataSourceRegister

implements ImportBeanDefinitionRegistrar, EnvironmentAware {

private static final Logger logger = LoggerFactory.getLogger(DynamicDataSourceRegister.class);

private ConversionService conversionService = new DefaultConversionService();

private PropertyValues dataSourcePropertyValues;

// 如配置文件中未指定数据源类型,使用该默认值

private static final Object DATASOURCE_TYPE_DEFAULT = "org.apache.tomcat.jdbc.pool.DataSource";

// private static final Object DATASOURCE_TYPE_DEFAULT =

// "com.zaxxer.hikari.HikariDataSource";

// 数据源

private DataSource defaultDataSource;

private Map customDataSources = new HashMap<>();

@Override

public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {

Map targetDataSources = new HashMap();

// 将主数据源添加到更多数据源中

targetDataSources.put("dataSource", defaultDataSource);

DynamicDataSourceContextHolder.dataSourceIds.add("dataSource");

// 添加更多数据源

targetDataSources.putAll(customDataSources);

for (String key : customDataSources.keySet()) {

DynamicDataSourceContextHolder.dataSourceIds.add(key);

}

// 创建DynamicDataSource

GenericBeanDefinition beanDefinition = new GenericBeanDefinition();

beanDefinition.setBeanClass(DynamicDataSource.class);

beanDefinition.setSynthetic(true);

MutablePropertyValues mpv = beanDefinition.getPropertyValues();

mpv.addPropertyValue("defaultTargetDataSource", defaultDataSource);

mpv.addPropertyValue("targetDataSources", targetDataSources);

registry.registerBeanDefinition("dataSource", beanDefinition);

logger.info("Dynamic DataSource Registry");

}

/**

* 创建DataSource

*

* @param type

* @param driverClassName

* @param url

* @param username

* @param password

* @return

* @author SHANHY

* @create 2016年1月24日

*/

@SuppressWarnings("unchecked")

public DataSource buildDataSource(Map dsMap) {

try {

Object type = dsMap.get("type");

if (type == null)

type = DATASOURCE_TYPE_DEFAULT;// 默认DataSource

Class extends DataSource> dataSourceType;

dataSourceType = (Class extends DataSource>) Class.forName((String) type);

String driverClassName = dsMap.get("driver-class-name").toString();

String url = dsMap.get("url").toString();

String username = dsMap.get("username").toString();

String password = dsMap.get("password").toString();

DataSourceBuilder factory = DataSourceBuilder.create().driverClassName(driverClassName).url(url)

.username(username).password(password).type(dataSourceType);

return factory.build();

} catch (ClassNotFoundException e) {

e.printStackTrace();

}

return null;

}

/**

* 加载多数据源配置

*/

@Override

public void setEnvironment(Environment env) {

initDefaultDataSource(env);

initCustomDataSources(env);

}

/**

* 初始化主数据源

*

* @author SHANHY

* @create 2016年1月24日

*/

private void initDefaultDataSource(Environment env) {

// 读取主数据源

RelaxedPropertyResolver propertyResolver = new RelaxedPropertyResolver(env, "spring.datasource.");

Map dsMap = new HashMap<>();

dsMap.put("type", propertyResolver.getProperty("type"));

dsMap.put("driver-class-name", propertyResolver.getProperty("driver-class-name"));

dsMap.put("url", propertyResolver.getProperty("url"));

dsMap.put("username", propertyResolver.getProperty("username"));

dsMap.put("password", propertyResolver.getProperty("password"));

defaultDataSource = buildDataSource(dsMap);

dataBinder(defaultDataSource, env);

}

/**

* 为DataSource绑定更多数据

*

* @param dataSource

* @param env

* @author SHANHY

* @create 2016年1月25日

*/

private void dataBinder(DataSource dataSource, Environment env){

RelaxedDataBinder dataBinder = new RelaxedDataBinder(dataSource);

//dataBinder.setValidator(new LocalValidatorFactory().run(this.applicationContext));

dataBinder.setConversionService(conversionService);

dataBinder.setIgnoreNestedProperties(false);//false

dataBinder.setIgnoreInvalidFields(false);//false

dataBinder.setIgnoreUnknownFields(true);//true

if(dataSourcePropertyValues == null){

Map rpr = new RelaxedPropertyResolver(env, "spring.datasource").getSubProperties(".");

Map values = new HashMap<>(rpr);

// 排除已经设置的属性

values.remove("type");

values.remove("driver-class-name");

values.remove("url");

values.remove("username");

values.remove("password");

dataSourcePropertyValues = new MutablePropertyValues(values);

}

dataBinder.bind(dataSourcePropertyValues);

}

/**

* 初始化更多数据源

*

* @author SHANHY

* @create 2016年1月24日

*/

private void initCustomDataSources(Environment env) {

// 读取配置文件获取更多数据源,也可以通过defaultDataSource读取数据库获取更多数据源

RelaxedPropertyResolver propertyResolver = new RelaxedPropertyResolver(env, "custom.datasource.");

String dsPrefixs = propertyResolver.getProperty("names");

for (String dsPrefix : dsPrefixs.split(",")) {// 多个数据源

Map dsMap = propertyResolver.getSubProperties(dsPrefix + ".");

DataSource ds = buildDataSource(dsMap);

customDataSources.put(dsPrefix, ds);

dataBinder(ds, env);

}

}

}

package org.springboot.sample.datasource;

import java.lang.annotation.Documented;

import java.lang.annotation.ElementType;

import java.lang.annotation.Retention;

import java.lang.annotation.RetentionPolicy;

import java.lang.annotation.Target;

/**

* 在方法上使用,用于指定使用哪个数据源

*

* @author 单红宇(365384722)

* @create 2016年1月23日

*/

@Target({ ElementType.METHOD, ElementType.TYPE })

@Retention(RetentionPolicy.RUNTIME)

@Documented

public @interface TargetDataSource {

String name();

}

本文代码博主是经过测试后没有问题才发出来共享给大家的。对于连接池参数配置会应用到所有数据源上。

比如配置一个:

spring.datasource.maximum-pool-size=80

那么我们所有的数据源都会自动应用上。

补充:

如果你使用的是SpringMVC,并集成了Shiro,一般按网上的配置你可能是:

那么你请不要这样做,请按下面方法配置:


版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:微信小程序 定位到当前城市实现实例代码
下一篇:bootstrap折叠调用collapse()后data
相关文章

 发表评论

暂时没有评论,来抢沙发吧~