在平时开发过程中,很多内部的项目都是直接访问多个数据库,这样平时一个项目一个数据库就不够用了,spring支持多数据源。笔者这里记录三种平时常看到的多数据源整合方式。
情景:数据库的读量比较大,一般的写操作不会影响数据库读。所以,项目就分为两个库,一个读库,一个读写库。
**项目环境:**ssm+mysql+tomcat
常规项目spring配置是:先声明一个数据源bean
,使用该数据源构建SqlSessionFactoryBean
,然后通过扫描的形式匹配对应的包使用该SqlSessionFactoryBean
。结果就是对应匹配这些dao包就使用这些数据源了。
spring具体核心配置:
<bean id="readAndWriteDataSouce" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
<property name="driverClass" value="${driver}"/>
<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}"/>
<property name="jdbcUrl" value="${urlForRead}"/>
<property name="user" value="${userForRead}"/>
<property name="password" value="${passwordForRead}"/>
bean>
<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.xmlvalue>
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.xmlvalue>
list>
property>
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>
小结:声明多个数据源,指定mybatis对应的xml和mapper接口文件。实际使用中就是在不同包下的代码,默认使用对应的数据源。就是简单的复制两份配置,对应不同的包和xml配置就行了。
同样创建数据源bean,然后把多个数据源加入动态bean管理,在使用这个动态bean创建sessionFactory工厂。其中动态bean的管理需要手动实现,代码中具体使用就使用自己这个动态bean进行切换再操作。
配置:
<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>
<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>
<bean id="dynamicDataSource" class="util.DynamicDataSource">
<property name="targetDataSources">
<map key-type="java.lang.String">
<entry key="dataSource1" value-ref="dataSource1">entry>
<entry key="dataSource2" value-ref="dataSource2">entry>
map>
property>
<property name="defaultTargetDataSource" ref="dataSource1" />
bean>
<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">
<list>
list>
property>
<property name="packagesToScan">
<list>
<value>pojovalue>
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 THREAD_DATA_SOURCE = new ThreadLocal();
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);
}
}