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>标签,点击后程序运行的结果分别如下图:

    参考资料:

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



  • 相关阅读:
    新概念英语(第一册)Lesson 1
    第七篇、Python之模块/包
    解压序列
    eval函数
    python--magic module 文件类型识别
    MIME_type
    彻底删除git中的文件(包括历史提交记录)
    for循环与range()函数
    Linux 内核的 Makefile
    Python module ---- abc
  • 原文地址:https://www.cnblogs.com/newbie27/p/10823219.html
Copyright © 2011-2022 走看看