springboot整合mybatis,动态数据源配置

此项目是数据查询接口服务。通过浏览器访问url,传递json参数,返回json数据。用springboot微服务构建,整合mybatis,查询数据库,由于数据保存在两个数据库,所以项目中通过service实现类所在包或者通过自定义注解进行动态的切换数据源。配置如下


1. 引入依赖

    <groupId>com.lancygroupId>
    <artifactId>interfaces-serviceartifactId>
    <version>0.0.1-SNAPSHOTversion>
    <packaging>jarpackaging>

        
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starterartifactId>
            <version>1.4.2.RELEASEversion>
        dependency>
        
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-webartifactId>
            <version>1.4.2.RELEASEversion>
        dependency>
        
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-testartifactId>
            <version>1.4.2.RELEASEversion>
            <scope>testscope>
        dependency>
        
        <dependency>
            <groupId>org.springframework.bootgroupId>
            <artifactId>spring-boot-starter-jdbcartifactId>
            <version>1.4.2.RELEASEversion>
        dependency>
        <dependency>
            <groupId>com.oraclegroupId>
            <artifactId>ojdbc6artifactId>
            <version>11.2.0.1.0version>
        dependency>
        
        <dependency>
            <groupId>org.mybatis.spring.bootgroupId>
            <artifactId>mybatis-spring-boot-starterartifactId>
            <version>1.1.1version>
        dependency>
        <dependency>
            <groupId>com.alibabagroupId>
            <artifactId>druidartifactId>
            <version>1.0.16version>
        dependency>
        <dependency>
            <groupId>org.springframeworkgroupId>
            <artifactId>spring-aopartifactId>
            <version>4.3.4.RELEASEversion>
        dependency>
        <dependency>
            <groupId>org.springframeworkgroupId>
            <artifactId>spring-aspectsartifactId>
            <version>4.3.4.RELEASEversion>
        dependency>
        <dependency>
            <groupId>org.aspectjgroupId>
            <artifactId>aspectjweaverartifactId>
            <version>1.8.9version>
        dependency>
        <dependency>
            <groupId>com.github.pagehelpergroupId>
            <artifactId>pagehelperartifactId>
            <version>4.1.6version>
        dependency>

<build>
    <plugins>
            
            <plugin>
                <groupId>org.springframework.bootgroupId>
                <artifactId>spring-boot-maven-pluginartifactId>
                <version>1.4.2.RELEASEversion>
                <configuration>
                    <fork>truefork>
                   <mainClass>com.lancy.interfaces.InterfacesApplicationmainClass>
                configuration>
                <executions>
                    <execution>
                        <goals>
                            <goal>repackagegoal>
                        goals>
                    execution>
                executions>
            plugin>
     plugins>
build>

2. springboot启动入口类配置

package com.lingnanpass.interfaces;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.ComponentScan;

@SpringBootApplication
@ComponentScan(basePackages = { "com.lancy.interfaces.common", "com.lancy.interfaces.*.dao",
        "com.lancy.interfaces.*.service.impl", "com.lancy.interfaces.controller",
        "com.lancy.interfaces.util" })
//@ComponentScan告诉Spring 哪些路径下的类(类上有注解) 用自动扫描注入的方式装入spring容器供spring管理生命周期。
public class InterfacesApplication {
     

    public static void main(String[] args) {
        SpringApplication.run(LntInterfacesApplication.class, args);
    }
}

3. 多数据源配置
3.1 application.properties 配置
springboot核心配置文件是指在resources根目录下的application.properties或application.yml配置文件,这里用properties配置文件,读取properties配置文件的方法有两种。一种是@Value(”${}”)。一种是@Autowired private Environment env; env.getProperty(“test.msg”);

#数据源1--自动切换数据源的包:lnt
jdbc1.db.type=oracle
jdbc1.driver = oracle.jdbc.driver.OracleDriver
jdbc1.url=jdbc:oracle:thin:@192.168.1.203:1521/test1
jdbc1.user=lancy
jdbc1.pass=123456
jdbc1.maxPoolSize=10
jdbc1.minPoolSize=2
jdbc1.initialPoolSize =2
jdbc1.maxWaitTime =60000
jdbc1.checkIdlePoolTime = 60000
jdbc1.minConnTime = 300000
jdbc1.filters=stat,log4j

