Spring Boot实现动态数据源切换可以先参看下http://blog.csdn.net/zero__007/article/details/48711017,了解大致下实现的原理。 首先我们使用db_dao.xml来配置各个datasource:
#jdbc.properties
jdbc.driver.master=com.mysql.jdbc.Driver
jdbc.url.master=jdbc:mysql://xxxxxx?useUnicode=true&characterEncoding=utf8
jdbc.username.master=xxxxxx
jdbc.password.master=xxxxxx
jdbc.driver.slave=com.mysql.jdbc.Driver
jdbc.url.slave=jdbc:mysql://xxxxxx
jdbc.username.slave=xxxxxx
jdbc.password.slave=xxxxxx
上面的配置文件中我们就已经配置好了我们的关键Bean---DynamicDataSource,DynamicDataSource.java如下:
package org.zero.datasource;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import java.util.HashSet;
import java.util.Set;
import java.util.logging.Logger;
public class DynamicDataSource extends AbstractRoutingDataSource {
private static final ThreadLocal datasourceHolder = new ThreadLocal<>();
@Override
public Object determineCurrentLookupKey() {
return datasourceHolder.get();
}
static void setDataSource(String sourceName) {
datasourceHolder.set(sourceName);
}
static void clearDataSource() {
datasourceHolder.remove();
}
}
不再细作代码分析,上面的文章已经有作解释。这次的实现数据源动态切换将用到AOP,通过注解来达到切换目的。
首先是自定义注解类:
package org.zero.datasource;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface DBSource {
String name();
}
这里定义了DBSource注解,然后可以将该注解写在dao层的接口方法或实现类的方法上来使用,当然我们需要AOP来识别注解:
package org.zero.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.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
import java.lang.reflect.Method;
@Aspect
@Order(-1)// 保证该AOP在@Transactional之前执行
@Component
public class DynamicDataSourceAspect {
@Before(value = "execution(* org.zero.dao..*.*(..))")
public void changeDataSource(JoinPoint point) throws Throwable {
String sourceName = null;
//获得当前访问的class
Class classes = point.getTarget().getClass();
//获得访问的方法名称
String methodName = point.getSignature().getName();
//定义的接口方法
Method abstractMethod = ((MethodSignature) point.getSignature()).getMethod();
if (abstractMethod.isAnnotationPresent(DBSource.class)) {
sourceName = abstractMethod.getAnnotation(DBSource.class).name();
System.out.println(("动态切换数据源:--- " + sourceName));
}
//接口方法参数类型
Class[] parameterTypes = abstractMethod.getParameterTypes();
try {
//实现类中的该方法
Method method = classes.getMethod(methodName, parameterTypes);
if (method.isAnnotationPresent(DBSource.class)) {
sourceName = method.getAnnotation(DBSource.class).name();
System.out.println("动态切换数据源:------ " + sourceName);
}
} catch (Exception e) {
e.printStackTrace();
}
if (sourceName != null) {
DynamicDataSource.setDataSource(sourceName);
}
}
@Pointcut("execution(* org.zero.dao..*.*(..))")
public void pointCut() {
}
@After("pointCut()")
public void after(JoinPoint point) {
System.out.println("after");
DynamicDataSource.clearDataSource();
}
}
上面的类会拦截org.zero.dao目录及其子目录的类的方法,先会获取接口方法上的注解,然后是实现类该方法的注解,优先级是实现类该方法的注解>接口方法上的注解。
dao层的代码如下:
package org.zero.dao;
import org.zero.datasource.DBSource;
import org.zero.entity.Book;
import java.util.List;
public interface IBookDao {
@DBSource(name="master")
Book queryById(long id);
List queryAll();
}
package org.zero.dao.impl;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.jdbc.core.BeanPropertyRowMapper;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.stereotype.Repository;
import org.zero.dao.IBookDao;
import org.zero.datasource.DBSource;
import org.zero.entity.Book;
import javax.sql.DataSource;
import java.util.List;
@Repository
public class BookDaoImpl implements IBookDao {
private JdbcTemplate jdbcTemplate;
@Override
public Book queryById(long id) {
String sql = "SELECT * FROM book WHERE book_id = ?";
List list = jdbcTemplate.query(sql, (rs, rowNum) -> {
Book book = new Book();
book.setBookId(rs.getLong("book_id"));
book.setName(rs.getString("name"));
book.setNumber(rs.getInt("number"));
return book;
}, id);
if (list.size() == 0) {
return null;
} else {
return list.get(0);
}
}
@Override
@DBSource(name="slave")
public List queryAll() {
List list = jdbcTemplate.query("SELECT * FROM book", new BeanPropertyRowMapper<>(Book.class));
return list;
}
@Autowired
@Qualifier("dataSource")
public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
}
至于service层和controller层的代码就省略了。
万事俱备,现在就是把容器启动了。这里需要格外注意,Spring Boot自带有DataSourceAutoConfiguration,需要把它禁掉,因为它会读取application.properties文件的spring.datasource.*属性并自动配置单数据源。在@SpringBootApplication注解中添加exclude属性即可。同时还需要引入我们自己的db_dao.xml文件。
package org.zero;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.context.annotation.ImportResource;
@SpringBootApplication(exclude = {
DataSourceAutoConfiguration.class
})
@ImportResource("classpath:db_dao.xml") //导入xml配置项
public class Application {
public static void main(String[] args) {
SpringApplication application = new SpringApplication(Application.class);
application.setWebEnvironment(true);
application.run(args);
}
}
既然知道了如何使用xml来配置的话,对使用代码作配置理解起来就应该很简单了,首先需要在application.properties定义数据源:
spring.datasource.type=com.alibaba.druid.pool.DruidDataSource
spring.datasource.master.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.master.url=jdbc:mysql://xxxxxx?useUnicode=true&characterEncoding=utf8
spring.datasource.master.username=xxxxxx
spring.datasource.master.password=xxxxxx
spring.datasource.slave.driver-class-name=com.mysql.jdbc.Driver
spring.datasource.slave.url=jdbc:mysql://xxxxxx?useUnicode=true&characterEncoding=utf8
spring.datasource.slave.username=xxxxxx
spring.datasource.slave.password=xxxxxx
然后按照之前的那个db_dao.xml的配置用代码来实现:
package org.zero.datasource;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
@Configuration
public class DataSourceConfig {
@Value("${spring.datasource.type}")
private Class dataSourceType;
@Bean(name="masterDataSource", destroyMethod = "close", initMethod="init")
@ConfigurationProperties(prefix = "spring.datasource.master")
public DataSource masterDataSource() {
return DataSourceBuilder.create().type(dataSourceType).build();
}
@Bean(name="slaveDataSource", destroyMethod = "close", initMethod="init")
@ConfigurationProperties(prefix = "spring.datasource.slave")
public DataSource slaveDataSource() {
return DataSourceBuilder.create().type(dataSourceType).build();
}
@Bean(name = "dataSource")
public DataSource dataSource() {
DynamicDataSource dynamicDataSource = new DynamicDataSource();
// 配置多数据源
Map targetDataSources = new HashMap<>();
targetDataSources.put("master", masterDataSource());
targetDataSources.put("slave", slaveDataSource());
dynamicDataSource.setTargetDataSources(targetDataSources);
dynamicDataSource.setDefaultTargetDataSource(slaveDataSource());
return dynamicDataSource;
}
}
剩下的就是启动类:
package org.zero;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.context.annotation.Import;
import org.springframework.context.annotation.ImportResource;
import org.zero.datasource.DataSourceConfig;
@SpringBootApplication(exclude = {
DataSourceAutoConfiguration.class
})
public class Application {
public static void main(String[] args) {
SpringApplication application = new SpringApplication(Application.class);
application.setWebEnvironment(true);
application.run(args);
}
}
参考:
https://blog.csdn.net/tjcyjd/article/details/78399771