Hibernate動(dòng)態(tài)數(shù)據(jù)源切換

使用目的

出于在審計(jì)廳項(xiàng)目建設(shè)的需求摘昌,我們?cè)陧?xiàng)目建設(shè)工程先是使用了單一的數(shù)據(jù)庫(kù)哥攘,經(jīng)過(guò)三個(gè)月的代碼編寫,完成了單機(jī)的項(xiàng)目部署晶伦,在經(jīng)過(guò)兩臺(tái)loadRunner進(jìn)行2k的并發(fā)訪問(wèn)時(shí),發(fā)現(xiàn)數(shù)據(jù)庫(kù)的寫日志緩沖區(qū)已經(jīng)爆滿啄枕,導(dǎo)致系統(tǒng)宕機(jī)婚陪。后來(lái)在老師的決策下將數(shù)據(jù)庫(kù)分庫(kù)存儲(chǔ),不同地區(qū)的數(shù)據(jù)利用切分工具進(jìn)行數(shù)據(jù)的切分频祝,然后使用ETL泌参、dts配合自己寫的腳本完成數(shù)據(jù)的遷移和各種角色、存儲(chǔ)過(guò)程常空、權(quán)限的設(shè)置沽一。

數(shù)據(jù)是分開存放了,那如何在保證單機(jī)系統(tǒng)的可運(yùn)行的情況下漓糙,使用多數(shù)據(jù)源呢铣缠。經(jīng)過(guò)調(diào)研,我們發(fā)現(xiàn)hibernate+spring可以使用多數(shù)據(jù)源,可以在使用中動(dòng)態(tài)切換數(shù)據(jù)源蝗蛙。

數(shù)據(jù)源切換的步驟

我們的項(xiàng)目中用到了17個(gè)私有數(shù)據(jù)庫(kù)實(shí)例蝇庭,117個(gè)模式。1個(gè)公共數(shù)據(jù)庫(kù)實(shí)例捡硅。

enter description here
enter description here

如上圖所示哮内,我們系統(tǒng)的數(shù)據(jù)源的切換過(guò)程大概是這種模型。剛開始所有用戶的登錄數(shù)據(jù)都是由公共數(shù)據(jù)源進(jìn)行統(tǒng)一管理的壮韭,后面的審計(jì)數(shù)據(jù)的查詢北发,自由上傳的輔助數(shù)據(jù)都是存放在用戶的私有數(shù)據(jù)源中的。

動(dòng)態(tài)數(shù)據(jù)源切換的實(shí)現(xiàn)

編寫動(dòng)態(tài)數(shù)據(jù)源類

利用springAbstractRoutingDataSource來(lái)實(shí)現(xiàn)動(dòng)態(tài)數(shù)據(jù)源的獲取泰涂。AbstractRoutingDataSource鲫竞,它的一個(gè)作用就是可以根據(jù)用戶發(fā)起的不同請(qǐng)求去轉(zhuǎn)換不同的數(shù)據(jù)源,比如根據(jù)用戶的不同地區(qū)語(yǔ)言選擇不同的數(shù)據(jù)庫(kù)


import java.sql.SQLException;

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

public class DynamicDataSource extends AbstractRoutingDataSource {

    // static Logger log = Logger.getLogger("DynamicDataSource");

    @Override
    protected Object determineCurrentLookupKey() {
        
        return DbContextHolder.getDbType();
    }

    @Override
    public boolean isWrapperFor(Class<?> iface) throws SQLException {
        
        return false;
    }

    @Override
    public <T> T unwrap(Class<T> iface) throws SQLException {
        
        return null;
    }
}

補(bǔ)充:dataSource.properties文件內(nèi)容

DRIVER_NAME    =    xx.xx.xxx.xxxx

DATABASE_PASSWORD =  xxxxx
DATABASE_USER     =  xxxx
#私有數(shù)據(jù)源
DATABASE_URL_DEFAULT=jdbc:xx://localhost
#共有數(shù)據(jù)源
DATABASE_URL_COMMON=jdbc:xx://localhost

