Spring動態加載、編輯數據源 用於多數據源

需求是根據不一樣的用戶分配不一樣的數據源,並且數據源最好可編輯,實現動態化。那最好的方案確定是把數據源信息存數據庫裏啊。 因而搜了好多文章,找到了這篇文章 但文章中有點問題,一直不走寫的代碼,發現有一點寫錯了,或者是配置沒寫全的緣故,並且缺乏一個文件,就在原來的基礎上稍微修改了一下。java

主要配置文件applicationContext.xml,不關鍵的已省略。mysql

<!--多數據源切換管理-->
    <bean id="dynamicDataSource" class="com.rongtai.acs.core.utils.DynamicDataSource">
        <property name="targetDataSources">
            <map>
            </map>
        </property>
        <!--默認數據源-->
        <property name="defaultTargetDataSource" ref="dataSource" />
    </bean>

    <!-- local development環境 -->
    <beans profile="development">
        <context:property-placeholder ignore-resource-not-found="true"
                                      location="classpath*:/application.properties" />

        <!-- Tomcat JDBC鏈接池 -->
        <bean id="dataSource" class="org.apache.tomcat.jdbc.pool.DataSource" destroy-method="close">
            <property name="driverClassName" value="${jdbc.driver}" />
            <property name="url" value="${jdbc.url}" />
            <property name="username" value="${jdbc.username}" />
            <property name="password" value="${jdbc.password}" />
            <property name="defaultAutoCommit" value="false" />
            <!-- 默認值是true,當從鏈接池取鏈接時,驗證這個鏈接是否有效-->
            <property name="testOnBorrow" value="true"/>
            <!--一條sql語句,用來驗證數據庫鏈接是否正常。這條語句必須是一個查詢模式,並至少返回一條數據。能夠爲任何能夠驗證數據庫鏈接是否正常的sql-->
            <property name="validationQuery" value="select 1"/>
            <!-- 是否自動回收超時鏈接-->
            <property name="removeAbandoned" value="true"/>
            <!-- 空閒時測試鏈接,必須配置validationQuery纔有效-->
            <property name="testWhileIdle" value="true"/>
            <!-- 鏈接池啓動時的初始值 -->
            <property name="initialSize" value="8"/>
            <!-- 鏈接Idle一個小時後超時 -->
            <property name="timeBetweenEvictionRunsMillis" value="3600000" />
            <property name="minEvictableIdleTimeMillis" value="3600000" />
        </bean>

        <!-- Jpa Entity Manager 配置 -->
        <bean id="entityManagerFactory" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
            <property name="dataSource" ref="dynamicDataSource"/>
            <property name="jpaVendorAdapter" ref="hibernateJpaVendorAdapter"/>
            <property name="packagesToScan" value="com.rongtai.acs"/>
            <property name="jpaProperties">
                <props>
                    <!-- 命名規則 My_NAME->MyName -->
                    <prop key="hibernate.ejb.naming_strategy">org.hibernate.cfg.ImprovedNamingStrategy</prop>
                    <prop key="hibernate.hbm2ddl.auto">update</prop>
                    <!--<prop key="hibernate.hbm2ddl.auto">create-drop</prop>-->
                    <prop key="hibernate.hbm2ddl.import_files">sql/mysql/init.sql</prop>
                    <prop key="hibernate.hbm2ddl.import_files_sql_extractor">
                        org.hibernate.tool.hbm2ddl.MultipleLinesSqlCommandExtractor
                    </prop>
                    <prop key="hibernate.connection.useUnicode">true</prop>
                    <prop key="hibernate.connection.characterEncoding">UTF-8</prop>
                    <prop key="hibernate.connection.charSet">UTF-8</prop>
                </props>
            </property>
        </bean>
    </beans>

類一 DynamicDataSource.javaspring

package com.rongtai.acs.core.utils;

import org.apache.commons.dbcp.BasicDataSource;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import org.springframework.jdbc.datasource.lookup.JndiDataSourceLookup;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Map;

public class DynamicDataSource extends AbstractRoutingDataSource {
    private Logger log = LoggerFactory.getLogger(this.getClass());

    private Map<Object, Object> _targetDataSources;

    /**
     * @see org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource#determineCurrentLookupKey()
     * @describe 數據源爲空或者爲0時,自動切換至默認數據源,即在配置文件中定義的dataSource數據源
     */
    @Override
    protected Object determineCurrentLookupKey() {
        String dataSourceName = DbContextHolder.getDBType();
        if (dataSourceName == null) {
            dataSourceName = Constants.DEFAULT_DATA_SOURCE_NAME;
        } else {
            this.selectDataSource(dataSourceName);
        }
        log.debug("--------> use datasource " + dataSourceName);
        return dataSourceName;
    }

