spring-boot mybatis 配置 主从分离 事务

首先是spring-boot的配置 web.xml  

<!DOCTYPE web-app PUBLIC
 "-//Sun Microsystems, Inc.//DTD Web Application 2.3//EN"
 "http://java.sun.com/dtd/web-app_2_3.dtd" >

<web-app xmlns="http://java.sun.com/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
		  http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd"
         version="3.0">

  <display-name>Archetype Created Web Application</display-name>
  <context-param>
    <param-name>spring.config.name</param-name>
    <param-value>cpp</param-value>
  </context-param>
  <context-param>
    <param-name>spring.config.path</param-name>
    <param-value>classpath:config/</param-value>
  </context-param>
  <context-param>
    <param-name>spring.profiles.active</param-name>
    <param-value>pro</param-value>
  </context-param>
  <context-param>
    <param-name>logging.config</param-name>
    <param-value>classpath:logbackConfig/logback-me-dev.xml</param-value>
  </context-param>
</web-app>

也可以不采用web.xml,直接在Application.java指向 cpp-pro.yml  (baseConfig,MvcConfig,SwaggerConfig,Redisconfig,HttpSessionConfig为其它config配置 跟本文无关) :

@SpringBootApplication
//@EnableAutoConfiguration
@EnableScheduling
public class Application extends SpringBootServletInitializer {

	
	public static void main(String[] args) {
		// TODO Auto-generated method stub
		//SpringApplication.run(Application.class, args);
		//System.out.println("classpath:/config/app-dev.yml");

		new SpringApplicationBuilder(BaseConfig.class,
				MvcConfig.class,
				SwaggerConfig.class,
				RedisConfig.class,
				HttpSessionConfig.class,
                   MyBatisConfig.class ).properties("spring.config.location=classpath:config/cpp-dev.yml").run(args); } }

  

cpp-pro.yml文件如下:

spring:
    redis:
        host: 127.0.0.1
        port: 6379
        timeout: 0
        pool:
          max-active: -1  #最大连接数
          max-idle: -1   #最大空闲数
          min-idle: 0     #最小空闲数
          max-wait: -1    #连接池耗尽时,新获取连接需要等待的最大时间
    freemarker:
        allow-request-override: false
        cache: true
        check-template-location: true
        charset: UTF-8
        content-type: text/html
        expose-request-attributes: false
        expose-session-attributes: false
        expose-spring-macro-helpers: false
        prefix:
        #suffix: .ftl
        suffix: .html
        template-loader-path: /freemarker/
        #request-context-attribute
        #settings.*
        #view-names:  # whitelist of view names that can be resolved
    datasource:
        driverClassName: com.mysql.jdbc.Driver
        url: jdbc:mysql://127.0.0.1:3306/me?zeroDateTimeBehavior=convertToNull&useUnicode=true&characterEncoding=utf8&autoReconnect=true&rewriteBatchedStatements=true&useSSL=false
        username: root
        password: huhanbo
        dsslave:
          driverClassName: com.mysql.jdbc.Driver
          url: jdbc:mysql://127.0.0.1:3306/me?zeroDateTimeBehavior=convertToNull&useUnicode=true&characterEncoding=utf8&autoReconnect=true&rewriteBatchedStatements=true&useSSL=false
          username: root
          password: huhanbo


mybatis:
typeAliasesPackage: com.me.mybatis.entity
mapperLocations: classpath:mybatisMapper/*.xml
 

另外 pom.xml的新加如下依赖 

<dependency>
            <groupId>org.mybatis.spring.boot</groupId>
            <artifactId>mybatis-spring-boot-starter</artifactId>
            <version>1.3.0</version>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.39</version>
        </dependency>

        <!-- https://mvnrepository.com/artifact/com.alibaba/druid -->
        <dependency>
            <groupId>com.alibaba</groupId>
            <artifactId>druid</artifactId>
            <version>1.1.2</version>
        </dependency>

 书写spring config文件:

package configuration;

import java.sql.SQLException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;

import javax.annotation.Resource;
import javax.sql.DataSource;

import com.me.common.datasource.DatabaseType;
import com.me.common.datasource.DynamicDataSource;
import org.apache.ibatis.mapping.DatabaseIdProvider;
import org.apache.ibatis.plugin.Interceptor;
import org.apache.ibatis.session.SqlSessionFactory;
import org.mybatis.spring.SqlSessionFactoryBean;
import org.mybatis.spring.annotation.MapperScan;
import org.mybatis.spring.boot.autoconfigure.ConfigurationCustomizer;
import org.mybatis.spring.boot.autoconfigure.MybatisAutoConfiguration;
import org.mybatis.spring.boot.autoconfigure.MybatisProperties;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration;
import org.springframework.boot.autoconfigure.jdbc.DataSourceBuilder;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Primary;
import org.springframework.context.annotation.PropertySource;
import org.springframework.core.io.ResourceLoader;
import org.springframework.core.io.support.PathMatchingResourcePatternResolver;
import org.springframework.jdbc.datasource.DataSourceTransactionManager;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
import com.alibaba.druid.pool.DruidDataSourceFactory;


import com.me.property.JdbcProperties;

@Configuration
@MapperScan(basePackages="com.me.mybatis.persistence")
@EnableTransactionManagement
public class MyBatisConfig {

    @Value("${mybatis.mapperLocations}")
    private String mapperLocations;
    
    @Value("${mybatis.typeAliasesPackage}")
    private String typeAliasesPackage;
    
    @Autowired
    private JdbcProperties jdbcProperties;


    @Bean
    @Primary
    public DataSource masterDataSource() throws Exception {

        Properties props = new Properties();
                 props.put("driverClassName", jdbcProperties.getDriverClassName());
                 props.put("url", jdbcProperties.getUrl());
                props.put("username", jdbcProperties.getUsername());
                props.put("password", jdbcProperties.getPassword());
                return DruidDataSourceFactory.createDataSource(props);
//        return DataSourceBuilder.create(Thread.currentThread().getContextClassLoader())
//                .driverClassName(jdbcProperties.getDriverClassName()).url(jdbcProperties.getUrl())
//                .username(jdbcProperties.getUsername()).password(jdbcProperties.getPassword()).build();
    }


    @Bean()
    public DataSource slaveDataSource() throws Exception {
        Properties props = new Properties();
        props.put("driverClassName", jdbcProperties.getDsslave().getDriverClassName());
        props.put("url", jdbcProperties.getDsslave().getUrl());
        props.put("username", jdbcProperties.getDsslave().getUsername());
        props.put("password", jdbcProperties.getDsslave().getPassword());
        return DruidDataSourceFactory.createDataSource(props);
    }


    /**
     * @Primary 该注解表示在同一个接口有多个实现类可以注入的时候,默认选择哪一个,而不是让@autowire注解报错
     *
     *
     */
    @Bean
    public DynamicDataSource dataSource(@Qualifier("masterDataSource") DataSource masterDataSource,
              @Qualifier("slaveDataSource") DataSource slaveDataSource) {
        Map<Object, Object> targetDataSources = new HashMap<>();
        targetDataSources.put(DatabaseType.masterDatasource, masterDataSource);
        targetDataSources.put(DatabaseType.slaveDatasource, slaveDataSource);

        DynamicDataSource dataSource = new DynamicDataSource();
        dataSource.setTargetDataSources(targetDataSources);// 该方法是AbstractRoutingDataSource的方法
        dataSource.setDefaultTargetDataSource(masterDataSource);// 默认的datasource设置为myTestDbDataSource
        return dataSource;
    }

    /**
     * 配置事务管理器
     */
    @Bean
    public DataSourceTransactionManager transactionManager(DynamicDataSource dataSource) throws Exception {
        return new DataSourceTransactionManager(dataSource);
    }

    @Bean
    public SqlSessionFactory createSqlSessionFactory(DynamicDataSource  dataSource) throws Exception {
        SqlSessionFactoryBean fb = new SqlSessionFactoryBean();
        fb.setDataSource(dataSource);
        fb.setMapperLocations(new PathMatchingResourcePatternResolver().getResources(mapperLocations));
        fb.setTypeAliasesPackage(typeAliasesPackage);
        return fb.getObject();
    }
}

使用web.xml指向cpp-pro.xml的startup class如下 (baseConfig,MvcConfig,SwaggerConfig,Redisconfig,HttpSessionConfig为其它config配置 跟本文无关)  :

package startup;

import configuration.*;
import org.springframework.boot.Banner.Mode;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.web.support.SpringBootServletInitializer;

//@SpringBootApplication
//@EnableAutoConfiguration
public class Application extends SpringBootServletInitializer {

    @Override
    protected SpringApplicationBuilder configure(SpringApplicationBuilder builder) {
        builder.sources(BaseConfig.class,
                MyBatisConfig.class,
                MvcConfig.class,
                WebSocketConfig.class,
                FastDfsConfig.class);
        return builder;
    }
    
    public static void main(String[] args) {
        // TODO Auto-generated method stub
//        SpringApplication.run(Application.class, args);

        SpringApplication app =new SpringApplicationBuilder(BaseConfig.class,
				MvcConfig.class,
				SwaggerConfig.class,
				RedisConfig.class,
				HttpSessionConfig.class,
           MyBatisConfig.class);

   app.setBannerMode(Mode.CONSOLE); 
   app.run(args);
    } 
}

  

事务在需要地方使用 @Transactional 即可

配置只读:

package com.me.common.datasource;


/**
  * 作用:
  * 1、保存一个线程安全的DatabaseType容器
  */
public class DatabaseContextHolder {
    private static final ThreadLocal<DatabaseType> contextHolder = new ThreadLocal<>();
             public static void setDatabaseType(DatabaseType type){
                contextHolder.set(type);
             }

            public static void clearDatabaseType(){
                contextHolder.remove();
            }

             public static DatabaseType getDatabaseType(){
                return contextHolder.get();
            }
}
package com.me.common.datasource;

public enum DatabaseType {
    masterDatasource,slaveDatasource
}
package com.me.common.datasource;

import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;

public class DynamicDataSource extends AbstractRoutingDataSource {

    protected Object determineCurrentLookupKey() {
                return DatabaseContextHolder.getDatabaseType();
    }
}
package com.me.common.datasource;

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

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface ReadOnlyConnection {
}
package com.me.common.datasource;

import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.core.Ordered;
import org.springframework.stereotype.Component;

@Aspect
@Component
public class ReadOnlyConnectionInterceptor implements Ordered {

    private static final Logger logger = LoggerFactory.getLogger(ReadOnlyConnectionInterceptor.class);

    @Around("@annotation(readOnlyConnection)")
    public Object proceed(ProceedingJoinPoint proceedingJoinPoint, ReadOnlyConnection readOnlyConnection) throws Throwable {
        try {
            logger.info("set database connection to read only");
            DatabaseContextHolder.setDatabaseType(DatabaseType.slaveDatasource);
            Object result = proceedingJoinPoint.proceed();
            return result;
        } finally {
            DatabaseContextHolder.clearDatabaseType();
            logger.info("restore database connection");
        }
    }

    @Override
    public int getOrder() {
        return 0;
    }
}
 
参考:
http://www.jb51.net/article/107706.htm
http://www.cnblogs.com/java-zhao/p/5413845.html
 
原文地址:https://www.cnblogs.com/huhanbo/p/7536636.html