spring-jdbc-RoutingDataSource

spring jdbc 提供了抽象类AbstractRoutingDataSource,来提供切换多数据源功能

应对场景:在一个项目中要与多个数据库打交道,尤其大项目,更要考虑垂直切分业务,以存储数据到不同的库。

spring 基于jdbc 的 DataSource ,提供了对选择数据库做路由切换的功能。

案例:下面的案例很好的实现了一个巧妙切库的功能,思路简单明了,另外很好的结合了spring aop的功能,使用方便简洁。

需求:
1、数据库common 库,存储了通用信息,仅有一个通用库。
2、代理商库customer库。每个代理商存储各自的独立信息,有多个代理商库。

现在我们要写一个IClientShardDao 来取common 库一个表中的数据和一个ILoginLogDao 来取customer 库中一个表的数据。

分析与实现:
1、DAO层,我们定义统一的JdbcTemplate 来操作数据,但对其的dataSource 的配置,采用 AbstractRoutingDataSource 的一个实现 DBRoutingDataSource来实现,而DBRoutingDataSource 又根据 DBContext 来确定当前操作的应该是那个dataSource ,DBContext 是一个 通过ThreadLocal 来实现的线程上下文安全的实例。
2、DBContext 信息的设置,可以考虑结合Spring AOP。对不同的DAO 操纵不同的库,可以手动设置DBContext,很灵活,但是AOP可以更规范也更方便简洁的在方法调用的前设定数据库的相关信息。


配置和代码:

1、数据源的配置(applicationContext-datasource.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:aop="http://www.springframework.org/schema/aop" xmlns:tx="http://www.springframework.org/schema/tx"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context-3.0.xsd">

<bean id="parentDataSource" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close" abstract="true">
<property name="driverClassName" value="${jdbc.driverClassName}" />
<property name="timeBetweenEvictionRunsMillis" value="60000" /> <!-- 每隔毫秒秒检查一次连接池中空闲的连接 -->
<property name="minEvictableIdleTimeMillis" value="600000" /> <!-- 连接池中连接可空闲的时间,毫秒 -->
<property name="removeAbandoned" value="true" /> <!-- 是否清理removeAbandonedTimeout秒没有使用的活动连接,清理后并没有放回连接池 -->
<property name="removeAbandonedTimeout" value="60" /> <!-- 活动连接的最大空闲时间 -->
<property name="minIdle" value="10" /> <!-- 最小空闲连接数 -->
<property name="maxWait" value="60000" /> <!-- 最大等待时间,当没有可用连接时,连接池等待连接释放的最大时间 -->
</bean>
<!-- common库的数据源 -- >
<bean id="comDataSource" parent="parentDataSource">
<property name="url" value="${jdbc.com.url}" />
<property name="username" value="${jdbc.com.username}" />
<property name="password" value="${jdbc.com.password}" />
</bean>
<!-- customer库的数据源-->
<bean id="cusDataSource" parent="parentDataSource">
<property name="url" value="${jdbc.cus.url}" />
<property name="username" value="${jdbc.cus.username}" />
<property name="password" value="${jdbc.cus.password}" />
</bean>


<!-- 配置路由切换数据源 -->
<bean id="dataSource" class="com.job.db.dbswitch.DBRoutingDataSource">
<property name="targetDataSources">
<map key-type="java.lang.String">
<entry key="${jdbc.com.key}" value-ref="comDataSource" />
<entry key="${jdbc.cus.key}" value-ref="cusDataSource" />
</map>
</property>
</bean>


<bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate">
<constructor-arg ref="dataSource"/>
</bean>
<!-- 启动AOP -->
<aop:aspectj-autoproxy/>

</beans>


2、spring 启动的配置和扫描包

<context:property-placeholder location="classpath*:*.properties" />
<context:component-scan base-package="com.job.service,
com.job.dao,com.job.db" />
<import resource="applicationContext-datasource.xml"/>


jdbc.properties 的内容:

jdbc.driverClassName=com.mysql.jdbc.Driver

jdbc.com.key=com
jdbc.com.url=jdbc:mysql://comip:3306/db?useUnicode=true&characterEncoding=utf8
jdbc.com.username=comuser
jdbc.com.password=compassword

jdbc.cus.key=cus
jdbc.cus.url=jdbc:mysql://cusip:3306/?useUnicode=true&characterEncoding=utf8
jdbc.cus.username=cususer
jdbc.cus.password=cuspassword



3、DBRoutingDataSource 的实现。

package com.job.db.dbswitch;

import java.sql.Connection;
import java.sql.SQLException;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

import com.job.vo.ClientShard;

/**
* @author wangxinchun1988@163.com
* @date 2014-7-11下午4:47:09
*/
public class DBRoutingDataSource extends AbstractRoutingDataSource {

private static Logger log = LoggerFactory.getLogger(DBRoutingDataSource.class);

public boolean isWrapperFor(Class arg0) throws SQLException {
return false;
}

public Object unwrap(Class arg0) throws SQLException {
return null;
}

public Object determineCurrentLookupKey() {
return DBContext.getDBKey();
}

@Override
public Connection getConnection() throws SQLException{
Connection con = super.getConnection();
changeUser(con);
return con;
}

@Override
public Connection getConnection(String username, String password) throws SQLException{
Connection con = super.getConnection(username, password);
changeUser(con);
return con;
}

/**
* 重要,在MYSQLD中使用use xxx切换数据库。
*/
private void changeUser(Connection con) {
ClientShard cs = DBContext.getCust();
if (DBContext.getDBKey()!=null
&& !DBContext.getDBKey().equals("com")){
if (DBContext.getCust().getId()!=null){
try {
con.createStatement().execute("use `"+DBContext.getCust().getDatabaseName()+"`");
} catch (SQLException e) {
log.error("change db error!!! {}", cs, e);
e.printStackTrace();
try {
if(con!=null&&!con.isClosed()){
con.close();
}
} catch (SQLException e1) {
log.error("close db error {}", cs, e1);
}
}
}
}
}

public java.util.logging.Logger getParentLogger() {
return null;
}

}

4、DBContext 的信息。

package com.job.db.dbswitch;
import com.job.vo.ClientShard;

public class DBContext {
private static final ThreadLocal<String> DBKeyl = new ThreadLocal<String>();
private static final ThreadLocal<ClientShard> custDBl = new ThreadLocal<ClientShard>();

public static String getDBKey(){
return DBKeyl.get();
}

public static void setDBKey(String dbKey){
DBKeyl.set(dbKey);
}

public static void clearDBKey(){
DBKeyl.remove();
}

public static void setCust(ClientShard cust){
custDBl.set(cust);
}

public static ClientShard getCust(){
return custDBl.get();
}

public static void releaseAll(){
DBKeyl.remove();
custDBl.remove();
}
}

5、 AOP对DBContext 的dbkey的设置。

package com.job.db.dbswitch;

import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.springframework.stereotype.Component;
/**
* DbSwitchAop
* @author wangxinchun1988@163.com
* @date 2014-7-11下午12:02:30
*/
@Aspect
@Component
public class DbSwitchAop {
// 使用common数据库
@Before("target(com.job.db.dbmark.ICommonDBMark)")
public void commonMethodBefore() {
DBContext.setDBKey("com");
}

// 使用customer数据库
@Before("target(com.job.db.dbmark.ICustomerDBMark)")
public void customerDaoMethodBefore() {
DBContext.setDBKey("cus");
}
}


package com.job.db.dbmark;

/**
* 通用db(单库)
* @author wangxinchun1988@163.com
* @date 2014-7-10下午7:59:23
*/
public interface ICommonDBMark {
}



/**
* 自定义db(多库)
* @author wangxinchun1988@163.com
* @date 2014-7-10下午7:59:11
*/
public interface ICustomerDBMark {

}


6、具体dao的实现。

package com.job.dao;

import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Repository;

import com.job.db.dbmark.ICustomerDBMark;
import com.job.db.dbswitch.DBContext;
import com.job.vo.ClientShard;
import com.job.vo.LoginLog;

/**
* 关键是对ICustomerDBMark 的实现,使其具有方法调用前,对DBContext设置相关dbkey信息
* @author wangxinchun1988@163.com
* @date 2014-7-11下午5:23:38
*/
@Repository
public class LoginLogDaoImpl implements ILoginLogDao,ICustomerDBMark{
@Autowired
private JdbcTemplate jdbcTemplate;
public List<LoginLog> getLoginLogList(ClientShard shard) {
String sql = "SELECT id, username, ip, login_time as loginTime FROM login_log limit 1";
DBContext.setCust(shard);
List<LoginLog> retList = jdbcTemplate.query(sql, new RowMapper<LoginLog>(){
public LoginLog mapRow(ResultSet rs, int rowNum)
throws SQLException {
LoginLog item = new LoginLog();
item.setId(rs.getLong("id"));
item.setUsername(rs.getString("username"));
item.setIp(rs.getString("ip"));
item.setLoginTime(rs.getDate("loginTime"));
return item;
}

});
System.out.println(retList);
return retList;
}
}


/**
* 对ICommonDBMark接口的实现非常重要,这是aop对DBContxt设置dbkey的途径
* @author wangxinchun1988@163.com
* @date 2014-7-11下午5:24:53
*/
@Repository
public class ClientShardDaoImpl implements IClientShardDao,ICommonDBMark {
@Autowired
private JdbcTemplate jdbcTemplate;

public List<ClientShard> queryDataList() {
DBContext.setDBKey("com");
String sql = "select id,host,port,databaseName,clientId,config,order_prefix as orderPrefix from client_shard limit 10";
List<ClientShard> dataList = jdbcTemplate.query(sql, new RowMapper<ClientShard>(){

public ClientShard mapRow(ResultSet rs, int rowNum)
throws SQLException {
ClientShard item = new ClientShard();
item.setId(rs.getLong("id"));
item.setClientId(rs.getString("clientId"));
item.setOrderPrefix(rs.getString("orderPrefix"));
item.setDatabaseName(rs.getString("databaseName"));
return item ;
}

});
System.out.println(dataList);
return dataList;
}
}


详细代码见附件~
  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值