java

关注公众号 jb51net

关闭
首页 > 软件编程 > java > SpringBoot+Mybatis plus多数据源

SpringBoot+Mybatis plus实现多数据源整合的实践

作者:书隐辞

本文主要介绍了SpringBoot+Mybatis plus实现多数据源整合的实践,文中通过示例代码介绍的非常详细,具有一定的参考价值,感兴趣的小伙伴们可以参考一下

SpringBoot 版本为1.5.10.RELEASE,Mybatis plus 版本为2.1.8。

第一步:填写配置信息:

spring:
  aop:
    proxy-target-class: true
    auto: true
  datasource:
    druid:
      # 数据库 1
      db1:
        url: jdbc:mysql://localhost:3306/db1?useUnicode=true&characterEncoding=utf8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true
        username: root
        password: root
        driver-class-name: com.mysql.jdbc.Driver
        initialSize: 5
        minIdle: 5
        maxActive: 20
      # 数据库 2
      db2:
        url: jdbc:mysql://localhost:3306/db2?useUnicode=true&characterEncoding=utf8&autoReconnect=true&zeroDateTimeBehavior=convertToNull&transformedBitIsBoolean=true
        username: root
        password: root
        driver-class-name: com.mysql.jdbc.Driver
        initialSize: 5
        minIdle: 5
        maxActive: 20

第二步: 数据源配置:

@Configuration
@MapperScan({"com.warm.system.mapper*"})
public class MybatisPlusConfig {

    /**
     * mybatis-plus分页插件<br>
     * 文档:http://mp.baomidou.com<br>
     */
    @Bean
    public PaginationInterceptor paginationInterceptor() {
        PaginationInterceptor paginationInterceptor = new PaginationInterceptor();
        //paginationInterceptor.setLocalPage(true);// 开启 PageHelper 的支持
        return paginationInterceptor;
    }

    /**
     * mybatis-plus SQL执行效率插件【生产环境可以关闭】
     */
    @Bean
    public PerformanceInterceptor performanceInterceptor() {
        return new PerformanceInterceptor();
    }

    @Bean(name = "db1")
    @ConfigurationProperties(prefix = "spring.datasource.druid.db1" )
    public DataSource db1 () {
        return DruidDataSourceBuilder.create().build();
    }

    @Bean(name = "db2")
    @ConfigurationProperties(prefix = "spring.datasource.druid.db2" )
    public DataSource db2 () {
        return DruidDataSourceBuilder.create().build();
    }
    /**
     * 动态数据源配置
     * @return
     */
    @Bean
    @Primary
    public DataSource multipleDataSource (@Qualifier("db1") DataSource db1,
                                          @Qualifier("db2") DataSource db2 ) {
        DynamicDataSource dynamicDataSource = new DynamicDataSource();
        Map< Object, Object > targetDataSources = new HashMap<>();
        targetDataSources.put(DBTypeEnum.db1.getValue(), db1 );
        targetDataSources.put(DBTypeEnum.db2.getValue(), db2);
        dynamicDataSource.setTargetDataSources(targetDataSources);
        dynamicDataSource.setDefaultTargetDataSource(db1);
        return dynamicDataSource;
    }

    @Bean("sqlSessionFactory")
    public SqlSessionFactory sqlSessionFactory() throws Exception {
        MybatisSqlSessionFactoryBean sqlSessionFactory = new MybatisSqlSessionFactoryBean();
        sqlSessionFactory.setDataSource(multipleDataSource(db1(),db2()));
        //sqlSessionFactory.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:/mapper/*/*Mapper.xml"));

        MybatisConfiguration configuration = new MybatisConfiguration();
        //configuration.setDefaultScriptingLanguage(MybatisXMLLanguageDriver.class);
        configuration.setJdbcTypeForNull(JdbcType.NULL);
        configuration.setMapUnderscoreToCamelCase(true);
        configuration.setCacheEnabled(false);
        sqlSessionFactory.setConfiguration(configuration);
        sqlSessionFactory.setPlugins(new Interceptor[]{ //PerformanceInterceptor(),OptimisticLockerInterceptor()
                paginationInterceptor() //添加分页功能
        });
        sqlSessionFactory.setGlobalConfig(globalConfiguration());
        return sqlSessionFactory.getObject();
    }

