動態添加數據源,根據用戶登錄切換數據庫.編程式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();

 

  


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM