Springboot統合mybatis、動的データソース構成


このアイテムは、データ・クエリー・インタフェース・サービスです.ブラウザでurlにアクセスし、jsonパラメータを渡し、jsonデータを返します.springbootマイクロサービスで構築し、mybatisを統合し、データベースをクエリーします.データは2つのデータベースに保存されているため、プロジェクトではサービスによってクラスが存在するパッケージを実現したり、カスタム注釈によってデータソースを動的に切り替えたりします.次のように構成されています.
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アプリケーション.propertiesプロファイルspringbootコアプロファイルとはresourcesルートディレクトリの下のアプリケーションを指す.propertiesまたはapplication.ymlプロファイル、ここでpropertiesプロファイルを使用して、propertiesプロファイルを読み込む方法は2つあります.1つは@Value('${}")です.1つは@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.Springが事前に実装してくれた親AbstractRoutingDataSourceを継承するだけで、DataSourceインタフェースを実装できます.
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接面を作成し,データソース切替論理を実現する.ここで問題があります.一般的にトランザクションはサービス層に追加されます.springの宣言トランザクション管理を使用すると、サービス層コードを呼び出す前にspringはaopでトランザクション制御コードを動的に追加します.したがって、トランザクションが有効であることを保証するには、springがトランザクションを追加する前にデータソースを動的に切り替えなければなりません.すなわち、動的切替データソースのaopは、少なくともサービスに追加する、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注入を一緒に置く.@コンフィギュレーションは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;
    }
}

参照可能