在平时开发过程中,很多内部的项目都是直接访问多个数据库,这样平时一个项目一个数据库就不够用了,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);


    }
}