#数据源2--自动切换数据源的包:ykt
jdbc2.db.type=oracle
jdbc2.driver = oracle.jdbc.driver.OracleDriver
jdbc2.url=jdbc:oracle:thin:@192.168.1.205:1521/test2
jdbc2.user=udev_ykt
jdbc2.pass=123456
jdbc2.maxPoolSize=10
jdbc2.minPoolSize=2
jdbc2.initialPoolSize =2
jdbc2.maxWaitTime =60000
jdbc2.checkIdlePoolTime = 60000
jdbc2.minConnTime = 300000
jdbc2.filters=stat,log4j

oracle.typeAliasesPackage=com.lancy.interfaces.*.entity
oracle.mapperLocations=com/lancy/interfaces/*/dao/oracle/impl*/*.xml

#以下为配置连接池监控界面的信息allow,允许访问监控的IP,用户名,密码,是否运行充值数据
oracle.druidAllow=10.230.0.1/255,192.168.1.100/255
oracle.druidUsername=admin
oracle.druidPassword=123456
oracle.druidResetEnable=false

3.2 动态数据源配置
首先定义一个类,用于保存当前线程使用的数据源名

package com.lancy.interfaces.common;
/**
 * @ClassName: DataSourceTypeManager.java
 * @Description: 
 */
public class DataSourceTypeManager {
     
    private static final ThreadLocal dataSourceTypes = new ThreadLocal(){
        @Override
        protected String initialValue(){
            return DataSourceRounting.defaultDataSourceKey;
        }
    };

    public static String get(){
        return dataSourceTypes.get();
    }

    public static void set(String dataSourceType){
        dataSourceTypes.set(dataSourceType);
    }

    public static void reset(){
        dataSourceTypes.set(DataSourceRounting.defaultDataSourceKey);
    }
}

然后自定义一个javax.sql.DataSource接口的实现,这里只需要继承Spring为我们预先实现好的父类AbstractRoutingDataSource即可:

package com.lancy.interfaces.common;

import java.util.Map;

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
/**
 * 多数据源路适配
 * @ClassName: DataSourceRounting.java
 * @Description: 多数据源路适配,由配置中多个数据源组成,动态按包或自确定注解数据源进行切换
 */
public class DataSourceRounting extends AbstractRoutingDataSource {
     
    public static String defaultDataSourceKey;//默认的数据源
    public static Map targetDataSources;//保存所有可用的数据源
    @Override
    protected Object determineCurrentLookupKey() {
        return DataSourceTypeManager.get();
    }

    public void setDefaultDataSourceKey(String defaultDataSourceKey) {
        DataSourceRounting.defaultDataSourceKey = defaultDataSourceKey;
    }

    public void setTargetDataSources(Map targetDataSources) {
        super.setTargetDataSources(targetDataSources);
        DataSourceRounting.targetDataSources = targetDataSources;
    }
}

然后编写AOP切面,实现数据源切换逻辑。这里有一个问题
一般事务都会在service层添加,如果使用spring的声明式事务管理,在调用service层代码之前,spring会通过aop的方式动态添加事务控制代码,所以如果要想保证事务是有效的,那么就必须在spring添加事务之前把数据源动态切换过来,也就是动态切换数据源的aop要至少在service上添加,而且要在spring声明式事物aop之前添加.

package com.lancy.interfaces.common;

import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Before;
import org.aspectj.lang.annotation.Pointcut;
import org.springframework.stereotype.Component;

/**
 * 数据源动态切换,aop实现
 * 
 * @ClassName: DataSourceAdvice.java
 * @Description:数据源动态切换,在调用相关子包下的serviceImpl时自动执行
 */
@Aspect
@Component
public class DataSourceAdvice {

    //切入点,调用serviceImpl类就会触发
    @Pointcut("execution(* com.lancy..service.impl..*(..))")
    public void aspect() {
    }

    @AfterReturning("aspect()")
    public void afterReturning() throws Throwable {
        DataSourceTypeManager.reset();//把数据源重置回默认
    }

