Spring Boot利用自定义注解实现多数据源-Spring专区论坛-技术-SpringForAll社区

Spring Boot利用自定义注解实现多数据源

自定义多数据源

SpringBoot利用自定义注解实现多数据源,前置知识:注解、Aop、SpringBoot整合Mybaits

1、搭建工程

创建一个SpringBoot工程,并引入依赖

<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.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>2.3.0</version>
        </dependency>
 
        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <scope>runtime</scope>
        </dependency>
 
        <!-- https://mvnrepository.com/artifact/com.alibaba/druid-spring-boot-starter -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid-spring-boot-starter</artifactId>
            <version>1.2.18</version>
        </dependency>
 
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

2、定义多数据源注解

/**
 * 1、定义多数据源注解
 * @author ss_419
 * TODO 这个注解将来可以加在service类上或者方法上,通过value属性来指定类或者方法应该使用那个数据源
 */
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.TYPE, ElementType.METHOD})
public @interface DataSource {
    String value() default DataSourceType.DEFAULT_DS_NAME;
}

3、创建一个多数据上下文对象

这个类用来存储当前线程所使用的数据源名称

/**
 * TODO 这个类用来存储当前线程所使用的数据源名称
 *
 * @author ss_419
 * @version 1.0
 * @date 2023/5/21 09:21
 */
public class DynamicDataSourceContextHolder {
    private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();
    public static void setDataSourceType(String dataSourceType) {
        CONTEXT_HOLDER.set(dataSourceType);
    }
 
    public static String getDataSourceType() {
        return CONTEXT_HOLDER.get();
    }
    public static void clearDataSourceType() {
        CONTEXT_HOLDER.remove();
    }
 
 
}

4、配置aop

  • @annotation(org.pp.dd.annotation.DataSource) 如果有@DataSource注解就给拦截下来

  • @within(org.pp.dd.annotation.DataSource) 表示类上有@DataSource注解就将类中的方法给拦截下来

/**
 * TODO
 *
 * @author ss_419
 * @version 1.0
 * @date 2023/5/21 09:42
 */
@Component
@Aspect
@Order(11)
public class DataSourceAspect {
 
    /**
     * 定义切点
     *
     * @annotation(org.pp.dd.annotation.DataSource) 如果有@DataSource注解就给拦截下来
     * @within(org.pp.dd.annotation.DataSource) 表示类上有@DataSource注解就将类中的方法给拦截下来
     */
    @Pointcut("@annotation(org.pp.dd.annotation.DataSource) || @within(org.pp.dd.annotation.DataSource)")
    public void pc() {
 
    }
    /**
     * 环绕通知
     *
     * @param pjp
     * @return
     */
    @Around("pc()")
    public Object around(ProceedingJoinPoint pjp) {
        // 获取方法上的有效注解
        DataSource dataSource = getDataSource(pjp);
        if (dataSource != null) {
            // 获取注解中数据源的名称
            String value = dataSource.value();
            DynamicDataSourceContextHolder.setDataSourceType(value);
        }
        try {
            return pjp.proceed();
        } catch (Throwable e) {
            throw new RuntimeException(e);
        } finally {
            DynamicDataSourceContextHolder.clearDataSourceType();
        }
 
    }
 
    private DataSource getDataSource(ProceedingJoinPoint pjp) {
        MethodSignature signature = (MethodSignature) pjp.getSignature();
        // 获取方法上的注解
        DataSource annotation = AnnotationUtils.findAnnotation(signature.getMethod(), DataSource.class);
 
        if (annotation != null) {
            // 说明方法上有注解
            return annotation;
        }
 
 
        return (DataSource) AnnotationUtils.findAnnotation(signature.getDeclaringType(), DataSource.class);
    }
}

5、读取参数DruidProperties

/**
 * TODO 读取数据源
 *
 * @author ss_419
 * @version 1.0
 * @date 2023/5/21 10:20
 */
@ConfigurationProperties(prefix = "spring.datasource")
public class DruidProperties {
    private String type;
    private String driverClassName;
    private Map<String, Map<String ,String>> ds;
    private Integer initialSize;
    private Integer minIdle;
    private Integer maxActive;
    private Integer maxWait;
 
    /**
     * 在这个方法中设置公共属性
     * @param dataSource
     * @return
     */
    public DataSource dataSource(DruidDataSource dataSource){
        dataSource.setInitialSize(initialSize);
        dataSource.setMinIdle(minIdle);
        dataSource.setMaxActive(maxActive);
        dataSource.setMaxWait(maxWait);
        return dataSource;
    }
 
    public String getType() {
        return type;
    }
 
    public void setType(String type) {
        this.type = type;
    }
 
    public String getDriverClassName() {
        return driverClassName;
    }
 
    public void setDriverClassName(String driverClassName) {
        this.driverClassName = driverClassName;
    }
 
    public Map<String, Map<String, String>> getDs() {
        return ds;
    }
 
    public void setDs(Map<String, Map<String, String>> ds) {
        this.ds = ds;
    }
 
