SpringBoot教程(二十四) | SpringBoot实现分布式定时任务之Quartz(多数据源配置)
- 前言
- 多数据源配置
- 引入aop依赖
- 1. properties配置多数据源
- 2. 创建数据源枚举类
- 3. 线程参数配置类
- 4. 数据源动态切换类
- 5. 多数据源配置类
- HikariCP 版本
- Druid 版本
- 6. 自定义多数据源切换注解
- 7. 数据源注解截面AOP
- 8. 测试 多数据源 是否起效
前言
我这边的SpringBoot的版本为2.6.13,其中Quartz是使用的以下方式引入
<!--quartz定时任务-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-quartz</artifactId>
</dependency>
由于我目前需要使用@QuartzDataSource去指定数据源,所以就不通过@DS注解去实现多数据源
而是通过自定义DynamicDataSource 去实现了多数据源
多数据源配置
引入aop依赖
<!--spring切面aop依赖-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
1. properties配置多数据源
# 应用服务 WEB 访问端口
server.port=9081
# 主数据源
spring.datasource.db1.url=jdbc:mysql://localhost:3306/sharding?useSSL=false&serverTimezone=UTC
spring.datasource.db1.jdbc-url=jdbc:mysql://localhost:3306/sharding?useSSL=false&serverTimezone=UTC
spring.datasource.db1.username=root
spring.datasource.db1.password=root
spring.datasource.db1.driver-class-name=com.mysql.cj.jdbc.Driver
# 次数据源
spring.datasource.db2.url=jdbc:mysql://localhost:3306/sharding?useSSL=false&serverTimezone=UTC
spring.datasource.db2.jdbc-url=jdbc:mysql://localhost:3306/sharding?useSSL=false&serverTimezone=UTC
spring.datasource.db2.username=root
spring.datasource.db2.password=root
spring.datasource.db2.driver-class-name=com.mysql.cj.jdbc.Driver
# quartz 数据源
#spring.datasource.task.url=jdbc:mysql://localhost:3306/quartz
spring.datasource.task.jdbc-url=jdbc:mysql://localhost:3306/quartz
spring.datasource.task.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.task.username=root
spring.datasource.task.password=root
# Quartz Scheduler 配置
# 指定作业存储类型为JDBC,使用数据库来存储作业和调度信息
spring.quartz.job-store-type=jdbc
# 在关闭时等待作业完成
spring.quartz.wait-for-jobs-to-complete-on-shutdown=true
# 不初始化数据库架构,假设数据库架构已经存在
spring.quartz.jdbc.initialize-schema=never
# Quartz Scheduler 属性配置
spring.quartz.properties.org.quartz.jobStore.dataSource=quartz_jobs
spring.quartz.properties.org.quartz.jobStore.driverDelegateClass=org.quartz.impl.jdbcjobstore.StdJDBCDelegate
spring.quartz.properties.org.quartz.jobStore.tablePrefix=QRTZ_
# 启用集群模式
spring.quartz.properties.org.quartz.jobStore.isClustered=true
# 集群检查间隔时间(毫秒)
spring.quartz.properties.org.quartz.jobStore.clusterCheckinInterval=1000
# 不使用属性来存储作业数据,改为使用BLOB字段
spring.quartz.properties.org.quartz.jobStore.useProperties=false
# 指定作业存储的类,这里使用Spring提供的LocalDataSourceJobStore,但通常这个配置是隐式的,
# 除非你有特殊的实现需求,否则通常不需要显式设置这个属性。
# spring.quartz.properties.org.quartz.jobStore.class=org.springframework.scheduling.quartz.LocalDataSourceJobStore
# 注意:上面的行已被注释掉,因为通常不需要显式设置
# 调度器实例名称和ID(org.quartz.scheduler.instanceName 这个是保证属于同一个集群)
spring.quartz.properties.org.quartz.scheduler.instanceName=SC_Scheduler
spring.quartz.properties.org.quartz.scheduler.instanceId=AUTO
# 线程池配置
# 线程池中线程的数量
spring.quartz.properties.org.quartz.threadPool.threadCount=25
# 线程优先级
spring.quartz.properties.org.quartz.threadPool.threadPriority=5
# 线程池实现类
spring.quartz.properties.org.quartz.threadPool.class=org.quartz.simpl.SimpleThreadPool
2. 创建数据源枚举类
package com.example.springbootfull.quartztest.enums;
/**
* 数据源
*/
public enum DataSourceType {
/**
* 数据源1
* */
DB1,
/**
* 数据源2
* */
DB2
}
3. 线程参数配置类
定义一个工具类来设置当前线程的数据源枚举值
package com.example.springbootfull.quartztest.datasource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* 数据源切换处理
*/
public class DynamicDataSourceContextHolder {
public static final Logger log = LoggerFactory.getLogger(DynamicDataSourceContextHolder.class);
/**
* 使用ThreadLocal维护变量,ThreadLocal为每个使用该变量的线程提供独立的变量副本,
* 所以每一个线程都可以独立地改变自己的副本,而不会影响其它线程所对应的副本。
*/
private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();
/**
* 设置数据源的变量
*/
public static void setDataSourceType(String dsType) {
log.info("切换到{}数据源", dsType);
CONTEXT_HOLDER.set(dsType);
}
/**
* 获得数据源的变量
*/
public static String getDataSourceType() {
return CONTEXT_HOLDER.get();
}
/**
* 清空数据源变量
*/
public static void clearDataSourceType() {
CONTEXT_HOLDER.remove();
}
}
4. 数据源动态切换类
package com.example.springbootfull.quartztest.datasource;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
/**
* Spring的AbstractRoutingDataSource抽象类,实现动态数据源(他的作用就是动态切换数据源)
* AbstractRoutingDataSource中的抽象方法determineCurrentLookupKey是实现数据源的route的核心,
* 这里对该方法进行Override。【上下文DynamicDataSourceContextHolder为一线程安全的ThreadLocal】
*/
public class DynamicDataSource extends AbstractRoutingDataSource {
/**
* 取得当前使用哪个数据源
* @return dbTypeEnum
*/
@Override
protected Object determineCurrentLookupKey() {
return DynamicDataSourceContextHolder.getDataSourceType();
}
}
5. 多数据源配置类
HikariCP 版本
package com.example.springbootfull.quartztest.config;
import com.baomidou.mybatisplus.core.MybatisConfiguration;
import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
import com.example.springbootfull.quartztest.datasource.DynamicDataSource;
import com.example.springbootfull.quartztest.enums.DataSourceType;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.type.JdbcType;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.autoconfigure.quartz.QuartzDataSource;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.boot.jdbc.DataSourceBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.core.io.Resource;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.transaction.PlatformTransactionManager;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
/**
* 多数据源配置
*/
@Configuration
public class DataSourceConfig {
/**
* 创建第一个数据源
*
* @return dataSource
*/
@Bean(name = "dataSource1")
@ConfigurationProperties(prefix = "spring.datasource.db1")
public DataSource dataSource1() {
return DataSourceBuilder.create().build();
}
/**
* 创建第二个数据源
*
* @return dataSource
*/
@Bean(name = "dataSource2")
@ConfigurationProperties(prefix = "spring.datasource.db2")
public DataSource dataSource2() {
return DataSourceBuilder.create().build();
}
//quartz数据库 dataSourceTask数据源
//使用@QuartzDataSource后,不需要动态配置
@Bean(name = "dataSourceTask")
@ConfigurationProperties(prefix = "spring.datasource.task")
@QuartzDataSource
public DataSource dataSourceTask() {
return DataSourceBuilder.create().build();
}
/**
* 动态数据源配置
*
* @return dataSource
*/
@Primary
@Bean("multipleDataSource")
public DataSource multipleDataSource(@Qualifier("dataSource1") DataSource db1,
@Qualifier("dataSource2") DataSource db2) {
DynamicDataSource dynamicDataSource = new DynamicDataSource();
Map<Object, Object> dataSources = new HashMap<>();
dataSources.put(DataSourceType.DB1, db1);
dataSources.put(DataSourceType.DB2, db2);
dynamicDataSource.setTargetDataSources(dataSources);
//默认数据源
dynamicDataSource.setDefaultTargetDataSource(db1);
return dynamicDataSource;
}
@Bean("sqlSessionFactory")
public SqlSessionFactory sqlSessionFactory(@Qualifier("multipleDataSource") DataSource multipleDataSource) throws Exception {
// 导入mybatissqlsession配置
MybatisSqlSessionFactoryBean sessionFactory = new MybatisSqlSessionFactoryBean();
// 指明数据源
sessionFactory.setDataSource(multipleDataSource);
// 设置mapper.xml的位置路径
Resource[] resources = new PathMatchingResourcePatternResolver().getResources("classpath*:mapper/**/*.xml");
sessionFactory.setMapperLocations(resources);
//指明实体扫描(多个package用逗号或者分号分隔)
//sessionFactory.setTypeAliasesPackage("com.szylt.projects.project.entity");
// 导入mybatis配置
MybatisConfiguration configuration = new MybatisConfiguration();
configuration.setJdbcTypeForNull(JdbcType.NULL);
configuration.setMapUnderscoreToCamelCase(true);
configuration.setCacheEnabled(false);
sessionFactory.setConfiguration(configuration);
return sessionFactory.getObject();
}
//数据源事务配置
@Bean
public PlatformTransactionManager transactionManager(DataSource multipleDataSource) {
return new DataSourceTransactionManager(multipleDataSource);
}
}
Druid 版本
需要先引入 Druid 依赖,这里使用的是 Druid 官方的 Starter
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.8</version>
</dependency>
然后,在properties配置文件中 为每个数据源配置DruidDataSour数据库连接池
spring.datasource.db1.type=com.alibaba.druid.pool.DruidDataSour
spring.datasource.db2.type=com.alibaba.druid.pool.DruidDataSour
spring.datasource.task.type=com.alibaba.druid.pool.DruidDataSour
接着把一下DataSourceConfig 类里面的
DataSource 对象 换成 DruidDataSource 对象
DataSourceBuilder 对象 换成 DruidDataSourceBuilder 对象
就好了
6. 自定义多数据源切换注解
package com.example.springbootfull.quartztest.annotation;
import com.example.springbootfull.quartztest.enums.DataSourceType;
import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
/**
* 自定义多数据源切换注解
* 优先级:先方法,后类,如果方法覆盖了类上的数据源类型,以方法的为准,否则以类上的为准
*/
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Documented
@Inherited
public @interface DataSource
{
/**
* 切换数据源名称
*/
public DataSourceType value() default DataSourceType.DB1;
}
7. 数据源注解截面AOP
package com.example.springbootfull.quartztest.aspectj;
import java.util.Objects;
import com.example.springbootfull.quartztest.annotation.DataSource;
import com.example.springbootfull.quartztest.datasource.DynamicDataSourceContextHolder;
import org.aspectj.lang.ProceedingJoinPoint;
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.core.annotation.AnnotationUtils;
import org.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
/**
* 多数据源处理
*
*/
@Aspect
@Order(1)
@Component
public class DataSourceAspect {
protected Logger logger = LoggerFactory.getLogger(getClass());
@Pointcut("@annotation(com.example.springbootfull.quartztest.annotation.DataSource)"
+ "|| @within(com.example.springbootfull.quartztest.annotation.DataSource)")
public void dsPointCut() {
}
@Around("dsPointCut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
DataSource dataSource = getDataSource(point);
if (dataSource != null) {
DynamicDataSourceContextHolder.setDataSourceType(dataSource.value().name());
}
try {
return point.proceed();
} finally {
// 销毁数据源 在执行方法之后
DynamicDataSourceContextHolder.clearDataSourceType();
}
}
/**
* 获取需要切换的数据源
*/
public DataSource getDataSource(ProceedingJoinPoint point) {
MethodSignature signature = (MethodSignature) point.getSignature();
DataSource dataSource = AnnotationUtils.findAnnotation(signature.getMethod(), DataSource.class);
if (Objects.nonNull(dataSource)) {
return dataSource;
}
return AnnotationUtils.findAnnotation(signature.getDeclaringType(), DataSource.class);
}
}
8. 测试 多数据源 是否起效
创建了一个控制层测试类
package com.example.springbootfull.mybatisplustest.controller;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.example.springbootfull.mybatisplustest.entity.SysUser;
import com.example.springbootfull.mybatisplustest.mapper.SysUserMapper;
import com.example.springbootfull.quartztest.annotation.DataSource;
import com.example.springbootfull.quartztest.enums.DataSourceType;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.stereotype.Controller;
/**
* <p>
* 前端控制器
* </p>
*
* @author jiang
* @since 2024-09-13
*/
@Controller
@RequestMapping("/mybatisplustest/sysUser")
public class SysUserController {
@Autowired
private SysUserMapper sysUserMapper;
@RequestMapping("/selectAll")
@DataSource(value = DataSourceType.DB1)
public void contextLoads() {
//第一页,10个
Page<SysUser> page = new Page<>(1, 10);
QueryWrapper<SysUser> wrapper = new QueryWrapper<>();
//查询liek人1的
//wrapper.eq("enabled", true).like("account", "小明机器人10号");
//查询授权=true的;
wrapper.like("account", "人1");
IPage<SysUser> iPage = sysUserMapper.selectPage(page, wrapper);
//总页数
System.out.println("总页数:"+iPage.getPages());
//总条数
System.out.println("总条数:"+iPage.getTotal());
//每页条数
System.out.println("每页条数:"+iPage.getSize());
//当前页的结果集
System.out.println("当前页的结果集:"+iPage.getRecords());
//当前页号
System.out.println("当前页号:"+iPage.getCurrent());
// // 创建实体类
// SysUser sysUser = new SysUser();
// sysUser.setAccount("mybtis呀");
// sysUser.setEnabled(Boolean.TRUE);
// sysUser.setCreateAt(new Date());
// sysUserDao.insertSelective(sysUser);
//
// // 根据自增ID检索实体
// SysUser sysUser1 = sysUserDao.selectByPrimaryKey(sysUser.getId());
// logger.info("user={}", sysUser1);
}
}
运行如下截图:
两者都有触发
参考文章如下:
【1】Springboot+Mybatis+MySql整合多数据源及其使用
【2】Springboot定时任务quartz整合(多数据源+quartz持久化到数据库)
【3】springboot+MybatisPlus+HikariCP多数据源动态配置(实战篇)
【4】springboot+mybatis-plus+quartz多数据源操作,亲测可用