目录
前言
大多数系统中,都需要数据库来持久化数据,在大多数情况下,一个系统只需要配置一个数据源便能够完成所有业务的查询,保存操作。也存在一个系统需要多个数据源的情况,不同的数据源对应不同的业务操作,这种场景下配置多个数据源,并且在代码中维护多套dao层就可以了。
还存在一种业务场景,所有的业务操作都是一样的,只有操作的数据源的不同,如果用多套dao层来实现,由于业务操作都一样,会出现多块一模一样的代码,这样的冗余代码是我们不希望看到,不利于维护。这种业务场景就很适合用动态数据源来实现。
可以使用 AbstractRoutingDataSource + ThreadLocal + AOP 来实现动态数据源切换
前置环境
JDK8 + SringBoot2 + MySQL8
分别创建数据库 test1 test2
分别在两个数据库中创建 user 表
create table user (
id int auto_increment primary key,
username varchar(255),
password varchar(255)
);
在test1.user 表中插入数据
insert into user(username, password) values('张三', '123456');
在test2.user 表中插入数据
insert into user(username, password) values('李四', '123456');
pom
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
</dependency>
<dependencies>
yml
server: port: 8888 spring: datasource: primary: jdbc-url: jdbc:mysql://localhost:3306/test1?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true username: root password: root driver-class-name: com.mysql.cj.jdbc.Driver secondary: jdbc-url: jdbc:mysql://localhost:3306/test2?useUnicode=true&characterEncoding=utf-8&useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true username: root password: root driver-class-name: com.mysql.cj.jdbc.Driver jpa: show-sql: true properties: hibernate: hbm2ddl: auto: update dialect: org.hibernate.dialect.MySQL5InnoDBDialect
Entity
UserEntity
@Entity
@Table ( name = "user")
public class UserEntity {
private Integer id;
private String username;
private String password;
@Id
@Column ( name = "id" )
public int getId() {
return id;
}
public void setId(Integer id) {
this.id = id;
}
@Basic
@Column ( name = "username" )
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
@Basic
@Column ( name = "password" )
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
}
Dao
IUserDao
@Repository (value = IUserDao.DAO_BEAN_NAME )
public interface IUserDao extends JpaRepository<UserEntity, Long> {
String DAO_BEAN_NAME = "userDao";
}
枚举类
枚举动态数据源,提高代码可读性
DataSourceEnums
public enum DataSourceEnums {
PRIMARY,
SECONDARY;
static {
set = Arrays.stream(values()).
map(e -> e.name()).collect(Collectors.toSet());
}
private static Set<String> set;
public static boolean isValid(String dataSource) {
return set.contains(dataSource);
}
}
数据源
线程切换数据源上下文,每个请求线程都维护一个自己的当前数据源变量
DynamicDataSourceContenxtHolder
public class DynamicDataSourceContextHolder {
/**
* 使用ThreadLocal维护变量,ThreadLocal为每个使用该变量的线程提供独立的变量副本,
* 所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
*/
private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();
/**
* 设置数据源变量
* @param dataSourceType
*/
public static void setDataSourceType(String dataSourceType){
System.out.printf("切换到{%s}数据源", dataSourceType);
CONTEXT_HOLDER.set(dataSourceType);
}
/**
* 获取数据源变量
* @return
*/
public static String getDataSourceType(){
return CONTEXT_HOLDER.get();
}
/**
* 清空数据源变量
*/
public static void clearDataSourceType(){
CONTEXT_HOLDER.remove();
}
}
DynamicDataSource
public class DynamicDataSource extends AbstractRoutingDataSource {
public DynamicDataSource(DataSource defaultTargetDataSource, Map<Object, Object> targetDataSources) {
super.setDefaultTargetDataSource(defaultTargetDataSource);
super.setTargetDataSources(targetDataSources);
// afterPropertiesSet()方法调用时用来将targetDataSources的属性写入resolvedDataSources中的
super.afterPropertiesSet();
}
/**
* 根据Key获取数据源的信息
*
* @return
*/
@Override
protected Object determineCurrentLookupKey() {
return DynamicDataSourceContextHolder.getDataSourceType();
}
}
DynamicDatasourceConfig
记得将类中的两个包路径修改成自己项目的包路径
REPOSITORY_PACKAGE
ENTITY_PACKAGE
@Configuration
@EnableTransactionManagement
@EnableJpaRepositories (
basePackages = DynamicDatasourceConfig.REPOSITORY_PACKAGE,
entityManagerFactoryRef = "dynamicEntityManagerFactory",
transactionManagerRef = "dynamicTransactionManager"
)
public class DynamicDatasourceConfig {
//--------------数据源配置-------------------
@Bean(name="primary")
@ConfigurationProperties(prefix = "spring.datasource.primary")
public DataSource primaryDataSource() {
return DataSourceBuilder.create().build();
}
@Bean(name="secondary")
@ConfigurationProperties(prefix = "spring.datasource.secondary")
public DataSource secondaryDataSource() {
return DataSourceBuilder.create().build();
}
@Primary
@Bean(name = "dynamicDataSource")
public DynamicDataSource dataSource(@Qualifier ("primary") DataSource primaryDataSource, @Qualifier ( "secondary" )DataSource secondaryDataSource) {
Map<Object, Object> targetDataSources = new HashMap<>();
targetDataSources.put(DataSourceEnums.PRIMARY.name(), primaryDataSource);
targetDataSources.put(DataSourceEnums.SECONDARY.name(), secondaryDataSource);
return new DynamicDataSource(primaryDataSource, targetDataSources);
}
/**
* 该方法仅在需要使用JdbcTemplate对象时选用
*
* @param dataSource 注入名为dynamicDataSource的bean
* @return 数据源JdbcTemplate对象
*/
@Bean(name = "dynamicJdbcTemplate")
public JdbcTemplate jdbcTemplate(@Qualifier("dynamicDataSource") DataSource dataSource) {
return new JdbcTemplate(dataSource);
}
//-------------jpa配置---------------
static final String REPOSITORY_PACKAGE = "com.your.dao";
private static final String ENTITY_PACKAGE = "com.your.entity";
/**
* 扫描spring.jpa.dynamic开头的配置信息
*
* @return jpa配置信息
*/
@Primary
@Bean (name = "dynamicJpaProperties")
@ConfigurationProperties (prefix = "spring.jpa")
public JpaProperties jpaProperties() {
return new JpaProperties();
}
/**
* 获取主库实体管理工厂对象
*
* @param dynamicDataSource 注入名为dynamicDataSource的数据源
* @param jpaProperties 注入名为dynamicJpaProperties的jpa配置信息
* @param builder 注入EntityManagerFactoryBuilder
* @return 实体管理工厂对象
*/
@Primary
@Bean(name = "dynamicEntityManagerFactory")
public LocalContainerEntityManagerFactoryBean entityManagerFactoryBean(
@Qualifier ("dynamicDataSource") DataSource dynamicDataSource,
@Qualifier("dynamicJpaProperties") JpaProperties jpaProperties,
EntityManagerFactoryBuilder builder
) {
return builder
// 设置数据源
.dataSource(dynamicDataSource)
// 设置jpa配置
.properties(jpaProperties.getProperties())
// 设置实体包名
.packages(ENTITY_PACKAGE)
// 设置持久化单元名,用于@PersistenceContext注解获取EntityManager时指定数据源
.persistenceUnit("dynamicPersistenceUnit").build();
}
/**
* 获取实体管理对象
*
* @param factory 注入名为dynamicEntityManagerFactory的bean
* @return 实体管理对象
*/
@Primary
@Bean(name = "dynamicEntityManager")
public EntityManager entityManager(@Qualifier("dynamicEntityManagerFactory") EntityManagerFactory factory) {
return factory.createEntityManager();
}
/**
* 获取主库事务管理对象
*
* @param factory 注入名为dynamicEntityManagerFactory的bean
* @return 事务管理对象
*/
@Primary
@Bean(name = "dynamicTransactionManager")
public JpaTransactionManager transactionManager(@Qualifier("dynamicEntityManagerFactory") EntityManagerFactory factory) {
return new JpaTransactionManager(factory);
}
}
AOP
对需要动态切换数据源的请求做切面,编写切换数据源逻辑
记得将切点表达式换成自己项目的路径
@Pointcut ("execution(* com.your.controller.DynamicController.*(..))")
DataSourceAspect
@Aspect
@Component
public class DataSourceAspect {
@Pointcut ("execution(* com.your.controller.DynamicController.*(..))")
public void dsPointCut() {}
@Around ("dsPointCut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
RequestAttributes ra = RequestContextHolder.getRequestAttributes();
ServletRequestAttributes sra = (ServletRequestAttributes) ra;
HttpServletRequest request = sra.getRequest();
String dataSource = request.getParameter("database");
if (dataSource != null && DataSourceEnums.isValid(dataSource)) {
DynamicDataSourceContextHolder.setDataSourceType(dataSource);
}
try {
return point.proceed();
} finally {
// 销毁数据源 在执行方法之后
DynamicDataSourceContextHolder.clearDataSourceType();
}
}
}
Controller
DynamicController
@RestController
@RequestMapping(value = "/test")
public class DynamicController {
@Resource
IUserDao userDao;
@GetMapping(value = "/findAll")
public List<UserEntity> findAll() {
return userDao.findAll();
}
}
启动类
由于使用的是自定义的数据源配置,在启动时需要将Spring Boot 中扫描默认数据源的配置类排除掉,注解如下所示:
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
@EnableTransactionManagement
public class DynamicApplication {
public static void main(String[] args) {
SpringApplication.run(DynamicApplication.class, args);
}
}
演示
请求 /test/findAll 或者 /test/findAll?database=PRIMARY
不传默认是 PRIMARY 数据库
请求 /test/findAll?database=SECONDARY
本站资源均来自互联网,仅供研究学习,禁止违法使用和商用,产生法律纠纷本站概不负责!如果侵犯了您的权益请与我们联系!
转载请注明出处: 免费源码网-免费的源码资源网站 » Spring Boot 动态数据源
发表评论 取消回复