`

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;
	}
}


详细代码见附件~

2
0
分享到:
评论
6 楼 wxyvgo 2014-08-28  
写的不错,借鉴了!
正好有类型的需求要实现。
5 楼 yunzhu 2014-07-23  
王新春 写道
evanzzy 写道
王新春 写道
evanzzy 写道
往基类Dao里面多注入几个数据源不就好了么,怎么写这么多

多个templete 会很乱的,现在统一了,更方便


多个template或一个template都可以实现多数据源,只不过多个来的更直观而已,用选择方法进行选择调用就可以。

你这个DBRoutingDataSource里面关闭connection连Finally都没用,也没有用现成的Spring的Connection管理方法,连接池的选用也有问题,还用了aop拦截降低代码执行效率,dao的具体实现也是硬编码,最关键的多数据源的事务处理也没有提到,你这篇文章基本上讲是一无是处,完全没用。

希望你把基本功学扎实了,把Spring和JdbcTemplate相关的方法源代码都看了再考虑这些“发明创造”的事情。


1、DBRoutingDataSource 里的 connection 需要关闭吗?
2、连接池的选用也有问题:什么问题,请指教!
3、aop拦截降低代码执行效率: 有些项目 可以在效率和便捷性之间做妥协的吧,我不认为aop拦截相对于数据库连接会有很大的性能问题,当然你可以这么认为。
4、dao的具体实现也是硬编码 :dao的实现是case,例子本身就是case,没有打算面面具到。
5、这篇文章基本上讲是一无是处:谢谢你.
6、希望你把基本功学扎实了,把Spring和JdbcTemplate相关的方法源代码都看了再考虑这些“发明创造”的事情:谢谢你,没有创造,只是把工作中实际项目中的用法拿出来分享思路,没有必要面面俱到。


不要理这个人,估计心情不好
4 楼 王新春 2014-07-12  
evanzzy 写道
王新春 写道
evanzzy 写道
往基类Dao里面多注入几个数据源不就好了么,怎么写这么多

多个templete 会很乱的,现在统一了,更方便


多个template或一个template都可以实现多数据源,只不过多个来的更直观而已,用选择方法进行选择调用就可以。

你这个DBRoutingDataSource里面关闭connection连Finally都没用,也没有用现成的Spring的Connection管理方法,连接池的选用也有问题,还用了aop拦截降低代码执行效率,dao的具体实现也是硬编码,最关键的多数据源的事务处理也没有提到,你这篇文章基本上讲是一无是处,完全没用。

希望你把基本功学扎实了,把Spring和JdbcTemplate相关的方法源代码都看了再考虑这些“发明创造”的事情。


1、DBRoutingDataSource 里的 connection 需要关闭吗?
2、连接池的选用也有问题:什么问题,请指教!
3、aop拦截降低代码执行效率: 有些项目 可以在效率和便捷性之间做妥协的吧,我不认为aop拦截相对于数据库连接会有很大的性能问题,当然你可以这么认为。
4、dao的具体实现也是硬编码 :dao的实现是case,例子本身就是case,没有打算面面具到。
5、这篇文章基本上讲是一无是处:谢谢你.
6、希望你把基本功学扎实了,把Spring和JdbcTemplate相关的方法源代码都看了再考虑这些“发明创造”的事情:谢谢你,没有创造,只是把工作中实际项目中的用法拿出来分享思路,没有必要面面俱到。
3 楼 evanzzy 2014-07-12  
王新春 写道
evanzzy 写道
往基类Dao里面多注入几个数据源不就好了么,怎么写这么多

多个templete 会很乱的,现在统一了,更方便


多个template或一个template都可以实现多数据源,只不过多个来的更直观而已,用选择方法进行选择调用就可以。

你这个DBRoutingDataSource里面关闭connection连Finally都没用,也没有用现成的Spring的Connection管理方法,连接池的选用也有问题,还用了aop拦截降低代码执行效率,dao的具体实现也是硬编码,最关键的多数据源的事务处理也没有提到,你这篇文章基本上讲是一无是处,完全没用。

希望你把基本功学扎实了,把Spring和JdbcTemplate相关的方法源代码都看了再考虑这些“发明创造”的事情。
2 楼 王新春 2014-07-12  
evanzzy 写道
往基类Dao里面多注入几个数据源不就好了么,怎么写这么多

多个templete 会很乱的,现在统一了,更方便
1 楼 evanzzy 2014-07-12  
往基类Dao里面多注入几个数据源不就好了么,怎么写这么多

相关推荐

    spring-boot2.0多数据源

    `spring-boot-starter-jdbc`和`spring-boot-starter-data-jpa`等启动器是实现多数据源的关键依赖,它们包含了连接池、JDBC和JPA等组件。 综上所述,Spring Boot 2.0多数据源功能允许开发者灵活地管理和切换多个...

    spring配置多数据源jdbc

    在Spring框架中,配置多数据源JDBC是一项常见的任务,特别是在构建分布式系统或者需要处理多个数据库的应用中。这里我们将深入探讨如何在Spring中实现这一功能,以及涉及的相关知识点。 首先,我们要理解数据源...

    Spring配置三种数据源及从属性文件中读取DB连接四要素

    Spring提供了`org.springframework.jdbc.datasource.DriverManagerDataSource`类,可以直接通过Java代码配置。但在实际项目中,通常我们会使用XML或Java配置来创建一个Bean,如下所示: ```xml ...

    springboot动态数据源.zip

    然而,当我们需要动态数据源时,需要引入额外的库,如`spring-boot-starter-jdbc`和`spring-boot-starter-aop`,这两个库分别用于JDBC支持和面向切面编程(AOP),这是实现动态数据源的关键组件。 接下来,我们需要...

    spring多数据源

    &lt;bean id="transactionManagerSecondary" class="org.springframework.jdbc.datasource.DataSourceTransactionManager"&gt; &lt;bean id="dynamicTransactionManager" class="org.springframework.jdbc.datasource....

    Spring+MyBatis多数据源配置实现示例

    Spring 框架结合 MyBatis 持久层框架,提供了灵活的数据源配置方案,使得我们可以根据业务需求切换或同时操作不同的数据库。下面将详细介绍如何在 Spring 和 MyBatis 配置中实现多数据源。 首先,我们需要准备两个...

    spring boot多数据源自动路由

    spring.datasource.dataSource1.url=jdbc:mysql://localhost:3306/db1 spring.datasource.dataSource1.username=root spring.datasource.dataSource1.password=root spring.datasource.dataSource2.url=jdbc:mysql:...

    spring整合quartz

    &lt;bean id="routingDataSource" class="org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource"&gt; &lt;map key-type="java.lang.String"&gt; &lt;entry key="primary" value-ref="dataSource"/&gt; ...

    spring动态选择数据源

    数据源(DataSource)是一个接口,它是Java的JDBC API的一部分,用于存储数据库连接信息并管理与数据库的连接。在Spring中,我们可以使用如Apache Commons DBCP或HikariCP等第三方库来实现数据源。 Spring动态数据...

    Spring+MyBatis多数据源配置实现

    在Spring中,我们可以使用`org.springframework.jdbc.datasource.DriverManagerDataSource`或`com.zaxxer.hikari.HikariDataSource`等来创建数据源。 配置多数据源时,我们通常会使用Spring的`...

    SpringBoot多数据源配置(方式三:基于AOP切面动态切换需要使用哪个数据源).docx

    - 实现`RoutingDataSource`接口,重写`determineCurrentLookupKey()`方法,根据上下文中的数据源标识符来决定使用哪个数据源。 ```java @Configuration public class DynamicDataSource extends ...

    一套Spring+Hibernate的多个数据库切换的源码

    1. **配置多个数据源**:在Spring的配置文件中,为每个数据库创建一个DataSource bean,例如`dataSource1`和`dataSource2`,并配置相应的JDBC连接信息。 2. **数据源路由**:引入Spring的AbstractRoutingDataSource...

    spring多数据源 创建 切换使用

    Spring框架提供了一种强大的机制来支持多数据源的配置与动态切换,帮助开发者解决这一问题。本文将详细阐述如何在Spring中创建、配置以及动态地切换多数据源。 首先,我们需要理解什么是多数据源。简单来说,多数据...

    Springboot mybatis多数据源配置项目实例

    spring.datasource.url=jdbc:mysql://localhost:3306/datasource1 spring.datasource.username=root spring.datasource.password=root spring.datasource.driver-class-name=com.mysql.jdbc.Driver ``` 在多数据源...

    springboot mybatis 集成多数据源 两种实现方式

    首先,我们需要创建一个自定义的`RoutingDataSource`,并实现`determineCurrentLookupKey`方法来确定当前的数据源: ```java @Configuration public class DynamicDataSourceConfig { @Autowired private Map, ...

    多元数据库配置

    spring.datasource.primary.url=jdbc:mysql://localhost:3306/master_db spring.datasource.primary.username=root spring.datasource.primary.password=root spring.datasource.primary.driver-class-name=...

    springboot集成多数据源

    spring.datasource.db1.url=jdbc:mysql://localhost:3306/db1 spring.datasource.db1.username=root spring.datasource.db1.password=password spring.datasource.db1.driver-class-name=com.mysql.jdbc.Driver ...

    springboot mybatis 多数据源 两种实现

    spring.datasource.primary.url=jdbc:mysql://localhost:3306/db1 spring.datasource.primary.username=root spring.datasource.primary.password=root spring.datasource.primary.driver-class-name=...

    数据库主从->读写分离

    routingDataSource.setTargetDataSources(Maps.newHashMap(DataSourceNames.MASTER, masterDataSource(), DataSourceNames.SLAVE, slave1DataSource())); routingDataSource.setDefaultTargetDataSource...

Global site tag (gtag.js) - Google Analytics