zoukankan      html  css  js  c++  java
  • springboot主从数据库

    是从springmvc的思路上来做的,主要就是配置主、从DataSource,
    再继承AbstractRoutingDataSource,重写determineCurrentLookupKey
    方法,通过Context结合 aop 进行数据主、从库的切换。

     

    上代码:

     

    路由,即实现多数据库的切换源

    /*
    *    重写的函数决定了最后选择的DataSource
    *    因为AbstractRoutingDataSource中获取连接方法为:
    @Override
        public Connection getConnection(String username, String password) throws SQLException {
            return determineTargetDataSource().getConnection(username, password);
        }
    */
    
    public class MultiRouteDataSource extends AbstractRoutingDataSource {
    
        @Override
        protected Object determineCurrentLookupKey() {
            return DataSourceContextHolder.getDataSource();
        }
    
    }

    注解,即用以标识选择主还是从数据库

    @Target({ ElementType.METHOD, ElementType.TYPE })
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface TargetDataSource {
        String value();
    }

    常规配置项,具体主从继承并通过

    @ConfigurationProperties(prefix = "master.datasource") 进行配置读取

    public class BaseDataSourceConfig {
    
        private String url;
        private String username;
        private String password;
        private String driverClassName;
        // 添加上getter、setter方法
    }

    多数据源设置

    @Configuration
    public class DataSourceComponent {
    
        @Resource
        MasterDataSourceConfig masterDataSourceConfig;
    
        @Resource
        FirstDataSourceConfig firstDataSourceConfig;
    
        @Resource
        SecondDataSourceConfig secondDataSourceConfig;
    
        /*
         * 一开始以为springboot的自动配置还是会生效,直接加了@Resource DataSource dataSource;
         * 显示是不work的,会报create bean 错误
         */
        public DataSource masterDataSource() {
            DataSource dataSource = new DataSource();
            dataSource.setUrl(masterDataSourceConfig.getUrl());
            dataSource.setUsername(masterDataSourceConfig.getUsername());
            dataSource.setPassword(masterDataSourceConfig.getPassword());
            dataSource.setDriverClassName(masterDataSourceConfig.getDriverClassName());
            return dataSource;
        }
    
        /*
         * 一开始在这里加了@Bean的注解,当然secondDataSource()也加了
         * 会导致springboot识别的时候,发现有多个
         * 所以,其实都不要加@Bean,最终有效的的DataSource就只需要一个multiDataSource即可
         */
        public DataSource firstDataSource() {
            DataSource dataSource = new DataSource();
            dataSource.setUrl(firstDataSourceConfig.getUrl());
            dataSource.setUsername(firstDataSourceConfig.getUsername());
            dataSource.setPassword(firstDataSourceConfig.getPassword());
            dataSource.setDriverClassName(firstDataSourceConfig.getDriverClassName());
            return dataSource;
        }
    
        public DataSource secondDataSource() {
            DataSource dataSource = new DataSource();
            dataSource.setUrl(secondDataSourceConfig.getUrl());
            dataSource.setUsername(secondDataSourceConfig.getUsername());
            dataSource.setPassword(secondDataSourceConfig.getPassword());
            dataSource.setDriverClassName(secondDataSourceConfig.getDriverClassName());
            return dataSource;
        }
    
        @Bean(name = "multiDataSource")
        public MultiRouteDataSource exampleRouteDataSource() {
            MultiRouteDataSource multiDataSource = new MultiRouteDataSource();
            Map<Object, Object> targetDataSources = new HashMap<>();
            targetDataSources.put("master", masterDataSource());
            targetDataSources.put("first", firstDataSource());
            targetDataSources.put("second", secondDataSource());
            multiDataSource.setTargetDataSources(targetDataSources);
            multiDataSource.setDefaultTargetDataSource(masterDataSource());
            return multiDataSource;
        }
    
        @Bean(name = "transactionManager")
        public DataSourceTransactionManager dataSourceTransactionManager() {
            DataSourceTransactionManager manager = new DataSourceTransactionManager();
            manager.setDataSource(exampleRouteDataSource());
            return manager;
        }
    
        @Bean(name = "sqlSessionFactory")
        public SqlSessionFactoryBean sqlSessionFactory() {
            SqlSessionFactoryBean sessionFactoryBean = new SqlSessionFactoryBean();
            sessionFactoryBean.setDataSource(exampleRouteDataSource());
            return sessionFactoryBean;
        }
    }

    当然少不了DataSourceContextHolder,用以保持当前线程的数据源选择。

    public class DataSourceContextHolder {
    
        private static final ThreadLocal<String> contextHolder = new ThreadLocal<>();
    
        public static void setDataSource(String value) {
            contextHolder.set(value);
        }
    
        public static String getDataSource() {
            return contextHolder.get();
        }
    
        public static void clearDataSource() {
            contextHolder.remove();
        }
    }

    最后,自然就是AOP+注解实现数据源切换啦

    @Aspect
    @Component
    public class DynamicDataSourceAspect {
    
        @Around("execution(public * com.wdm.example.service..*.*(..))")
        public Object around(ProceedingJoinPoint pjp) throws Throwable {
            MethodSignature methodSignature = (MethodSignature) pjp.getSignature();
            Method targetMethod = methodSignature.getMethod();
            if(targetMethod.isAnnotationPresent(TargetDataSource.class)){
                String targetDataSource = targetMethod.getAnnotation(TargetDataSource.class).value() ;
                DataSourceContextHolder.setDataSource(targetDataSource);
            }
            Object result = pjp.proceed();
            DataSourceContextHolder.clearDataSource();
            return result;
        }
    }

    那用法就是如下了:

    package com.wdm.example.service;
    
    import java.util.Date;
    
    import javax.annotation.Resource;
    
    import org.springframework.stereotype.Service;
    
    import com.wdm.example.dao.UserDao;
    import com.wdm.example.datasource.TargetDataSource;
    import com.wdm.example.model.User;
    import com.wdm.example.service.UserService;
    
    /*
     * @author wdmyong
     * 20170416
     */
    @Service
    public class UserService {
    
        @Resource
        UserDao userDao;
    
        public User getById(Integer id) {
            return userDao.getById(id);
        }
    
        @TargetDataSource("master")
        public User getById0(Integer id) {
            return userDao.getById(id);
        }
    
        @TargetDataSource("first")
        public User getById1(Integer id) {
            return userDao.getById(id);
        }
    
        @TargetDataSource("second")
        public User getById2(Integer id) {
            return userDao.getById(id);
        }
    
        public void insert(User user) {
            Date now = new Date();
            user.setCreateTime(now);
            user.setModifyTime(now);
            userDao.insert(user);
        }
    
        public void update(User user) {
            user.setModifyTime(new Date());
            userDao.update(user);
        }
    
    }

    自己在网上找的时候不是全的,包括上文注释中提到的出现的问题,也是根据错误提示多个DataSource目标,以及没设置就是没有DataSource了。

    PS:其实之前一直以为DataSource听起来挺悬乎,没去细想,当然主要由于自己是半路出家的Java、web开发,本身也没那么熟悉,所以没理解哈,
    
    现在想想DataSource其实就是保存了些配置,说白了是url和账号密码,就是连接数据库的,相当于你用命令行连接了数据库进行了操作一样,各种
    
    数据库DataSource的实现高功能多半应该是做了些连接池的管理,以及连接的打开关闭之类,其实实质上我觉得应该就是说最后用的就是那个url加
    
    上账号密码就能连接并操作了。这样的话,多数据源的切换就好理解了,结合 aop 在函数入口之前设置好当前线程数据源,以及根据路由数据库类
    
    AbstractRoutingDataSource将选择数据源留给子类实现的方法
    
    determineCurrentLookupKey,从而在service方法入口设置数据源,在使用时取到数据源。

    大PS:这应该算是我写的最全的一次Java的博客了!!!

  • 相关阅读:
    为什么要有binary-to-text encoding?
    海量网络存储系统原理与设计(三)
    Java中的Inner Class (一)
    海量网络存储系统原理与设计(二)
    海量网络存储系统原理与设计(一)
    [JavaScript]顺序的异步执行
    [PAT]素因子分解(20)
    [PAT]求集合数据的均方差(15)
    [PAT]数列求和(20)
    【C-001】printf理解
  • 原文地址:https://www.cnblogs.com/hawk-whu/p/6741293.html
Copyright © 2011-2022 走看看