在平时开发过程中,很多内部的项目都是直接访问多个数据库,这样平时一个项目一个数据库就不够用了,spring支持多数据源。笔者这里记录三种平时常看到的多数据源整合方式。
第一种:复制多个bean
情景:数据库的读量比较大,一般的写操作不会影响数据库读。所以,项目就分为两个库,一个读库,一个读写库。
**项目环境:**ssm+mysql+tomcat
常规项目spring配置是:先声明一个数据源bean,使用该数据源构建SqlSessionFactoryBean,然后通过扫描的形式匹配对应的包使用该SqlSessionFactoryBean。结果就是对应匹配这些dao包就使用这些数据源了。
spring具体核心配置:
<!--START 多数据源配置 -->
<bean id="readAndWriteDataSouce" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
<!-- 指定连接数据库的驱动 -->
<property name="driverClass" value="${driver}"/>
<!-- 指定连接数据库的URL -->
<property name="jdbcUrl" value="${url}"/>
<!-- 指定连接数据库的用户名 -->
<property name="user" value="${user}"/>
<!-- 指定连接数据库的密码 -->
<property name="password" value="${password}"/>
</bean>
<bean id="readDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
<!-- 指定连接数据库的驱动 -->
<property name="driverClass" value="${driver}"/>
<!-- 指定连接数据库的URL -->
<property name="jdbcUrl" value="${urlForRead}"/>
<!-- 指定连接数据库的用户名 -->
<property name="user" value="${userForRead}"/>
<!-- 指定连接数据库的密码 -->
<property name="password" value="${passwordForRead}"/>
</bean>
<!-- END -->
<!-- MyBatis配置:数据源、Mapper XML、事务管理器 -->
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="readAndWriteDataSouce" />
<property name="configLocation" value="classpath:mybatis.xml" />
<property name="mapperLocations">
<list>
<value>classpath:cn/hicard/vipcard/entity/**/*-mapper.xml</value>
</list>
</property>
</bean>
<bean id="sqlSessionFactoryRead" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="readDataSource" />
<property name="configLocation" value="classpath:mybatis.xml" />
<property name="mapperLocations">
<list>
<value>classpath:cn/hicard/vipcard/entity/**/*-read-mapper.xml</value>
</list>
</property>
</bean>
<!-- 通过扫描的模式,扫描目录在cn/hicard/vipcard/dao目录下,所有的dao都继承BaseDao接口的接口, 这样一个bean就可以了 -->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="cn.hicard.vipcard.dao"/>
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" />
</bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="cn.hicard.vipcard.read"/>
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactoryRead" />
</bean>
<!-- 定义事务这里只需要一个数据源有事务,就只开一个 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="readDataSource" />
</bean>
<!-- 配置 Annotation 驱动,扫描@Transactional注解的类定义事务 -->
<tx:annotation-driven transaction-manager="transactionManager" proxy-target-class="true"/>
小结:声明多个数据源,指定mybatis对应的xml和mapper接口文件。实际使用中就是在不同包下的代码,默认使用对应的数据源。就是简单的复制两份配置,对应不同的包和xml配置就行了。
第二种:使用DynamicDataSource
同样创建数据源bean,然后把多个数据源加入动态bean管理,在使用这个动态bean创建sessionFactory工厂。其中动态bean的管理需要手动实现,代码中具体使用就使用自己这个动态bean进行切换再操作。
配置:
<!-- 使用spring管理DBCP数据源1 -->
<bean id="dataSource1" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<property name="driverClassName" value="${jdbc.driver}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
<property name="defaultAutoCommit" value="true"></property>
</bean>
<!-- 使用spring管理DBCP数据源 2 -->
<bean id="dataSource2" class="org.apache.commons.dbcp.BasicDataSource"
destroy-method="close">
<property name="driverClassName" value="${jdbc.driver}" />
<property name="url" value="${jdbc.url2}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password2}" />
<property name="defaultAutoCommit" value="true"></property>
</bean>
<!-- 动态数据源配置,这个class要实现 -->
<bean id="dynamicDataSource" class="util.DynamicDataSource">
<property name="targetDataSources">
<map key-type="java.lang.String">
<!-- 指定lookupKey和与之对应的数据源,切换时使用的为key -->
<entry key="dataSource1" value-ref="dataSource1"></entry>
<entry key="dataSource2" value-ref="dataSource2"></entry>
</map>
</property>
<!-- 这里可以指定默认的数据源 -->
<property name="defaultTargetDataSource" ref="dataSource1" />
</bean>
<!-- 添加spring和hibernate的集成包,这里也可以使用其他的框架工厂如mybatis -->
<bean id="sessionFactory"
class="org.springframework.orm.hibernate4.LocalSessionFactoryBean">
<property name="dataSource" ref="dynamicDataSource"></property>
<property name="hibernateProperties">
<value>
hibernate.dialect=org.hibernate.dialect.MySQLDialect
hibernate.show_sql=true
hibernate.format_sql=true
</value>
</property>
<property name="mappingResources"> <!-- 生成了hibernate的xxx.hbm.xml映射文件添加到这里 -->
<list>
</list>
</property>
<property name="packagesToScan"> <!-- hibernates生成为注解模式的时候才用 -->
<list>
<value>pojo</value>
</list>
</property>
</bean>
util.DynamicDataSource由自己实现,实现AbstractRoutingDataSource,数据源由自己指定。
DynamicDataSource:
public class DynamicDataSource extends AbstractRoutingDataSource {
@Override
protected Object determineCurrentLookupKey() {
// 从自定义的位置获取数据源标识
return DynamicDataSourceHolder.getDataSource();
}
}
DynamicDataSourceHolder:
public class DynamicDataSourceHolder {
/**
* 注意:数据源标识保存在线程变量中,避免多线程操作数据源时互相干扰
*/
private static final ThreadLocal<String> THREAD_DATA_SOURCE = new ThreadLocal<String>();
public static String getDataSource() {
return THREAD_DATA_SOURCE.get();
}
public static void setDataSource(String dataSource) {
THREAD_DATA_SOURCE.set(dataSource);
}
public static void clearDataSource() {
THREAD_DATA_SOURCE.remove();
}
}
访问可以在控制器直接根据 注册进动态数据源的key值进行选择。
控制器例子:
这样就设置好了为数据源1。需要注意的是,切换数据源要在事务之前就可以了。否则不生效。
@Controller
public class Test {
@Autowired
private DynDataSourceService dataSourceService;
@RequestMapping("/Test")
public String test() {
DynamicDataSourceHolder.setDataSource("dataSource1");
dataSourceService.find(3);
return "index.jsp";
}
}
第三种:使用自定义注解来实现动态数据源的切换
简单理解就是自定义一个注解,然后在对应需要使用数据库的方法上使用该注解,通过aop切面的方式解析注解的值来设置对应的数据源。还是在第二种基础上,加上注解和aop,方便切换而已。
配置增加:
<!-- 注解实现动态切换数据源 -->
<bean id="dataSourceAspect" class="util.DataSourceAspect" />
<aop:config>
<aop:aspect ref="dataSourceAspect">
拦截所有service方法,切面插入拦截的方法,获取注解
<aop:pointcut id="dataSourcePointcut" expression="execution(* dao.*.*(..))" />
<aop:before pointcut-ref="dataSourcePointcut" method="intercept" />
</aop:aspect>
</aop:config>
声明注解@DataSource
@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface DataSource {
String value();
}
其中util.DataSourceAspect
public class DataSourceAspect {
/**
* 拦截目标方法,获取由@DataSource指定的数据源标识,设置到线程存储中以便切换数据源
*
* @param point
* @throws Exception
*/
public void intercept(JoinPoint point) throws Exception {
Class<?> target = point.getTarget().getClass();
MethodSignature signature = (MethodSignature) point.getSignature();
// 默认使用目标类型的注解,如果没有则使用其实现接口的注解
for (Class<?> clazz : target.getInterfaces()) {
resolveDataSource(clazz, signature.getMethod());
}
resolveDataSource(target, signature.getMethod());
}
/**
* 提取目标对象方法注解和类型注解中的数据源标识
*
* @param clazz
* @param method
*/
private void resolveDataSource(Class<?> clazz, Method method) {
try {
Class<?>[] types = method.getParameterTypes();
// 默认使用类型注解
if (clazz.isAnnotationPresent(DataSource.class)) {
DataSource source = clazz.getAnnotation(DataSource.class);
DynamicDataSourceHolder.setDataSource(source.value());
}
// 方法注解可以覆盖类型注解
Method m = clazz.getMethod(method.getName(), types);
if (m != null && m.isAnnotationPresent(DataSource.class)) {
DataSource source = m.getAnnotation(DataSource.class);
DynamicDataSourceHolder.setDataSource(source.value());
}
} catch (Exception e) {
System.out.println(clazz + ":" + e.getMessage());
}
}
}
实现就在dao的方法上加上注解即可。也可以是父接口上面。
注意: 这里的事务是添加到切面添加到dao包的,保证注解是在事务之前执行。如果事务在注解前,注解无效的。
@Repository
public class DynDataSourceDao extends HibernateDaoSupport {
// 注入工厂
@Resource(name = "sessionFactory")
public void setSuperSessionFactory(SessionFactory sessionFactory) {
this.setSessionFactory(sessionFactory);
}
@DataSource("dataSource2")
public void find(int id) {
/** 切换数据源必须在事务之前 */
Student entity=new Student();
entity.setUserName("阿罗1sa1");
entity.setPassWord("123456");
Blog blog=new Blog();
blog.setContent("tesaat");
blog.setTitle("777");
// this.getHibernateTemplate().save(entity);
this.getHibernateTemplate().save(blog);
}
}