hibernate 动态切换数据库
2018-07-09 本文已影响9人
一个冬季
学习参考网址
Spring MVC 动态切换数据库
动态切换数据源
切换数据库+ThreadLocal+AbstractRoutingDataSource 一这篇说的很好
配置
最近有个需求是这样的,APP切换不同的地址,后台服务器呢都是相同的代码,不同点是后台数据库地址不同。
<!-- 配置数据源 c3p0 重点-->
<bean id="dataSource1" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<!-- 基本信息 -->
<property name="jdbcUrl" value="${jdbcUrl}"></property>
<property name="driverClass" value="${driverClassName}"></property>
<property name="user" value="${jdbc_username}"></property>
<property name="password" value="${jdbc_password}"></property>
<!-- 其他配置 -->
<!--连接池中保留的最大连接数。Default: 15 -->
<property name="maxPoolSize" value="50"></property>
<!--当连接池中的连接耗尽的时候c3p0一次同时获取的连接数。Default: 10 -->
<property name="acquireIncrement" value="10"></property>
<!-- 控制数据源内加载的PreparedStatements数量。如果maxStatements与maxStatementsPerConnection均为0,则缓存被关闭。Default:
0 -->
<property name="maxStatements" value="8"></property>
<!-- maxStatementsPerConnection定义了连接池内单个连接所拥有的最大缓存statements数。Default:
0 -->
<property name="maxStatementsPerConnection" value="10"></property>
<!--最大空闲时间,300秒内未使用则连接被丢弃。若为0则永不丢弃。Default: 0 -->
<property name="maxIdleTime" value="120"></property>
</bean>
<!-- 配置第二个数据源 重点-->
<bean id="dataSource2" class="com.mchange.v2.c3p0.ComboPooledDataSource">
<!-- 基本信息 -->
<property name="jdbcUrl" value="${xiaomi_jdbcUrl}"></property>
<property name="driverClass" value="${driverClassName}"></property>
<property name="user" value="${jdbc_username}"></property>
<property name="password" value="${jdbc_password}"></property>
<!-- 其他配置 -->
<!--连接池中保留的最大连接数。Default: 15 -->
<property name="maxPoolSize" value="50"></property>
<!--当连接池中的连接耗尽的时候c3p0一次同时获取的连接数。Default: 10 -->
<property name="acquireIncrement" value="10"></property>
<!-- 控制数据源内加载的PreparedStatements数量。如果maxStatements与maxStatementsPerConnection均为0,则缓存被关闭。Default:
0 -->
<property name="maxStatements" value="8"></property>
<!-- maxStatementsPerConnection定义了连接池内单个连接所拥有的最大缓存statements数。Default:
0 -->
<property name="maxStatementsPerConnection" value="10"></property>
<!--最大空闲时间,300秒内未使用则连接被丢弃。若为0则永不丢弃。Default: 0 -->
<property name="maxIdleTime" value="120"></property>
</bean>
<!-- mysql 动态数据源设置 重点-->
<bean id="mysqlDynamicDataSource" class="com.gao.utils.DynamicDataSource">
<property name="targetDataSources">
<!-- 标识符类型 -->
<map key-type="com.gao.utils.DBType">
<entry key="dataSource1" value-ref="dataSource1"/>
<entry key="dataSource2" value-ref="dataSource2"/>
</map>
</property>
<!-- 默认使用的数据源 重点-->
<property name="defaultTargetDataSource" ref="dataSource1"/>
</bean>
<!-- 配置hibernate的sessionFactory,并让spring的ioc进行管理 -->
<bean id="sessionFactory" class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
<!-- 配置数据源属性 重点 -->
<property name="dataSource" ref="mysqlDynamicDataSource"></property>
<!-- 引入hibernate的属性配置文件 -->
<property name="configLocation" value="classpath:hibernate.cfg.xml"></property>
<!-- 扫描实体类,将其映射为具体的数据库表 -->
<property name="packagesToScan" value="com.gao.model"></property>
</bean>
上面配置了2个数据源分别是dataSource1,dataSource2,当然了里面的jdbcUrl是不同的。配置完2个数据源后,我又配置了DynamicDataSource,这个是自己定义的一个类,DBType也是自己定义的一个类,再写一个默认的数据库源,最后就是配置sessionFactory
DynamicDataSource.java
package com.gao.utils;
import java.util.logging.Logger;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
/**
* 创建动态数据源类,继承org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource这个类.
* @author Gao
*
*/
public class DynamicDataSource extends AbstractRoutingDataSource {
public static final Logger logger = Logger.getLogger(DynamicDataSource.class.toString());
@Override
protected Object determineCurrentLookupKey() {
DBType key = ContextHolder.getDbType();//获得当前数据源标识符
logger.info("当前数据源 :" + key);
return key;
}
}
DBType.java
package com.gao.utils;
/**
* 切换数据源需要标识符,标识符是Object类型
* @author Gao
*
*/
public enum DBType {
dataSource1, dataSource2;
}
ContextHolder.java 切换线程的类
package com.gao.utils;
/**
* 创建一个用于切换数据源(设置或者获得上下文)的工具类
* @author Gao
*
*/
public class ContextHolder {
private static final ThreadLocal<Object> holder = new ThreadLocal<Object>();
/**
* 提供给AOP去设置当前的线程的数据源的信息
* 切换数据库
* @param dbType 数据库名-配置文件中一致
*/
public static void setDbType(DBType dbType) {
try {
holder.set(dbType);
} catch (Exception e) {
e.printStackTrace();
}
}
/**
* 提供给AbstractRoutingDataSource的实现类,通过key选择数据源
* @return java.lang.String
*/
public static DBType getDbType() {
return (DBType) holder.get();
}
public static void clearDbType() {
holder.remove();
}
}
使用方法:我就拿获取用户信息来说,在m层切换数据源
@RequestMapping("/user")
@Controller
public class UserController {
@Autowired
private UserService userService;
@ResponseBody
@RequestMapping("/getUserMessage")
public Map<String, Object> userMessage(@RequestParam("type")String type) throws Exception{
Map<String, Object> resultMap=new HashMap<String, Object>();
//切换
if(type.equals("leixing01")){
ContextHolder.setDbType(DBType.dataSource1);
}else{
// 切换到数据源 dataSource2
ContextHolder.setDbType(DBType.dataSource2);
}
resultMap.put("data", userService.getAllUser("aaa"));
return resultMap;
}
}
切换地址要在controller层切换。
额外补充点知识:
DynamicDataSource 继承 AbstractRoutingDataSource
AbstractRoutingDataSource类可以理解为DataSource的路由中介,可以通过它来切换数据库前面我们继承了AbstractRoutingDataSource并且重写了determineCurrentLookupKey()方法切换数据库
@Override
public Connection getConnection() throws SQLException {
return determineTargetDataSource().getConnection();
}
protected DataSource determineTargetDataSource() {
Assert.notNull(this.resolvedDataSources, "DataSource router not initialized");
Object lookupKey = determineCurrentLookupKey();//注意这里
//获取key,根据key切换地址,resolvedDataSources通过在xml配置获取到
DataSource dataSource = this.resolvedDataSources.get(lookupKey);
if (dataSource == null && (this.lenientFallback || lookupKey == null)) {
dataSource = this.resolvedDefaultDataSource;
}
if (dataSource == null) {
throw new IllegalStateException("Cannot determine target DataSource for lookup key [" + lookupKey + "]");
}
return dataSource;
}
//切换数据库
protected abstract Object determineCurrentLookupKey();
里面有个resolvedDataSources,它是一个map类型的
public void setTargetDataSources(Map<Object, Object> targetDataSources) {
this.targetDataSources = targetDataSources;
}
@Override
public void afterPropertiesSet() {
if (this.targetDataSources == null) {
throw new IllegalArgumentException("Property 'targetDataSources' is required");
}
this.resolvedDataSources = new HashMap<Object, DataSource>(this.targetDataSources.size());
for (Map.Entry<Object, Object> entry : this.targetDataSources.entrySet()) {
Object lookupKey = resolveSpecifiedLookupKey(entry.getKey());
DataSource dataSource = resolveSpecifiedDataSource(entry.getValue());
this.resolvedDataSources.put(lookupKey, dataSource);
}
if (this.defaultTargetDataSource != null) {
this.resolvedDefaultDataSource = resolveSpecifiedDataSource(this.defaultTargetDataSource);
}
}
上面的targetDataSources的赋值在这里实现
<!-- mysql 动态数据源设置-->
<bean id="mysqlDynamicDataSource" class="com.gao.utils.DynamicDataSource">
<property name="targetDataSources">
<!-- 标识符类型 -->
<map key-type="com.gao.utils.DBType">
<entry key="dataSource1" value-ref="dataSource1"/>
<entry key="dataSource2" value-ref="dataSource2"/>
</map>
</property>
<!-- 默认使用的数据源 -->
<property name="defaultTargetDataSource" ref="dataSource1"/>
</bean>
ThreadLocal类的目的:为每个线程创建独立的局部变量副本,线程之间的ThradLocal互不影响(不同线程使用的不同的数据库,互补影响,线程安全)。
public T get() {
Thread t = Thread.currentThread();//当前线程
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null)
return (T)e.value;
}
return setInitialValue();
}
public void set(T value) {
Thread t = Thread.currentThread();//当前线程
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}