
前言: xxl-job 的GLUE模式是继承自IJobHandler的Java类代码,它在执行器中运行,支持注入执行器中的其它服务来调用封装好的方法。在xxl-job的使用过程中,发现有大量需要进行数据查询再通过少量逻辑处理的需求,这些需求往往逻辑大不相同,难以进行统一封装,如:需要从数据库中查询数据,对查询结果进行处理,拼接再进行企业微信推送的需求。
在这种情况下,我们就需要在GLUE模式中进行一定的代码开发,而比较棘手的是如何进行数据库查询,且不必在每个任务中写大量重复代码。在spring boot的项目中,通常是通过调用mapper中的方法执行sql来进行数据查询,mapper中会指定数据源,然后会通过实体类来接收查询结果,通过get实体类属性来获取指定数据。那么我们如何在GLUE模式中指定数据源,进行数据库查询并获取到指定数据呢?
在这里我想到可以Spring AOP环绕数据库查询方法执行指定数据源,通过dynamic以及Drui数据库连接池来实现多数据源管理。这里是我想到的一个实现方法,虽能实现预定需求但仍感觉不太简洁,后续想到更好地方式会进行分享,大家如有好的想法也可以进行交流,文中如有不恰当的地方欢迎指正。
一 版本信息
- xxl-job:2.5.0-SNAPSHOT
- spring boot:2.7.6
- dynamic:3.4.1
- druid:1.2.8 pom依赖
xml
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
<dependency>
<groupId>com.xuxueli</groupId>
<artifactId>xxl-job-core</artifactId>
<version>2.5.0-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.8</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>dynamic-datasource-spring-boot-starter</artifactId>
<version>3.4.1</version>
</dependency>
二 多数据源
1、在yaml中配置数据源信息
首先在application.yml中配置上数据源及数据库连接池信息
yaml
# 应用名称
spring:
application:
name: xxl-job-actuator
datasource:
dynamic:
primary: dataOne
datasource:
dataOne:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/data_test_db1
username: user
password: pwd
dataTwo:
driver-class-name: com.mysql.cj.jdbc.Driver
url: jdbc:mysql://localhost:3306/data_test_db2
username: user
password: pwd
druid:
initialSize: 5 # 初始化连接大小
minIdle: 5 # 最小连接池数量
maxActive: 200 # 最大连接池数量
maxWait: 60000 # 获取连接时最大等待时间,单位毫秒
timeBetweenEvictionRunsMillis: 60000 # 配置间隔多久才进行一次检测,检测需要关闭的空闲连接,单位是毫秒
minEvictableIdleTimeMillis: 300000 # 配置一个连接在池中最小生存的时间,单位是毫秒
validationQuery: SELECT 1 # 测试连接
testWhileIdle: true # 申请连接的时候检测,建议配置为true,不影响性能,并且保证安全性
testOnBorrow: false # 获取连接时执行检测,建议关闭,影响性能
testOnReturn: false # 归还连接时执行检测,建议关闭,影响性能
poolPreparedStatements: false # 是否开启PSCache,PSCache对支持游标的数据库性能提升巨大,oracle建议开启,mysql下建议关闭
maxPoolPreparedStatementPerConnectionSize: 20 # 开启poolPreparedStatements后生效
removeAbandoned: true # 是否开启abandoned connection检测
logAbandoned: false # 是否记录abandoned connection
xxl:
job:
admin:
addresses: http://localhost:8080/xxl-job-admin
accessToken: token
executor:
appname: xxl-job-actuator-executor
address:
ip:
port: 8082
logpath: /data/applogs/xxl-job/jobhandler
logretentiondays: 30
server:
port: 8081
2、获取yaml中数据源配置信息
通过注解@ConfigurationProperties来获取yaml中的配置信息 获取数据源信息
java
package com.dl.xxljobactuator.datasource;
import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import java.util.Map;
/**
* @Description: 绑定YAML中的数据源配置
*/
@Getter
@Setter
@Configuration
@ConfigurationProperties(prefix = "spring.datasource.dynamic")
public class DynamicDataSourceProperties {
private String primary;
private Map<String, DataSourceProperties> datasource;
@Setter
@Getter
public static class DataSourceProperties {
private String url;
private String username;
private String password;
private String driverClassName;
}
}
获取druid连接池信息
java
package com.dl.xxljobactuator.datasource;
import lombok.Getter;
import lombok.Setter;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import java.util.Properties;
/**
* @Description: 绑定druid线程池参数
*/
@Getter
@Setter
@Configuration
@ConfigurationProperties(prefix = "spring.datasource.dynamic.druid")
public class DruidProperties {
private int initialSize;
private int minIdle;
private int maxActive;
private int maxWait;
private int timeBetweenEvictionRunsMillis;
private int minEvictableIdleTimeMillis;
private String validationQuery;
private boolean testWhileIdle;
private boolean testOnBorrow;
private boolean testOnReturn;
private boolean poolPreparedStatements;
private int maxPoolPreparedStatementPerConnectionSize;
private Properties connectProperties;
private boolean removeAbandoned;
private int removeAbandonedTimeout;
private boolean logAbandoned;
}
3、注入数据源
先创建数据源上下文
java
package com.dl.xxljobactuator.datasource;
/**
* @Description: 创建一个线程安全的数据源上下文持有者类,用于存储当前线程的数据源键
*/
public class DynamicDataSourceContextHolder {
// 使用ThreadLocal存储数据源名称,确保每个线程都有自己的数据源名称副本
private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();
// 设置当前线程的数据源名称
public static void setDataSourceKey(String key) {
contextHolder.set(key);
}
// 获取当前线程的数据源名称
public static String getDataSourceKey() {
return contextHolder.get();
}
// 清除当前线程的数据源名称
public static void clearDataSourceKey() {
contextHolder.remove();
}
}
注入数据源
java
package com.dl.xxljobactuator.config;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
import com.dl.xxljobactuator.datasource.DruidProperties;
import com.dl.xxljobactuator.datasource.DynamicDataSourceContextHolder;
import com.dl.xxljobactuator.datasource.DynamicDataSourceProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import javax.annotation.Resource;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
/**
* @Description: 将yml中的数据源放入容器中
*/
@Configuration
public class DynamicDataSourceConfig {
@Resource
private DynamicDataSourceProperties dynamicDataSourceProperties;
@Resource
private DruidProperties druidProperties;
@Bean
public DataSource dataSource() {
AbstractRoutingDataSource routingDataSource = new AbstractRoutingDataSource() {
@Override
protected Object determineCurrentLookupKey() {
return DynamicDataSourceContextHolder.getDataSourceKey();
}
};
Map<Object, Object> dataSourceMap = new HashMap<>();
// 将yaml中的数据源绑定druid连接池参数
dynamicDataSourceProperties.getDatasource().forEach((key, value) -> {
DruidDataSource dataSource = DruidDataSourceBuilder.create().build();
dataSource.setUrl(value.getUrl());
dataSource.setUsername(value.getUsername());
dataSource.setPassword(value.getPassword());
dataSource.setDriverClassName(value.getDriverClassName());
// 绑定Druid连接池参数
dataSource.setInitialSize(druidProperties.getInitialSize());
dataSource.setMinIdle(druidProperties.getMinIdle());
dataSource.setMaxActive(druidProperties.getMaxActive());
dataSource.setTimeBetweenEvictionRunsMillis(druidProperties.getTimeBetweenEvictionRunsMillis());
dataSource.setMinEvictableIdleTimeMillis(druidProperties.getMinEvictableIdleTimeMillis());
// Oracle数据库与mysql数据库检测连接sql不同
if (value.getDriverClassName().contains("oracle")) {
dataSource.setValidationQuery("SELECT 1 FROM DUAL");
} else {
dataSource.setValidationQuery(druidProperties.getValidationQuery());
}
dataSource.setTestWhileIdle(druidProperties.isTestWhileIdle());
dataSource.setTestOnBorrow(druidProperties.isTestOnBorrow());
dataSource.setTestOnReturn(druidProperties.isTestOnReturn());
dataSource.setPoolPreparedStatements(druidProperties.isPoolPreparedStatements());
dataSource.setMaxPoolPreparedStatementPerConnectionSize(druidProperties.getMaxPoolPreparedStatementPerConnectionSize());
dataSource.setConnectProperties(druidProperties.getConnectProperties());
dataSource.setRemoveAbandoned(druidProperties.isRemoveAbandoned());
dataSource.setRemoveAbandonedTimeout(druidProperties.getRemoveAbandonedTimeout());
dataSource.setLogAbandoned(druidProperties.isLogAbandoned());
dataSourceMap.put(key, dataSource);
});
// 设置默认数据源为primary配置的数据源
routingDataSource.setDefaultTargetDataSource(dataSourceMap.get(dynamicDataSourceProperties.getPrimary()));
routingDataSource.setTargetDataSources(dataSourceMap);
return routingDataSource;
}
}
4、封装常用查询方法
这里我们封装一些常用的查询方法
注意:因这里做测试,没有进行防SQL注入操作
先实现常见及自定义sql生成工具
java
package com.dl.xxljobactuator.datasource;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.jdbc.SQL;
/**
* @Description: 动态sql生成
*/
public class DynamicSqlProvider {
public String simpleSelectSql(@Param("fields") String fields, @Param("tableName") String tableName, @Param("condition") String condition,
@Param("groupBy") String groupBy) {
return new SQL() {{
SELECT(fields);
FROM(tableName);
if (condition != null && !condition.isEmpty()) {
WHERE(condition);
}
if (groupBy != null && !groupBy.isEmpty()) {
GROUP_BY(groupBy);
}
}}.toString();
}
public String simpleUpdateSql(@Param("tableName") String tableName, @Param("set") String set, @Param("condition") String condition) {
return new SQL() {{
UPDATE(tableName);
SET(set);
if (condition != null && !condition.isEmpty()) {
WHERE(condition);
}
}}.toString();
}
public String simpleInsertSql(@Param("tableName") String tableName, @Param("fields") String fields, @Param("values") String values) {
return new SQL() {{
INSERT_INTO(tableName);
VALUES(fields, values);
}}.toString();
}
public String simpleDeleteSql(@Param("tableName") String tableName, @Param("condition") String condition) {
return new SQL() {{
DELETE_FROM(tableName);
if (condition != null && !condition.isEmpty()) {
WHERE(condition);
}
}}.toString();
}
public String commonSql(@Param("sql") String sql) {
return sql;
}
public String singleResultSql(@Param("sql") String sql) {
return sql;
}
public String stringListSql(@Param("sql") String sql) {
return sql;
}
}
再在mapper中定义查询方法
java
package com.dl.xxljobactuator.mapper;
import com.dl.xxljobactuator.datasource.DynamicSqlProvider;
import org.apache.ibatis.annotations.*;
import java.util.List;
import java.util.Map;
/**
* @Description: 执行动态sql
*/
@Mapper
public interface DynamicMapper {
@SelectProvider(type = DynamicSqlProvider.class, method = "singleResultSql")
String executeSingleResultSql(@Param("sql") String sql);
@SelectProvider(type = DynamicSqlProvider.class, method = "stringListSql")
List<String> executeStringListSql(@Param("sql") String sql);
@SelectProvider(type = DynamicSqlProvider.class, method = "commonSql")
List<Map<String, Object>> executeCommonSql(@Param("sql") String sql);
@SelectProvider(type = DynamicSqlProvider.class, method = "simpleSelectSql")
List<Map<String, Object>> executeSimpleSelectSql(@Param("fields") String fields, @Param("tableName") String tableName,
@Param("condition") String condition, @Param("groupBy") String groupBy);
@UpdateProvider(type = DynamicSqlProvider.class, method = "simpleUpdateSql")
int executeSimpleUpdateSql(@Param("tableName") String tableName, @Param("set") String set, @Param("condition") String condition);
@InsertProvider(type = DynamicSqlProvider.class, method = "simpleInsertSql")
int executeSimpleInsertSql(@Param("tableName") String tableName, @Param("fields") String fields, @Param("values") String values);
@DeleteProvider(type = DynamicSqlProvider.class, method = "simpleDeleteSql")
int executeSimpleDeleteSql(@Param("tableName") String tableName, @Param("condition") String condition);
}
定义一个接收查询结果的实体类
java
package com.dl.xxljobactuator.entity;
import java.util.HashMap;
import java.util.Map;
/**
* @Description: 动态实体 接收不同数据源查询结果
*/
public class DynamicEntity {
private final Map<String, Object> properties = new HashMap<>();
public void setProperty(String key, Object value) {
properties.put(key, value);
}
public Object getProperty(String key) {
return properties.get(key);
}
@Override
public String toString() {
return "DynamicEntity{" + "properties=" + properties + '}';
}
}
在工具类中定义一个获取指定字段数据的方法
java
package com.dl.xxljobactuator.util;
import com.dl.xxljobactuator.entity.DynamicEntity;
import java.util.List;
import java.util.Map;
/**
* @Description: 常用工具类
*/
public class Utils {
public static void dataAnalysis(List<DynamicEntity> list, List<Map<String, Object>> results) {
// 处理数据
for (Map<String, Object> result : results) {
DynamicEntity entity = new DynamicEntity();
for (String key : result.keySet()) {
entity.setProperty(key, result.get(key));
}
list.add(entity);
}
}
}
5、指定数据源查询数据
现在就可以在GLUE模式中进行指定数据源查询并获取结果了
java
package com.dl.xxljobactuator.service;
import com.dl.xxljobactuator.datasource.DynamicDataSourceContextHolder;
import com.dl.xxljobactuator.entity.DynamicEntity;
import com.dl.xxljobactuator.mapper.DynamicMapper;
import com.dl.xxljobactuator.util.Utils;
import com.xxl.job.core.context.XxlJobHelper;
import com.xxl.job.core.handler.IJobHandler;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* @Description: GLUE(Java)任务Demo
* 传递sql参数、指定数据源、动态实体类
*/
public class DemoGlueJobHandler extends IJobHandler {
@Resource
private DynamicMapper dynamicMapper;
@Override
public void execute() {
executeSql("dataTwo", "select name from test;");
XxlJobHelper.log("原指定数据源方式测试成功!");
XxlJobHelper.log("执行完成!");
}
public void executeSql(String dataSourceName, String sql) {
DynamicDataSourceContextHolder.setDataSourceKey(dataSourceName);
List<DynamicEntity> list = new ArrayList<>();
try {
List<Map<String, Object>> results = dynamicMapper.executeCommonSql(sql);
// 处理数据
Utils.dataAnalysis(list, results);
} catch (Exception e) {
XxlJobHelper.log(e);
// 将推送结果置失败
XxlJobHelper.handleFail();
} finally {
DynamicDataSourceContextHolder.clearDataSourceKey();
}
list.forEach(entity -> XxlJobHelper.log(entity.toString() + entity.getProperty("name")));
}
}
执行结果
ini
DynamicEntity{properties={name=name1}}name1
DynamicEntity{properties={name=name2}}name2
原指定数据源方式测试成功!
执行完成!
通过这个GLUE的代码我们可以看到,当指定数据源进行sql查询时需要在上下文中set数据源名称,当任务执行完成后需要对数据源进行清理,这就意味着每当我们进行数据库查询时都要写一套这样的代码,这样会很麻烦,代码也会很臃肿。
三 通过Spring AOP进行优化
1、使用一个服务类封装查询方法
创建一个实现类通过DynamicMapper执行各种动态SQL操作,所有方法都包含dataSource参数用于数据源切换(由切面处理)
java
package com.dl.xxljobactuator.service;
import com.dl.xxljobactuator.mapper.DynamicMapper;
import org.springframework.stereotype.Service;
import javax.annotation.Resource;
import java.util.List;
import java.util.Map;
/**
* @Description: 数据库查询服务实现类
*/
@Service
public class QueryDataServiceImpl {
@Resource
private DynamicMapper dynamicMapper;
public String queryData(String dataSource, String sql) {
return dynamicMapper.executeSingleResultSql(sql);
}
public List<String> executeStringListSql(String dataSource, String sql) {
return dynamicMapper.executeStringListSql(sql);
}
public List<Map<String, Object>> executeCommonSql(String dataSource, String sql) {
return dynamicMapper.executeCommonSql(sql);
}
public List<Map<String, Object>> executeSimpleSelectSql(String dataSource, String fields, String tableName,
String condition, String groupBy) {
return dynamicMapper.executeSimpleSelectSql(fields, tableName, condition, groupBy);
}
public int executeSimpleUpdateSql(String dataSource, String tableName, String set, String condition) {
return dynamicMapper.executeSimpleUpdateSql(tableName, set, condition);
}
public int executeSimpleInsertSql(String dataSource, String tableName, String fields, String values) {
return dynamicMapper.executeSimpleInsertSql(tableName, fields, values);
}
public int executeSimpleDeleteSql(String dataSource, String tableName, String condition) {
return dynamicMapper.executeSimpleDeleteSql(tableName, condition);
}
}
2、定义切面类
定义一个数据源切换的切面类,主要功能包括:
- 使用AspectJ定义切面,拦截QueryDataServiceImpl类中的方法
- 从方法参数中查找名为"dataSource"的参数获取数据源名称
- 在方法执行前切换到指定数据源,执行后清除数据源设置
- 通过DynamicDataSourceContextHolder管理数据源上下文
- 使用@Order(1)确保切面优先级较高
java
package com.dl.xxljobactuator.aspect;
import com.dl.xxljobactuator.datasource.DynamicDataSourceContextHolder;
import lombok.extern.slf4j.Slf4j;
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.springframework.core.annotation.Order;
import org.springframework.stereotype.Component;
/**
* @Description: 数据源切换切面类
*/
@Aspect
@Order(1)
@Component
public class DataSourceAspect {
// 定义切点
@Pointcut("within(com.dl.xxljobactuator.service.QueryDataServiceImpl)")
public void dsPointCut() {
}
// 定时环绕增强
@Around("dsPointCut()")
public Object around(ProceedingJoinPoint point) throws Throwable {
// 从方法参数中获取数据源名称
String dataSourceName = getDataSourceFromArgs(point);
if (dataSourceName != null && !dataSourceName.isEmpty()) {
// 设置数据源
DynamicDataSourceContextHolder.setDataSourceKey(dataSourceName);
}
try {
// 执行方法
return point.proceed();
} finally {
// 执行方法后消除数据源
DynamicDataSourceContextHolder.clearDataSourceKey();
}
}
/**
* 从方法参数中获取数据源名称
*/
private String getDataSourceFromArgs(ProceedingJoinPoint point) {
// 获取方法签名
MethodSignature signature = (MethodSignature) point.getSignature();
// 获取参数名称
String[] parameterNames = signature.getParameterNames();
// 获取参数值
Object[] args = point.getArgs();
if (parameterNames != null && args != null && parameterNames.length == args.length) {
// 遍历参数名称,查找名为"dataSource"的参数
for (int i = 0; i < parameterNames.length; i++) {
if ("dataSource".equals(parameterNames[i]) && args[i] instanceof String) {
return (String) args[i];
}
}
}
return null;
}
}
3、使用案例
我们通过一个GLUE案例来对比两者的区别
java
package com.dl.xxljobactuator.service;
import com.dl.xxljobactuator.datasource.DynamicDataSourceContextHolder;
import com.dl.xxljobactuator.entity.DynamicEntity;
import com.dl.xxljobactuator.mapper.DynamicMapper;
import com.dl.xxljobactuator.util.Utils;
import com.xxl.job.core.context.XxlJobHelper;
import com.xxl.job.core.handler.IJobHandler;
import javax.annotation.Resource;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
/**
* @Description: GLUE(Java)任务Demo
* 传递sql参数、指定数据源、动态实体类
*/
public class DemoGlueJobHandler extends IJobHandler {
@Resource
private QueryDataServiceImpl queryDataService;
@Resource
private DynamicMapper dynamicMapper;
@Override
public void execute() {
List<DynamicEntity> listString = new ArrayList<>();
List<Map<String, Object>> listResults = queryDataService.executeCommonSql("dataTwo",
"select name from test;");
Utils.dataAnalysis(listString, listResults);
listString.forEach(entity -> XxlJobHelper.log(entity.toString()));
XxlJobHelper.log("指定数据源成功!");
List<String> list = queryDataService.executeStringListSql("dataOne",
"SELECT name FROM test WHERE name in ('小明','小华')");
list.forEach(XxlJobHelper::log);
XxlJobHelper.log("切换数据源成功!");
executeSql("dataTwo", "select name from test;");
XxlJobHelper.log("原指定数据源方式测试成功!");
List<String> listNew = queryDataService.executeStringListSql("",
"SELECT name FROM test WHERE name in ('小明','小华')");
listNew.forEach(XxlJobHelper::log);
XxlJobHelper.log("默认数据源测试成功");
XxlJobHelper.log("执行完成!");
}
public void executeSql(String dataSourceName, String sql) {
DynamicDataSourceContextHolder.setDataSourceKey(dataSourceName);
List<DynamicEntity> list = new ArrayList<>();
try {
List<Map<String, Object>> results = dynamicMapper.executeCommonSql(sql);
// 处理数据
Utils.dataAnalysis(list, results);
} catch (Exception e) {
XxlJobHelper.log(e);
// 将推送结果置失败
XxlJobHelper.handleFail();
} finally {
DynamicDataSourceContextHolder.clearDataSourceKey();
}
list.forEach(entity -> XxlJobHelper.log(entity.toString() + entity.getProperty("name")));
}
}
执行结果
ini
2025-08-17 08:59:12 [org.codehaus.groovy.vmplugin.v8.IndyInterface#fromCache]-[321]-[xxl-job, JobThread-3-1755392352063] DynamicEntity{properties={name=name1}}
2025-08-17 08:59:12 [org.codehaus.groovy.vmplugin.v8.IndyInterface#fromCache]-[321]-[xxl-job, JobThread-3-1755392352063] DynamicEntity{properties={name=name2}}
2025-08-17 08:59:12 [org.codehaus.groovy.vmplugin.v8.IndyInterface#fromCache]-[321]-[xxl-job, JobThread-3-1755392352063] 指定数据源成功!
2025-08-17 08:59:12 [sun.reflect.NativeMethodAccessorImpl#invoke0]-[-2]-[xxl-job, JobThread-3-1755392352063] 小明
2025-08-17 08:59:12 [sun.reflect.NativeMethodAccessorImpl#invoke0]-[-2]-[xxl-job, JobThread-3-1755392352063] 小华
2025-08-17 08:59:12 [org.codehaus.groovy.vmplugin.v8.IndyInterface#fromCache]-[321]-[xxl-job, JobThread-3-1755392352063] 切换数据源成功!
2025-08-17 08:59:12 [org.codehaus.groovy.vmplugin.v8.IndyInterface#fromCache]-[321]-[xxl-job, JobThread-3-1755392352063] DynamicEntity{properties={points_name=name1}}name1
2025-08-17 08:59:12 [org.codehaus.groovy.vmplugin.v8.IndyInterface#fromCache]-[321]-[xxl-job, JobThread-3-1755392352063] DynamicEntity{properties={points_name=name2}}name2
2025-08-17 08:59:12 [org.codehaus.groovy.vmplugin.v8.IndyInterface#fromCache]-[321]-[xxl-job, JobThread-3-1755392352063] 原指定数据源方式测试成功!
2025-08-17 08:59:12 [sun.reflect.NativeMethodAccessorImpl#invoke0]-[-2]-[xxl-job, JobThread-3-1755392352063] 小明
2025-08-17 08:59:12 [sun.reflect.NativeMethodAccessorImpl#invoke0]-[-2]-[xxl-job, JobThread-3-1755392352063] 小华
2025-08-17 08:59:12 [org.codehaus.groovy.vmplugin.v8.IndyInterface#fromCache]-[321]-[xxl-job, JobThread-3-1755392352063] 默认数据源测试成功
2025-08-17 08:59:12 [org.codehaus.groovy.vmplugin.v8.IndyInterface#fromCache]-[321]-[xxl-job, JobThread-3-1755392352063] 执行完成!
通过对比我们可以看到通过环绕增强来执行数据源上下文相关操作后只需要调用实现类的方法传入数据源名称和sql参数即可,无需再每次执行数据查询都要再进行数据源上下文的操作。