动态添加数据源,根据用户登录切换数据库.编程式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();
  
原文地址:https://www.cnblogs.com/sweetchildomine/p/6848070.html