spring多数据源解决方案

在平时开发过程中,很多内部的项目都是直接访问多个数据库,这样平时一个项目一个数据库就不够用了,spring支持多数据源。笔者这里记录三种平时常看到的多数据源整合方式。

第一种:复制多个bean

情景:数据库的读量比较大,一般的写操作不会影响数据库读。所以,项目就分为两个库,一个读库,一个读写库。
**项目环境:**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配置就行了。

第二种:使用DynamicDataSource

同样创建数据源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);


    }
}

你可能感兴趣的:(Spring)