zoukankan      html  css  js  c++  java
  • 动态添加数据源,根据用户登录切换数据库.编程式Spring事务.

    根据用户注册,系统自动创建私有数据库,用户登录,动态添加数据源到Spring数据路由,Session超时删除数据源

    好处:当数据量大的时候,类似水平切割效果,效率会高一些

    坏处:数据源切换,Spring 事务处理比较繁琐,数据连接处理不好会有很大消耗,如果涉及后台系统管理数据,也比较繁琐.

    使用Spring数据源路由,现在好像没有直接添加数据源的方法,无奈之下只能用反射.

    用户登录成功时,在Spring Security UserDetailService.loadUserByUsername 里面添加用户数据源

            /**
                 * 加入用户数据源
                 */
                routingDataSource.addDataSource(userid);
        /**
         * 根据用户创建数据源
         */
        public void addDataSource(String userid) {
            if (StringUtils.isBlank(userid))
                return;
            DbInfo dbInfo = getDbInfoService().getDbInfoByUserId(userid);
            try {
                Field targetDataSources = AbstractRoutingDataSource.class.getDeclaredField("targetDataSources");
                Field resolvedDataSources = AbstractRoutingDataSource.class.getDeclaredField("resolvedDataSources");
                targetDataSources.setAccessible(true);
                resolvedDataSources.setAccessible(true);
                Map<Object, Object> dataSources = (Map<Object, Object>) targetDataSources.get(this);
                if (dataSources.get(userInfo.getId().toString()) != null)
                    return;
                Map<Object, DataSource> dataSources2 = (Map<Object, DataSource>) resolvedDataSources.get(this);
                DruidDataSource dds = new DruidDataSource();
                dds.setUrl("jdbc:mysql://" + dbInfo.getDbaddr() +
                        ":" + dbInfo.getDbport() + "/" + dbInfo.getDbname() + "?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true&useSSL=true");
                dds.setUsername(dbInfo.getUsername());
                dds.setPassword(dbInfo.getPwd());
                dataSources.put(userid, dds);
                dataSources2.put(userid, dds);
            } catch (NoSuchFieldException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }
    
    
    

    加入了数据源,当然需要删除,可以在Session监听器里面,销毁Session的时候删除

        /**
         * 根据用户删除数据源
         */
        public void removeDataSource(String userid) {
            if (StringUtils.isBlank(userid))
                return;
            try {
                Field targetDataSources = AbstractRoutingDataSource.class.getDeclaredField("targetDataSources");
                Field resolvedDataSources = AbstractRoutingDataSource.class.getDeclaredField("resolvedDataSources");
                targetDataSources.setAccessible(true);
                resolvedDataSources.setAccessible(true);
                Map<Object, Object> dataSources = (Map<Object, Object>) targetDataSources.get(this);
                if (dataSources.get(userInfo.getUsrno()) != null) {
                    Map<Object, DataSource> dataSources2 = (Map<Object, DataSource>) resolvedDataSources.get(this);
                    dataSources.remove(userid);
                    dataSources2.remove(userid);
                }
            } catch (NoSuchFieldException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
        }

    注解加Aop 切换数据源

    注解

    /**
     * Created by 为 .
     * 根据当前用户切换数据源
     */
    @Target(ElementType.METHOD)
    @Retention(RetentionPolicy.RUNTIME)
    public @interface SwitchDataSource {
    }

    Spring AOP,新版本的SpringAOP 可以很好切入监听器,因为监听器可以被Spring容器管理了,变相加强了SpringAop,这样就不需要使用原生Aspectj了

    /**
     * Created by 为 on 2017-4-27.
     */
    @Component
    @Aspect
    @Order(0)//配置Spring注解事务时,在事务之前切换数据源
    public class SwitchDataSourceAspectj {
    
        //定义切点
        @Pointcut("@annotation(com.lzw.common.annotation.SwitchDataSource)")
        public void switchDataSource(){}
    
        @Around("switchDataSource()")
        public Object arounduserDataSource(ProceedingJoinPoint joinPoint){
            DataSourceContextHolder.user();
            try {
                return joinPoint.proceed();
            } catch (Throwable throwable) {
                throwable.printStackTrace();
            }finally {
                DataSourceContextHolder.write();
            }
            return null;
        }
    }

    这样可以在方法上添加注解切换数据源(注意事务与切换数据源的注解顺序),不过如果在一个方法中需要多次切换到不同数据源查询数据,会消耗很多连接数,为了更好控制数据库连接数,需要使用Spring事务

    编程式Spring事务

    注入TransactionManager

        @Resource
        private PlatformTransactionManager platformTransactionManager;

    开始事务处理,每个用户单独数据库,访问量不大,所以没有配置连接池,每次重新获取连接性能比较低,开启事务是为了数据库连接重用

         //为了节省连接数,尽可能在一次切换里获取需要的数据
            DataSourceContextHolder.user();
        //TransactionTemplate 必须每次new出来,不能使用Spring单例注入,设置的数据会一直存在.
            TransactionTemplate transactionTemplate = new TransactionTemplate(platformTransactionManager);
            transactionTemplate.setPropagationBehavior(Propagation.REQUIRES_NEW.value());
            transactionTemplate.execute(new TransactionCallbackWithoutResult() {
                @Override
                public void doInTransactionWithoutResult(TransactionStatus status) {
                  //数据库操作代码
                }
            });
            DataSourceContextHolder.write();
      
  • 相关阅读:
    Android_AyscnTask
    Android_handler
    Android_网络操作
    Android_网络基础
    Android_Fragment
    Android_activity 生命周期
    Android-Dialog
    android_menu
    Android-约束布局
    andorid_相对布局小练习
  • 原文地址:https://www.cnblogs.com/sweetchildomine/p/6848070.html
Copyright © 2011-2022 走看看