    public Integer getInitialSize() {
        return initialSize;
    }
 
    public void setInitialSize(Integer initialSize) {
        this.initialSize = initialSize;
    }
 
    public Integer getMinIdle() {
        return minIdle;
    }
 
    public void setMinIdle(Integer minIdle) {
        this.minIdle = minIdle;
    }
 
    public Integer getMaxActive() {
        return maxActive;
    }
 
    public void setMaxActive(Integer maxActive) {
        this.maxActive = maxActive;
    }
 
    public Integer getMaxWait() {
        return maxWait;
    }
 
    public void setMaxWait(Integer maxWait) {
        this.maxWait = maxWait;
    }
}

6、加载数据源LoadDataSource

/** * TODO 加载数据源 * * @author ss_419 * @version 1.0 * @date 2023/5/21 10:30 */@Component@EnableConfigurationProperties(DruidProperties.class)public class LoadDataSource {     @Autowired    DruidProperties druidProperties;     public Map<String, DataSource> loadAllDataSource() {        Map<String, DataSource> map = new HashMap<>();        Map<String, Map<String, String>> ds = druidProperties.getDs();         try {            Set<String> keySet = ds.keySet();            for (String key : keySet) {                map.put(key, druidProperties.dataSource((DruidDataSource) DruidDataSourceFactory.createDataSource(ds.get(key))));            }        } catch (Exception e) {            throw new RuntimeException(e);        }         return map;    }}

7、定义数据源管理器

当系统需要调用数据源的时候,数据源以key-value存起来,当需要数据源时调用determineCurrentLookupKey()方法来获取数据源。
由于本人实力原因,解答不了大家这里的疑惑。大致功能 通过修改本地线程的值,来实现数据源的切换。

/**
 * TODO 设置数据源
 * 当系统需要调用数据源的时候,数据源以key-value存起来,当需要数据源时调用determineCurrentLookupKey()方法
 * @author ss_419
 * @version 1.0
 * @date 2023/5/21 10:47
 */
@Component
public class DynamicDataSource extends AbstractRoutingDataSource {
 
 
    public DynamicDataSource(LoadDataSource loadDataSource) {
        //1、设置所有的数据源
        Map<String, DataSource> allDs = loadDataSource.loadAllDataSource();
        super.setTargetDataSources(new HashMap<>(allDs));
        //2、设置默认数据源
        super.setDefaultTargetDataSource(allDs.get(DataSourceType.DEFAULT_DS_NAME));
 
        super.afterPropertiesSet();
    }
 
    /**
     * 这个方法用来返回数据源名称,当系统需要获取数据源的时候会自动调用该方法获取数据源名称
     * @return
     */
    @Override
    protected Object determineCurrentLookupKey() {
        return DynamicDataSourceContextHolder.getDataSourceType();
    }
}

定一个用于存储数据库类型的接口,这个接口类似于枚举类:

/**
 * TODO
 *
 * @author ss_419
 * @version 1.0
 * @date 2023/5/21 10:54
 */
public interface DataSourceType {
    String DEFAULT_DS_NAME = "master";
    String DS_SESSION_KEY = "ds_session_key";
}

8、测试

创建User实体:

/**
 * TODO
 *
 * @author ss_419
 * @version 1.0
 * @date 2023/5/21 11:15
 */
public class User {
    private Integer id;
    private String username;
    private String password;
 
    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                ", password='" + password + '\'' +
                '}';
    }
 
    public Integer getId() {
        return id;
    }
 
    public void setId(Integer id) {
        this.id = id;
    }
 
    public String getUsername() {
        return username;
    }
 
    public void setUsername(String username) {
        this.username = username;
    }
 
    public String getPassword() {
        return password;
    }
 
    public void setPassword(String password) {
        this.password = password;
    }
}

创建UserService:

@Service
// 在类上加注解的效果,会使该类的所有方法都切入到新的数据源中
//@DataSource
public class UserService {
 
    @Autowired
    UserMapper userMapper;
    // 在方法上加注解的效果,只会让指定的方法切入到另一个数据源中
    //@DataSource("slave")
    public List<User> findUsers(){
        return userMapper.findAllUsers();
    }
}

创建UserMapper:

@Mapper
public interface UserMapper {
 
 
    @Select("SELECT * FROM user")
    List<User> findAllUsers();
}

测试类:

@SpringBootTest
class DynamicDatasourcesApplicationTests {
 
    @Autowired
    UserService userService;
 
    @Test
    void contextLoads() {
 
        List<User> users = userService.findUsers();
        users.stream()
                .forEach(user -> System.out.println(user));
    }
 
}

默认选择主库的数据源:

d2b5ca33bd20231029094456

 

执行结果如下:

d2b5ca33bd20231029094505

 

在Service上加上注解,指定数据源为从库:

d2b5ca33bd20231029094525

 


执行结果如下:

d2b5ca33bd20231029094515

作者:JavaCoderPan,来源:https://www.cnblogs.com/atwood-pan/p/17418673.html

请登录后发表评论

    没有回复内容