• SpringAOP--动态数据源


    前言

      通过注解和AOP,实现主从数据源的切换。

    示例

    首先项目布局:

    1:实体类,与数据库表的映射

    @Data
    @Builder
    public class UserBean {
    
        private Long id;
    
        private String name;
    
        private String password;
    }
    View Code

    2:dao类与对应的xml的mapper

    @Repository
    public interface UserDao {
        //新增
        int insertUser(UserBean userBean);
        //查询
        List<UserBean> getAllUser();
    }
    dao接口
    <?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.wht.springdynamicdatasource.dao.UserDao">
        <!-- 创建用户(Create) -->
        <insert id="insertUser" parameterType="com.wht.springdynamicdatasource.entity.UserBean">
            insert into users(name,password) values(#{name},#{password})
        </insert>
    
        <!-- 查询全部用户 -->
        <select id="getAllUser" resultType="com.wht.springdynamicdatasource.entity.UserBean">
            select * from users
        </select>
    </mapper>
    mapper.xml

    3:Service与实现类

    public interface UserService {
    
        int insertUser(UserBean user);
    
        List<UserBean> getAllUser();
    }
    service接口
    @Service
    @Transactional
    // @DataSource("SLAVE_DATASOURCE")
    public class UserServiceImpl implements UserService {
    
        @Autowired
        private UserDao userDao;
    
        @Override
        // @DataSource("MASTER_DATASOURCE")
        public int insertUser(UserBean user) {
            return userDao.insertUser(user);
        }
    
        @Override
        @DataSource("SLAVE_DATASOURCE")
        public List<UserBean> getAllUser() {
            return userDao.getAllUser();
        }
    }
    service实现类

    4:动态数据源配置

    (1)注解:value参数指定数据源类型

    @Target(value = {ElementType.METHOD,ElementType.FIELD,ElementType.PARAMETER,ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface DataSource {
        String value() default "MASTER-DATASOURCE";
    }

    (2)切面:指明要切哪个方法,如何获取该方法上的注解和注解值,从而根据注解配置切换数据源实例;

    @Aspect
    @Order(1)
    @Component
    public class DataSourceAspect {
        private Logger logger = LoggerFactory.getLogger(this.getClass());
    
        @Pointcut(value = "execution(* com.wht.springdynamicdatasource.service.*.*(..))")
        public void dataPointCut(){
        };
    
        @Before("dataPointCut()")
        public void before(JoinPoint joinPoint){
            Class<?> aClass = joinPoint.getTarget().getClass();
            // 获取类级别注解
            DataSource classAnnotation = aClass.getAnnotation(DataSource.class);
            if (classAnnotation != null){
                String dataSource = classAnnotation.value();
                logger.info("this is datasource: "+ dataSource);
                DynamicDataSource.putDataSourceKey(dataSource);
            }else {
                MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature();
                Method method = methodSignature.getMethod();
                DataSource methodAnnotation = method.getAnnotation(DataSource.class);
                if (methodAnnotation != null){
                    String dataSource = methodAnnotation.value();
                    logger.info("this is dataSource: "+ dataSource);
                    DynamicDataSource.putDataSourceKey(dataSource);
                }
            }
        }
        @After("dataPointCut()")
        public void after(JoinPoint joinPoint){
            logger.info("执行完毕!");
            DynamicDataSource.removeDataSourceKey();
        }
    }
    View Code

    (3)自定义数据源路由:通过ThreadLocal保存当前线程要使用的数据源实例名称,determineCurrentLookupKey决定使用哪个数据源实例进行数据库mysql操作

    public class DynamicDataSource  extends AbstractRoutingDataSource {
        private static final Logger log = LoggerFactory.getLogger(DynamicDataSource.class);
        //数据源key值
        private static final ThreadLocal<String> holder = new ThreadLocal<>();
    
        //设置数据源
        public static void putDataSourceKey(String key){
            log.info("切换数据源到{}", key);
            holder.set(key);
        }
        //获取数据源
        public static String getDataSourceKey(){
            return holder.get();
        }
        //移除
        public static void removeDataSourceKey(){
            holder.remove();
        }
        // 该方法决定使用哪个数据源
        @Override
        protected Object determineCurrentLookupKey() {
            Object o = getDataSourceKey();
            log.info("当前数据源为:===", getDataSourceKey());
            return getDataSourceKey();
        }
    }
    View Code

     通过枚举,方便进行多个数据源的名称等管理:

    @Getter
    public enum  DataSourceName {
    
        MASTER("MASTER_DATASOURCE", 1),
        SLAVE("SLAVE_DATASOURCE", 2);
    
        private String name;
        private int index;
    
        DataSourceName(String name, int index) {
            this.name = name;
            this.index = index;
        }
    }
    View Code

    (4)重要:多数据源的生成与配置,以及事务配置。多数据源最终目的是用自定义的数据源取代默认数据源配置,生成自定义的、线程安全的SqlSessionTemplate 

    /**
     * @createtime 2019/9/3
     * @description 根据配置文件生成多个DataSource实例,将其注入自定义DynamicDataSource中,
     * 该类继承了AbstractRoutingDataSource,进行路由管理。由sqlSessionFactory扫描mybatis相关配置,
     * 生成对应工厂实例。
     */
    @Configuration
    @EnableTransactionManagement
    //指定dao的扫描位置和sqlSessionFactoryRef的引用实例。
    @MapperScan(basePackages = {"com.wht.springdynamicdatasource.dao"}, sqlSessionFactoryRef = "sqlSessionFactory")
    public class DataSourceConfig {
        //yml中配置的mapper扫描路径
        @Value("${mybatis.mapperLocations}")
        private String mapperLocations;
    
        //yml中配置的mybatis配置文件位置
        @Value("${mybatis.configLocation}")
        private String configLocation;
    
        /**
         * step1:通过指定配置文件前缀,生成主、从两个DataSource实例
         * @return
         */
        @Bean(name = "masterDataSource")
        @ConfigurationProperties(prefix = "spring.datasource.master")
        public DataSource masterDataSource(){
            DruidDataSource masterDataSource = DataSourceBuilder.create().type(DruidDataSource.class).build();
            masterDataSource.setName("masterDataSource");
            return masterDataSource;
        }
        //slave数据源实例
        @Bean(name = "slaveDataSource")
        @ConfigurationProperties(prefix = "spring.datasource.slave")
        public DataSource slaveDataSource(){
            DruidDataSource slaveDataSource = DataSourceBuilder.create().type(DruidDataSource.class).build();
            slaveDataSource.setName("slaveDataSource");
            return slaveDataSource;
        }
    
        /**
         * step: 通过DynamicDataSource继承Spring提供的数据库路由管理
         * 接口AbstractRoutingDataSource,将多个数据源实例注入其中。
         * @return
         */
        @Bean(name = "dataSource")
        public DataSource dataSource(){
            DynamicDataSource dynamicDataSource = new DynamicDataSource();
            //默认数据源
            dynamicDataSource.setDefaultTargetDataSource(masterDataSource());
            //多数据源
            Map<Object,Object> dataSourceMaps = new HashMap<>(2);
            //构建数据源映射map
            dataSourceMaps.put(DataSourceName.MASTER.getName(), masterDataSource());
            dataSourceMaps.put(DataSourceName.SLAVE.getName(), slaveDataSource());
            dynamicDataSource.setTargetDataSources(dataSourceMaps);
            return dynamicDataSource;
        }
    
        /**
         * step3:通过上一步的自定义多数据源路由Bean,指定mybatis配置文件位置和mapper.xml的位置。
         * 通过SqlSessionFactoryBean,生成SqlSessionFactory;
         * @param dynamicDataSource
         * @return
         * @throws Exception
         */
        @Bean(name = "sqlSessionFactory")
        public SqlSessionFactory sqlSessionFactory(@Qualifier("dataSource") DataSource dynamicDataSource) throws Exception {
            SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean();
            sqlSessionFactoryBean.setDataSource(dynamicDataSource);
            //扫描mapper配置
            sqlSessionFactoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(mapperLocations));
            //扫描mybatis配置文件
            sqlSessionFactoryBean.setConfigLocation(new ClassPathResource(configLocation));
            return sqlSessionFactoryBean.getObject();
        }
    
        /**
         * step4:通过上一步的SqlSessionFactory,生成线程安全的SqlSessionTemplate,
         * 该Bean为mybatis管理数据库连接的最核心类;
         * @param sqlSessionFactory
         * @return
         * @throws Exception
         */
        @Bean
        public SqlSessionTemplate sqlSessionTemplate(@Qualifier("sqlSessionFactory") SqlSessionFactory sqlSessionFactory) throws Exception {
            /* 使用上面配置的Factory,SqlSessionTemplate是线程安全的,是mybatis的管理数据库的最主要的一个类*/
            SqlSessionTemplate template = new SqlSessionTemplate(sqlSessionFactory);
            return template;
        }
    
        /**
         *  事务管理PlatformTransactionManager是Spring提供的事务管理的顶层抽象,
         *  通过手动配置,来代替默认的自动拾物配置,
         *  这样就可以在方法中直接使用注解@Transactional时,指定事务管理的数据源。
         * @param dataSource
         * @return
         */
        @Bean
        public PlatformTransactionManager transactionManager(@Qualifier("dataSource") DataSource dataSource){
            return new DataSourceTransactionManager(dataSource);
        }
    }

    5:启动类上,排除spring的自动数据源配置。 

    @SpringBootApplication(exclude = DataSourceAutoConfiguration.class)
    public class SpringDynamicDatasourceApplication {
    
        public static void main(String[] args) {
            SpringApplication.run(SpringDynamicDatasourceApplication.class, args);
        }
    
    }
    View Code

     6:配置文件:

    mybatis的配置文件

    <?xml version="1.0" encoding="UTF-8"?><!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
    <configuration>
        <!-- 全局参数 -->
        <settings>
            <!-- 使全局的映射器启用或禁用缓存。 -->
            <setting name="cacheEnabled" value="false"/>
            <!-- 全局启用或禁用延迟加载。当禁用时,所有关联对象都会即时加载。 -->
            <setting name="lazyLoadingEnabled" value="true"/>
            <!-- 当启用时,有延迟加载属性的对象在被调用时将会完全加载任意属性。否则,每种属性将会按需要加载。 -->
            <setting name="aggressiveLazyLoading" value="true"/>
            <!-- 是否允许单条sql 返回多个数据集  (取决于驱动的兼容性) default:true -->
            <setting name="multipleResultSetsEnabled" value="true"/>
            <!-- 是否可以使用列的别名 (取决于驱动的兼容性) default:true -->
            <setting name="useColumnLabel" value="true"/>
            <!-- 允许JDBC 生成主键。需要驱动器支持。如果设为了true,这个设置将强制使用被生成的主键,有一些驱动器不兼容不过仍然可以执行。  default:false  -->
            <setting name="useGeneratedKeys" value="true"/>
            <!-- 指定 MyBatis 如何自动映射 数据基表的列 NONE:不隐射 PARTIAL:部分  FULL:全部  -->
            <setting name="autoMappingBehavior" value="PARTIAL"/>
            <!-- 这是默认的执行类型  (SIMPLE: 简单; REUSE: 执行器可能重复使用prepared statements语句;BATCH: 执行器可以重复执行语句和批量更新)  -->
            <setting name="defaultExecutorType" value="SIMPLE"/>
            <!-- 使用驼峰命名法转换字段。 -->
            <setting name="mapUnderscoreToCamelCase" value="true"/>
            <!-- 设置本地缓存范围 session:就会有数据的共享  statement:语句范围 (这样就不会有数据的共享 ) defalut:session -->
            <setting name="localCacheScope" value="SESSION"/>
            <!-- 设置但JDBC类型为空时,某些驱动程序 要指定值,default:OTHER,插入空值时不需要指定类型 -->
            <setting name="jdbcTypeForNull" value="NULL"/>
        </settings>
    </configuration>
    View Code

    spring的启动配置文件

    #logging日志配置
    logging:
      level:
        root: info
        org:
          springframework:
            web: DEBUG
    ##指向mapper的xml文件位置
    mybatis:
      # 配置mapper的扫描,找到所有的mapper.xml映射文件
      mapperLocations: mybatis/mapper/*.xml
      # 加载全局的配置文件
      configLocation: mybatis/mybatis-config.xml
    spring:
      datasource:
        ## master 数据源配置
        master:
          url: jdbc:mysql://localhost:3306/mytest?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&serverTimezone=GMT%2B8
          username: root
          password: ????
          driverClassName: com.mysql.cj.jdbc.Driver
        ## cluster 数据源配置
        slave:
          url: jdbc:mysql://localhost:3306/mytest1?useUnicode=true&characterEncoding=UTF-8&allowMultiQueries=true&serverTimezone=GMT%2B8
          username: root
          password: ????
          driverClassName: com.mysql.cj.jdbc.Driver
    View Code
  • 相关阅读:
    计时器
    画刷
    研究:窗口映射
    文本和字体
    MFC源码实现文件对照表
    动态链接库编程:非MFC DLL
    对话框
    C/C++的Name Mangling
    MFC框架仿真<四>动态创建
    MFC框架仿真<三>R T T I
  • 原文地址:https://www.cnblogs.com/whtblog/p/11495205.html
Copyright © 2020-2023  润新知