    /**
     * 到數據庫中查找名稱爲dataSourceName的數據源
     *
     * @author Geloin
     * @date Jan 20, 2014 12:15:41 PM
     * @param dataSourceName
     */
    private void selectDataSource(String dataSourceName) {
        Object sid = DbContextHolder.getDBType();
        if (StringUtils.isEmpty(dataSourceName)
                || dataSourceName.trim().equals("dataSource")) {
            DbContextHolder.setDBType("dataSource");
            return;
        }
        Object obj = this._targetDataSources.get(dataSourceName);
        if (obj != null && sid.equals(dataSourceName)) {
            return;
        } else {
            DataSource dataSource = this.getDataSource(dataSourceName);
            if (null != dataSource) {
                this.setDataSource(dataSourceName, dataSource);
            }
        }
    }

    @Override
    public void setTargetDataSources(Map<Object, Object> targetDataSources) {
        this._targetDataSources = targetDataSources;
        super.setTargetDataSources(this._targetDataSources);
        afterPropertiesSet();
    }

    private void addTargetDataSource(String key, DataSource dataSource) {
        this._targetDataSources.put(key, dataSource);
        this.setTargetDataSources(this._targetDataSources);
    }

    private DataSource createDataSource(String driverClassName, String url,
                                        String username, String password) {
        BasicDataSource dataSource = new BasicDataSource();
        dataSource.setDriverClassName(driverClassName);
        dataSource.setUrl(url);
        dataSource.setUsername(username);
        dataSource.setPassword(password);
        return dataSource;
    }

    /**
     * 到數據庫中查詢名稱爲dataSourceName的數據源
     *
     * @author Geloin
     * @date Jan 20, 2014 12:18:12 PM
     * @param dataSourceName
     * @return
     */
    private DataSource getDataSource(String dataSourceName) {
        this.selectDataSource(Constants.DEFAULT_DATA_SOURCE_NAME);
        this.determineCurrentLookupKey();
        Connection conn = null;
        try {
            conn = this.getConnection();
            StringBuilder builder = new StringBuilder();
            builder.append("SELECT C_NAME,C_TYPE,C_URL,C_USER_NAME,");
            builder.append("C_PASSWORD,C_JNDI_NAME,C_DRIVER_CLASS_NAME ");
            builder.append("FROM IA_DATA_SOURCE WHERE c_name = ?");

            PreparedStatement ps = conn.prepareStatement(builder.toString());
            ps.setString(1, dataSourceName);
            ResultSet rs = ps.executeQuery();
            if (rs.next()) {

                Integer type = rs.getInt("C_TYPE");
                if (StringUtils.isNotEmpty(String.valueOf(type))) {
                    // DB
                    String url = rs.getString("C_URL");
                    String userName = rs.getString("C_USER_NAME");
                    String password = rs.getString("C_PASSWORD");
                    String driverClassName = rs
                            .getString("C_DRIVER_CLASS_NAME");
                    DataSource dataSource = this.createDataSource(
                            driverClassName, url, userName, password);
                    return dataSource;
                } else {
                    // JNDI
                    String jndiName = rs.getString("C_JNDI_NAME");

                    JndiDataSourceLookup jndiLookUp = new JndiDataSourceLookup();
                    DataSource dataSource = jndiLookUp.getDataSource(jndiName);
                    return dataSource;
                }

            }
            rs.close();
            ps.close();
        } catch (SQLException e) {
            log.error(String.valueOf(e));
        } finally {
            try {
                conn.close();
            } catch (SQLException e) {
                log.error(String.valueOf(e));
            }
        }
        return null;
    }

    /**
     * 將已存在的數據源存儲到內存中
     *
     * @author Geloin
     * @date Jan 20, 2014 12:24:13 PM
     * @param dataSourceName
     * @param dataSource
     */
    private void setDataSource(String dataSourceName, DataSource dataSource) {
        this.addTargetDataSource(dataSourceName, dataSource);
        DbContextHolder.setDBType(dataSourceName);
    }

}

類二 DbContextHolder.javasql

package com.rongtai.acs.core.utils;

public class DbContextHolder {

    private static final ThreadLocal<String> contextHolder = new ThreadLocal<String>();

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

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

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

    /**
     *  切換數據源語句     字符串爲實體類GsoftDataSource中的name屬性也就是數據庫表IA_DATA_SOURCE中的c_name字段
     *  DbContextHolder.setDBType("dataSourceName");
     */
}

