spring 动态数据源切换实例
我们很多项目中业务都需要涉及到多个数据源,最简单的做法就是直接在java代码里面lookup需要的数据源,但是这样的做法很明显耦合度太高了,
而且当逻辑流程不够严谨的时候就会出现各种大家不愿意看到的问题,由于我们现在的大多项目已经离不开spring了,spring也提供各种强大的功能,
很明显这种动态数据源功能也包括在内,具体实现类请看org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource
这里我演示下如何使用spring提供的动态数据源
先写个接口,default设置为null是因为当程序里没有找到相关联的源就会调用默认源
- /**
- * 数据源切换接口
- *
- * @author shadow
- * @create 2013.04.03
- */
- public interface DataSourceEntry {
- // 默认数据源
- public final static String DEFAULT_SOURCE = null;
- /**
- * 还原数据源
- *
- * @param joinPoint
- */
- public void restore(JoinPoint join);
- /**
- * 设置数据源
- *
- * @param dataSource
- */
- public void set(String source);
- /**
- * 获取数据源
- *
- * @return String
- */
- public String get();
- /**
- * 清空数据源
- */
- public void clear();
- }
/**
* 数据源切换接口
*
* @author shadow
* @create 2013.04.03
*/
public interface DataSourceEntry {
// 默认数据源
public final static String DEFAULT_SOURCE = null;
/**
* 还原数据源
*
* @param joinPoint
*/
public void restore(JoinPoint join);
/**
* 设置数据源
*
* @param dataSource
*/
public void set(String source);
/**
* 获取数据源
*
* @return String
*/
public String get();
/**
* 清空数据源
*/
public void clear();
}
然后写个实现类,从当前线程里取出对应的数据源名
- /**
- * 数据源切换实现类类
- *
- * @author shadow
- * @create 2013.04.03
- */
- public class DataSourceEntryImpl implements DynamicTypeEntry {
- private final static ThreadLocal<String> local = new ThreadLocal<String>();
- public void clear() {
- local.remove();
- }
- public String get() {
- return local.get();
- }
- public void restore(JoinPoint join) {
- local.set(DEFAULT_SOURCE);
- }
- public void set(String source) {
- local.set(source);
- }
- }
/**
* 数据源切换实现类类
*
* @author shadow
* @create 2013.04.03
*/
public class DataSourceEntryImpl implements DynamicTypeEntry {
private final static ThreadLocal<String> local = new ThreadLocal<String>();
public void clear() {
local.remove();
}
public String get() {
return local.get();
}
public void restore(JoinPoint join) {
local.set(DEFAULT_SOURCE);
}
public void set(String source) {
local.set(source);
}
}
然后写个继承AbstractRoutingDataSource的类,并注入DataSourceEntry,重写determineCurrentLookupKey模版方法
- /**
- * 获取数据源(依赖SPRING框架)
- *
- * @author shadow
- * @create 2013.04.03
- */
- public class DynamicDataSource extends AbstractRoutingDataSource {
- private DataSourceEntry dataSourceEntry;
- @Override
- protected Object determineCurrentLookupKey() {
- return this.dataSourceEntry.get();
- }
- @Resource
- public void setDataSourceEntry(DataSourceEntry dataSourceEntry) {
- this.dataSourceEntry = dataSourceEntry;
- }
- }
/**
* 获取数据源(依赖SPRING框架)
*
* @author shadow
* @create 2013.04.03
*/
public class DynamicDataSource extends AbstractRoutingDataSource {
private DataSourceEntry dataSourceEntry;
@Override
protected Object determineCurrentLookupKey() {
return this.dataSourceEntry.get();
}
@Resource
public void setDataSourceEntry(DataSourceEntry dataSourceEntry) {
this.dataSourceEntry = dataSourceEntry;
}
}
最后就是配置下xml文件,以后只需要直接管理dynamicDataSource这个接口就可以了,至于他内部是哪个数据源是不需要关注,写的切面还原是为了保证每次调用完另外的数据源
都会还原成默认数据源,防止有的人忘记设置回默认的,导致其他代码出问题
- <!-- JDBC模板 -->
- <bean id="jdbcTemplate"
- class="org.springframework.jdbc.core.JdbcTemplate">
- <property name="dataSource" ref="dynamicDataSource" />
- </bean>
- <!-- 获取数据源配置 -->
- <bean
- class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
- <property name="locations">
- <value>classpath:properties/jdbc.properties</value>
- </property>
- </bean>
- <!-- 配置动态数据源 -->
- <bean id="dynamicDataSource"
- class="com.shadow.system.base.source.DynamicDataSource">
- <!-- 通过key-value的形式来关联数据源 -->
- <property name="targetDataSources">
- <map key-type="java.lang.String">
- <entry value-ref="C3P0_MYSQL" key="C3P0_MYSQL"></entry>
- <entry value-ref="C3P0_MYSQL2" key="C3P0_MYSQL2"></entry>
- </map>
- </property>
- <property name="defaultTargetDataSource" ref="C3P0_MYSQL" />
- </bean>
- <!-- JNDI方式 -->
- <bean id="C3P0_MYSQL"
- class="org.springframework.jndi.JndiObjectFactoryBean">
- <property name="resourceRef">
- <value>false</value>
- </property>
- <property name="jndiName">
- <value>${JNDI.template}</value>
- </property>
- </bean>
- <!-- JNDI方式 -->
- <bean id="C3P0_MYSQL2"
- class="org.springframework.jndi.JndiObjectFactoryBean">
- <property name="resourceRef">
- <value>false</value>
- </property>
- <property name="jndiName">
- <value>${JNDI.test}</value>
- </property>
- </bean>
- <!-- 配置数据源切换实现类 -->
- <bean id="dataSourceEntry"
- class="com.shadow.system.base.source.DataSourceEntryImpl" />
- <!-- 切面还原默认数据源 -->
- <aop:config>
- <aop:aspect id="dataSourceHolderAdviceAspect"
- ref="dataSourceEntry">
- <aop:after method="restore"
- pointcut="execution(* com.shadow.mvc.service.*Service.*(..))" />
- </aop:aspect>
- </aop:config>
<!-- JDBC模板 -->
<bean id="jdbcTemplate"
class="org.springframework.jdbc.core.JdbcTemplate">
<property name="dataSource" ref="dynamicDataSource" />
</bean>
<!-- 获取数据源配置 -->
<bean
class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<value>classpath:properties/jdbc.properties</value>
</property>
</bean>
<!-- 配置动态数据源 -->
<bean id="dynamicDataSource"
class="com.shadow.system.base.source.DynamicDataSource">
<!-- 通过key-value的形式来关联数据源 -->
<property name="targetDataSources">
<map key-type="java.lang.String">
<entry value-ref="C3P0_MYSQL" key="C3P0_MYSQL"></entry>
<entry value-ref="C3P0_MYSQL2" key="C3P0_MYSQL2"></entry>
</map>
</property>
<property name="defaultTargetDataSource" ref="C3P0_MYSQL" />
</bean>
<!-- JNDI方式 -->
<bean id="C3P0_MYSQL"
class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="resourceRef">
<value>false</value>
</property>
<property name="jndiName">
<value>${JNDI.template}</value>
</property>
</bean>
<!-- JNDI方式 -->
<bean id="C3P0_MYSQL2"
class="org.springframework.jndi.JndiObjectFactoryBean">
<property name="resourceRef">
<value>false</value>
</property>
<property name="jndiName">
<value>${JNDI.test}</value>
</property>
</bean>
<!-- 配置数据源切换实现类 -->
<bean id="dataSourceEntry"
class="com.shadow.system.base.source.DataSourceEntryImpl" />
<!-- 切面还原默认数据源 -->
<aop:config>
<aop:aspect id="dataSourceHolderAdviceAspect"
ref="dataSourceEntry">
<aop:after method="restore"
pointcut="execution(* com.shadow.mvc.service.*Service.*(..))" />
</aop:aspect>
</aop:config>
至于程序里如何变换数据源,你可以在切面上检测哪些方法加入before方法,或者在程序里直接使用DataSourceEntry调用set方法,具体的怎么用不作代码说明了
PropertyPlaceholderConfigurer类的使用注意
如果你在spring的applicationcontext.xml中需要使用属性配置文件,那PropertyPlaceholderConfigurer这个类就是必须的。
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="location">
<value>classpath:conf/setting.properties</value>
</property>
<property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE" />
<property name="ignoreUnresolvablePlaceholders" value="true"></property>
</bean>
假设如果你仅仅就需要一个属性文件就没什么了,但如果你需要两个配置文件,并且两个配置文件里都有name属性。这里假设setting.properties属性文件
里有name=hello,同时setting_new.properties里面也有name=xiaoQ。然后我又加个PropertyPlaceholderConfigurer,那么我们就看看完整的信息
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<!-- Root Context: defines shared resources visible to all other web components -->
<bean id="mockAnno" class="org.mm.annoTest.MockTestInterfaceImpl"/>
<bean id="impls" class="org.mm.wind.HandlerInterfaceImpl"/>
<bean id="s3" class="org.mm.wind.S3impl">
<property name="names" value="${name}"/>
</bean>
<bean id="handler" class="org.mm.anno.MockWiredHandler"/>
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="location">
<value>classpath:conf/setting.properties</value>
</property>
<property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE" />
<property name="ignoreUnresolvablePlaceholders" value="true"></property>
</bean>
<bean class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="location">
<value>classpath:conf/setting_new.properties</value>
</property>
<property name="systemPropertiesModeName" value="SYSTEM_PROPERTIES_MODE_OVERRIDE" />
<property name="ignoreUnresolvablePlaceholders" value="true"></property>
</bean>
</beans>
这样的配置会有什么结果。结果就是name=hello
而程序的执行流程是怎么样的呢?可能有人会觉得初始化的时候会把properties文件加载到内存然后再初始化bean的属性。
当然我开始的时候也是那么认为。但如果是这样的话,那么后来加载的这个属性问价的那么就会override先加载的name,但
这个和我们的结果却不相匹配。于是debug下。说下这个加载的流程吧:
加载第一个PropertyPlaceholderConfigurer类的时候则扫描所有的bean然后把能赋值属性的都给赋值,当加载第二个属性配置文件
的时候则再次扫描所有的bean然后把能赋值属性的都给赋值。所以这里我们先加载了第一个配置文件的时候就已经给name赋值了。
当第二次加载setting_new.properties这个属性文件的时候。根本就没有属性可以赋值了。
望大家多多指点。
Spring的PropertyPlaceholderConfigurer应用
基本的使用方法是:(1)
<bean id="propertyConfigurerForAnalysis" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="location"> <value>classpath:/spring/include/dbQuery.properties</value> </property> </bean>
其中classpath是引用src目录下的文件写法。
当存在多个Properties文件时,配置就需使用locations了:(2)
<bean id="propertyConfigurer" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="locations"> <list> <value>classpath:/spring/include/jdbc-parms.properties</value> <value>classpath:/spring/include/base-config.properties</value> </list> </property> </bean>
接下来我们要使用多个PropertyPlaceholderConfigurer来分散配置,达到整合多工程下的多个分散的Properties 文件,其配置如下:(3)
<bean id="propertyConfigurerForProject1" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="order" value="1" /> <property name="ignoreUnresolvablePlaceholders" value="true" /> <property name="location"> <value>classpath:/spring/include/dbQuery.properties</value> </property> </bean>
<bean id="propertyConfigurerForProject2" class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <property name="order" value="2" /> <property name="ignoreUnresolvablePlaceholders" value="true" /> <property name="locations"> <list> <value>classpath:/spring/include/jdbc-parms.properties</value> <value>classpath:/spring/include/base-config.properties</value> </list> </property> </bean>
其中order属性代表其加载顺序,而ignoreUnresolvablePlaceholders为是否忽略不可解析的 Placeholder,如配置了多个PropertyPlaceholderConfigurer,则需设置为true
至此你已经了解到了如何使用PropertyPlaceholderConfigurer,如何使用多个Properties文件,以及如何配置多个PropertyPlaceholderConfigurer来分解工程中分散的Properties文件。
hibernate+spring 连接多个数据库,动态切换(多帐套)的实现
在现实应用中,可能我们后台所有连接的数据库可能不止一个,有可能要求一套代码同时可以连接多个数据库,并且要求在多个数据库中动态切换,本人也遇到这样的需求,就把自己的解决方案分享一下,该方法值针对hibernate+spring框架。
说先来说一下,我的这个需求:
先来讲一下帐套的概念:
按我的理解,帐套就是一个帐套就是一个数据库的映射。如后台有数据库a,b,c,那么我就建有一个映射
帐套a ---数据库a
帐套b---数据库b
帐套c---数据库c
用户看到的是帐套a,帐套b,帐套c,但是程序操作的数据库a,数据库b,数据库c,即当用户选择帐套a,程序对应的数据库就是数据库a,当用户选择帐套b,程序对应的数 据库就是b。
需求:
用户再登陆时,用户可以选择要登录的帐套(帐套就是一个队数据库的映射),要同时允许多个用户在登陆不同的帐套,也就是说程序对应的数据库,一直在不停地变化着,因为每个用户选择的帐套不同,程序操作的数据库就不同。
解决思路:
通过对需求分析,可以得到结论就是:每一个用户都操作着一个帐套,每一个帐套都对应着一个数据库,这里也可以说是一个对照表如:
用户1--帐套a
用户2--帐套b
根据帐套和数据库的映射,可以得到
用户1--数据库a
用户2--数据库b
因为我的是web应用,所以每一个用户就是一个会话(session),所以我用session的sessioinId作为用户的唯一标识。当用户在登陆系统时会被要求选择一个帐套,这个帐套会session绑在一起,作为一组键值对,存在放内存中,用户的每一次请求,都会根据sessionid获取到当前这个sessionId对应的帐套,进而获取到对应的数据库,然后将数据库切换为该sessionId 对应的数据。
原理都已经讲清楚,现在只需要实现2点,
1.记录每一个session对应的帐套
2.每一次请求切换对应的数据库
以下,讲一下如何实现这两点
记录每一个session对应的帐套
维护2个map,一个是当前线程map,另一个是sessionIdMap,在登陆时将用户信息和帐套信息写人
public class SpObserver {
/**
*description:
*@author lwq
*/
@SuppressWarnings("unchecked")
private static ThreadLocal local = new ThreadLocal();
@SuppressWarnings("unchecked")
//当前线程---数据库名称
public static void putSp(String dbName) {
local.set(dbName);
}
public static String getSp() {
return (String)local.get();
}
}
public class SessionMap {
/**
*description:
*@author lwq
*/
@SuppressWarnings("unchecked")
private static Map sessionMap=new HashMap();
@SuppressWarnings("unchecked")
public static void put(String sessionId,String dbName){
remove(sessionId);
sessionMap.put(sessionId, dbName);
}
public static void remove(String sessionId){
if(sessionMap.containsKey(sessionId)){
sessionMap.remove(sessionId);
}
}
public static String get(String sessionId){
if(sessionMap.containsKey(sessionId)){
String dbName=(String)sessionMap.get(sessionId);
return dbName;
}else{
return null;
}
}
//可以判断有多少个客户端
public static int getSize(){
return sessionMap.size();
}
}
切换数据库
配置dataSource
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName" value="${jdbc.driverClassName}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
</bean>
<bean id="multiDataSource" class="com.onesun.common.system.MultiDataSource">
<property name="dataSource">
<ref bean="dataSource"/>
</property>
</bean>
在实现一个BasicDataSource,
public class MultiDataSource implements DataSource,ApplicationContextAware{
....
public DataSource getDataSource(){
String sp = SpObserver.getSp();
String dbName=SessionMap.get(sp);
return getDataSource(dbName);
}
public DataSource getDataSource(String dbName) {
System.out.println("数据库:"+dbName);
try{
if(dbName==null||dbName.equals("")){
return this.dataSource;
}
return (DataSource)this.applicationContext.getBean(dbName);
}catch(Exception e ){
try{
return this.loadDataSource(dbName);
}catch(Exception e2){
return this.createDataSource(dbName);
}
}
}
...
}
dbName就是数据库名称,应该还要对应着一个dbName.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"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-2.5.xsd">
<bean id="wxtl" class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://127.0.0.1:3306/dbName"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</bean>
</beans>
以上都只是一个思路,没有完整的代码,希望能给正在寻求解决方案的朋友帮助
spring配置数据源的4种方式简介
在spring中配置数据源这是做项目不可避免的,今天我把了解到的配置方式在这里做个总结。
目前知道4种方式。
1.jdbc
org.springframework.jdbc.datasource.DriverManagerDataSource
2.dbcp
org.apache.commons.dbcp.BasicDataSource
3.c3p0
com.mchange.v2.c3p0.ComboPooledDataSource
4.jndi
org.springframework.jndi.JndiObjectFactoryBean
首先,jdbc建立连接是只要有连接就新建一个connection,根本没有连接池的作用。不常用。
其次,jndi需要在web server中配置数据源,不方便于部署。不推荐。
常用的还是dbcp和c3p0.
这里我给一个有jdbc, dbcp, c3p0 配置数据源的例子。(经过本人测试)。
applicationContext.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:p="http://www.springframework.org/schema/p"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd">
<bean id="propertyConfigurer"
class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<list>
<value>/dataSource.properties</value>
<value>/hibernateProperties.properties</value>
</list>
</property>
</bean>
<!-- spring配置数据源方式1,dbcp -->
<!--
<bean id="dataSource"
class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName"
value="oracle.jdbc.driver.OracleDriver">
</property>
<property name="url"
value="jdbc:oracle:thin:@127.0.0.1:1521:orcl">
</property>
<property name="username" value="system"></property>
<property name="password" value="system"></property>
</bean>
-->
<!-- spring配置数据源方式2,dbcp propertyConfigurer-->
<!--
<bean id="dataSource"
class="org.apache.commons.dbcp.BasicDataSource">
<property name="driverClassName"
value="${connection.driver_class}">
</property>
<property name="url"
value="${connection.url}">
</property>
<property name="username" value="${connection.username}"></property>
<property name="password" value="${connection.password}"></property>
</bean>
-->
<!-- spring配置数据源方式3, c3p0 -->
<!--
<bean id="dataSource"
class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
<property name="driverClass" value="oracle.jdbc.driver.OracleDriver"/>
<property name="jdbcUrl" value="jdbc:oracle:thin:@127.0.0.1:1521:orcl"/>
<property name="user" value="system"/>
<property name="password" value="system"/>
</bean>
-->
<!-- spring配置数据源方式4, c3p0 propertyConfigurer -->
<!--
<bean id="dataSource"
class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
<property name="driverClass" value="${connection.driver_class}"/>
<property name="jdbcUrl" value="${connection.url}"/>
<property name="user" value="${connection.username}"/>
<property name="password" value="${connection.password}"/>
</bean>
-->
<!-- spring配置数据源方式5, jdbc -->
<!--
<bean id="dataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName">
<value>oracle.jdbc.driver.OracleDriver</value>
</property>
<property name="url">
<value>jdbc:oracle:thin:@127.0.0.1:1521:orcl</value>
</property>
<property name="username">
<value>system</value>
</property>
<property name="password">
<value>system</value>
</property>
</bean>
-->
<!-- spring配置数据源方式6, jdbc propertyConfigurer -->
<bean id="dataSource"
class="org.springframework.jdbc.datasource.DriverManagerDataSource">
<property name="driverClassName">
<value>${connection.driver_class}</value>
</property>
<property name="url">
<value>${connection.url}</value>
</property>
<property name="username">
<value>${connection.username}</value>
</property>
<property name="password">
<value>${connection.password}</value>
</property>
</bean>
<bean id="sessionFactory"
class="org.springframework.orm.hibernate3.LocalSessionFactoryBean">
<property name="dataSource">
<ref bean="dataSource" />
</property>
<!-- spring配置hibernate属性,方式一,使用hibernate.cfg.xml文件 -->
<!--
<property name="configLocation" value="classpath:hibernate.cfg.xml"></property>
-->
<!-- spring配置hibernate属性,方式二,直接配置属性值 -->
<!--
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">org.hibernate.dialect.OracleDialect</prop>
<prop key="hibernate.format_sql">true</prop>
<prop key="hibernate.show_sql">true</prop>
<prop key="hibernate.connection.pool_size">3</prop>
</props>
</property>
<property name="mappingResources">
<list>
<value>event.hbm.xml</value>
<value>person.hbm.xml</value>
</list>
</property>
-->
<!-- spring配置hibernate属性,方式三,配置助手propertyConfigurer使用properties文件 -->
<property name="hibernateProperties">
<props>
<prop key="hibernate.dialect">${dialect}</prop>
<prop key="hibernate.format_sql">${format_sql}</prop>
<prop key="hibernate.show_sql">${show_sql}</prop>
<prop key="hibernate.connection.pool_size">${connection.pool_size}</prop>
</props>
</property>
<property name="mappingResources">
<list>
<value>event.hbm.xml</value>
<value>person.hbm.xml</value>
</list>
</property>
</bean>
</beans>
其中涉及到的properties文件有:
dataSource.properties
connection.url=jdbc:oracle:thin:@127.0.0.1:1521:orcl
connection.username=system
connection.password=system
connection.driver_class=oracle.jdbc.driver.OracleDriver
hibernateProperties.properties
dialect=org.hibernate.dialect.OracleDialect
format_sql=true
show_sql=true
connection.pool_size=3
大家注意到了,
class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer">
<property name="locations">
<list>
<value>/dataSource.properties</value>
<value>/hibernateProperties.properties</value>
</list>
</property>
</bean>
可以取到了。这其实并没有改变连接池的本质,还属于同一种连接方法。
本例主要讲解dataSource的配置。