    @Before("aspect()")
    public void before(JoinPoint joinPoint) throws Throwable {
        Object target = joinPoint.getTarget();
        // 非业务包下不切换数据源,判断两次,防止代理对象没有切换数据源
        String cls = target.toString().toLowerCase();
        if (!cls.startsWith("com.lancy.interfaces.")) {
            cls = target.getClass().getName().toLowerCase();
        }
        if (!cls.startsWith("com.lancy.interfaces.")) {
            return;
        }
        String className = cls.replace("com.lancy.interfaces.", "");
        String packageName = className.substring(0, className.indexOf("."));
        //packageName 有lnt 或者ykt 。因为这两个包下dao,service,mapper就是对应一种数据源,所以这里也可以通过包名切换数据源
        // 手动数据源
        String dsName = "";
        //如果类上有DataSource注解,则数据源字符串就是注解上标注的数据源字符串
        if (target.getClass().isAnnotationPresent(DataSource.class)) {
            DataSource datasource = target.getClass().getAnnotation(DataSource.class);
            dsName = datasource.value();
        }
        //如果注解上的数据源字符串在系统保存的数据源集合里
        if (DataSourceRounting.targetDataSources.containsKey(dsName)) {
            // 当前线程按指定数据源
            DataSourceTypeManager.set(dsName);
        } else if (DataSourceRounting.targetDataSources.containsKey(packageName)) {
            DataSourceTypeManager.set(packageName);
        } else {
            DataSourceTypeManager.set(DataSourceRounting.defaultDataSourceKey);
        }
    }

}

定义数据源注解

package com.lancy.interfaces.common;

import java.lang.annotation.Documented;
import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 指定数据源注解
 * 
 * @ClassName: DataSource.java
 * @Description: 可以注解服务类(service层)或方法,注解类表示改类整个类都使用同一个数据源
 */
@Documented
@Retention(RetentionPolicy.RUNTIME)
@Target({ ElementType.TYPE, ElementType.METHOD })
public @interface DataSource {
     
    // 默认按类名自动设置
    String value();

    // 定义静态数据源
    public static final String  LNT = "lnt";
    public static final String  YKT = "ykt";
}

3.3 springboot集成mybatis 实现
这里把数据源注入和sqlSessionFactory注入都放在一起。
@Configuration可理解为用spring的时候xml里面的标签
@Bean可理解为用spring的时候xml里面的标签

package com.lancy.interfaces.common;

import java.util.HashMap;
import java.util.Map;
import java.util.Properties;

import javax.sql.DataSource;

import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.type.JdbcType;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.core.env.Environment;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;

import com.alibaba.druid.pool.DruidDataSource;
import com.github.pagehelper.PageHelper;

/**
 * springboot集成mybatis的基本步骤 1)创建数据源 2)创建SqlSessionFactory
 */
/**
 * @ClassName: OracleMybatisConfig
 * @Description: Oracle数据源与mybatis集成类
 */
@Configuration
@MapperScan(basePackages = "com.lancy.interfaces.*.dao")
//MapperScan配置我们需要扫描的Mapper位置
public class OracleMybatisConfig {
    @Autowired
    private Environment env;

    /**
     * @Desctiption 创建数据库数据源
     * @return DataSource 数据源
     */
    @Bean
    public DataSource lntDataSource() throws Exception {
        DruidDataSource druidDataSource = new DruidDataSource();
        druidDataSource.setDriverClassName(env.getProperty("jdbc1.driver"));
        druidDataSource.setUrl(env.getProperty("jdbc1.url"));
        druidDataSource.setUsername(env.getProperty("jdbc1.user"));
        druidDataSource.setPassword(env.getProperty("jdbc1.pass"));
        druidDataSource.setMaxActive(Integer.parseInt(env.getProperty("jdbc1.maxPoolSize")));
        druidDataSource.setInitialSize(Integer.parseInt(env.getProperty("jdbc1.initialPoolSize")));
        druidDataSource.setMaxWait(Integer.parseInt(env.getProperty("jdbc1.maxPoolSize")));
        druidDataSource.setMinIdle(Integer.parseInt(env.getProperty("jdbc1.minPoolSize")));
        druidDataSource.setRemoveAbandoned(true);
        druidDataSource.setRemoveAbandonedTimeout(60);
        druidDataSource.setLogAbandoned(true);
        druidDataSource.setTestWhileIdle(true);
        druidDataSource.setFilters(env.getProperty("jdbc1.filters"));
        druidDataSource.setValidationQuery(env.getProperty("oracle.validationQuery"));
        return druidDataSource;
    }

    /**
     * @Desctiption 创建数据库数据源
     * @return DataSource 数据源
     */
    @Bean
    public DataSource yktDataSource() throws Exception {
        DruidDataSource druidDataSource = new DruidDataSource();
        druidDataSource.setDriverClassName(env.getProperty("jdbc2.driver"));
        druidDataSource.setUrl(env.getProperty("jdbc2.url"));
        druidDataSource.setUsername(env.getProperty("jdbc2.user"));
        druidDataSource.setPassword(env.getProperty("jdbc2.pass"));
        druidDataSource.setMaxActive(Integer.parseInt(env.getProperty("jdbc2.maxPoolSize")));
        druidDataSource.setInitialSize(Integer.parseInt(env.getProperty("jdbc2.initialPoolSize")));
        druidDataSource.setMaxWait(Integer.parseInt(env.getProperty("jdbc2.maxPoolSize")));
        druidDataSource.setMinIdle(Integer.parseInt(env.getProperty("jdbc2.minPoolSize")));
        druidDataSource.setRemoveAbandoned(true);
        druidDataSource.setRemoveAbandonedTimeout(60);
        druidDataSource.setLogAbandoned(true);
        druidDataSource.setTestWhileIdle(true);
        druidDataSource.setFilters(env.getProperty("jdbc2.filters"));
        druidDataSource.setValidationQuery(env.getProperty("oracle.validationQuery"));
        return druidDataSource;
    }

    /**
     * @Primary 该注解表示在同一个接口有多个实现类可以注入的时候,默认选择哪一个,而不是让@autowire注解报错
     * @Qualifier 根据名称进行注入,通常是在具有相同的多个类型的实例的一个注入(例如有多个DataSource类型的实例)
     */
    @Bean
    @Primary
    public DataSourceRounting dynamicDataSource(@Qualifier("lntDataSource") DataSource lntDataSource,
            @Qualifier("yktDataSource") DataSource yktDataSource) {
        Map targetDataSources = new HashMap();
        targetDataSources.put("lnt", lntDataSource);
        targetDataSources.put("ykt", yktDataSource);
        DataSourceRounting dynamicDataSource = new DataSourceRounting();
        dynamicDataSource.setDefaultDataSourceKey("lnt");
        dynamicDataSource.setTargetDataSources(targetDataSources);
        return dynamicDataSource;
    }

    /**
     * @Desctiption 根据数据源创建Mybatis的SqlSessionFactory
     * @return SqlSessionFactory
     */
    @Bean
    public SqlSessionFactory sqlSessionFactory(@Qualifier("lntDataSource") DataSource lntDataSource,
            @Qualifier("yktDataSource") DataSource yktDataSource, PageHelper pageHelper) throws Exception {
        SqlSessionFactoryBean fb = new SqlSessionFactoryBean();
        fb.setDataSource(this.dynamicDataSource(lntDataSource, yktDataSource));// 指定数据源(这个必须有,否则报错),因为DataSourceRounting是javax.sql.DataSource接口的实现类
        // 下边两句仅仅用于*.xml文件,如果整个持久层操作不需要使用到xml文件的话(只用注解就可以搞定),则不加
        fb.setTypeAliasesPackage(env.getProperty("oracle.typeAliasesPackage"));// 指定基包
        org.apache.ibatis.session.Configuration configuration = new org.apache.ibatis.session.Configuration();
        configuration.setJdbcTypeForNull(JdbcType.VARCHAR);
        configuration.setCallSettersOnNulls(true); // map类型数据空值字段显示
        fb.setConfiguration(configuration);
        fb.setPlugins(new Interceptor[] { pageHelper });
        fb.setMapperLocations(
                new PathMatchingResourcePatternResolver().getResources(env.getProperty("oracle.mapperLocations")));// 指定xml文件位置,相当于配置文件里的
        return fb.getObject();
    }

