关于在spring中使用多线程操作数据库时,遇到的mapper为null的问题

本文介绍了解决在线程中使用@Resource或@Autowired注解无法注入Spring Bean的问题。提供了两种解决方案:通过构造函数传递所需的Bean,或使用ApplicationContext.getBean()方法静态获取Bean。并提供了一个封装类SpringApplicationContextHolder,用于在线程中重新获取Bean。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

在线程中使用@Resource或者@Autowired注入全部为NULL,
原来是Spring不能在线程中注入。

有两种方法可以参考,以下方法本人在实际项目中都已使用到过

  1. 要的Bean作为线程的的构造函数的参数传入
  2. 使用ApplicationContext.getBean方法来静态的获取Bean

构造方法的不做细讲,这次讲下第二种,在线程中不能注入那就重新获取bean吧

这里需要有一个获取bean的封装类,作为分享也是给自己做个记录

public class SpringApplicationContextHolder implements ApplicationContextAware {

    private static ApplicationContext context;

    @Override
    public void setApplicationContext(ApplicationContext context) {
        SpringApplicationContextHolder.context = context;
    }

    public static ApplicationContext getContext() {
        return context;
    }

    public static <T> T getBean(String name) {
        return (T) context.getBean(name);
    }

    public static <T> T getBean(String name, ApplicationContext applicationContext) {
        return (T) applicationContext.getBean(name);
    }

    public static <T> T getBean(Class<T> clazz) {
        Map<String, T> beanMaps = context.getBeansOfType(clazz);
        if (beanMaps != null && !beanMaps.isEmpty()) {
            return beanMaps.values().iterator().next();
        } else {
            return null;
        }
    }

    public static <T> T getBean(Class<T> clazz, ApplicationContext applicationContext) {
        Map<String, T> beanMaps = applicationContext.getBeansOfType(clazz);
        if (beanMaps != null && !beanMaps.isEmpty()) {
            return beanMaps.values().iterator().next();
        } else {
            return null;
        }
    }
}

如何使用?

AbcMapper abcMapper = SpringApplicationContextHolder.getBean(AbcMapper.class);

 

另外网上还有一种方法,本人未实践,这里就放个链接https://www.cnblogs.com/bencakes/p/6139542.html吧,实践过有效的朋友可以评论处告知下