利用Druid實(shí)現(xiàn)私有數(shù)據(jù)源和公共數(shù)據(jù)源

    <bean id="dataSource_common" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
        <property name="driverClassName" value="${DRIVER_NAME}" />
        <property name="url" value="${DATABASE_URL_DEFAULT}" />
        <property name="username" value="${DATABASE_USER}" />
        <property name="password" value="${DATABASE_PASSWORD}" />
        <property name="filters" value="stat" />
        <property name="maxActive" value="20" />
        <property name="initialSize" value="1" />
        <property name="maxWait" value="60000" />
        <property name="minIdle" value="1" />
        <property name="timeBetweenEvictionRunsMillis" value="3000" />
        <property name="minEvictableIdleTimeMillis" value="300000" />
        <property name="validationQuery" value="SELECT 'x'" />
        <property name="testWhileIdle" value="true" />
        <property name="testOnBorrow" value="false" />
        <property name="testOnReturn" value="false" />
        <property name="poolPreparedStatements" value="true" />
        <property name="maxPoolPreparedStatementPerConnectionSize" value="20" />
    </bean>


    <bean id="dataSource_default" class="com.alibaba.druid.pool.DruidDataSource" init-method="init" destroy-method="close">
        <property name="driverClassName" value="${DRIVER_NAME}" />
        <property name="url" value="${DATABASE_URL_COMMON}" />
        <property name="username" value="${DATABASE_USER}" />
        <property name="password" value="${DATABASE_PASSWORD}" />
        <property name="filters" value="stat" />
        <property name="maxActive" value="20" />
        <property name="initialSize" value="1" />
        <property name="connectionProperties" value="druid.stat.slowSqlMillis=5000" />
        <property name="maxWait" value="60000" />
        <property name="minIdle" value="1" />
        <property name="timeBetweenEvictionRunsMillis" value="3000" />
        <property name="minEvictableIdleTimeMillis" value="300000" />
        <property name="validationQuery" value="SELECT 'x'" />
        <property name="testWhileIdle" value="true" />
        <property name="testOnBorrow" value="false" />
        <property name="testOnReturn" value="false" />
        <property name="poolPreparedStatements" value="true" />
        <property name="maxPoolPreparedStatementPerConnectionSize" value="20" />
    </bean>

聲明動(dòng)態(tài)數(shù)據(jù)源springBean

map中可以存放多個(gè)數(shù)據(jù)源逼蒙,在使用的時(shí)候指定key就可以獲取對(duì)應(yīng)的數(shù)據(jù)源了.

<bean id="dynamicDataSource" class="dao.DynamicDataSource">
    <property name="targetDataSources">
        <map key-type="java.lang.String">
            <!--私有數(shù)據(jù)源-->
            <entry key="default" value-ref="dataSource_default" />
            <!--公共數(shù)據(jù)源-->
            <entry key="common" value-ref="dataSource_common" />
        </map>
    </property>
    <!--默認(rèn)的數(shù)據(jù)源-->
    <property name="defaultTargetDataSource" ref="dataSource_default" />
</bean>

配置Hibernate的SessionFactory

這里是在spring的配置文件中配置Hibernate的SessionFactorty,這里使用的dataSource已經(jīng)是動(dòng)態(tài)的數(shù)據(jù)源了寄疏。

    <!-- 配置會(huì)話工廠 -->
    <bean id="sessionFactory"
        class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
        <!-- 設(shè)置數(shù)據(jù)源 -->
        <property name="dataSource" ref="dynamicDataSource" />
        <!-- 接管了hibernate對(duì)象映射文件 -->
        <property name="mappingResources">
            <list>
                <value>xxx/xxx.hbm.xml</value>
            </list>
        </property>

        <!--指定hibernate的屬性值 -->
        <property name="hibernateProperties">
            <props>
                <prop key="hibernate.dialect">org.hibernate.dialect.xxx</prop>
                <!-- <prop key="hibernate.hbm2ddl.auto">update</prop> -->
                <prop key="hibernate.show_sql">false</prop>
                <prop key="hibernate.format_sql">true</prop>
                <!-- -->
                <prop key="javax.persistence.validation.mode">none</prop>
                <prop key="connection.driver_class">xx.xxx.xx.xx</prop>
            </props>
        </property>
    </bean>

