Spring JDBC_多数据源和事务的配置
直接帖代码:
maven项目,以下是pom:
<dependencies>
<dependency>
<groupId>org.apache.tomcat</groupId>
<artifactId>tomcat-jdbc</artifactId>
<version>8.0.14</version>
</dependency>
<!--spring context -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context</artifactId>
<version>4.1.1.RELEASE</version>
</dependency>
<!--spring core -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-core</artifactId>
<version>4.1.1.RELEASE</version>
</dependency>
<!--spring bean -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-beans</artifactId>
<version>4.1.1.RELEASE</version>
</dependency>
<!--spring aop -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-aop</artifactId>
<version>4.1.1.RELEASE</version>
</dependency>
<!--spring jdbc -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-jdbc</artifactId>
<version>4.1.1.RELEASE</version>
</dependency>
<!-- spring tx -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-tx</artifactId>
<version>4.1.1.RELEASE</version>
</dependency>
<!-- spring test -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-test</artifactId>
<version>4.1.1.RELEASE</version>
</dependency>
<!-- spring-batch-core -->
<dependency>
<groupId>org.springframework.batch</groupId>
<artifactId>spring-batch-core</artifactId>
<version>3.0.2.RELEASE</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.10</version>
<scope>test</scope>
</dependency>
<!--aspectj -->
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjrt</artifactId>
<version>1.8.2</version>
</dependency>
<dependency>
<groupId>org.aspectj</groupId>
<artifactId>aspectjweaver</artifactId>
<version>1.8.2</version>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>5.1.6</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>3.8.1</version>
<scope>test</scope>
</dependency>
</dependencies>
主要说一下我的思路:
首先配置多个数据源,继承spring 的AbstractRoutingDataSource 实现管理多数据源
然后考虑怎么实现动态选择多数据源,使用ThreadLocal,在线程中传递参数,通过对应的数据源的key动态的选择多数据源
以上就是基本思路,但还要考虑如何管理事务。这里考虑使用@Transactional标签管理事务,当你配置好事务,并且在代码中动态选择数据源时,发现根本不好使,原因就是你开启了事务,早在你动态选择数据源之前,开启的事务就已经连接了数据库,等到执行具体的sql语句时,选择的数据源总是事务中连接的数据库,下面是DataSourceUtils.java中的一段代码,通过该代码,你可以看到具体是如何选择数据库连接的:
public static Connection doGetConnection(DataSource dataSource) throws SQLException {
Assert.notNull(dataSource, "No DataSource specified");
ConnectionHolder conHolder = (ConnectionHolder) TransactionSynchronizationManager.getResource(dataSource);
if (conHolder != null && (conHolder.hasConnection() || conHolder.isSynchronizedWithTransaction())) {
conHolder.requested();
if (!conHolder.hasConnection()) {
logger.debug("Fetching resumed JDBC Connection from DataSource");
conHolder.setConnection(dataSource.getConnection());
}
return conHolder.getConnection();
}
// Else we either got no holder or an empty thread-bound holder here.
logger.debug("Fetching JDBC Connection from DataSource");
Connection con = dataSource.getConnection();
if (TransactionSynchronizationManager.isSynchronizationActive()) {
logger.debug("Registering transaction synchronization for JDBC Connection");
// Use same Connection for further JDBC actions within the transaction.
// Thread-bound object will get removed by synchronization at transaction completion.
ConnectionHolder holderToUse = conHolder;
if (holderToUse == null) {
holderToUse = new ConnectionHolder(con);
}
else {
holderToUse.setConnection(con);
}
holderToUse.requested();
TransactionSynchronizationManager.registerSynchronization(
new ConnectionSynchronization(holderToUse, dataSource));
holderToUse.setSynchronizedWithTransaction(true);
if (holderToUse != conHolder) {
TransactionSynchronizationManager.bindResource(dataSource, holderToUse);
}
}
return con;
}
好了,思路就又来了,既然在开启事务的时候就选择了数据源,那么必须要在事务开启之前选择数据源:很简单的做法,其实就是在开启事务的方法上加上切面,实现动态选择数据源。
下面是spring的配置文件。
<?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:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
xsi:schemaLocation="http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd
http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd
http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<aop:aspectj-autoproxy />
<!-- 定义包的扫描规则 -->
<context:component-scan base-package="com.lyx">
<context:include-filter type="annotation"
expression="org.springframework.stereotype.Service" />
<context:include-filter type="annotation"
expression="org.springframework.stereotype.Repository" />
<context:include-filter type="annotation"
expression="org.springframework.stereotype.Component" />
</context:component-scan>
<!--tomcat jdbc pool数据源配置 -->
<bean id="localDataSource" class="org.apache.tomcat.jdbc.pool.DataSource"
destroy-method="close">
<property name="poolProperties">
<bean class="org.apache.tomcat.jdbc.pool.PoolProperties">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url"
value="jdbc:mysql://10.255.9.79:3306/local_database" />
<property name="username" value="root" />
<property name="password" value="034039" />
</bean>
</property>
</bean>
<!--tomcat jdbc pool数据源配置 -->
<bean id="threadDataSource" class="org.apache.tomcat.jdbc.pool.DataSource"
destroy-method="close">
<property name="poolProperties">
<bean class="org.apache.tomcat.jdbc.pool.PoolProperties">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url"
value="jdbc:mysql://10.255.9.79:3306/thread_database" />
<property name="username" value="root" />
<property name="password" value="034039" />
</bean>
</property>
</bean>
<!--tomcat jdbc pool数据源配置 -->
<bean id="remoteDataSource" class="org.apache.tomcat.jdbc.pool.DataSource"
destroy-method="close">
<property name="poolProperties">
<bean class="org.apache.tomcat.jdbc.pool.PoolProperties">
<property name="driverClassName" value="com.mysql.jdbc.Driver" />
<property name="url"
value="jdbc:mysql://10.255.9.79:3306/remote_database" />
<property name="username" value="root" />
<property name="password" value="034039" />
</bean>
</property>
</bean>
<!--使用枚举类型作为key -->
<bean id="dataSource" class="com.lyx.DynamicDataSource">
<property name="targetDataSources">
<map key-type="com.lyx.DataSourceLookupKey">
<entry key="REMOTE_DATASOURCE" value-ref="remoteDataSource" />
<entry key="THREAD_DATASOURCE" value-ref="threadDataSource" />
<entry key="LOCAL_DATASOURCE" value-ref="localDataSource" />
</map>
</property>
<property name="defaultTargetDataSource" ref="localDataSource" />
</bean>
<!-- spring的事务管理器 -->
<bean id="transactionManager"
class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
<!-- 动态选择数据源的切面 -->
<bean id = "determineDataSourceAspect" class="com.lyx.DetermineDataSourceAspect"/>
<!-- 切面的配置 -->
<aop:config>
<aop:aspect id="determineDataSourceAspect" ref="determineDataSourceAspect">
<aop:pointcut id="dataSourcePoint"
expression="@annotation(com.lyx.RequireDataSource)" />
<aop:before method="determineDataSource" pointcut-ref="dataSourcePoint" />
</aop:aspect>
</aop:config>
<tx:annotation-driven transaction-manager="transactionManager" />
</beans>
看到这里,你可能会问,为什么没有使用aspect的风格来配置切面,实现aop,答案就是那种方式不好用,@Transactional代理比我配置的注解的代理要提前一步,导致动态选择数据源的切面发挥不了作用。而这种方式就可以很好的工作。。
相关的类:
DataSourceLookupKey.java
package com.lyx;
/**
*
* @author Lenovo
*/
public enum DataSourceLookupKey {
LOCAL_DATASOURCE("本地数据库"), REMOTE_DATASOURCE("远程数据库"), THREAD_DATASOURCE(
"THREAD数据库");
private String value;
private DataSourceLookupKey(String value) {
this.value = value;
}
@Override
public String toString() {
return this.value;
}
}
DbContextHolder.java
package com.lyx;
/**
*
* @author Lenovo
*/
public class DbContextHolder {
// 利用ThreadLocal解决线程安全问题
private static final ThreadLocal<DataSourceLookupKey> contextHolder = new ThreadLocal<DataSourceLookupKey>();
// 设置数据源
public static void setDbType(DataSourceLookupKey dbType) {
contextHolder.set(dbType);
}
// 获取数据源
public static DataSourceLookupKey getDbType() {
return (DataSourceLookupKey) contextHolder.get();
}
// 清空
public static void clearDbType() {
contextHolder.remove();
}
}
DynamicDataSource.java
package com.lyx;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected DataSourceLookupKey determineCurrentLookupKey() {
// TODO Auto-generated method stub
if (!(DbContextHolder.getDbType() == null)) {
System.out.println("====>" + DbContextHolder.getDbType());
} else {
System.out.println("====>没有初始化数据库上下文环境");
}
return DbContextHolder.getDbType();
}
}
DetermineDataSourceAspect.java
package com.lyx;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.reflect.MethodSignature;
public class DetermineDataSourceAspect {
public void determineDataSource(JoinPoint point) {
System.out
.println("determine datasource begin>>>>>>>>>>>>>>>>>>>>>>>>>");
RequireDataSource datasource = ((MethodSignature) point.getSignature())
.getMethod().getAnnotation(RequireDataSource.class);
System.out.println("====>" + datasource.name());
DbContextHolder.setDbType(datasource.name());
System.out.println("determine datasource end>>>>>>>>>>>>>>>>>>>>>>>>>");
}
}
RequireDataSource.java
package com.lyx;
import java.lang.annotation.ElementType;
import java.lang.annotation.Inherited;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
@Target({ ElementType.METHOD, ElementType.TYPE })
@Retention(RetentionPolicy.RUNTIME)
@Inherited
public @interface RequireDataSource {
/**
* the name of datasource
*
* @return
*/
DataSourceLookupKey name();
}
PersonDao.java
package com.lyx;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.List;
import javax.sql.DataSource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.jdbc.core.JdbcTemplate;
import org.springframework.jdbc.core.RowMapper;
import org.springframework.stereotype.Repository;
@Repository
public class PersonDao {
private JdbcTemplate jdbcTemplate;
@Autowired
public void setDataSource(DataSource dataSource) {
this.jdbcTemplate = new JdbcTemplate(dataSource);
}
/**
* 查询记录的个数
*
* @return
*/
public long count() {
long rowCount = this.jdbcTemplate.queryForObject(
"select count(*) from people", Long.class);
return rowCount;
}
/**
* 查询同名的人的个数
*
* @param name
* @return
*/
public long countPeopleByName(String name) {
long countOfActorsNamedJoe = this.jdbcTemplate.queryForObject(
"select count(*) from people where first_name = ?", Long.class,
name);
return countOfActorsNamedJoe;
}
/**
* 根据Id查询姓名
*
* @param id
* @return
*/
public String queryNameById(int id) {
String lastName = this.jdbcTemplate.queryForObject(
"select last_name from t_actor where person_id = ?",
new Object[] { id }, String.class);
return lastName;
}
/**
* 根据Id查找实体
*
* @param id
* @return
*/
public People findPeopleById(int id) {
People people = this.jdbcTemplate.queryForObject(
"select first_name, last_name from people where person_id = ?",
new Object[] { id }, new RowMapper<People>() {
public People mapRow(ResultSet rs, int rowNum)
throws SQLException {
People p = new People();
p.setFirstName(rs.getString("first_name"));
p.setLastName(rs.getString("last_name"));
return p;
}
});
return people;
}
/**
* 分页查询people
*
* @param pageNo
* @param pageSize
* @return
*/
public List<People> findPeopleList(int pageNo, int pageSize) {
int start = (pageNo - 1) * pageSize;
List<People> peoples = this.jdbcTemplate.query(
"select first_name, last_name from people limit ?,?",
new Object[] { start, pageSize }, new RowMapper<People>() {
public People mapRow(ResultSet rs, int rowNum)
throws SQLException {
People p = new People();
p.setFirstName(rs.getString("first_name"));
p.setLastName(rs.getString("last_name"));
return p;
}
});
return peoples;
}
/**
* 分页查询
*
* @param pageNo
* @param pageSize
* @return
*/
public List<People> findPeopleList0(int pageNo, int pageSize) {
int start = (pageNo - 1) * pageSize;
List<People> peoples = this.jdbcTemplate.query(
"select first_name, last_name from people limit ?,?",
new Object[] { start, pageSize }, new PeopleRowMapper());
return peoples;
}
private static class PeopleRowMapper implements RowMapper<People> {
public People mapRow(ResultSet rs, int rowNum) throws SQLException {
// TODO Auto-generated method stub
People p = new People();
p.setFirstName(rs.getString("first_name"));
p.setLastName(rs.getString("last_name"));
return p;
}
}
/**
* 插入实体
*
* @param people
*/
public void addPeople(People people) {
this.jdbcTemplate.update(
"insert into people (first_name, last_name) values (?, ?)",
people.getFirstName(), people.getLastName());
}
/**
* 更新实体
*
* @param id
*/
public void updatePeopleName(int id, String name) {
this.jdbcTemplate
.update("update people set last_name = ? where person_id = ?",
name, id);
}
/**
* 删除实体
*
* @param id
*/
public void deletePeople(int id) {
this.jdbcTemplate.update("delete from people where person_id = ?", id);
}
/**
* 执行sql语句
*
* @param sql
* create table mytable (id integer, name varchar(100))
*/
public void execute(String sql) {
this.jdbcTemplate.execute(sql);
}
}
PersonService.java
package com.lyx;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class PersonService {
@Autowired
private PersonDao personDao;
@RequireDataSource(name = DataSourceLookupKey.REMOTE_DATASOURCE)
@Transactional
public void addperson() {
System.out.println("-----addPerson begin-----");
System.out.println("====>" + DbContextHolder.getDbType());
People people = new People();
people.setFirstName("adasdfasdfwe");
people.setLastName("adadfeexcsdwadfsafd");
this.personDao.addPeople(people);
System.out.println("-----addPerson end-----");
System.out.println("====>" + DbContextHolder.getDbType());
}
}
测试类:
AppMain.java
package com.lyx;
import java.sql.Connection;
import java.sql.SQLException;
import javax.sql.DataSource;
import org.springframework.context.ApplicationContext;
import org.springframework.context.support.ClassPathXmlApplicationContext;
import org.springframework.jdbc.datasource.ConnectionHolder;
import org.springframework.jdbc.datasource.DataSourceUtils.ConnectionSynchronization;
import org.springframework.transaction.support.TransactionSynchronizationManager;
import org.springframework.util.Assert;
public class AppMain {
public static void main(String[] args) {
@SuppressWarnings("resource")
ApplicationContext context = new ClassPathXmlApplicationContext(
new String[] { "classpath:spring-dao.xml" });
PersonService personService = (PersonService) context
.getBean("personService");
personService.addperson();
}
}
结果:
determine datasource begin>>>>>>>>>>>>>>>>>>>>>>>>>
====>远程数据库
determine datasource end>>>>>>>>>>>>>>>>>>>>>>>>>
====>远程数据库
-----addPerson begin-----
====>远程数据库
-----addPerson end-----
====>远程数据库
如果你要动态选择数据库,就在该方法上加上注解@RequireDataSource,name值为数据库的枚举值。。
============================END============================