<think>好的,用户的问题是关于如何通过Java注解实现Mapper类的多数据源动态切换。我需要结合之前提供的引用内容来解答这个问题。 首先,用户提到了使用注解,这让我想到引用[3]中的@DS注解示例。那里展示了在Service层通过@DS注解切换数据源的方法,用户可能需要类似的实现,但针对Mapper层。需要确认的是,动态数据源框架是否支持在Mapper层直接使用注解,比如MyBatis的Mapper接口。 接下来,引用[2]提到DynamicDataSource类继承AbstractRoutingDataSource并重写determineCurrentLookupKey方法,这说明动态数据源的核心是通过这个类来决定当前线程使用哪个数据源。而数据源的切换通常依赖于一个上下文持有者,比如DataSourceContextHolder,用来保存当前线程的数据源标识。 用户的需求是Mapper类的多数据源切换,所以需要考虑如何在Mapper接口的方法上添加注解,并在执行数据库操作动态切换。可能的步骤包括: 1. 配置多个数据源,并注册为Spring的Bean。 2. 创建动态数据源类,继承AbstractRoutingDataSource,并重写determineCurrentLookupKey方法,从上下文获取数据源标识。 3. 创建自定义注解(如@DS),用于在Mapper的方法上指定数据源。 4. 使用AOP拦截Mapper方法,根据注解的值设置数据源上下文。 引用[3]中的@DS注解已经存在,可能需要查看是否支持直接用在Mapper上,或者是否需要调整AOP的切面范围。例如,在Service层,AOP通常拦截服务方法,而要在Mapper层拦截,可能需要将切面定义在Mapper接口的方法上。 另外,需要考虑事务管理的问题。动态切换数据源,要确保在同一个事务中不会意外切换数据源,否则可能导致事务不一致。不过用户的问题可能暂不需要深入讨论事务,先专注于动态切换的实现。 最后,给出一个示例代码,展示如何在Mapper接口的方法上使用@DS注解,并说明配置的关键点,比如启用动态数据源、AOP配置等。需要确保代码示例正确,并参考之前引用中的实现方式,特别是DynamicDataSource和DataSourceContextHolder的使用。 总结步骤: - 配置数据源 - 动态数据源路由 - 自定义注解 - AOP实现注解解析和数据源切换 - Mapper使用注解 需要检查是否有遗漏的部分,比如是否需要手动设置数据源,或者在Spring Boot中的自动配置如何处理。此外,是否需要引入特定的依赖库,比如dynamic-datasource-spring-boot-starter,这可能会简化配置。</think>### 通过Java注解实现Mapper类多数据源动态切换的示例 #### 1. 核心实现原理 动态切换数据源的核心是通过**线程级变量**保存当前数据源标识,结合AOP拦截注解,在Mapper方法执行前动态设置数据源。实现步骤如下: 1. **配置多数据源**:定义主/从数据源的Bean 2. **创建动态数据源路由类**:继承`AbstractRoutingDataSource`,重写路由逻辑[^2] 3. **自定义注解`@DS`**:标注在Mapper方法或类上指定数据源 4. **AOP切面拦截**:解析`@DS`注解并设置当前线程数据源标识 --- #### 2. 完整代码示例 ##### (1) 数据源配置类 ```java @Configuration public class DataSourceConfig { // 主数据源 @Bean @ConfigurationProperties(prefix = "spring.datasource.master") public DataSource masterDataSource() { return DataSourceBuilder.create().build(); } // 从数据源 @Bean @ConfigurationProperties(prefix = "spring.datasource.slave") public DataSource slaveDataSource() { return DataSourceBuilder.create().build(); } // 动态数据源路由 @Bean public DataSource dynamicDataSource( @Qualifier("masterDataSource") DataSource master, @Qualifier("slaveDataSource") DataSource slave) { Map<Object, Object> targetDataSources = new HashMap<>(); targetDataSources.put("master", master); targetDataSources.put("slave", slave); DynamicDataSource routingDataSource = new DynamicDataSource(); routingDataSource.setDefaultTargetDataSource(master); routingDataSource.setTargetDataSources(targetDataSources); return routingDataSource; } } ``` ##### (2) 动态数据源路由实现 ```java public class DynamicDataSource extends AbstractRoutingDataSource { @Override protected Object determineCurrentLookupKey() { return DataSourceContextHolder.getDataSourceKey(); // 从线程变量获取标识 } } ``` ##### (3) 数据源上下文持有者 ```java public class DataSourceContextHolder { private static final ThreadLocal<String> CONTEXT = new ThreadLocal<>(); public static void setDataSourceKey(String key) { CONTEXT.set(key); } public static String getDataSourceKey() { return CONTEXT.get(); } public static void clear() { CONTEXT.remove(); } } ``` ##### (4) 自定义注解`@DS` ```java @Retention(RetentionPolicy.RUNTIME) @Target({ElementType.METHOD, ElementType.TYPE}) public @interface DS { String value() default "master"; // 默认主库 } ``` ##### (5) AOP切面实现 ```java @Aspect @Component public class DataSourceAspect { // 拦截所有Mapper接口的方法 @Pointcut("execution(* com.example.mapper.*.*(..))") public void mapperPointcut() {} @Before("mapperPointcut()") public void beforeMethod(JoinPoint joinPoint) { MethodSignature signature = (MethodSignature) joinPoint.getSignature(); Method method = signature.getMethod(); DS dsAnnotation = method.getAnnotation(DS.class); if (dsAnnotation == null) { dsAnnotation = joinPoint.getTarget().getClass().getAnnotation(DS.class); } if (dsAnnotation != null) { DataSourceContextHolder.setDataSourceKey(dsAnnotation.value()); } } @After("mapperPointcut()") public void afterMethod() { DataSourceContextHolder.clear(); // 清理线程变量 } } ``` ##### (6) Mapper接口使用示例 ```java public interface UserMapper { @DS("master") // 写入操作使用主库 void insertUser(User user); @DS("slave") // 查询操作使用从库 User selectById(Long id); } ``` --- #### 3. 关键配置说明 | 配置项 | 作用 | |----------------------|--------------------------------------------------------------------| | `dynamicDataSource` | 组合多个数据源,作为Spring的主数据源 | | `@DS`注解优先级 | 方法级注解 > 类级注解 > 默认值 | | AOP清理线程变量 | 必须清除线程变量,避免后续操作使用错误数据源 | --- #### 4. 性能优化建议 1. **连接池配置**:为每个数据源单独配置连接池参数(如Druid) 2. **注解缓存**:在AOP中缓存方法注解解析结果,减少反射调用 3. **事务管理**:在`@Transactional`中需保持数据源一致性,建议将注解标注在Service层 ---
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值