測(cè)試數(shù)據(jù)源是否可以連接成功

這里使用的是基于注解的測(cè)試類是牢,測(cè)試的運(yùn)行結(jié)果如下圖所示。

import dao.DynamicDataSource;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import java.sql.SQLException;

/**
 * Created by frank on 16-5-30.
 */
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration("classpath:applicationContext.xml")
public class NIuTest {
    @Autowired
    ApplicationContext context;
    @Test
    public void testContext() throws SQLException {
        DynamicDataSource ds = (DynamicDataSource) context.getBean("dynamicDataSource");
        System.out.println("loginTimeOutValue:"+ds.getLoginTimeout());
    }

}
enter description here
enter description here

創(chuàng)建線程私有的數(shù)據(jù)源上下文

創(chuàng)建一個(gè)數(shù)據(jù)源上下文持有者,該類使用了ThreadLocal能夠保證線程私有陕截,使得不同地區(qū)的用戶訪問(wèn)時(shí)驳棱,不會(huì)出現(xiàn)數(shù)據(jù)源沖突。

public class DbContextHolder {
    private static final ThreadLocal contextHolder = new ThreadLocal();
    public static final String DATA_SOURCE_DEFAULT = "default";
    public static final String DATA_SOURCE_COMMON = "common";
    

    public static void setDbType(String dbType) {
        contextHolder.set(dbType);
    }

    public static String getDbType() {
        return (String) contextHolder.get();
    }

    public static void clearDbType() {
        contextHolder.remove();
    }

}

在程序運(yùn)行時(shí)农曲,如何切換數(shù)據(jù)源

service類中調(diào)用Hibernate進(jìn)行數(shù)據(jù)操作之前社搅,如果需要切換數(shù)據(jù)源到私有的數(shù)據(jù)源,可以調(diào)用下面的方法.然后springDynamicDataSource實(shí)例則會(huì)調(diào)用determineCurrentLookupKey方法進(jìn)行數(shù)據(jù)源的切換乳规。

DbContextHolder.setDbType(DbContextHolder.DATA_SOURCE_COMMON);

一個(gè)更為實(shí)際的例子如下形葬,如果我們想要獲取某條用戶輸入的sql語(yǔ)句可以得到多少條數(shù)據(jù),那么我們需將數(shù)據(jù)源切換到用戶私有的數(shù)據(jù)源暮的。

    public long getAllMessageCount_Common(String sql)
            throws HibernateException, NullPointerException, SQLException {
        DbContextHolder.setDbType(DbContextHolder.DATA_SOURCE_COMMON);
        StringBuilder hql = null;
        hql = new StringBuilder("SELECT count(*) from (");
        hql.append(sql);
        hql.append(")");
        // logger.info("得到的sql語(yǔ)句:" + hql);
        long result = basicDao.executeQueryCountSQL_throwEX(hql.toString());
        logger.info("后臺(tái)得到的數(shù)據(jù)count:" + result);
        return result;
    }

從上面可以看出笙以,我們使用的是DbContextHolder.setDbType()方法來(lái)動(dòng)態(tài)的進(jìn)行數(shù)據(jù)源的切換。

總結(jié)

