天天看點

動态添加資料源,根據使用者登入切換資料庫.程式設計式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();      

繼續閱讀