    @Bean
    public GlobalConfiguration globalConfiguration() {
        GlobalConfiguration conf = new GlobalConfiguration(new LogicSqlInjector());
        conf.setLogicDeleteValue("-1");
        conf.setLogicNotDeleteValue("1");
        conf.setIdType(0);
        conf.setMetaObjectHandler(new MyMetaObjectHandler());
        conf.setDbColumnUnderline(true);
        conf.setRefresh(true);
        return conf;
    }
}

第三步:利用AOP进行数据源的动态切换:

@Component
@Aspect
@Order(-100) //这是为了保证AOP在事务注解之前生效,Order的值越小,优先级越高
@Slf4j
public class DataSourceSwitchAspect {

    @Pointcut("execution(* com.warm.system.service.db1..*.*(..))")
    private void db1Aspect() {
    }

    @Pointcut("execution(* com.warm.system.service.db2..*.*(..))")
    private void db2Aspect() {
    }

    @Before( "db1Aspect()" )
    public void db1() {
        log.info("切换到db1 数据源...");
        DbContextHolder.setDbType(DBTypeEnum.db1);
    }

    @Before("db2Aspect()" )
    public void db2 () {
        log.info("切换到db2 数据源...");
        DbContextHolder.setDbType(DBTypeEnum.db2);
    }
}

public class DbContextHolder {
    private static final ThreadLocal contextHolder = new ThreadLocal<>();
    /**
     * 设置数据源
     * @param dbTypeEnum
     */
    public static void setDbType(DBTypeEnum dbTypeEnum) {
        contextHolder.set(dbTypeEnum.getValue());
    }

    /**
     * 取得当前数据源
     * @return
     */
    public static String getDbType() {
        return (String) contextHolder.get();
    }

    /**
     * 清除上下文数据
     */
    public static void clearDbType() {
        contextHolder.remove();
    }
}

public enum DBTypeEnum {
    db1("db1"), db2("db2");
    private String value;

    DBTypeEnum(String value) {
        this.value = value;
    }

    public String getValue() {
        return value;
    }
}

public class DynamicDataSource extends AbstractRoutingDataSource {
    /**
     * 取得当前使用哪个数据源
     * @return
     */
    @Override
    protected Object determineCurrentLookupKey() {
        return DbContextHolder.getDbType();
    }
}

OK!写个单元测试来验证一下:

@SpringBootTest
@RunWith(SpringJUnit4ClassRunner.class)
public class DataTest {
    @Autowired
    private UserService userService;
    @Autowired
    private OrderService orderService;

    @Test
    public void test() {
        userService.getUserList().stream().forEach(item -> System.out.println(item));
        orderService.getOrderList().stream().forEach(item -> System.out.println(item));
    }
}

如图所示,证明数据源能动态切换了。

这里写图片描述

具体项目结构和代码参考Github

踩坑记录:

直接调用Mybatis plus 的service方法AOP不会生效,即数据源不会动态切换,解决方法:在自己的service层中封装一下,调用自定义的service方法AOP即能正常生效了,如下所示:

@Service
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements UserService {

    @Override
    public List<User> getUserList() {
        return selectList(null);
    }
}

application.yml 定义的mybatis plus 配置信息不生效,如:

