华佗养生网
您的当前位置:首页SpringBoot动态数据源示例(多数据源自动切换)

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

来源:华佗养生网
SpringBoot动态数据源⽰例(多数据源⾃动切换)

本⽂实现案例场景:

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

为了在开发中以最简单的⽅法使⽤,本⽂基于注解和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.Driverspring.datasource.url=jdbc:mysql://localhost:3306/testspring.datasource.username=rootspring.datasource.password=123456

# 更多数据源

custom.datasource.names=ds1,ds2

custom.datasource.ds1.driver-class-name=com.mysql.jdbc.Drivercustom.datasource.ds1.url=jdbc:mysql://localhost:3306/test1custom.datasource.ds1.username=rootcustom.datasource.ds1.password=123456

custom.datasource.ds2.driver-class-name=com.mysql.jdbc.Drivercustom.datasource.ds2.url=jdbc:mysql://localhost:3306/test2custom.datasource.ds2.username=rootcustom.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(\"数据源[{}]不存在,使⽤默认数据源 > {}\ } else {

logger.debug(\"Use DataSource : {} > {}\ DynamicDataSourceContextHolder.setDataSourceType(ds.name()); } }

@After(\"@annotation(ds)\")

public void restoreDataSource(JoinPoint point, TargetDataSource ds) {

logger.debug(\"Revert DataSource : {} > {}\ 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\

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\ mpv.addPropertyValue(\"targetDataSources\ registry.registerBeanDefinition(\"dataSource\

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 dataSourceType;

dataSourceType = (Class) 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\

dsMap.put(\"driver-class-name\ dsMap.put(\"url\

dsMap.put(\"username\ dsMap.put(\"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,⼀般按⽹上的配置你可能是:

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

以上就是本⽂的全部内容,希望对⼤家的学习有所帮助,也希望⼤家多多⽀持。

因篇幅问题不能全部显示,请点此查看更多更全内容