    @Bean
    public PageHelper pageHelper() {
        PageHelper pageHelper = new PageHelper();
        Properties p = new Properties();
        p.setProperty("offsetAsPageNum", "true");
        p.setProperty("rowBoundsWithCount", "true");
        p.setProperty("reasonable", "true");
        p.setProperty("dialect", "oracle");
        pageHelper.setProperties(p);
        return pageHelper;
    }

}

4. 日志配置
springboot的日志配置默认在resources目录下logback.xml或者logback-spring.xml文件



<configuration scan="false" debug="false">
    
    
    <property name="LOG_HOME" value="E:/lnt/logs" />
    
    <property name="appName" value="service">property>
    
    <appender name="stdout" class="ch.qos.logback.core.ConsoleAppender">
        <layout class="ch.qos.logback.classic.PatternLayout">
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%npattern>
        layout>
        
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
          
          <level>debuglevel>
          
          <onMatch>ACCEPTonMatch>
          
          <onMismatch>DENYonMismatch>
        filter>
    appender>

    
    <appender name="fileInfo" class="ch.qos.logback.core.rolling.RollingFileAppender">
        
        
        <file>${LOG_HOME}/${appName}.logfile>
        
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            
            <fileNamePattern>${LOG_HOME}/${appName}-%d{yyyy-MM-dd}-%i.logfileNamePattern>
            
            <MaxHistory>365MaxHistory>
            
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MBmaxFileSize>
            timeBasedFileNamingAndTriggeringPolicy>
        rollingPolicy>
        
        <layout class="ch.qos.logback.classic.PatternLayout">
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [ %thread ] - [ %-5level ] [ %logger{50} : %line ] - %msg%npattern>
        layout>
        <append>trueappend>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>infolevel>
            <onMatch>ACCEPTonMatch>
            <onMismatch>DENYonMismatch>
        filter>
    appender>
    <appender name="fileError" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <file>${LOG_HOME}/${appName}-error.logfile>
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${LOG_HOME}/${appName}-error-%d{yyyy-MM-dd}-%i.logfileNamePattern>
            <MaxHistory>365MaxHistory>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>100MBmaxFileSize>
            timeBasedFileNamingAndTriggeringPolicy>
        rollingPolicy>
        <layout class="ch.qos.logback.classic.PatternLayout">
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [ %thread ] - [ %-5level ] [ %logger{50} : %line ] - %msg%npattern>
        layout>
        <append>trueappend>
        <filter class="ch.qos.logback.classic.filter.LevelFilter">
            <level>errorlevel>
            <onMatch>ACCEPTonMatch>
            <onMismatch>DENYonMismatch>
        filter>
    appender>

     
    <logger name="com.ibatis" level="DEBUG" />
    <logger name="com.ibatis.common.jdbc.SimpleDataSource" level="DEBUG" />
    <logger name="com.ibatis.common.jdbc.ScriptRunner" level="DEBUG" />
    <logger name="com.ibatis.sqlmap.engine.impl.SqlMapClientDelegate" level="DEBUG" />

    <logger name="java.sql.Connection" level="DEBUG" />
    <logger name="java.sql.Statement" level="DEBUG" />
    <logger name="java.sql.PreparedStatement" level="DEBUG" />
    <root level="DEBUG">
        <appender-ref ref="fileError" />
        <appender-ref ref="fileInfo" />
         
        <appender-ref ref="stdout" />
    root>
configuration>

到此springboot整合mybatis实现多数据源项目配置就完成了,开发的时候是需要关注业务逻辑,一般的controller如下

@RestController
@RequestMapping(value = "/")
public class CardInfoAction {
     

    @RequestMapping(value = "/test",method = RequestMethod.POST)
    @ResponseBody
    public Object getCardInfo(@RequestBody String cardId) {
        Map resultMap = new HashMap();
        resultMap.put("test","success")
        return resultMap;
    }
}

可参考
可参考

你可能感兴趣的:(spring-boot,spring-boot,mybatis,多数据源)