springboot+mybatis-plus+druid 动态

作者: DimonHo | 来源:发表于2019-12-15 15:01 被阅读0次

    一. springboot+mybatis-plus+druid实现动态数据源以及监控

    1. 创建一个springboot项目dynamic-db,数据库以mysql为例

    init springboot
    dynamic-db

    2. 引入依赖jar包

    • pom.xml
    <?xml version="1.0" encoding="UTF-8"?>
    <project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
             xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
        <modelVersion>4.0.0</modelVersion>
        <parent>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-parent</artifactId>
            <version>2.2.2.RELEASE</version>
            <relativePath/> <!-- lookup parent from repository -->
        </parent>
        <groupId>com.example</groupId>
        <artifactId>dynamic-db</artifactId>
        <version>0.0.1-SNAPSHOT</version>
        <name>dynamic-db</name>
        <description>Demo project for Spring Boot</description>
    
        <properties>
            <java.version>1.8</java.version>
            <mybatis-plus.version>3.2.0</mybatis-plus.version>
            <druid.version>1.1.21</druid.version>
            <p6spy.version>3.8.6</p6spy.version>
        </properties>
    
        <dependencies>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-web</artifactId>
                <scope>compile</scope>
            </dependency>
            <dependency>
                <groupId>com.alibaba</groupId>
                <artifactId>druid-spring-boot-starter</artifactId>
                <version>${druid.version}</version>
            </dependency>
            <dependency>
                <groupId>p6spy</groupId>
                <artifactId>p6spy</artifactId>
                <version>${p6spy.version}</version>
            </dependency>
            <dependency>
                <groupId>com.baomidou</groupId>
                <artifactId>mybatis-plus-boot-starter</artifactId>
                <version>${mybatis-plus.version}</version>
            </dependency>
            <dependency>
                <groupId>mysql</groupId>
                <artifactId>mysql-connector-java</artifactId>
                <scope>runtime</scope>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-aop</artifactId>
            </dependency>
            <dependency>
                <groupId>org.projectlombok</groupId>
                <artifactId>lombok</artifactId>
                <optional>true</optional>
            </dependency>
            <dependency>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-starter-test</artifactId>
                <scope>test</scope>
                <exclusions>
                    <exclusion>
                        <groupId>org.junit.vintage</groupId>
                        <artifactId>junit-vintage-engine</artifactId>
                    </exclusion>
                </exclusions>
            </dependency>
        </dependencies>
        <build>
            <plugins>
                <plugin>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-maven-plugin</artifactId>
                </plugin>
            </plugins>
        </build>
    </project>
    

    3. 配置application.yml,添加多个数据源连接属性

    server:
      port: 8080
    spring:
      aop:
        proxy-target-class: true
        auto: true
      datasource:
        db1:
          driver-class-name: com.p6spy.engine.spy.P6SpyDriver  ## 也可以使用mysql官方驱动,这里替换成p6spy驱动方便打印sql日志
          url: jdbc:p6spy:mysql://mysql-dev.database:3306/dynamic-source?autoReconnect=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false
          username: root
          password: 123456
          db-type: mysql
          validation-query: "select 1"
          max-active: 16
          min-idle: 4
        db2:
          driver-class-name: com.p6spy.engine.spy.P6SpyDriver
          url: jdbc:p6spy:mysql://mysql-dev.database:3306/dynamic-target?autoReconnect=true&useUnicode=true&characterEncoding=UTF-8&useSSL=false
          username: root
          password: 123456
          db-type: mysql
          validation-query: "select 1"
        druid:  # druid 监控配置
          filter:
            stat:
              enabled: true  #是否激活sql监控
            wall:
              enabled: true  #是否激活sql防火墙
          web-stat-filter:
            enabled: true
          stat-view-servlet:
            enabled: true
            url-pattern: "/druid/*"   #浏览器访问localhost:8080/druid,进入监控界面
            login-username: admin
            login-password: admin@123
    

    4.创建datasource相关配置类

    • DbGlobal
    /**
     * @Author: He Zhigang
     * @Date: 2019/12/15 12:14
     * @Description:
     */
    public class DbGlobal {
    
        /**
         * 第一个数据源自动生成的包名名称
         */
        public static final String DB1 = "source";
        /**
         * 第二个数据源自动生成的包名名称
         */
        public static final String DB2 = "target";
    
        //TODO 跟多数据源继续添加DB3,DB4,...
        
    }
    
    • DataSourceContextHolder
    /**
     * @Author: He Zhigang
     * @Date: 2019/12/10 12:45
     * @Description: 数据源上线文
     */
    public class DataSourceContextHolder {
        /**
         * 实际上就是开启多个线程,每个线程进行初始化一个数据源
         */
        private static final ThreadLocal<String> CONTEXT_HOLDER = new ThreadLocal<>();
    
        /**
         * 取得当前数据源
         *
         * @return
         */
        public static String getDbType() {
            return CONTEXT_HOLDER.get();
        }
    
        /**
         * 设置数据源
         *
         * @param dbEnum
         */
        public static void setDbType(String dbEnum) {
            CONTEXT_HOLDER.set(dbEnum);
        }
    
        /**
         * 清除上下文数据
         */
        public static void clearDbType() {
            CONTEXT_HOLDER.remove();
        }
    }
    
    • DynamicDataSource
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource;
    
    /**
     * @Author: He Zhigang
     * @Date: 2019/12/10 12:44
     * @Description: 动态路由数据源
     */
    @Slf4j
    public class DynamicDataSource extends AbstractRoutingDataSource {
        @Override
        protected Object determineCurrentLookupKey() {
            String datasource = DataSourceContextHolder.getDbType();
            log.debug("使用数据源 {}", datasource);
            return datasource;
        }
    }
    
    • MybatisPlusConfig
    import com.alibaba.druid.spring.boot.autoconfigure.DruidDataSourceBuilder;
    import com.baomidou.mybatisplus.core.MybatisConfiguration;
    import com.baomidou.mybatisplus.extension.plugins.PaginationInterceptor;
    import com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean;
    import org.apache.ibatis.session.SqlSessionFactory;
    import org.apache.ibatis.type.JdbcType;
    import org.mybatis.spring.annotation.MapperScan;
    import org.springframework.beans.factory.annotation.Qualifier;
    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.transaction.annotation.EnableTransactionManagement;
    
    import javax.sql.DataSource;
    import java.util.HashMap;
    import java.util.Map;
    
    /**
     * @Author: He Zhigang
     * @Date: 2019/12/10 12:41
     * @Description:
     */
    @EnableTransactionManagement
    @Configuration
    @MapperScan("com.example.dynamicdb.*.mapper") // 扫描mapperdao的地址
    public class MybatisPlusConfig {
    
        @Bean
        public PaginationInterceptor paginationInterceptor() {
            return new PaginationInterceptor();
        }
    
        @Bean(name = DbGlobal.DB1)
        @ConfigurationProperties(prefix = "spring.datasource.db1")
        public DataSource source() {
            return DruidDataSourceBuilder.create().build();
        }
    
        @Bean(name = DbGlobal.DB2)
        @ConfigurationProperties(prefix = "spring.datasource.db2")
        public DataSource target() {
            return DruidDataSourceBuilder.create().build();
        }
    
        /**
         * 动态数据源配置
         *
         * @return
         */
        @Bean
        @Primary
        public DataSource multipleDataSource(@Qualifier(DbGlobal.DB1) DataSource source,
                                             @Qualifier(DbGlobal.DB2) DataSource target) {
            DynamicDataSource dynamicDataSource = new DynamicDataSource();
            Map<Object, Object> targetDataSources = new HashMap<>();
            targetDataSources.put(DbGlobal.DB1, source);
            targetDataSources.put(DbGlobal.DB2, target);
            dynamicDataSource.setTargetDataSources(targetDataSources);
            // 程序默认数据源,这个要根据程序调用数据源频次,经常把常调用的数据源作为默认
            dynamicDataSource.setDefaultTargetDataSource(source);
            return dynamicDataSource;
        }
    
        @Bean("sqlSessionFactory")
        public SqlSessionFactory sqlSessionFactory() throws Exception {
            MybatisSqlSessionFactoryBean sqlSessionFactory = new MybatisSqlSessionFactoryBean();
            sqlSessionFactory.setDataSource(multipleDataSource(source(), target()));
    
            MybatisConfiguration configuration = new MybatisConfiguration();
            configuration.setJdbcTypeForNull(JdbcType.NULL);
            configuration.setMapUnderscoreToCamelCase(true);
            configuration.setCacheEnabled(false);
            sqlSessionFactory.setConfiguration(configuration);
            // 乐观锁插件
            //PerformanceInterceptor(),OptimisticLockerInterceptor()
            // 分页插件
            sqlSessionFactory.setPlugins(paginationInterceptor());
            return sqlSessionFactory.getObject();
        }
    }
    
    • DataSourceAspect
    import lombok.extern.slf4j.Slf4j;
    import org.aspectj.lang.annotation.Aspect;
    import org.aspectj.lang.annotation.Before;
    import org.aspectj.lang.annotation.Pointcut;
    import org.springframework.core.annotation.Order;
    import org.springframework.stereotype.Component;
    
    /**
     * @Author: He Zhigang
     * @Date: 2019/12/10 12:55
     * @Description: AOP拦截动态切换数据源
     */
    @Component
    @Order(value = -100)
    @Slf4j
    @Aspect
    public class DataSourceAspect {
        @Pointcut("execution(* com.example.dynamicdb."+ DbGlobal.DB1+".mapper..*.*(..))")
        private void sourceAspect() {
        }
    
        @Pointcut("execution(* com.example.dynamicdb."+ DbGlobal.DB2+".mapper..*.*(..))")
        private void targetAspect() {
        }
    
        @Before("sourceAspect()")
        public void source() {
            log.info("切换到{} 数据源...",DbGlobal.DB1);
            DataSourceContextHolder.setDbType(DbGlobal.DB1);
        }
    
        @Before("targetAspect()")
        public void target() {
            log.info("切换到{} 数据源...",DbGlobal.DB2);
            DataSourceContextHolder.setDbType(DbGlobal.DB2);
        }
    }
    

    最后,在把p6spy的配置文件加上

    #3.2.1以上使用
    #modulelist=com.baomidou.mybatisplus.extension.p6spy.MybatisPlusLogFactory,com.p6spy.engine.outage.P6OutageFactory
    #3.2.1以下使用或者不配置
    modulelist=com.p6spy.engine.logging.P6LogFactory,com.p6spy.engine.outage.P6OutageFactory
    # 自定义日志打印
    logMessageFormat=com.baomidou.mybatisplus.extension.p6spy.P6SpyLogger
    #日志输出到控制台
    appender=com.baomidou.mybatisplus.extension.p6spy.StdoutLogger
    # 使用日志系统记录 sql
    #appender=com.p6spy.engine.spy.appender.Slf4JLogger
    # 设置 p6spy driver 代理
    deregisterdrivers=true
    # 取消JDBC URL前缀
    useprefix=true
    # 配置记录 Log 例外,可去掉的结果集有error,info,batch,debug,statement,commit,rollback,result,resultset.
    excludecategories=info,debug,result,commit,resultset
    # 日期格式
    dateformat=yyyy-MM-dd HH:mm:ss
    # 实际驱动可多个
    driverlist=com.mysql.cj.jdbc.Driver
    # 是否开启慢SQL记录
    outagedetection=true
    # 慢SQL记录标准 2 秒
    outagedetectioninterval=2
    

    以上,就能实现动态数据源的切换了,注释都在代码里面都十分清楚了。


    目录结构

    不出意外的话启动后访问 http://localhost:8080/druid/index.html 就可以看到druid提供的监控页面了。
    源码地址:https://github.com/DimonHo/dynamic-db/tree/no-auto-generator


    二. 自动生成代码

    一般我用到动态数据都是源于数据迁移的需求,这就表示数据库一般是已经建好了的,里面可能已经有了几十上百个表,如果要一个一个手动创建与表对应的entity工作量可能有点大。现在我们再来看看如何利用com.baomidou:mybatis-plus-generator:3.2.0来给我们自动生成数据库里面已有表对应的实体类。

    1. 在上面的项目基础上,我们再到pom.xml中添加两个jar包

            <!--国内超级好用的一个util包,囊括了常用的工具方法-->
            <dependency>
                <groupId>cn.hutool</groupId>
                <artifactId>hutool-all</artifactId>
                <version>${hutool.version}</version>
            </dependency>
            <!--自动生成代码-->
            <dependency>
                <groupId>com.baomidou</groupId>
                <artifactId>mybatis-plus-generator</artifactId>
                <version>${mybatis-plus.version}</version>
            </dependency>
    

    2. 我们创建一个自动生成代码的类

    根据官方示例代码 https://mybatis.plus/guide/generator.html 修改
    我们首先创建一个配置类auto-generator.properties,把相关可以自定义的配置放入配置文件里面,方便修改。

    • 新建auto-generator.properties配置文件
    ## 输出路径,默认:D:\\。 多模块项目需要加上项目名称: \\dynamic-db/src/main/java
    global.outputDir=/src/main/java
    global.fileOverride=false
    global.open=false
    global.author=DimonHo
    global.baseResultMap=true
    global.baseColumnList=true
    global.swagger=false
    
    ## 自定义service类名称,默认:I%sService
    global.serviceName=%sService
    ## 自定义controller类名称,默认:%sController
    global.controllerName=
    ## 自定义mapper类名称,默认:%sMapper
    global.mapperName=
    ## 自定义mapper xml名称,默认:%sMapper
    global.xmlName=
    ## 自定义service实现类名称,默认:%ServiceImpl
    global.serviceImplName=
    global.entityName=
    ## 是否在xml中添加二级缓存配置
    global.enableCache=true
    
    ## 模板引擎类型:默认velocity,可选值:freemarker,beetl
    injection.templateType=freemarker
    
    package.parent=com.example.dynamicdb
    package.controller=controller
    package.entity=entity
    package.mapper=mapper
    package.service=service
    package.serviceImpl=service.impl
    
    ## 指定相关父类
    strategy.superEntityClass=
    strategy.superControllerClass=com.example.dynamicdb.controller.BaseController
    ## 指定父类Entity中的字段,多个以","分隔
    strategy.superEntityColumns=
    
    • 新建 CustomMetaObjectHandler
    import com.baomidou.mybatisplus.core.handlers.MetaObjectHandler;
    import lombok.extern.slf4j.Slf4j;
    import org.apache.ibatis.reflection.MetaObject;
    import org.springframework.stereotype.Component;
    
    import java.util.Date;
    
    /**
     * @Author: He Zhigang
     * @Date: 2019/12/11 20:30
     * @Description: 自定义自动填充字段处理策略
     */
    @Slf4j
    @Component
    public class CustomMetaObjectHandler implements MetaObjectHandler {
    
        @Override
        public void insertFill(MetaObject metaObject) {
            log.info("start insert fill ....");
            this.setFieldValByName("gmtCreate", new Date(), metaObject);
            this.setFieldValByName("gmtModified", new Date(), metaObject);
        }
    
        @Override
        public void updateFill(MetaObject metaObject) {
            log.info("start update fill ....");
            this.setFieldValByName("gmtModified", new Date(), metaObject);
        }
    }
    
    • 修改DataSourceContextHolder,在其中中添加一个常量:
        /**
         * 保存所有的数据源
         */
        public static final Map<String, DataSource> DATA_SOURCE_MAP = new HashMap<>();
    
    • DbGlobal中再添加几个配置字段
        /**
         * 需要自动生成的表,为空表示全部生成,与STRATEGY_EXCLUDE不能同时设置
         */
        public static final Map<String, String[]> STRATEGY_INCLUDE = MapUtil.builder(DB1, new String[]{})
                .put(DB2, new String[]{})
                //TODO put DB3,...
                .build();
    
        /**
         * 排除自动生成的表,为空表示不排除,与STRATEGY_INCLUDE不能同时设置
         */
        public static final Map<String, String[]> STRATEGY_EXCLUDE = MapUtil.builder(DB1, new String[]{"scopus_journal","scopus_journal2","t_update_log","t_journal_metadata"})
                .put(DB2, new String[]{})
                //TODO put DB3,...
                .build();
    
        /**
         * 数据源DB1中需要自动填充的字段
         */
        public static final List<TableFill> DB1_TABLE_FILL = CollectionUtil.newArrayList(
                new TableFill("gmt_create", FieldFill.INSERT),
                new TableFill("gmt_modified", FieldFill.INSERT_UPDATE)
                //TODO new TableFill("columnName",FieldFill),...
        );
    
        /**
         * 数据源DB2中需要填充的字段
         */
        public static final List<TableFill> DB2_TABLE_FILL = CollectionUtil.newArrayList(
                new TableFill("gmt_create", FieldFill.INSERT),
                new TableFill("gmt_modified", FieldFill.INSERT_UPDATE)
                //TODO new TableFill("columnName",FieldFill),...
        );
    
        public static final Map<String, List<TableFill>> STRATEGY_TABLE_FILL = MapUtil.builder(DB1, DB1_TABLE_FILL)
                //TODO put(DB2,DB2_TABLE_FILL),...
                .build();
    
    
    • 新建 AutoGeneratorBean
    import cn.hutool.core.collection.CollectionUtil;
    import cn.hutool.core.text.StrSpliter;
    import cn.hutool.core.util.ArrayUtil;
    import cn.hutool.core.util.StrUtil;
    import com.alibaba.druid.pool.DruidDataSource;
    import com.baomidou.mybatisplus.core.toolkit.StringPool;
    import com.baomidou.mybatisplus.generator.AutoGenerator;
    import com.baomidou.mybatisplus.generator.InjectionConfig;
    import com.baomidou.mybatisplus.generator.config.*;
    import com.baomidou.mybatisplus.generator.config.po.TableInfo;
    import com.baomidou.mybatisplus.generator.config.rules.DateType;
    import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
    import com.baomidou.mybatisplus.generator.engine.FreemarkerTemplateEngine;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.boot.ApplicationArguments;
    import org.springframework.boot.ApplicationRunner;
    import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
    import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
    import org.springframework.context.annotation.Configuration;
    
    import javax.sql.DataSource;
    import java.util.ArrayList;
    import java.util.List;
    import java.util.ResourceBundle;
    
    /**
     * @Author: He Zhigang
     * @Date: 2019/12/11 9:29
     * @Description:
     */
    @Slf4j
    @Configuration
    @ConditionalOnClass(value = AutoGenerator.class)
    @ConditionalOnProperty(prefix = "mybatis-plus", name = "auto-generator", havingValue = "true")
    public class AutoGeneratorBean implements ApplicationRunner {
    
        /**
         * auto_generator配置文件名称,位于resources目录下
         */
        private static final String AUTO_GENERATOR = "auto-generator";
    
        @Override
        public void run(ApplicationArguments args) throws Exception {
            //读取配置文件
            ResourceBundle rb = ResourceBundle.getBundle(AUTO_GENERATOR);
            DataSourceContextHolder.DATA_SOURCE_MAP.forEach((db, datasource) -> {
                log.info(db + "模块自动创建开始。。。");
                String projectPath = System.getProperty("user.dir");
                // 代码生成器
                AutoGenerator mpg = new AutoGenerator();
                mpg.setGlobalConfig(globalConfig(rb, projectPath))
                        .setDataSource(dataSourceConfig(datasource))
                        .setPackageInfo(packageConfig(rb, db))
                        .setCfg(InjectionConfig(rb, db, projectPath))
                        .setTemplate(templateConfig())
                        .setStrategy(strategyConfig(rb, db))
                        .setTemplateEngine(new FreemarkerTemplateEngine())
                        .execute();
                log.info(db + "模块自动创建完成!");
            });
        }
    
        private InjectionConfig InjectionConfig(ResourceBundle rb, String db, String projectPath) {
            InjectionConfig cfg = new InjectionConfig() {
                @Override
                public void initMap() {
                    // to do nothing
                }
            };
            //模板引擎默认是velocity,可选freemarker,beetl
            String templatePath = "/templates/mapper.xml";
            switch (rb.getString("injection.templateType")) {
                case "freemarker":
                    templatePath = templatePath + ".ftl";
                    break;
                case "beetl":
                    templatePath = templatePath + ".btl";
                    break;
                default:
                    templatePath = templatePath + ".vm";
            }
            // 自定义输出配置
            List<FileOutConfig> focList = new ArrayList<>();
            // 自定义配置会被优先输出
            focList.add(new FileOutConfig(templatePath) {
                @Override
                public String outputFile(TableInfo tableInfo) {
                    // 自定义输出文件名 , 如果你 Entity 设置了前后缀、此处注意 xml 的名称会跟着发生变化!!
                    return projectPath + "/src/main/resources/mapper/" + db
                            + "/" + String.format(StrUtil.blankToDefault(rb.getString("global.xmlName"), "%sMapper"), tableInfo.getEntityName()) + StringPool.DOT_XML;
                }
            });
            cfg.setFileOutConfigList(focList);
            return cfg;
        }
    
        /**
         * 配置模板
         *
         * @return
         */
        private TemplateConfig templateConfig() {
            TemplateConfig templateConfig = new TemplateConfig();
            // 配置自定义输出模板
            //指定自定义模板路径,注意不要带上.ftl/.vm, 会根据使用的模板引擎自动识别
            templateConfig.setXml(null);
            return templateConfig;
        }
    
        private StrategyConfig strategyConfig(ResourceBundle rb, String db) {
            StrategyConfig strategy = new StrategyConfig()
                    .setNaming(NamingStrategy.underline_to_camel)
                    .setColumnNaming(NamingStrategy.underline_to_camel)
                    .setEntityLombokModel(true)
                    .setRestControllerStyle(true)
                    .setControllerMappingHyphenStyle(true)
                    .setTablePrefix(db + "_");
            if (ArrayUtil.isNotEmpty(DbGlobal.STRATEGY_INCLUDE.get(db))) {
                strategy.setInclude(DbGlobal.STRATEGY_INCLUDE.get(db));
            } else if (ArrayUtil.isNotEmpty(DbGlobal.STRATEGY_EXCLUDE.get(db))) {
                strategy.setExclude(DbGlobal.STRATEGY_EXCLUDE.get(db));
            }
            if (CollectionUtil.isNotEmpty(DbGlobal.STRATEGY_TABLE_FILL.get(db))) {
                strategy.setTableFillList(DbGlobal.STRATEGY_TABLE_FILL.get(db));
            }
            if (StrUtil.isNotBlank(rb.getString("strategy.superEntityClass"))) {
                strategy.setSuperEntityClass(rb.getString("strategy.superEntityClass"));
                if (StrUtil.isNotBlank(rb.getString("strategy.superEntityColumns"))) {
                    // 写于父类中的公共字段
                    strategy.setSuperEntityColumns(StrSpliter.splitToArray(rb.getString("strategy.superEntityColumns"), ",", 0, true, true));
                }
            }
            strategy.setSuperControllerClass(rb.getString("strategy.superControllerClass"));
            return strategy;
        }
    
        private PackageConfig packageConfig(ResourceBundle rb, String db) {
            return new PackageConfig()
                    .setParent(rb.getString("package.parent"))
                    .setModuleName(db)
                    .setController(StrUtil.blankToDefault(rb.getString("package.controller"), "controller"))
                    .setEntity(StrUtil.blankToDefault(rb.getString("package.entity"), "entity"))
                    .setMapper(StrUtil.blankToDefault(rb.getString("package.mapper"), "mapper"))
                    .setService(StrUtil.blankToDefault(rb.getString("package.service"), "service"))
                    .setServiceImpl(StrUtil.blankToDefault(rb.getString("package.serviceImpl"), "service.impl"));
        }
    
        private DataSourceConfig dataSourceConfig(DataSource datasource) {
            DruidDataSource ds = (DruidDataSource) datasource;
            return new DataSourceConfig()
                    .setUrl(ds.getUrl())
                    .setDriverName(ds.getDriverClassName())
                    .setUsername(ds.getUsername())
                    .setPassword(ds.getPassword());
        }
    
        private GlobalConfig globalConfig(ResourceBundle rb, String projectPath) {
            return new GlobalConfig()
                    .setOutputDir(projectPath + rb.getString("global.outputDir"))
                    .setFileOverride(Boolean.parseBoolean(rb.getString("global.fileOverride")))
                    .setAuthor(rb.getString("global.author"))
                    .setControllerName(rb.getString("global.controllerName"))
                    .setEntityName(rb.getString("global.entityName"))
                    .setMapperName(rb.getString("global.mapperName"))
                    .setXmlName(rb.getString("global.xmlName"))
                    .setServiceName(rb.getString("global.serviceName"))
                    .setServiceImplName(rb.getString("global.serviceImplName"))
                    .setEnableCache(Boolean.parseBoolean(rb.getString("global.enableCache")))
                    .setSwagger2(Boolean.parseBoolean(rb.getString("global.swagger")))
                    .setBaseColumnList(Boolean.parseBoolean(rb.getString("global.baseColumnList")))
                    .setBaseResultMap(Boolean.parseBoolean(rb.getString("global.baseResultMap")))
                    .setDateType(DateType.ONLY_DATE)
                    .setOpen(Boolean.parseBoolean(rb.getString("global.open")));
        }
    }
    
    • 修改MybatisPlusConfig
        @Autowired
        CustomMetaObjectHandler customMetaObjectHandler;
    
        /**
         * 动态数据源配置
         *
         * @return
         */
        @Bean
        @Primary
        public DataSource multipleDataSource(@Qualifier(DbGlobal.DB1) DataSource source,
                                             @Qualifier(DbGlobal.DB2) DataSource target) {
            DynamicDataSource dynamicDataSource = new DynamicDataSource();
    
            // 将所有的数据源放入map,后面遍历Map,对其中所有的数据源自动生成代码 start======
            DataSourceContextHolder.DATA_SOURCE_MAP.put(DbGlobal.DB1, source);
            DataSourceContextHolder.DATA_SOURCE_MAP.put(DbGlobal.DB2, target);
            // 将所有的数据源放入map,后面遍历Map,对其中所有的数据源自动生成代码 end======
    
            Map<Object, Object> targetDataSources = new HashMap<>();
            targetDataSources.put(DbGlobal.DB1, source);
            targetDataSources.put(DbGlobal.DB2, target);
            dynamicDataSource.setTargetDataSources(targetDataSources);
            // 程序默认数据源,这个要根据程序调用数据源频次,经常把常调用的数据源作为默认
            dynamicDataSource.setDefaultTargetDataSource(source);
            return dynamicDataSource;
        }
    
        @Bean("sqlSessionFactory")
        public SqlSessionFactory sqlSessionFactory() throws Exception {
            MybatisSqlSessionFactoryBean sqlSessionFactory = new MybatisSqlSessionFactoryBean();
            sqlSessionFactory.setDataSource(multipleDataSource(source(), target()));
    
            MybatisConfiguration configuration = new MybatisConfiguration();
            configuration.setJdbcTypeForNull(JdbcType.NULL);
            configuration.setMapUnderscoreToCamelCase(true);
            configuration.setCacheEnabled(false);
            sqlSessionFactory.setConfiguration(configuration);
            //PerformanceInterceptor(),OptimisticLockerInterceptor()
            //添加分页功能
            sqlSessionFactory.setPlugins(paginationInterceptor());
            // 设置自动填充策略 start========
            GlobalConfig globalConfig = new GlobalConfig();
            globalConfig.setMetaObjectHandler(customMetaObjectHandler);
            sqlSessionFactory.setGlobalConfig(globalConfig);
            // 设置自动填充策略 end========
            return sqlSessionFactory.getObject();
        }
    
    • 新建一个BaseController抽象类,所有的controller集成该类,可自动生成你所需要的基础rest接口
    import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
    import com.baomidou.mybatisplus.core.metadata.IPage;
    import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
    import com.baomidou.mybatisplus.extension.service.IService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.*;
    
    /**
     * @Author: He Zhigang
     * @Date: 2019/12/10 16:17
     * @Description:
     */
    public abstract class BaseController<T> {
    
        @Autowired
        public IService<T> service;
    
        @PostMapping()
        public Object add(@RequestBody T t) {
            return service.save(t);
        }
    
        @PutMapping()
        public Object update(@RequestBody T t) {
            return service.updateById(t);
        }
    
        @GetMapping("/{id}")
        public T get(@PathVariable Long id) {
            return service.getById(id);
        }
    
        @GetMapping()
        public IPage<T> find(@RequestParam(required = false,defaultValue = "0") Integer page,
                             @RequestParam(required = false,defaultValue = "10") Integer size) {
            Page<T> pageable = new Page<>(page, size);
            return service.page(pageable, new QueryWrapper<T>().orderByDesc("id"));
        }
    }
    
    • 在resource/templates目录下创建congtroller.java.ftl模板
    package ${package.Controller};
    
    import org.springframework.web.bind.annotation.RequestMapping;
    import org.springframework.beans.factory.annotation.Autowired;
    import ${package.Entity}.${entity};
    import ${package.Service}.${table.serviceName};
    <#if restControllerStyle>
    import org.springframework.web.bind.annotation.RestController;
    <#else>
    import org.springframework.stereotype.Controller;
    </#if>
    <#if superControllerClassPackage??>
    import ${superControllerClassPackage};
    </#if>
    <#if swagger2>
    import io.swagger.annotations.ApiOperation;
    import org.springframework.web.bind.annotation.*;
    </#if>
    
    /**
    * <p>
        * ${table.comment!} 前端控制器
        * </p>
    *
    * @author ${author}
    * @since ${date}
    */
    <#if restControllerStyle>
    @RestController
    <#else>
    @Controller
    </#if>
    @RequestMapping("<#if package.ModuleName??>/${package.ModuleName}</#if>/<#if controllerMappingHyphenStyle??>${controllerMappingHyphen}<#else>${table.entityPath}</#if>")
    <#if superControllerClass??>
    public class ${table.controllerName} extends ${superControllerClass}<${entity}> {
    <#else>
    public class ${table.controllerName} {
    </#if>
    
    }
    
    
    • application.yml中添加自动生成代码配置开关
    mybatis-plus:
      auto-generator: true#自动生成代码开关,缺省false
    

    以上准备工作做好后,Just run it!
    注意:第一次启动生成完代码后,再重启一次,生成的代码才能生效。
    源码地址:https://github.com/DimonHo/dynamic-db/tree/auto-generator

    目录结构

    自动生成代码目录结构:

    image.png
    此时,你可以直接访问 http://localhost:8080/[数据源名称]/[表名] 就可以返回数据表的数据了
    如:http://localhost:8080/source/user

    相关文章

      网友评论

        本文标题:springboot+mybatis-plus+druid 动态

        本文链接:https://www.haomeiwen.com/subject/lupknctx.html