zoukankan      html  css  js  c++  java
  • AOP-MyBatis 多数据源

    MyBatis 多数据源

    一、AOP多数据源

    pom.xml

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
    
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
    
    
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-aop</artifactId>
            <version>5.1.5.RELEASE</version>
        </dependency>
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
        </dependency>
        <dependency>
            <groupId>org.aspectj</groupId>
            <artifactId>aspectjweaver</artifactId>
            <version>1.9.2</version>
        </dependency>
    

    两个数据源,所以要指定相关数据库的名称,其中主数据源为primary,次数据源为secondary如下:

    #配置主数据库
    spring.datasource.primary.jdbc-url=jdbc:mysql://localhost:3306/testdatasource1?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC&useSSL=false
    spring.datasource.primary.username=root
    spring.datasource.primary.password=root
    spring.datasource.primary.driver-class-name=com.mysql.cj.jdbc.Driver
    
    ##配置次数据库
    spring.datasource.secondary.jdbc-url=jdbc:mysql://localhost:3306/testdatasource2?useUnicode=true&characterEncoding=UTF-8&serverTimezone=UTC&useSSL=false
    spring.datasource.secondary.username=root
    spring.datasource.secondary.password=root
    spring.datasource.secondary.driver-class-name=com.mysql.cj.jdbc.Driver
    
    
    spring.http.encoding.charset=UTF-8
    spring.http.encoding.enabled=true
    spring.http.encoding.force=true
    

    Springboot2.0 在配置数据库连接的时候需要使用jdbc-url,如果只使用url的话会报 jdbcUrl is required with driverClassName.错误。

    新建一个配置文件,DynamicDataSourceConfig 用来配置我们相关的bean,代码如下

    @Configuration
    @MapperScan(basePackages = "com.mzd.multipledatasources.mapper", sqlSessionFactoryRef = "SqlSessionFactory") //basePackages 我们接口文件的地址
    public class DynamicDataSourceConfig {
    
        // 将这个对象放入Spring容器中
        @Bean(name = "PrimaryDataSource")
        // 表示这个数据源是默认数据源
        @Primary
        // 读取application.properties中的配置参数映射成为一个对象
        // prefix表示参数的前缀
        @ConfigurationProperties(prefix = "spring.datasource.primary")
        public DataSource getDateSource1() {
            return DataSourceBuilder.create().build();
        }
    
    
        @Bean(name = "SecondaryDataSource")
        @ConfigurationProperties(prefix = "spring.datasource.secondary")
        public DataSource getDateSource2() {
            return DataSourceBuilder.create().build();
        }
    
        
        @Bean(name = "dynamicDataSource")
        public DynamicDataSource DataSource(@Qualifier("PrimaryDataSource") DataSource primaryDataSource,
                                            @Qualifier("SecondaryDataSource") DataSource secondaryDataSource) {
            
            //这个地方是比较核心的targetDataSource 集合是我们数据库和名字之间的映射
            Map<Object, Object> targetDataSource = new HashMap<>();
            targetDataSource.put(DataSourceType.DataBaseType.Primary, primaryDataSource);
            targetDataSource.put(DataSourceType.DataBaseType.Secondary, secondaryDataSource);
            DynamicDataSource dataSource = new DynamicDataSource(); 
            dataSource.setTargetDataSources(targetDataSource);
            dataSource.setDefaultTargetDataSource(primaryDataSource);//设置默认对象
            return dataSource;
        }
        
        
        @Bean(name = "SqlSessionFactory")
        public SqlSessionFactory SqlSessionFactory(@Qualifier("dynamicDataSource") DataSource dynamicDataSource)
                throws Exception {
            SqlSessionFactoryBean bean = new SqlSessionFactoryBean();
            bean.setDataSource(dynamicDataSource);
            bean.setMapperLocations(
                    new PathMatchingResourcePatternResolver().getResources("classpath*:mapping/*/*.xml"));//设置我们的xml文件路径
            return bean.getObject();
        }
    }
    

    而在这所有的配置中,最核心的地方就是DynamicDataSource这个类了,DynamicDataSource是我们自定义的动态切换数据源的类,该类继承了AbstractRoutingDataSource 类并重写了它的determineCurrentLookupKey()方法。

    AbstractRoutingDataSource 类内部维护了一个名为targetDataSources的Map,并提供的setter方法用于设置数据源关键字与数据源的关系,实现类被要求实现其determineCurrentLookupKey()方法,由此方法的返回值决定具体从哪个数据源中获取连接。同时AbstractRoutingDataSource类提供了程序运行时动态切换数据源的方法,在dao类或方法上标注需要访问数据源的关键字,路由到指定数据源,获取连接。

    DynamicDataSource代码如下:

    public class DynamicDataSource extends AbstractRoutingDataSource {
    
        @Override
        protected Object determineCurrentLookupKey() {
            DataSourceType.DataBaseType dataBaseType = DataSourceType.getDataBaseType();
            return dataBaseType;
        }
    
    }
    

    DataSourceType类的代码如下:

    public class DataSourceType {
    
        //内部枚举类,用于选择特定的数据类型
        public enum DataBaseType {
            Primary, Secondary
        }
    
        // 使用ThreadLocal保证线程安全
        private static final ThreadLocal<DataBaseType> TYPE = new ThreadLocal<DataBaseType>();
    
        // 往当前线程里设置数据源类型
        public static void setDataBaseType(DataBaseType dataBaseType) {
            if (dataBaseType == null) {
                throw new NullPointerException();
            }
            TYPE.set(dataBaseType);
        }
    
        // 获取数据源类型
        public static DataBaseType getDataBaseType() {
            DataBaseType dataBaseType = TYPE.get() == null ? DataBaseType.Primary : TYPE.get();
            return dataBaseType;
        }
    
        // 清空数据类型
        public static void clearDataBaseType() {
            TYPE.remove();
        }
    
    }
    

    接下来编写我们相关的Mapper和xml文件,代码如下:

    @Component
    @Mapper
    public interface PrimaryUserMapper {
        List<User> findAll();
    }
    
    
    @Component
    @Mapper
    public interface SecondaryUserMapper {
        List<User> findAll();
    }
    
    
    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.jdkcb.mybatisstuday.mapper.one.PrimaryUserMapper">
    
        <select id="findAll" resultType="com.jdkcb.mybatisstuday.pojo.User">
                    select * from sys_user;
        </select>
    
    </mapper>
    
    
    <?xml version="1.0" encoding="UTF-8" ?>
    <!DOCTYPE mapper
            PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
            "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    <mapper namespace="com.jdkcb.mybatisstuday.mapper.two.SecondaryUserMapper">
    
        <select id="findAll" resultType="com.jdkcb.mybatisstuday.pojo.User">
                    select * from sys_user2;
        </select>
    
    
    </mapper>
    

    相关接口文件编写好之后,就可以编写我们的aop代码了:

    @Aspect
    @Component
    public class DataSourceAop {
        //在primary方法前执行
        @Before("execution(* com.jdkcb.mybatisstuday.controller.UserController.primary(..))")
        public void setDataSource2test01() {
            System.err.println("Primary业务");
            DataSourceType.setDataBaseType(DataSourceType.DataBaseType.Primary);
        }
    
        //在secondary方法前执行
        @Before("execution(* com.jdkcb.mybatisstuday.controller.UserController.secondary(..))")
        public void setDataSource2test02() {
            System.err.println("Secondary业务");
            DataSourceType.setDataBaseType(DataSourceType.DataBaseType.Secondary);
        }
    }
    

    编写我们的测试 UserController: 代码如下:

    @RestController
    public class UserController {
    
        @Autowired
        private PrimaryUserMapper primaryUserMapper;
        @Autowired
        private SecondaryUserMapper secondaryUserMapper;
    
    
        @RequestMapping("primary")
        public Object primary(){
            List<User> list = primaryUserMapper.findAll();
            return list;
        }
        @RequestMapping("secondary")
        public Object secondary(){
            List<User> list = secondaryUserMapper.findAll();
            return list;
        }
    
    }
    
    1. 测试:
      启动项目,在浏览器中分别输入http://127.0.0.1:8080/primary 和http://127.0.0.1:8080/primary ,结果如下:
    [{"user_id":1,"user_name":"张三","user_age":25}]
    
    [{"user_id":1,"user_name":"李四","user_age":30}]
    

    等等等等,啧啧啧,我看你这不行啊,还不够灵活,几个菜啊,喝成这样,这就算灵活了?

    那肯定不能的,aop也有aop的好处,比如两个包下的代码分别用两个不同的数据源,就可以直接用aop表达式就可以完成了,但是,如果想本例中方法级别的拦截,就显得优点不太灵活了,这个适合就需要我们的注解上场了。

    二、配合注解实现

    首先自定义我们的注解 @DataSource

    /**
     * 切换数据注解 可以用于类或者方法级别 方法级别优先级 > 类级别
     */
    @Target({ElementType.METHOD, ElementType.TYPE, ElementType.PARAMETER})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface DataSource {
        String value() default "primary"; //该值即key值,默认使用默认数据库
    }
    

    通过使用aop拦截,获取注解的属性value的值。如果value的值并没有在我们DataBaseType里面,则使用我们默认的数据源,如果有的话,则切换为相应的数据源。

    @Aspect
    @Component
    public class DynamicDataSourceAspect {
        private static final Logger logger = LoggerFactory.getLogger(DynamicDataSourceAspect.class);
    
        @Before("@annotation(dataSource)")//拦截我们的注解
        public void changeDataSource(JoinPoint point, DataSource dataSource) throws Throwable {
            String value = dataSource.value();
            if (value.equals("primary")){
                DataSourceType.setDataBaseType(DataSourceType.DataBaseType.Primary);
            }else if (value.equals("secondary")){
                DataSourceType.setDataBaseType(DataSourceType.DataBaseType.Secondary);
            }else {
                DataSourceType.setDataBaseType(DataSourceType.DataBaseType.Primary);//默认使用主数据库
            }
    
        }
    
        @After("@annotation(dataSource)") //清除数据源的配置
        public void restoreDataSource(JoinPoint point, DataSource dataSource) {
            DataSourceType.clearDataBaseType();
    
    
        }
    }
    

    7 .测试:
    修改我们的mapper,添加我们的自定义的@DataSouse注解,并注解掉我们DataSourceAop类里面的内容。如下:

    @Component
    @Mapper
    public interface PrimaryUserMapper {
    
        @DataSource
        List<User> findAll();
    }
    
    @Component
    @Mapper
    public interface SecondaryUserMapper {
    
        @DataSource("secondary")//指定数据源为:secondary
        List<User> findAll();
    }
    

    启动项目,在浏览器中分别输入http://127.0.0.1:8080/primary 和http://127.0.0.1:8080/primary ,结果如下:

    [{"user_id":1,"user_name":"张三","user_age":25}]
    
    [{"user_id":1,"user_name":"李四","user_age":30}]
    

    转载自:https://blog.csdn.net/weixin_45395031/article/details/109162436

  • 相关阅读:
    命名规范
    操作文件和目录
    使用本地shadow socks代理
    发送邮件
    sql参数化
    定义常量
    获取嵌套字典值的方法
    通过字符串调用函数
    用字典优化过长的if 语句
    操作文件和目录
  • 原文地址:https://www.cnblogs.com/k-class/p/13963178.html
Copyright © 2011-2022 走看看