zoukankan      html  css  js  c++  java
  • 【原创】SSM框架(2):多数据源支持,动态配置依赖的数据源

    一、SSM框架配置多数据源的原理

      原理:MyBatis在创建SqlSession时,动态的使用不同的dataSource,就可以动态的使用不同的数据源。

      那么,怎样才能动态的使用不同的dataSource呢? 在Spring框架中,提供了一个类AbstractRoutingDataSource,顾名思义叫做路由选择。该类AbstractRoutingDataSource继承AbstractDataSource,AbstractDataSource又实现了DataSource,因此,可以使用AbstractRoutingDataSource来完成我们的需求。AbstractRoutingDataSource中有一个determineTargetDataSource()方法,该方法是用来决定目标数据源的;而determineTargetDataSource()方法中通过determineCurrentLookupKey()方法来决定lookupKey;然后使用封装好了的map集合resolvedDataSources,通过lookupKey为key值取得dataSource。因此这里面最重要的就是determineCurrentLookupKey()方法获取key值,在这里Spring把这个方法抽象出来,交给用户来实现。

    二、实践:在SSM框架中,实现多数据源访问(目录结构如下图)

     

    1、第一步:搭建SSM项目

      搭建步骤参考:SSM框架(1):使用Maven搭建SSM项目实践

    2、创建动态数据源代理类:DynamicDataSource 和 DynamicDataSourceHolder

    • DynamicDataSource:实现Spring定义的路由选择抽象类AbstractRoutingDataSource,用来创建动态的数据源。
    • DynamicDataSourceHolder:用来保存数据源标识,从而在实例化DynamicDataSource时,来指定要使用的数据源实例 
    package com.newbie.util;
    
    import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
    
    /**
     * DynamicDataSource由自己实现,实现AbstractRoutingDataSource,数据源由自己指定。
     */
    public class DynamicDataSource extends AbstractRoutingDataSource {
        @Override
        protected Object determineCurrentLookupKey() {
            // 从自定义的位置获取数据源标识
            return DynamicDataSourceHolder.getDataSourceKey();
        }
    }
    package com.newbie.util;
    
    /**
     * 自定义类:用来保存数据源标识,从而在实例化DynamicDataSource时来指定要使用的数据源实例
     */
    public class DynamicDataSourceHolder {
    
        /**
         * 注意:数据源标识保存在线程变量中,避免多线程操作数据源时互相干扰
         */
        private static final ThreadLocal<String> DATA_SOURCE_KEY = new ThreadLocal<String>();
    
        public static String getDataSourceKey(){
            return DATA_SOURCE_KEY.get();
        }
    
        public static void setDataSourceKey(String dataSourceKey){
            DATA_SOURCE_KEY.set(dataSourceKey);
        }
    
        public static void clearDataSourceKey(){
            DATA_SOURCE_KEY.remove();
        }
    
    }

    3、自定义多数据源注解@AnnotationDBSourceKey 以及 解析数据源注解的切面类DynamicDBSourceAspect

    • AnnotationDBSourceKey:自定义注解类。在要动态设置数据源的方法上,使用注解来标识数据源。
    • DynamicDBSourceAspect:切面类。在执行横切的方法前,获取方法的@AnnotationDBSourceKey注解,解析注解的值来设置DynamicDataSourceHolder类中的数据源标识,进而达到动态设置数据源的目的。
    package com.newbie.util.annotation;
    
    import java.lang.annotation.ElementType;
    import java.lang.annotation.Retention;
    import java.lang.annotation.RetentionPolicy;
    import java.lang.annotation.Target;
    
    /**
     * 自定义注解类型:使用注解来标识数据源
     */
    @Target({ElementType.TYPE,ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface AnnotationDBSourceKey {
        /**
         * value的值:来标识数据源
         * @return
         */
        String value();
    }
    package com.newbie.util.annotation;
    
    import com.newbie.util.DynamicDataSourceHolder;
    import org.aspectj.lang.JoinPoint;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Before;
    import org.aspectj.lang.reflect.MethodSignature;
    import org.springframework.stereotype.Component;
    
    import java.lang.reflect.Method;
    
    /**
     * 定义切面类:在执行DAO接口的方法前,获取方法的@AnnotationDBSourceKey注解,根据注解的值来动态设置数据源
     */
    @Component
    @Aspect
    public class DynamicDBSourceAspect {
        /**
         * 拦截目标方法,获取由@DataSource指定的数据源标识,设置到线程存储中以便切换数据源
         *
         * @param point
         * @throws Exception
         */
        @Before("execution(* com.newbie.service.*.*(..))")
        public void intercept(JoinPoint point) throws Exception {
            Class<?> target = point.getTarget().getClass();
            MethodSignature signature = (MethodSignature) point.getSignature();
            System.out.println("intercept()----,target:"+target+",signature:"+signature+",signature.getMethod():"+signature.getMethod());
    
            // 默认使用目标类型的注解,如果没有则使用其实现接口的注解
            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(AnnotationDBSourceKey.class)) {
                    AnnotationDBSourceKey source = clazz.getAnnotation(AnnotationDBSourceKey.class);
                    DynamicDataSourceHolder.setDataSourceKey(source.value());
                }
                // 方法注解可以覆盖类型注解
                Method m = clazz.getMethod(method.getName(), types);
                if (m != null && m.isAnnotationPresent(AnnotationDBSourceKey.class)) {
                    AnnotationDBSourceKey source = m.getAnnotation(AnnotationDBSourceKey.class);
                    DynamicDataSourceHolder.setDataSourceKey(source.value());
                }
            } catch (Exception e) {
                System.out.println(clazz + ":" + e.getMessage());
            }
        }
    }

    4、修改数据源配置:将单数据源配置改为多数据源配置

    • db.properties : 数据源静态参数配置文件,增加主从数据源的配置信息
    • spring-mybatis.xml : 增加主从数据源的实例(masterslave1slave2);增加DynamicDataSource的动态代理类的实例,并将DynamicDataSource实例的参数targetDataSources,设置为主从数据源的实例集合。
    #db.properties配置文件内容
    #配置主从数据库的链接地址
    #主库
    master.jdbc.url=jdbc:mysql://localhost:3306/nb_master?useUnicode=true&characterEncoding=utf8
    #从库1
    slave1.jdbc.url=jdbc:mysql://localhost:3306/nb_slave1?useUnicode=true&characterEncoding=utf8
    #从库2
    slave2.jdbc.url=jdbc:mysql://www.newbie.com:10127/nb_slave2?useUnicode=true&characterEncoding=utf8
    
    #配置其他信息
    jdbc.driver=com.mysql.jdbc.Driver
    jdbc.username=root
    jdbc.password=xxxxxx
    #最大连接数
    c3p0.maxPoolSize=30
    #最小连接数
    c3p0.minPoolSize=10
    #关闭连接后不自动commit
    c3p0.autoCommitOnClose=false
    #获取连接超时时间
    c3p0.checkoutTimeout=10000
    #当获取连接失败重试次数
    c3p0.acquireRetryAttempts=2
    ##### spring-mybatis.xml配置文件内容 #####
    <?xml version="1.0" encoding="UTF-8"?>
    <beans xmlns="http://www.springframework.org/schema/beans"
           xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
           xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"
           xsi:schemaLocation="http://www.springframework.org/schema/beans
           http://www.springframework.org/schema/beans/spring-beans.xsd
           http://www.springframework.org/schema/context
           http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd">
    
        <!--读取静态配置文件,获取相关数据库连接参数 -->
        <context:property-placeholder location="classpath:db.properties"/>
        <!-- 配置数据源 -->
        <bean id="abstractDataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" abstract="true" destroy-method="close">
            <property name="driverClass" value="${jdbc.driver}"/>
            <property name="user" value="${jdbc.username}"/>
            <property name="password" value="${jdbc.password}"/>
            <property name="maxPoolSize" value="${c3p0.maxPoolSize}"/>
            <property name="minPoolSize" value="${c3p0.minPoolSize}"/>
            <property name="autoCommitOnClose" value="${c3p0.autoCommitOnClose}"/>
            <property name="checkoutTimeout" value="${c3p0.checkoutTimeout}"/>
            <property name="acquireRetryAttempts" value="${c3p0.acquireRetryAttempts}"/>
        </bean>
        <!-- 配置数据源:主库 -->
        <bean id="master" parent="abstractDataSource">
            <property name="jdbcUrl" value="${master.jdbc.url}"/>
        </bean>
        <!-- 配置数据源:从库1 -->
        <bean id="slave1" parent="abstractDataSource">
            <property name="jdbcUrl" value="${slave1.jdbc.url}"/>
        </bean>
        <!-- 配置数据源:从库2 -->
        <bean id="slave2" parent="abstractDataSource">
            <property name="jdbcUrl" value="${slave2.jdbc.url}"/>
        </bean>
    
        <!-- 动态数据源配置,这个class要完成实例化 -->
        <bean id="dynamicDataSource" class="com.newbie.util.DynamicDataSource">
            <property name="targetDataSources">
                <!-- 指定lookupKey和与之对应的数据源,切换时使用的为key -->
                <map key-type="java.lang.String">
                    <entry key="master" value-ref="master"/>
                    <entry key="slave1" value-ref="slave1"/>
                    <entry key="slave2" value-ref="slave2"/>
                </map>
            </property>
            <!-- 这里可以指定默认的数据源 -->
            <property name="defaultTargetDataSource" ref="master"/>
        </bean>
    
        <!-- 配置MyBatis创建数据库连接的工厂类(此处配置和单数据源相同,不需改变) -->
        <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
            <!--数据源指定为:动态数据源DynamicDataSource -->
            <property name="dataSource" ref="dynamicDataSource"/>
            <!-- mapper配置文件 -->
            <property name="mapperLocations" value="classpath:mapper/*.xml"/>
        </bean>
    
        <!-- 配置自动扫描DAO接口包,动态实现DAO接口实例,注入到Spring容器中进行管理(此处配置和单数据源相同,不需改变) -->
        <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
            <!-- 注入SqlSession工厂对象:SqlSessionFactoryBean -->
            <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
            <!-- 指定要扫描的DAO接口所在包 -->
            <property name="basePackage" value="com.newbie.dao"/>
        </bean>
    
    </beans>

    5、创建 UserController、UserService,在UserService中动态设置数据源

    • UserService : 调用DynamicDataSourceHolder.setDataSourceKey(dataSourceKey)来动态设置数据源,实现多数据源的动态访问。
    • UserController : 接收客户端请求,调用UserService的方法处理请求,然后将结果响应给客户。
    package com.newbie.service.impl;
    
    import com.newbie.dao.UserDAO;
    import com.newbie.domain.User;
    import com.newbie.service.IUserService;
    import com.newbie.util.DynamicDataSourceHolder;
    import com.newbie.util.annotation.AnnotationDBSourceKey;
    import org.springframework.stereotype.Service;
    
    import javax.annotation.Resource;
    import java.util.List;
    @Service
    public class UserService implements IUserService {
        @Resource
        private UserDAO userDAO;
    
        /**
         * 通过设置:DynamicDataSourceHolder.setDataSourceKey(dataSourceKey);的方式,来动态设置数据源
         */
        public List<User> searchAllUser(String dataSourceKey) {
            if(dataSourceKey == null){
                dataSourceKey = "master";
            }
            DynamicDataSourceHolder.setDataSourceKey(dataSourceKey);
            return userDAO.selectAll();
        }
    
        /*
         * 以下三个方法:searchMaster()、searchSlave1()、searchSlave2()
         * 使用自定义注解的方式,来动态设置数据源
         */
        @AnnotationDBSourceKey("master")
        public List<User> searchMaster() {
            return userDAO.selectAll();
        }
        @AnnotationDBSourceKey("slave1")
        public List<User> searchSlave1() {
            return userDAO.selectAll();
        }
        @AnnotationDBSourceKey("slave2")
        public List<User> searchSlave2() {
            return userDAO.selectAll();
        }
    }
    package com.newbie.controller;
    
    import com.newbie.domain.User;
    import com.newbie.service.IUserService;
    import org.springframework.stereotype.Controller;
    import org.springframework.ui.Model;
    import org.springframework.web.bind.annotation.RequestMapping;
    
    import javax.annotation.Resource;
    import java.util.List;
    
    /**
     * 控制器:接收客户端请求,处理后将结果响应给客户
     */
    @Controller
    public class UserController {
        @Resource
        private IUserService userService;
    
        /**
         * 通过设置:DynamicDataSourceHolder.setDataSourceKey(dataSourceKey);的方式,来动态设置数据源
         */
        @RequestMapping("/searchAllUser")
        public String searchAllUser(Model model, String dataSourceKey){
            List<User> users = userService.searchAllUser(dataSourceKey);
            model.addAttribute("message","dataSourceKey = "+dataSourceKey);
            model.addAttribute("user",users.get(0));
            return "showInfo";
        }
    
        /*
         * 以下三个方法:searchMaster()、searchSlave1()、searchSlave2()
         * 使用自定义注解的方式,来动态设置数据源
         */
        @RequestMapping("/searchMaster")
        public String searchMaster(Model model){
            List<User> users = userService.searchMaster();
            model.addAttribute("message","dataSourceKey = master , method : searchMaster()");
            model.addAttribute("user",users.get(0));
            return "showInfo";
        }
        @RequestMapping("/searchSlave1")
        public String searchSlave1(Model model){
            List<User> users = userService.searchSlave1();
            model.addAttribute("message","dataSourceKey = slave1 , method : searchSlave1()");
            model.addAttribute("user",users.get(0));
            return "showInfo";
        }
        @RequestMapping("/searchSlave2")
        public String searchSlave2(Model model){
            List<User> users = userService.searchSlave2();
            model.addAttribute("message","dataSourceKey = slave2 , method : searchSlave2()");
            model.addAttribute("user",users.get(0));
            return "showInfo";
        }
    }

    6、UserDAO、UserMapper.xml保持不变(和单数据源中的配置相同)

    package com.newbie.dao;
    
    import com.newbie.domain.User;
    import java.util.List;
    
    /**
     * 数据库操作对象:用户类
     */
    public interface UserDAO {
        List<User> selectAll();
    }
    <?xml version="1.0" encoding="UTF-8"?>
    <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
    
    <!-- mapper配置文件,其中namespace指定要动态实例的DAO接口全局类名称 -->
    <mapper namespace="com.newbie.dao.UserDAO">
        <!-- 查询所有用户 -->
        <select id="selectAll" resultType="com.newbie.domain.User">
            select id,username,title from user
        </select>
    
    </mapper>
    -- DROP TABLE user;
    CREATE TABLE user(
        id VARCHAR(25),
        username VARCHAR(50),
        title VARCHAR(255)
        ); 
    insert into user(id,username,title) values('123','简小六','主库master');
    insert into user(id,username,title) values('123','简小六','从库slave1');
    insert into user(id,username,title) values('123','简小六','从库slave2');

    7、创建前端jsp页面,测试多数据源访问结果(首先访问index.jsp页面,点击<a>标签跳转后,在showInfo.jsp页面中显示结果)

    #### index.jsp页面内容 ####
    <%
    @ page contentType="text/html;charset=UTF-8" language="java" %> <html> <head> <title>测试多数据源模式</title> </head> <body> <h2> 通过设置:DynamicDataSourceHolder.setDataSourceKey(dataSourceKey);的方式,来动态设置数据源</h2> <a href="searchAllUser?dataSourceKey=master">查询主库:用户信息</a><br/><br/> <a href="searchAllUser?dataSourceKey=slave1">查询从库1:用户信息</a><br/><br/> <a href="searchAllUser?dataSourceKey=slave2">查询从库2:用户信息</a><br/><br/> <br/><hr/><hr/><br/> <h2> 使用自定义注解的方式,来动态设置数据源</h2> <a href="searchMaster">查询主库:用户信息</a><br/><br/> <a href="searchSlave1">查询从库1:用户信息</a><br/><br/> <a href="searchSlave2">查询从库2:用户信息</a><br/><br/> </body> </html>
    #### showInfo.jsp页面内容  ####
    <%@ page contentType="text/html;charset=UTF-8" language="java" %>
    <html>
    <head>
        <title>显示结果信息</title>
    </head>
    <body>
    处理信息:${message}<br/>
    处理结果:<br/>
    &nbsp;&nbsp;&nbsp;&nbsp;用户ID:${user.id}
    &nbsp;&nbsp;&nbsp;&nbsp;username:${user.username}
    &nbsp;&nbsp;&nbsp;&nbsp;title:${user.title}
    </body>
    </html>

    8、程序运行结果

    (1)发布项目,请求以下地址:http://localhost:8080/ssm-multi-DBSource/index.jsp

    (2)index.jsp页面显示如下:

    (3)以上六个<a>标签,点击后程序运行的结果分别如下图:

    参考资料:

    欢迎转载,转载请注明出处!



  • 相关阅读:
    (Good Bye 2019) Codeforces 1270B Interesting Subarray
    (Good Bye 2019) Codeforces 1270A Card Game
    Codeforces 1283D Christmas Trees(BFS)
    Codeforces 1283C Friends and Gifts
    Codeforces 1283B Candies Division
    1095 Cars on Campus (30)
    1080 Graduate Admission (30)
    1099 Build A Binary Search Tree (30)
    1018 Public Bike Management (30)
    1087 All Roads Lead to Rome (30)
  • 原文地址:https://www.cnblogs.com/newbie27/p/10823219.html
Copyright © 2011-2022 走看看