#MyBatis
mybatis-plus:
  mapper-locations: classpath:/mapper/*/*Mapper.xml
  #实体扫描,多个package用逗号或者分号分隔
  typeAliasesPackage: com.jinhuatuo.edu.sys.entity
  global-config:
    #主键类型  0:"数据库ID自增", 1:"用户输入ID",2:"全局唯一ID (数字类型唯一ID)", 3:"全局唯一ID UUID";
    id-type: 0
    #字段策略 0:"忽略判断",1:"非 NULL 判断"),2:"非空判断"
    field-strategy: 2
    #驼峰下划线转换
    db-column-underline: true
    #刷新mapper 调试神器
    refresh-mapper: true
    #数据库大写下划线转换
    #capital-mode: true
    #序列接口实现类配置
    #key-generator: com.baomidou.springboot.xxx
    #逻辑删除配置
    #logic-delete-value: 0
    #logic-not-delete-value: 1
    #自定义填充策略接口实现
    meta-object-handler: com.jinhuatuo.edu.config.mybatis.MyMetaObjectHandler
    #自定义SQL注入器
    #sql-injector: com.baomidou.springboot.xxx
  configuration:
    map-underscore-to-camel-case: true
    cache-enabled: false

解决方法: 所有这些配置在MybatisPlusConfig 类中用代码的方式进行配置,分页插件亦是如此,否则统计列表总数的数据会拿不到,参考代码即可。

在application.yml配置

logging:
  level: debug

控制台也不会打印Mybatis 执行的SQL语句,解决方法:自定义日志输出方案,如在classpath下直接引入日志配置文件如logback-spring.xml即可,同时application.yml无需再配置日志信息。

logback-spring.xml配置参考:

<?xml version="1.0" encoding="UTF-8"?>
<!-- scan:配置文件如果发生改变,将会重新加载,默认值为true -->
<configuration scan="true" scanPeriod="10 seconds">  
    <!-- <include resource="org/springframework/boot/logging/logback/base.xml"/> -->
    <!-- 日志文件路径 -->
    <!-- <springProperty  name="logFilePath" source="logging.path"/> -->
    <property resource="application.yml" />
    <substitutionProperty name="LOG_HOME" value="${logging.path}" />
    <substitutionProperty name="PROJECT_NAME" value="${spring.application.name}" />
    <!--<substitutionProperty name="CUR_NODE" value="${info.node}" />-->

    <!-- 日志数据库路径 -->
    <!-- <springProperty  name="logDbPath" source="spring.datasource.one.url"/>
    <springProperty  name="logDbDriver" source="spring.datasource.one.driver-class-name"/>
    <springProperty  name="logDbUser" source="spring.datasource.one.username"/>
    <springProperty  name="logDbPwd" source="spring.datasource.one.password"/> -->

    <!-- 将日志文件 -->
    <appender name="file" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <append>true</append>
        <encoder>
            <pattern>
                [ %-5level] [%date{yyyy-MM-dd HH:mm:ss}] %logger{96} [%line] - %msg%n
            </pattern>
            <charset>utf-8</charset>
        </encoder>

        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${LOG_HOME}/${PROJECT_NAME}-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>50 MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <!--最多保留30天log-->
            <maxHistory>30</maxHistory>
        </rollingPolicy>
    </appender>
    <!-- 将日志错误文件-->
    <appender name="file_error" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <append>true</append>
        <encoder>
            <pattern>
                [ %-5level] [%date{yyyy-MM-dd HH:mm:ss}] %logger{96} [%line] - %msg%n
            </pattern>
            <charset>utf-8</charset>
        </encoder>

        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <fileNamePattern>${LOG_HOME}/${PROJECT_NAME}-error-%d{yyyy-MM-dd}.%i.log</fileNamePattern>
            <timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
                <maxFileSize>50 MB</maxFileSize>
            </timeBasedFileNamingAndTriggeringPolicy>
            <!--最多保留30天log-->
            <maxHistory>30</maxHistory>
        </rollingPolicy>
        <filter class="ch.qos.logback.classic.filter.ThresholdFilter">
            <level>ERROR</level>
            <!-- <onMatch>ACCEPT</onMatch>
            <onMismatch>DENY </onMismatch> -->
        </filter>
    </appender>
    <!-- 将日志写入控制台 -->
    <appender name="console" class="ch.qos.logback.core.ConsoleAppender">
        <encoder>
            <pattern>
                [ %-5level] [%date{yyyy-MM-dd HH:mm:ss}] %logger{96} [%line] - %msg%n
            </pattern>
            <!--<charset>utf-8</charset>-->
        </encoder>
    </appender>
    <!-- 将日志写入数据库 -->
    <!-- <appender name="DB-CLASSIC-MYSQL-POOL" class="ch.qos.logback.classic.db.DBAppender">  
        <connectionSource class="ch.qos.logback.core.db.DataSourceConnectionSource">  
            <dataSource class="org.springframework.jdbc.datasource.DriverManagerDataSource">  
                <driverClassName>${logDbDriver}</driverClassName>  
                <url>${logDbPath}</url>  
                <username>${logDbUser}</username>  
                <password>${logDbPwd}</password>  
            </dataSource>  
        </connectionSource>  
    </appender>  -->
    <!-- spring扩展,分环境配置log信息 -->
    <springProfile name="dev">

        <!-- <logger name="sand.dao" level="DEBUG"/> -->
        <!-- <logger name="org.springframework.web" level="INFO"/> -->
        <logger name="org.springboot.sample" level="TRACE" />
        <logger name="org.springframework.cloud" level="INFO" />
        <logger name="com.netflix" level="INFO"></logger>
        <logger name="org.springframework.boot" level="INFO"></logger>
        <logger name="org.springframework.web" level="INFO"/>
        <logger name="jdbc.sqltiming" level="debug"/>
        <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" />
        <logger name="java.sql.ResultSet" level="debug" />
        <logger name="com.warm" level="debug"/>
        <root level="DEBUG">
            <appender-ref ref="console" />
            <appender-ref ref="file" />
        </root>
        <root level="ERROR">
            <appender-ref ref="file_error" />
            <!-- <appender-ref ref="DB-CLASSIC-MYSQL-POOL" /> -->
        </root>
    </springProfile>
    <springProfile name="test">
        <logger name="org.springboot.sample" level="TRACE" />
        <logger name="org.springframework.cloud" level="INFO" />
        <logger name="com.netflix" level="INFO"></logger>
        <logger name="org.springframework.boot" level="INFO"></logger>

        <logger name="org.springframework.web" level="INFO"/>
        <logger name="jdbc.sqltiming" level="debug"/>
        <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" />
        <logger name="java.sql.ResultSet" level="debug" />
        <logger name="com.warm" level="DEBUG"/>
        <root level="DEBUG">
            <!-- <appender-ref ref="console" /> -->
            <appender-ref ref="file" />
        </root>
        <root level="ERROR">
            <appender-ref ref="file_error" />
            <!-- <appender-ref ref="DB-CLASSIC-MYSQL-POOL" /> -->
        </root>
    </springProfile>
    <springProfile name="prod">
        <logger name="org.springboot.sample" level="TRACE" />
        <logger name="org.springframework.cloud" level="INFO" />
        <logger name="com.netflix" level="INFO"></logger>
        <logger name="org.springframework.boot" level="INFO"></logger>

        <logger name="org.springframework.web" level="INFO"/>
        <logger name="jdbc.sqltiming" level="debug"/>
        <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" />
        <logger name="java.sql.ResultSet" level="debug" />
        <logger name="com.warm" level="info"/>
        <root level="DEBUG">
            <!-- <appender-ref ref="console" /> -->
            <appender-ref ref="file" />
        </root>
        <root level="ERROR">
            <appender-ref ref="file_error" />
            <!-- <appender-ref ref="DB-CLASSIC-MYSQL-POOL" /> -->
        </root>
    </springProfile>
</configuration>

到此这篇关于SpringBoot+Mybatis plus实现多数据源整合的实践的文章就介绍到这了,更多相关SpringBoot+Mybatis plus多数据源内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文