【原創】SSM框架(2):多數據源支持,動態配置依賴的數據源


一、SSM框架配置多數據源的原理

  原理:MyBatis在創建SqlSession時,動態的使用不同的dataSource,就可以動態的使用不同的數據源。

  那么,怎樣才能動態的使用不同的dataSource呢? 在Spring框架中,提供了一個類AbstractRoutingDataSource,顧名思義叫做路由選擇。該類AbstractRoutingDataSource繼承AbstractDataSource,AbstractDataSource又實現了DataSource,因此,可以使用AbstractRoutingDataSource來完成我們的需求。AbstractRoutingDataSource中有一個determineTargetDataSource()方法,該方法是用來決定目標數據源的;而determineTargetDataSource()方法中通過determineCurrentLookupKey()方法來決定lookupKey;然后使用封裝好了的map集合resolvedDataSources,通過lookupKey為key值取得dataSource。因此這里面最重要的就是determineCurrentLookupKey()方法獲取key值,在這里Spring把這個方法抽象出來,交給用戶來實現。

 

 

二、實踐:在SSM框架中,實現多數據源訪問(目錄結構如下圖)

 

1、第一步:搭建SSM項目

  搭建步驟參考:SSM框架(1):使用Maven搭建SSM項目實踐

2、創建動態數據源代理類:DynamicDataSource 和 DynamicDataSourceHolder

  • DynamicDataSource:實現Spring定義的路由選擇抽象類AbstractRoutingDataSource,用來創建動態的數據源。
  • DynamicDataSourceHolder:用來保存數據源標識,從而在實例化DynamicDataSource時,來指定要使用的數據源實例 
package com.newbie.util;

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

/**
 * DynamicDataSource由自己實現,實現AbstractRoutingDataSource,數據源由自己指定。
 */
public class DynamicDataSource extends AbstractRoutingDataSource {
    @Override
    protected Object determineCurrentLookupKey() {
        // 從自定義的位置獲取數據源標識
        return DynamicDataSourceHolder.getDataSourceKey();
    }
}

 

package com.newbie.util;

/**
 * 自定義類:用來保存數據源標識,從而在實例化DynamicDataSource時來指定要使用的數據源實例
 */
public class DynamicDataSourceHolder {

    /**
     * 注意:數據源標識保存在線程變量中,避免多線程操作數據源時互相干擾
     */
    private static final ThreadLocal<String> DATA_SOURCE_KEY = new ThreadLocal<String>();

    public static String getDataSourceKey(){
        return DATA_SOURCE_KEY.get();
    }

    public static void setDataSourceKey(String dataSourceKey){
        DATA_SOURCE_KEY.set(dataSourceKey);
    }

    public static void clearDataSourceKey(){
        DATA_SOURCE_KEY.remove();
    }

}

 

3、自定義多數據源注解@AnnotationDBSourceKey 以及 解析數據源注解的切面類DynamicDBSourceAspect

  • AnnotationDBSourceKey:自定義注解類。在要動態設置數據源的方法上,使用注解來標識數據源。
  • DynamicDBSourceAspect:切面類。在執行橫切的方法前,獲取方法的@AnnotationDBSourceKey注解,解析注解的值來設置DynamicDataSourceHolder類中的數據源標識,進而達到動態設置數據源的目的。
package com.newbie.util.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 自定義注解類型:使用注解來標識數據源
 */
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface AnnotationDBSourceKey {
    /**
     * value的值:來標識數據源
     * @return
     */
    String value();
}
package com.newbie.util.annotation;

import com.newbie.util.DynamicDataSourceHolder;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

/**
 * 定義切面類:在執行DAO接口的方法前,獲取方法的@AnnotationDBSourceKey注解,根據注解的值來動態設置數據源
 */