數(shù)據(jù)源的動(dòng)態(tài)切換就整理到這里冻辩,項(xiàng)目已經(jīng)做了很久了猖腕,今天在整理項(xiàng)目中使用到的知識(shí)時(shí),想到這塊自己可以整理一下恨闪,才寫此文作為記錄吧倘感。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市咙咽,隨后出現(xiàn)的幾起案子老玛,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 211,884評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件逻炊,死亡現(xiàn)場(chǎng)離奇詭異互亮,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)余素,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,347評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門豹休,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人桨吊,你說(shuō)我怎么就攤上這事威根。” “怎么了视乐?”我有些...
    開封第一講書人閱讀 157,435評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵洛搀,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我佑淀,道長(zhǎng)留美,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 56,509評(píng)論 1 284
  • 正文 為了忘掉前任伸刃,我火速辦了婚禮谎砾,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘捧颅。我一直安慰自己景图,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,611評(píng)論 6 386
  • 文/花漫 我一把揭開白布碉哑。 她就那樣靜靜地躺著挚币,像睡著了一般。 火紅的嫁衣襯著肌膚如雪扣典。 梳的紋絲不亂的頭發(fā)上妆毕,一...
    開封第一講書人閱讀 49,837評(píng)論 1 290
  • 那天,我揣著相機(jī)與錄音激捏,去河邊找鬼设塔。 笑死,一個(gè)胖子當(dāng)著我的面吹牛远舅,可吹牛的內(nèi)容都是我干的闰蛔。 我是一名探鬼主播,決...
    沈念sama閱讀 38,987評(píng)論 3 408
  • 文/蒼蘭香墨 我猛地睜開眼图柏,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼序六!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起蚤吹,我...
    開封第一講書人閱讀 37,730評(píng)論 0 267
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤例诀,失蹤者是張志新(化名)和其女友劉穎随抠,沒(méi)想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體繁涂,經(jīng)...
    沈念sama閱讀 44,194評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡拱她,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,525評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了扔罪。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片秉沼。...
    茶點(diǎn)故事閱讀 38,664評(píng)論 1 340
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖矿酵,靈堂內(nèi)的尸體忽然破棺而出唬复,到底是詐尸還是另有隱情,我是刑警寧澤全肮,帶...
    沈念sama閱讀 34,334評(píng)論 4 330
  • 正文 年R本政府宣布敞咧,位于F島的核電站,受9級(jí)特大地震影響辜腺,放射性物質(zhì)發(fā)生泄漏休建。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 39,944評(píng)論 3 313
  • 文/蒙蒙 一哪自、第九天 我趴在偏房一處隱蔽的房頂上張望丰包。 院中可真熱鬧,春花似錦壤巷、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,764評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至宙彪,卻和暖如春矩动,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背释漆。 一陣腳步聲響...
    開封第一講書人閱讀 31,997評(píng)論 1 266
  • 我被黑心中介騙來(lái)泰國(guó)打工悲没, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人男图。 一個(gè)月前我還...
    沈念sama閱讀 46,389評(píng)論 2 360
  • 正文 我出身青樓示姿,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親逊笆。 傳聞我的和親對(duì)象是個(gè)殘疾皇子栈戳,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,554評(píng)論 2 349

推薦閱讀更多精彩內(nèi)容

  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn)难裆,斷路器子檀,智...
    卡卡羅2017閱讀 134,633評(píng)論 18 139
  • Spring Boot 參考指南 介紹 轉(zhuǎn)載自:https://www.gitbook.com/book/qbgb...
    毛宇鵬閱讀 46,773評(píng)論 6 342
  • Android 自定義View的各種姿勢(shì)1 Activity的顯示之ViewRootImpl詳解 Activity...
    passiontim閱讀 171,791評(píng)論 25 707
  • 今天是2017年5月20日褂痰,是年輕人最有激情的一天亩进。是對(duì)心愛的人表白日,可以想象:鄭州市民政局結(jié)婚登記大廳的場(chǎng)面是...
    新吶喊閱讀 1,010評(píng)論 7 9
  • 誰(shuí)不說(shuō)咱家鄉(xiāng)好?每個(gè)人都對(duì)自己的故鄉(xiāng)有一種特殊的情感驶冒。尤其是當(dāng)離鄉(xiāng)多年后回到故土苟翻,激動(dòng)彷徨同時(shí)存在,近鄉(xiāng)情更怯骗污。即...
    凱里木閱讀 250評(píng)論 0 0