• spring boot整合mybatis(数据源切换详细分析)


    自己根绝视频知识整理,项目地址:https://gitee.com/zhangjunqing/spring-boot/tree/master/springboot-mybatis-sample

    1  准备知识

            1.1  spring的@Import标签可以将外部类引入到容器生成bean对象,spring 4.2后支持普通java类的引入

            1.2  spring可以通过实现ImportBeanDefinitionRegistrar接口的registerBeanDefinitions方法来注册bean

            1.3  spring可以通过实现EnvironmentAware接口的setEnvironment方法来获取配置信息

            1.4 spring的数据源主要存在 AbstractRoutingDataSource当中,并且可以通过实现此抽象类来动态切换

           

            解决思路:

                (一):通过@import实现数据源动态切换的类引入到spring 容器当中  

                (二):通过实现EnvironmentAware接口来读取配置文件中配置的数据源配置信息,然后生成默认数据源和其他数据源

                (三):通过实现ImportBeanDefinitionRegistrar接口,将生成的默认数据源和其他数据源注入到spring容器当中

                (四):spring容器启动后,默认数据源对象和其他数据源会注入到你实现的AbstractRoutingDataSource的类的对象中

                (五):通过切面技术通过你定义的自定义注解的含义,通过AbstractRoutingDataSource实现类,来进行数据源的动态选择

    2 整合mybatis

          2.1  启动类,通过@Import引入类,生成对象。

    package com.springboot;
    import org.mybatis.spring.annotation.MapperScan;
    import org.springframework.boot.SpringApplication;
    import org.springframework.boot.autoconfigure.SpringBootApplication;
    import org.springframework.context.annotation.Import;
    import com.springboot.dynamicdatasource.DynamicDataSourceRegister;
    
    //@EnableTransactionManagement
    @MapperScan("com.springboot.mapper")//mybatis的映射接口
    @Import(value = {DynamicDataSourceRegister.class})
    //@Import({ImportTest.class})
    @SpringBootApplication  //注意此类的包地址应该是最大的包,这样他就可以自动扫描其下包的所有类.否则也可以(scanBasePackages={"com.springboot.controller"})指定要扫描的包
    public class App {
        public static void main(String[] args) throws Exception {
            SpringApplication.run(App.class, args);
        }
    }

       2.2 查看DynamicDataSourceRegister类,并且实现EnvironmentAware和ImportBeanDefinitionRegistrar接口

        下面代码注意点:

                         由于实现了 EnvironmentAware  接口。setEnvironment方法会在初始化调用读取数据源配置信息,读取完后根据配置信息生成数据源

                         由于实现了ImportBeanDefinitionRegistrar接口。registerBeanDefinitions方法会调用,在此方法中可以将生成的数据源注入到容器当中(特别注意,注入defaultTargetDataSource                                  和targetDataSources变量名必须是这个,后面会解释原因

                  

    package com.springboot.dynamicdatasource;
    
    import java.util.HashMap;
    import java.util.Map;
    
    import javax.sql.DataSource;
    
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.beans.MutablePropertyValues;
    import org.springframework.beans.factory.support.BeanDefinitionRegistry;
    import org.springframework.beans.factory.support.GenericBeanDefinition;
    import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder;
    import org.springframework.boot.bind.RelaxedPropertyResolver;
    import org.springframework.context.EnvironmentAware;
    import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
    import org.springframework.core.env.Environment;
    import org.springframework.core.type.AnnotationMetadata;
    
    public class DynamicDataSourceRegister implements
            ImportBeanDefinitionRegistrar, EnvironmentAware {
        private static final Logger logger = LoggerFactory.getLogger(DynamicDataSourceRegister.class);
        
        
        
        // 如配置文件中未指定数据源类型,使用该默认值
        private static final Object DATASOURCE_TYPE_DEFAULT = "org.apache.tomcat.jdbc.pool.DataSource";
        
        
        // 数据源
        private DataSource defaultDataSource;
        
        private Map<String, DataSource> customDataSources = new HashMap<String, DataSource>();
        
        /**
         * 用于动态注入数据
         */
        @Override
        public void registerBeanDefinitions(
                AnnotationMetadata importingClassMetadata,
                BeanDefinitionRegistry registry) {
            Map<Object, Object> targetDataSources = new HashMap<Object, Object>();
            // 将主数据源添加到更多数据源中
            targetDataSources.put("dataSource", defaultDataSource);
            DynamicDataSourceContextHolder.dataSourceIds.add("dataSource");
            // 添加更多数据源
            targetDataSources.putAll(customDataSources);
            for (String key : customDataSources.keySet()) {
                DynamicDataSourceContextHolder.dataSourceIds.add(key);
            }
            
            // 创建DynamicDataSource
            GenericBeanDefinition beanDefinition = new GenericBeanDefinition();
            beanDefinition.setBeanClass(DynamicDataSource.class);
            beanDefinition.setSynthetic(true);
            MutablePropertyValues mpv = beanDefinition.getPropertyValues();
            mpv.addPropertyValue("defaultTargetDataSource", defaultDataSource);
            mpv.addPropertyValue("targetDataSources", targetDataSources);
            registry.registerBeanDefinition("dataSource", beanDefinition);
            
            logger.info("Dynamic DataSource Registry");
        }
        
        
        /**
         * 加载多数据源配置
         */
        @Override
        public void setEnvironment(Environment env) {
            initDefaultDataSource(env);
            initCustomDataSources(env);
        }
        
        /**
         * 创建DataSource
         *
         * @param type
         * @param driverClassName
         * @param url
         * @param username
         * @param password
         * @return
         * @author SHANHY
         * @create 2016年1月24日
         */
        @SuppressWarnings("unchecked")
        public DataSource buildDataSource(Map<String, Object> dsMap) {
            try {
                Object type = dsMap.get("type");
                if (type == null)
                    type = DATASOURCE_TYPE_DEFAULT;// 默认DataSource
                    
                Class<? extends DataSource> dataSourceType;
                dataSourceType = (Class<? extends DataSource>)Class.forName((String)type);
                
                String driverClassName = dsMap.get("driver-class-name").toString();
                String url = dsMap.get("url").toString();
                String username = dsMap.get("username").toString();
                String password = dsMap.get("password").toString();
                
                DataSourceBuilder factory = DataSourceBuilder.create()
                        .driverClassName(driverClassName)
                        .url(url)
                        .username(username)
                        .password(password)
                        .type(dataSourceType);
                return factory.build();
            }
            catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
            return null;
        }
        
        
        
        /**
         * 初始化主数据源
         *
         * @author SHANHY
         * @create 2016年1月24日
         */
        private void initDefaultDataSource(Environment env) {
            // 读取主数据源
            RelaxedPropertyResolver propertyResolver = new RelaxedPropertyResolver(
                    env, "spring.datasource.");
            Map<String, Object> dsMap = new HashMap<String, Object>();
            dsMap.put("type", propertyResolver.getProperty("type"));
            dsMap.put("driver-class-name",
                    propertyResolver.getProperty("driver-class-name"));
            dsMap.put("url", propertyResolver.getProperty("url"));
            dsMap.put("username", propertyResolver.getProperty("username"));
            dsMap.put("password", propertyResolver.getProperty("password"));
            
            defaultDataSource = buildDataSource(dsMap);
            
        }
        
        
        /**
         * 初始化更多数据源
         *
         * @author SHANHY
         * @create 2016年1月24日
         */
        private void initCustomDataSources(Environment env) {
            // 读取配置文件获取更多数据源,也可以通过defaultDataSource读取数据库获取更多数据源
            RelaxedPropertyResolver propertyResolver = new RelaxedPropertyResolver(
                    env, "custom.datasource.");
            String dsPrefixs = propertyResolver.getProperty("names");
            for (String dsPrefix : dsPrefixs.split(",")) {// 多个数据源
                Map<String, Object> dsMap = propertyResolver.getSubProperties(dsPrefix
                        + ".");
                DataSource ds = buildDataSource(dsMap);
                customDataSources.put(dsPrefix, ds);
            }
        }
        
    }

       

       2.3 定义注解,主要用来标识使用那个数据源

               

    package com.springboot.dynamicdatasource;
    
    import java.lang.annotation.Documented;
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    @Target({ElementType.METHOD, ElementType.TYPE})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface TargetDataSource1 {
        String name();
    }

        2.4 在需要数据源的service方法中增加TargetDataSource1 上面注解来标识需要那个数据源

       

    @Service
    @Transactional(propagation=Propagation.REQUIRED)
    public class UserServiceImpl implements UserService{
        
        @Resource
        private UserMapper userMapper;
    
        @Override
        public void insert(User user) {
            userMapper.insert(user);
        }
        
        @Override
        @TargetDataSource1(name="ds1")
        public void insertdb1(User user) {
            userMapper.insert(user);
        }
        
        @Override
        @TargetDataSource1(name="ds2")
        public void insertdb2(User user) {
            userMapper.insert(user);
        }
    
    }

        2.5 DynamicDataSourceContextHolder 用来存储当前线程所需要的数据源名称        

    package com.springboot.dynamicdatasource;
    
    import java.util.ArrayList;
    import java.util.List;
    
    public class DynamicDataSourceContextHolder {
        
        /**
         * 线程安全,用来存储本次执行需要数据源的名称
         */
        private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();
        
        public static List<String> dataSourceIds = new ArrayList<String>();
        
        public static void setDataSourceType(String dataSourceType) {
            contextHolder.set(dataSourceType);
        }
        
        public static String getDataSourceType() {
            return contextHolder.get();
        }
        
        public static void clearDataSourceType() {
            contextHolder.remove();
        }
        
        /**
         * 判断指定DataSrouce当前是否存在
         *
         * @param dataSourceId
         * @return
         * @author SHANHY
         * @create  2016年1月24日
         */
        public static boolean containsDataSource(String dataSourceId) {
            return dataSourceIds.contains(dataSourceId);
        }
    }

      2.6 定义切面,读取service上的注解,然后获取注解指定的数据源名称,将其存储到DynamicDataSourceContextHolder中,方法执行完之后再清空此数据。

           注意:必须指定执行顺序,防止出现问题

    package com.springboot.dynamicdatasource;
    
    import org.aspectj.lang.JoinPoint;
    import org.aspectj.lang.annotation.After;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Before;
    import org.slf4j.Logger;
    import org.slf4j.LoggerFactory;
    import org.springframework.core.annotation.Order;
    import org.springframework.stereotype.Component;
    
    @Aspect
    //保证该AOP在@Transactional之前执行
    @Order(-1)
    @Component
    public class DynamicDataSourceAspect {
        private static final Logger logger = LoggerFactory.getLogger(DynamicDataSourceAspect.class);
        
        /** 
         * @Description 在方法执行之前执行  @annotation(ds) 会拦截有ds这个注解的方法即有 TargetDataSource这个注解的
         * @param @param point
         * @param @param ds
         * @param @throws Throwable 参数 
         * @return void 返回类型  
         * @throws 
         */
        @Before("@annotation(targetDataSource)")
        public void changeDataSource(JoinPoint point, TargetDataSource1 targetDataSource)
                throws Throwable {
            String dsId = targetDataSource.name();
            if (!DynamicDataSourceContextHolder.containsDataSource(dsId)) {
                logger.error("数据源[{}]不存在,使用默认数据源 > {}",
                        targetDataSource.name(),
                        point.getSignature());
            }
            else {
                logger.debug("Use DataSource : {} > {}",
                        targetDataSource.name(),
                        point.getSignature());
                DynamicDataSourceContextHolder.setDataSourceType(targetDataSource.name());
            }
        }
        
        /** 
         * @Description 在方法执行之后执行  @annotation(ds) 会拦截有ds这个注解的方法即有 TargetDataSource这个注解的 
         * @param @param point
         * @param @param ds 参数 
         * @return void 返回类型  
         * @throws 
         */
        
        @After("@annotation(targetDataSource)")
        public void restoreDataSource(JoinPoint point, TargetDataSource1 targetDataSource) {
            logger.debug("Revert DataSource : {} > {}",
                    targetDataSource.name(),
                    point.getSignature());
            DynamicDataSourceContextHolder.clearDataSourceType();
        }
    }

       

        2.7 通过实现DynamicDataSource抽象类,并且根据DynamicDataSourceContextHolder存储的数据源名称来进行数据源的选取。

        特别注意:AbstractRoutingDataSource 是用来做数据源管理用的,其中 targetDataSources,defaultTargetDataSource变量,其实就是上面DynamicDataSourceRegister类中注入到spring容器中的数据源,故需要DynamicDataSourceRegister注入这两个对象时名称必须和这两个变量一致

    package com.springboot.dynamicdatasource;
    
    import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
    
    /**
     * 用于动态数据源的获取
     * @author 76524
     *
     */
    public class DynamicDataSource extends AbstractRoutingDataSource {
        
        @Override
        protected Object determineCurrentLookupKey() {
            System.out.println(DynamicDataSourceContextHolder.getDataSourceType());
            // TODO Auto-generated method stub
            return DynamicDataSourceContextHolder.getDataSourceType();
        }
        
    }

            

  • 相关阅读:
    高内聚 低耦合
    关系型数据库-三范式
    Excel 批量重命名照片
    完整性约束
    testCompile failed: multiple points
    Java日志体系
    Mac 修改HostName
    mac 配置/etc/profile重启后不生效
    mac命令行配置
    logback删除日志
  • 原文地址:https://www.cnblogs.com/zhangjunqing/p/7701992.html
Copyright © 2020-2023  润新知