@Component
@Aspect
public class DynamicDBSourceAspect {
    /**
     * 攔截目標方法,獲取由@DataSource指定的數據源標識,設置到線程存儲中以便切換數據源
     *
     * @param point
     * @throws Exception
     */
    @Before("execution(* com.newbie.service.*.*(..))")
    public void intercept(JoinPoint point) throws Exception {
        Class<?> target = point.getTarget().getClass();
        MethodSignature signature = (MethodSignature) point.getSignature();
        System.out.println("intercept()----,target:"+target+",signature:"+signature+",signature.getMethod():"+signature.getMethod());

        // 默認使用目標類型的注解,如果沒有則使用其實現接口的注解
        for (Class<?> clazz : target.getInterfaces()) {
            resolveDataSource(clazz, signature.getMethod());
        }
        resolveDataSource(target, signature.getMethod());
    }

    /**
     * 提取目標對象方法注解和類型注解中的數據源標識
     *
     * @param clazz
     * @param method
     */
    private void resolveDataSource(Class<?> clazz, Method method) {
        try {
            Class<?>[] types = method.getParameterTypes();
            // 默認使用類型注解
            if (clazz.isAnnotationPresent(AnnotationDBSourceKey.class)) {
                AnnotationDBSourceKey source = clazz.getAnnotation(AnnotationDBSourceKey.class);
                DynamicDataSourceHolder.setDataSourceKey(source.value());
            }
            // 方法注解可以覆蓋類型注解
            Method m = clazz.getMethod(method.getName(), types);
            if (m != null && m.isAnnotationPresent(AnnotationDBSourceKey.class)) {
                AnnotationDBSourceKey source = m.getAnnotation(AnnotationDBSourceKey.class);
                DynamicDataSourceHolder.setDataSourceKey(source.value());
            }
        } catch (Exception e) {
            System.out.println(clazz + ":" + e.getMessage());
        }
    }
}

 

4、修改數據源配置:將單數據源配置改為多數據源配置

  • db.properties : 數據源靜態參數配置文件,增加主從數據源的配置信息
  • spring-mybatis.xml : 增加主從數據源的實例(master\slave1\slave2);增加DynamicDataSource的動態代理類的實例,並將DynamicDataSource實例的參數targetDataSources,設置為主從數據源的實例集合。
#db.properties配置文件內容
#配置主從數據庫的鏈接地址 #主庫 master.jdbc.url=jdbc:mysql://localhost:3306/nb_master?useUnicode=true&characterEncoding=utf8
#從庫1
slave1.jdbc.url=jdbc:mysql://localhost:3306/nb_slave1?useUnicode=true&characterEncoding=utf8
#從庫2
slave2.jdbc.url=jdbc:mysql://www.newbie.com:10127/nb_slave2?useUnicode=true&characterEncoding=utf8