類三 Constants.java數據庫

package com.rongtai.acs.core.utils;

public class Constants {
    public static String DEFAULT_DATA_SOURCE_NAME="dataSource";
    public static String DataSourceType="";
}

類四 實體類 GsoftDataSource.javaapache

package com.rongtai.acs.core.utils;


import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;


@Entity
@Table(name = "IA_DATA_SOURCE")
public class GsoftDataSource {
    @Id
//    @SequenceGenerator(name = "IA_DATA_SOURCE_SEQ", sequenceName = "IA_DATA_SOURCE_SEQ", allocationSize = 1)
//    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "IA_DATA_SOURCE_SEQ")
    private Long id;

    /**
     * data source name
     */
    @Column(name = "C_NAME", unique = true)
    private String name;

    /**
     * data source type, default is database<br />
     */
    @Column(name = "C_TYPE")
    private Integer type = DataSourceType.DB.intValue();

    /**
     * 數據庫類型,目前只支持MySql和Oracle<br />
     */
    @Column(name = "C_DATA_TYPE")
    private Integer dataType = DataType.ORACLE.intValue();

    @Column(name = "C_URL")
    private String url;

    @Column(name = "C_USER_NAME")
    private String userName;

    @Column(name = "C_PASSWORD")
    private String password;

    @Column(name = "C_JNDI_NAME")
    private String jndiName;

    @Column(name = "C_DRIVER_CLASS_NAME")
    private String driverClassName;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public Integer getType() {
        return type;
    }

    public void setType(Integer type) {
        this.type = type;
    }

    public String getUrl() {
        return url;
    }

    public void setUrl(String url) {
        this.url = url;
    }

    public String getUserName() {
        return userName;
    }

    public void setUserName(String userName) {
        this.userName = userName;
    }

    public String getPassword() {
        return password;
    }

    public void setPassword(String password) {
        this.password = password;
    }

    public String getJndiName() {
        return jndiName;
    }

    public void setJndiName(String jndiName) {
        this.jndiName = jndiName;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getDriverClassName() {
        return driverClassName;
    }

    public void setDriverClassName(String driverClassName) {
        this.driverClassName = driverClassName;
    }

    public Integer getDataType() {
        return dataType;
    }

    public void setDataType(Integer dataType) {
        this.dataType = dataType;
    }
    public enum DataType {
        ORACLE(0),
        MYSQL(1);
        private Integer value;
        public Integer intValue() {
            return this.value;
        }
        DataType(Integer value) {
            this.value = value;
        }
    }
    public enum DataSourceType {
        DB(0),
        ss(1);
        private Integer value;
        DataSourceType(Integer value) {
            this.value = value;
        }

        public Integer intValue() {
        return this.value;
        }
    }
}

實體類須要建對應的數據庫表,因爲我只用到了mysql,只能只說它了,sql語句以下:tomcat

CREATE TABLE `ia_data_source` (
  `id` BIGINT(20) NOT NULL AUTO_INCREMENT,
  `c_data_type` INT(11) NOT NULL,
  `c_driver_class_name` VARCHAR(255) DEFAULT NULL,
  `c_jndi_name` VARCHAR(255) DEFAULT NULL,
  `c_name` VARCHAR(255) DEFAULT NULL,
  `c_password` VARCHAR(255) DEFAULT NULL,
  `c_type` INT(11) NOT NULL,
  `c_url` VARCHAR(255) DEFAULT NULL,
  `c_user_name` VARCHAR(255) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `UK_bo3uh3stkpnq52ffugvt3934r` (`c_name`)
) ENGINE=INNODB AUTO_INCREMENT=3 DEFAULT CHARSET=utf8;

表數據截下圖app

切換數據源語句,只需在service對數據進行增刪改查時加上如下語句ide

DbContextHolder.setDBType("IA_DATA_SOURCE中某個數據源的name屬性的值");  

個人是測試

分析:原來文章的bug是不能加載加入的這些文件,緣由是在Jpa Entity Manager 配置的地方,DataSource沒有用配置好的數據源,這是必要的;另外是缺乏Constants.java類,害的我揣摩了好久。

須要注意的是包名若是更改的話,記得配置文件中也要把對應的Class類路徑作下更改。

原本要實現的是在用戶登陸時根據不一樣的用戶類型,選擇不一樣的數據源,還未實現。先實現了這個demo,等完成時,再貼代碼。

歡迎前來交流 ^_^ !

相關文章
相關標籤/搜索