欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

spring多数据源解决方案

程序员文章站 2022-05-24 14:49:54
...

版权声明:本文为博主原创文章,未经博主允许不得转载。 https://blog.csdn.net/qq_35830949/article/details/80885745


在平时开发过程中,很多内部的项目都是直接访问多个数据库,这样平时一个项目一个数据库就不够用了,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>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55

小结:声明多个数据源,指定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>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54

util.DynamicDataSource由自己实现,实现AbstractRoutingDataSource,数据源由自己指定。
DynamicDataSource:

public class DynamicDataSource extends AbstractRoutingDataSource {

    @Override
    protected Object determineCurrentLookupKey() {
        // 从自定义的位置获取数据源标识
        return DynamicDataSourceHolder.getDataSource();
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

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();
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19

访问可以在控制器直接根据 注册进动态数据源的key值进行选择。

控制器例子:
这样就设置好了为数据源1。*需要注意的是,切换数据源要在事务之前就可以了。否则不生效。*

@Controller
public class Test {
    @Autowired
    private DynDataSourceService dataSourceService;
    @RequestMapping("/Test")
    public String test() {
        DynamicDataSourceHolder.setDataSource("dataSource1");
        dataSourceService.find(3);
        return "index.jsp";
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13

第三种:使用自定义注解来实现动态数据源的切换

简单理解就是自定义一个注解,然后在对应需要使用数据库的方法上使用该注解,通过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>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

声明注解@DataSource

@Target({ElementType.TYPE,ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface DataSource {
    String value();
}
  • 1
  • 2
  • 3
  • 4
  • 5

其中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());
        }
    }

}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43

实现就在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);


    }
}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
					<link href="https://csdnimg.cn/release/phoenix/mdeditor/markdown_views-7b4cdcb592.css" rel="stylesheet">
            </div>
相关标签: 多数据源