#配置其他信息
jdbc.driver=com.mysql.jdbc.Driver
jdbc.username=root
jdbc.password=xxxxxx
#最大連接數
c3p0.maxPoolSize=30
#最小連接數
c3p0.minPoolSize=10
#關閉連接后不自動commit
c3p0.autoCommitOnClose=false
#獲取連接超時時間
c3p0.checkoutTimeout=10000
#當獲取連接失敗重試次數
c3p0.acquireRetryAttempts=2
##### spring-mybatis.xml配置文件內容 #####
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">

    <!--讀取靜態配置文件,獲取相關數據庫連接參數 -->
    <context:property-placeholder location="classpath:db.properties"/>
    <!-- 配置數據源 -->
    <bean id="abstractDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" abstract="true" destroy-method="close">
        <property name="driverClass" value="${jdbc.driver}"/>
        <property name="user" value="${jdbc.username}"/>
        <property name="password" value="${jdbc.password}"/>
        <property name="maxPoolSize" value="${c3p0.maxPoolSize}"/>
        <property name="minPoolSize" value="${c3p0.minPoolSize}"/>
        <property name="autoCommitOnClose" value="${c3p0.autoCommitOnClose}"/>
        <property name="checkoutTimeout" value="${c3p0.checkoutTimeout}"/>
        <property name="acquireRetryAttempts" value="${c3p0.acquireRetryAttempts}"/>
    </bean>
    <!-- 配置數據源:主庫 -->
    <bean id="master" parent="abstractDataSource">
        <property name="jdbcUrl" value="${master.jdbc.url}"/>
    </bean>
    <!-- 配置數據源:從庫1 -->
    <bean id="slave1" parent="abstractDataSource">
        <property name="jdbcUrl" value="${slave1.jdbc.url}"/>
    </bean>
    <!-- 配置數據源:從庫2 -->
    <bean id="slave2" parent="abstractDataSource">
        <property name="jdbcUrl" value="${slave2.jdbc.url}"/>
    </bean>

    <!-- 動態數據源配置,這個class要完成實例化 -->
    <bean id="dynamicDataSource" class="com.newbie.util.DynamicDataSource">
        <property name="targetDataSources">
            <!-- 指定lookupKey和與之對應的數據源,切換時使用的為key -->
            <map key-type="java.lang.String">
                <entry key="master" value-ref="master"/>
                <entry key="slave1" value-ref="slave1"/>
                <entry key="slave2" value-ref="slave2"/>
            </map>
        </property>
        <!-- 這里可以指定默認的數據源 -->
        <property name="defaultTargetDataSource" ref="master"/>
    </bean>

    <!-- 配置MyBatis創建數據庫連接的工廠類(此處配置和單數據源相同,不需改變) -->
    <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
        <!--數據源指定為:動態數據源DynamicDataSource -->
        <property name="dataSource" ref="dynamicDataSource"/>
        <!-- mapper配置文件 -->
        <property name="mapperLocations" value="classpath:mapper/*.xml"/>
    </bean>

    <!-- 配置自動掃描DAO接口包,動態實現DAO接口實例,注入到Spring容器中進行管理(此處配置和單數據源相同,不需改變) -->
    <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
        <!-- 注入SqlSession工廠對象:SqlSessionFactoryBean -->
        <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
        <!-- 指定要掃描的DAO接口所在包 -->
        <property name="basePackage" value="com.newbie.dao"/>
    </bean>

</beans>

 

5、創建 UserController、UserService,在UserService中動態設置數據源

  • UserService : 調用DynamicDataSourceHolder.setDataSourceKey(dataSourceKey)來動態設置數據源,實現多數據源的動態訪問。
  • UserController : 接收客戶端請求,調用UserService的方法處理請求,然后將結果響應給客戶。

 

package com.newbie.service.impl;

import com.newbie.dao.UserDAO;
import com.newbie.domain.User;
import com.newbie.service.IUserService;
import com.newbie.util.DynamicDataSourceHolder;
import com.newbie.util.annotation.AnnotationDBSourceKey;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import java.util.List;
@Service
public class UserService implements IUserService {
    @Resource
    private UserDAO userDAO;

    /**
     * 通過設置:DynamicDataSourceHolder.setDataSourceKey(dataSourceKey);的方式,來動態設置數據源
     */
    public List<User> searchAllUser(String dataSourceKey) {
        if(dataSourceKey == null){
            dataSourceKey = "master";
        }
        DynamicDataSourceHolder.setDataSourceKey(dataSourceKey);
        return userDAO.selectAll();
    }

    /*
     * 以下三個方法:searchMaster()、searchSlave1()、searchSlave2()
     * 使用自定義注解的方式,來動態設置數據源
     */
    @AnnotationDBSourceKey("master")
    public List<User> searchMaster() {
        return userDAO.selectAll();
    }
    @AnnotationDBSourceKey("slave1")
    public List<User> searchSlave1() {
        return userDAO.selectAll();
    }
    @AnnotationDBSourceKey("slave2")
    public List<User> searchSlave2() {
        return userDAO.selectAll();
    }
}
package com.newbie.controller;

import com.newbie.domain.User;
import com.newbie.service.IUserService;
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.RequestMapping;

import javax.annotation.Resource;
import java.util.List;

/**
 * 控制器:接收客戶端請求,處理后將結果響應給客戶
 */
@Controller
public class UserController {
    @Resource
    private IUserService userService;

    /**
     * 通過設置:DynamicDataSourceHolder.setDataSourceKey(dataSourceKey);的方式,來動態設置數據源
     */
    @RequestMapping("/searchAllUser")
    public String searchAllUser(Model model, String dataSourceKey){
        List<User> users = userService.searchAllUser(dataSourceKey);
        model.addAttribute("message","dataSourceKey = "+dataSourceKey);
        model.addAttribute("user",users.get(0));
        return "showInfo";
    }

    /*
     * 以下三個方法:searchMaster()、searchSlave1()、searchSlave2()
     * 使用自定義注解的方式,來動態設置數據源
     */
    @RequestMapping("/searchMaster")
    public String searchMaster(Model model){
        List<User> users = userService.searchMaster();
        model.addAttribute("message","dataSourceKey = master , method : searchMaster()");
        model.addAttribute("user",users.get(0));
        return "showInfo";
    }
    @RequestMapping("/searchSlave1")
    public String searchSlave1(Model model){
        List<User> users = userService.searchSlave1();
        model.addAttribute("message","dataSourceKey = slave1 , method : searchSlave1()");
        model.addAttribute("user",users.get(0));
        return "showInfo";
    }
    @RequestMapping("/searchSlave2")
    public String searchSlave2(Model model){
        List<User> users = userService.searchSlave2();
        model.addAttribute("message","dataSourceKey = slave2 , method : searchSlave2()");
        model.addAttribute("user",users.get(0));
        return "showInfo";
    }
}

 

6、UserDAO、UserMapper.xml保持不變(和單數據源中的配置相同)

package com.newbie.dao;

import com.newbie.domain.User;
import java.util.List;

/**
 * 數據庫操作對象:用戶類
 */
public interface UserDAO {
    List<User> selectAll();
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<!-- mapper配置文件,其中namespace指定要動態實例的DAO接口全局類名稱 -->
<mapper namespace="com.newbie.dao.UserDAO">
    <!-- 查詢所有用戶 -->
    <select id="selectAll" resultType="com.newbie.domain.User">
        select id,username,title from user
    </select>

</mapper>
-- DROP TABLE user;
CREATE TABLE user(
    id VARCHAR(25),
    username VARCHAR(50),
    title VARCHAR(255)
    ); 
insert into user(id,username,title) values('123','簡小六','主庫master');
insert into user(id,username,title) values('123','簡小六','從庫slave1');
insert into user(id,username,title) values('123','簡小六','從庫slave2');

 

7、創建前端jsp頁面,測試多數據源訪問結果(首先訪問index.jsp頁面,點擊<a>標簽跳轉后,在showInfo.jsp頁面中顯示結果)

#### index.jsp頁面內容 ####
<%
@ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>測試多數據源模式</title> </head> <body> <h2> 通過設置:DynamicDataSourceHolder.setDataSourceKey(dataSourceKey);的方式,來動態設置數據源</h2> <a href="searchAllUser?dataSourceKey=master">查詢主庫:用戶信息</a><br/><br/> <a href="searchAllUser?dataSourceKey=slave1">查詢從庫1:用戶信息</a><br/><br/> <a href="searchAllUser?dataSourceKey=slave2">查詢從庫2:用戶信息</a><br/><br/> <br/><hr/><hr/><br/> <h2> 使用自定義注解的方式,來動態設置數據源</h2> <a href="searchMaster">查詢主庫:用戶信息</a><br/><br/> <a href="searchSlave1">查詢從庫1:用戶信息</a><br/><br/> <a href="searchSlave2">查詢從庫2:用戶信息</a><br/><br/> </body> </html>
#### showInfo.jsp頁面內容  ####
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<html>
<head>
    <title>顯示結果信息</title>
</head>
<body>
處理信息:${message}<br/>
處理結果:<br/>
&nbsp;&nbsp;&nbsp;&nbsp;用戶ID:${user.id}
&nbsp;&nbsp;&nbsp;&nbsp;username:${user.username}
&nbsp;&nbsp;&nbsp;&nbsp;title:${user.title}
</body>
</html>

 

8、程序運行結果

(1)發布項目,請求以下地址:http://localhost:8080/ssm-multi-DBSource/index.jsp

(2)index.jsp頁面顯示如下:

(3)以上六個<a>標簽,點擊后程序運行的結果分別如下圖:

 

參考資料:

歡迎轉載,轉載請注明出處!




免責聲明!

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



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