基於mybatis-plus的代碼生成


基於mybatis-plus的代碼生成

前言

隨着敏捷開發模式的推廣,伴着日益增長的需求,日常工作中我們越來越注重效率和便捷性。今天我們就來探討下如何自動生成代碼,准確地說是如何依賴數據庫生成我們的entity、mapper、mybatis xml、service、serviceImpl、controller,搭建我們的項目模板,提高我們的開發效率。這里我們的實現方式是基於mybatis-plus來實現的,廢話少說,直接開始吧。

創建項目

這里創建的maven項目,核心依賴如下:

  <properties>
        <mybatis-plus.version>2.1.9</mybatis-plus.version>
        <mybatis-plus-generator.version>2.1.9</mybatis-plus-generator.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus-generate</artifactId>
            <version>${mybatis-plus-generator.version}</version>
        </dependency>

        <dependency>
            <groupId>com.baomidou</groupId>
            <artifactId>mybatis-plus</artifactId>
            <version>${mybatis-plus.version}</version>
        </dependency>

這里需要還需要引入slf4j-log4j12和slf4j-api的依賴,否則會報找不到org/slf4j/LoggerFactory的錯誤:

   <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.7.12</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.6.1</version>
        </dependency>

因為要通過數據庫自動構建代碼,所以數據庫驅動必不可少,這里演示用的是mysql數據庫:

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.16</version>
        </dependency>

項目創建完后,下面我們要編寫代碼自動生成器。

導入代碼模板

在編寫自動生成器之前,更重要的工作是導入代碼模板,模板決定了后續你代碼生成后的樣式,如果第一次接觸,先不要考慮如何自己構建模板,我們先照貓畫虎改一個唄:

entity.java的模板:

文件名及路徑:

  • mybatis-templates/entity.java.vm
package ${package.Entity};

#if(${activeRecord})
import java.io.Serializable;
#end

#foreach($pkg in ${table.importPackages})
import ${pkg};
#end

#if(${entityLombokModel})
import com.baomidou.mybatisplus.annotations.Version;
import lombok.Data;
import lombok.experimental.Accessors;
import java.io.Serializable;
#end

/**********************************************
 * 描述:$!{table.comment}
 *
 * @author ${author}
 * @version 1.0
 * @date: ${date}
 *********************************************/
#if(${entityLombokModel})
@Data
@Accessors(chain = true)
#end
#if(${table.convert})
@TableName("${table.name}")
#end
#if(${superEntityClass})
public class ${entity} implements Serializable {
#elseif(${activeRecord})
public class ${entity} extends Model<${entity}> {
#else
public class ${entity} implements Serializable {
#end

    private static final long serialVersionUID = 1L;

## ----------  BEGIN 字段循環遍歷  ----------
#foreach($field in ${table.fields})
#if(${field.keyFlag})
#set($keyPropertyName=${field.propertyName})
#end
#if("$!field.comment" != "")
    /**
     * ${field.comment}
     */
#end
#if(${field.keyFlag})
## 主鍵
#if(${field.keyIdentityFlag})
	@TableId(value="${field.name}", type= IdType.AUTO)
#elseif(${field.convert})
    @TableId("${field.name}")
#end
## 普通字段
#elseif(${field.fill})
## -----   存在字段填充設置   -----
#if(${field.convert})
	@TableField(value = "${field.name}", fill = FieldFill.${field.fill})
#else
	@TableField(fill = FieldFill.${field.fill})
#end
#elseif(${field.convert})
	@TableField("${field.name}")
#end
## 樂觀鎖注解
#if(${versionFieldName}==${field.name})
	@Version
#end
## 邏輯刪除注解
#if(${logicDeleteFieldName}==${field.name})
    @TableLogic
#end
	private ${field.propertyType} ${field.propertyName};
#end
## ----------  END 字段循環遍歷  ----------

#if(!${entityLombokModel})
#foreach($field in ${table.fields})
#if(${field.propertyType.equals("boolean")})
#set($getprefix="is")
#else
#set($getprefix="get")
#end

	public ${field.propertyType} ${getprefix}${field.capitalName}() {
		return ${field.propertyName};
	}

#if(${entityBuilderModel})
	public ${entity} set${field.capitalName}(${field.propertyType} ${field.propertyName}) {
#else
	public void set${field.capitalName}(${field.propertyType} ${field.propertyName}) {
#end
		this.${field.propertyName} = ${field.propertyName};
#if(${entityBuilderModel})
		return this;
#end
	}
#end
#end

#if(${entityColumnConstant})
#foreach($field in ${table.fields})
	public static final String ${field.name.toUpperCase()} = "${field.name}";

#end
#end

#if(!${entityLombokModel})
	@Override
	public String toString() {
		return "${entity}{" +
#foreach($field in ${table.fields})
#if($!{velocityCount}==1)
			"${field.propertyName}=" + ${field.propertyName} +
#else
			", ${field.propertyName}=" + ${field.propertyName} +
#end
#end
			"}";
	}
#end
}

mapper.java的模板:

文件名及路徑:

  • mybatis-templates/mapper.java.vm
package ${package.Mapper};


/**********************************************
 * 描述:$!{table.comment}
 *
 * @author ${author}
 * @version 1.0
 * @date: ${date}
 *********************************************/
public interface ${table.mapperName} {

}

service.java的模板:

文件名及路徑:

  • mybatis-templates/service.java.vm
package ${package.Service};

import ${package.Entity}.${entity};

/**********************************************
 * 描述:$!{table.comment}
 *
 * @author ${author}
 * @version 1.0
 * @date: ${date}
 *********************************************/
#if(${kotlin})
interface ${table.serviceName}
#else
public interface ${table.serviceName} {

}
#end

servicempl.java的模板:

文件名及路徑:

  • mybatis-templates/serviceImpl.java.vm
package ${package.ServiceImpl};

import ${package.Entity}.${entity};
import ${package.Mapper}.${table.mapperName};
import ${package.Service}.${table.serviceName};
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**********************************************
 * 描述:$!{table.comment}
 *
 * @author ${author}
 * @version 1.0
 * @date: ${date}
 *********************************************/
@Service("")
public class ${table.serviceImplName} implements ${table.serviceName} {

    private static Logger _log = LoggerFactory.getLogger(${table.serviceImplName}.class);
}

controller.java的模板:

文件名及路徑:

  • mybatis-templates/controller.java.vm
package ${package.Controller};

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.beans.factory.annotation.Autowired;
import ${package.Service}.${table.serviceName};
import io.swagger.annotations.Api;
import io.swagger.annotations.ApiOperation;

#if(${restControllerStyle})
import org.springframework.web.bind.annotation.RestController;
#else
import org.springframework.stereotype.Controller;
#end
#if(${superControllerClassPackage})
import ${superControllerClassPackage};
#end

/**********************************************
 * 描述:$!{table.comment}
 *
 * @author ${author}
 * @version 1.0
 * @date: ${date}
 *********************************************/
#if(${restControllerStyle})
@RestController
#else
@Controller
#end
@Api(value = "$!{table.comment}", description = "$!{table.comment}")
@RequestMapping("#if(${package.ModuleName})/${package.ModuleName}#end/#if(${controllerMappingHyphenStyle})${controllerMappingHyphen}#else${table.entityPath}#end")
#if(${superControllerClass})
public class ${table.controllerName} {
#else
public class ${table.controllerName} {
#end
    private static Logger _log = LoggerFactory.getLogger(${table.controllerName}.class);

    @Autowired
    private ${table.serviceName} service;
}

肯定有小伙伴在疑惑,上面的代碼都是神馬東西,我在了解mybatis-plus自動生成代碼之前,第一次看到如上代碼也是一臉懵逼。其實上面這些模板都是基於java的模板引擎 Velocity 編寫的, Velocity是一個基於java的模板引擎(template engine)。它允許任何人僅僅簡單的使用模板語言(template language)來引用由java代碼定義的對象。 這里就不過多說明了,因為我也不是特別熟悉,有興趣的小伙伴可以自行研究。

如果你不知道該如何改,那就先別動,等我們寫完了自動生成器,你自然就知道咋改了,就算不知道,你也可以改着試呀。

編寫代碼自動生成器

我們的傳統是先寫代碼,后解釋,代碼如下:

package io.github.syske.mybatisplus;

import com.baomidou.mybatisplus.generator.AutoGenerator;
import com.baomidou.mybatisplus.generator.InjectionConfig;
import com.baomidou.mybatisplus.generator.config.*;
import com.baomidou.mybatisplus.generator.config.rules.DbType;
import com.baomidou.mybatisplus.generator.config.rules.NamingStrategy;
import io.github.syske.mybatisplus.util.PropertiesFileUtil;

import java.io.File;
import java.util.HashMap;
import java.util.Map;

/**
 * @program: mybatisPlus
 * @description: 代碼生成器
 * @author: liu yan
 * @create: 2019-11-30 12:13
 */
public class MybatisPlusGenerator {
    private static String JDBC_DRIVER = PropertiesFileUtil.getInstance("generator").get("generator.jdbc.driver");
    private static String JDBC_URL = PropertiesFileUtil.getInstance("generator").get("generator.jdbc.url");
    private static String JDBC_USERNAME = PropertiesFileUtil.getInstance("generator").get("generator.jdbc.username");
    private static String JDBC_PASSWORD = PropertiesFileUtil.getInstance("generator").get("generator.jdbc.password");
    private static String PROEJCT_NAME = "mybatisplus";//項目文件夾名稱,我的項目名稱就是mybatisplus
    private static String BASE_PACKAGE_NAME="io.github.syske"; // 包前綴,也就是你的代碼要生成在那個包底下

    public static void main(String[] args) {

        String[] table = {"user"}; // 表名稱,也就是你要生成那些表的代碼,我這里配置了一個表
        String modelPath = "./"; // 模塊路徑,因為我就一個模塊所以路徑就是./,如果你的項目有多個模塊,那就要指定你的模塊名稱
        for(int i = 0 ; i<table.length;i++) {
            shell(modelPath, table[i]);
        }
    }

    private static void shell(String modelPath, String tabName){

        File file = new File(modelPath);
        String path = file.getAbsolutePath();
        AutoGenerator mpg = new AutoGenerator();
        // 全局配置
        GlobalConfig gc = new GlobalConfig();
        gc.setOutputDir(path+"/src/main/java");
        gc.setOpen(false);
        gc.setFileOverride(true);
        gc.setActiveRecord(true);
        gc.setEnableCache(true);// XML 二級緩存
        gc.setBaseResultMap(true);// XML ResultMap
        gc.setBaseColumnList(true);// XML columList
        gc.setAuthor("syske");

        // 自定義文件命名,注意 %s 會自動填充表實體屬性!
        gc.setMapperName("%sMapper");
        gc.setXmlName("%sMapper");
        gc.setServiceName("%sService");
        gc.setServiceImplName("%sServiceImpl");
        gc.setControllerName("%sController");
        mpg.setGlobalConfig(gc);

        // 數據源配置
        DataSourceConfig dsc = new DataSourceConfig();
        // 數據源類型orcale和mysql是不一樣的
        dsc.setDbType(DbType.MYSQL);

        dsc.setDriverName(JDBC_DRIVER);
        dsc.setUsername(JDBC_USERNAME);
        dsc.setPassword(JDBC_PASSWORD);
        dsc.setUrl(JDBC_URL);
        mpg.setDataSource(dsc);

        // 策略配置
        StrategyConfig strategy = new StrategyConfig();
        strategy.setNaming(NamingStrategy.underline_to_camel);// 表名生成策略
        strategy.setInclude(new String[] {tabName}); // 需要生成的表
        mpg.setStrategy(strategy);

        // 包配置
        PackageConfig pc = new PackageConfig();
        pc.setParent(BASE_PACKAGE_NAME);
        pc.setController("controller");
        pc.setEntity("dao.model");
        pc.setMapper("dao.mapper");
        pc.setService("service");
        pc.setServiceImpl("service.impl");
        pc.setXml("dao.mapper");
        pc.setModuleName(PROEJCT_NAME);
        mpg.setPackageInfo(pc);

        // 注入自定義配置,可以在 VM 中使用 cfg.abc 【可無】
        InjectionConfig cfg = new InjectionConfig() {
            @Override
            public void initMap() {
                Map<String, Object> map = new HashMap<String, Object>();
                map.put("abc", this.getConfig().getGlobalConfig().getAuthor() + "-mp");
                this.setMap(map);
            }
        };
        mpg.setCfg(cfg);
        //多模塊
        TemplateConfig tc = getTemplateConfig(gc,pc,modelPath,tabName, false);
        if (tc.getMapper() == null && tc.getXml() == null && tc.getService() == null &&
                tc.getServiceImpl() == null && tc.getController() == null && tc.getEntity(false) == null) {
            return;
        }
        // 關閉默認 xml 生成,調整生成 至 根目錄(單模塊)
        mpg.setTemplate(tc);

        // 自定義模板配置,可以 copy 源碼 mybatis-plus/src/main/resources/template 下面內容修改,
        // 放置自己項目的 src/main/resources/template 目錄下, 默認名稱一下可以不配置,也可以自定義模板名稱

        // 如上任何一個模塊如果設置 空 OR Null 將不生成該模塊。

        // 執行生成
        mpg.execute();
        // 打印注入設置【可無】
        System.err.println(mpg.getCfg().getMap().get("abc"));
    }


    /**
     * 控制包生成的路徑與是否覆蓋生成
     * @param gc // 全局配置
     * @param pc 包配置
     * @param model model名
     * @param tabName 表名
     * @param isCover 是否覆蓋生成代碼
     * @return TemplateConfig
     */
    private static TemplateConfig getTemplateConfig(GlobalConfig gc, PackageConfig pc, String model, String tabName, boolean isCover) {
        TemplateConfig tc = new TemplateConfig();
        String entity = getName(tabName,"_");
        String path = model + "/src/main/java/" +replace( pc.getParent());
        if (!isCover) {
            if (model.endsWith("dao")) {
                String mapperPath =path + "/" + replace(pc.getMapper()) + "/" + gc.getMapperName().replace("%s",entity) + ".java";
                if (isExists(mapperPath)) {
                    tc.setMapper(null);
                    System.out.println(gc.getMapperName().replace("%s",entity) + ".java 文件已存在");
                }

                String modelPath = path + "/" + replace(pc.getEntity()) + "/" + entity + ".java";
                if (isExists(modelPath)) {
                    tc.setEntity(null);
                    System.out.println(entity + ".java 文件已存在");
                }
                tc.setController(null);
                tc.setXml(null);
                tc.setService(null);
                tc.setServiceImpl(null);
            } else if (model.endsWith("api")) {
                String servicePath = path + "/" +replace(pc.getService()) + "/" +  gc.getServiceName().replace("%s",entity) + ".java";
                if (isExists(servicePath)) {
                    tc.setService(null);
                    System.out.println(gc.getServiceName().replace("%s",entity) + ".java 文件已存在");
                }
                tc.setController(null);
                tc.setEntity(null);
                tc.setServiceImpl(null);
                tc.setMapper(null);
                tc.setXml(null);
            }  else if (model.endsWith("service")) {
                String serviceImplPath = path + "/" +replace(pc.getServiceImpl()) + "/" +  gc.getServiceImplName().replace("%s",entity) + ".java";
                if (isExists(serviceImplPath)) {
                    tc.setServiceImpl(null);
                    System.out.println(gc.getServiceImplName().replace("%s",entity) + ".java 文件已存在");
                }
                String mapperXmlPath =path + "/" + replace(pc.getXml()) + "/" + gc.getXmlName().replace("%s",entity) + ".xml";
                if (isExists(mapperXmlPath)) {
                    tc.setXml(null);
                    System.out.println(gc.getXmlName().replace("%s",entity) + ".xml 文件已存在");
                }
                tc.setController(null);
                tc.setService(null);
                tc.setMapper(null);
                tc.setEntity(null);
            }else if (model.endsWith("web")) {
                String controllerPath = path + "/" +replace(pc.getController()) + "/" + gc.getControllerName().replace("%s",entity) + ".java";;
                if (isExists(controllerPath)) {
                    tc.setController(null);
                    System.out.println(gc.getControllerName().replace("%s",entity) + ".java 文件已存在");
                }
                tc.setMapper(null);
                tc.setXml(null);
                tc.setService(null);
                tc.setServiceImpl(null);
                tc.setEntity(null);
            }
        } else {
            if (model.endsWith("dao")) {
                tc.setController(null);
                tc.setService(null);
                tc.setXml(null);
                tc.setServiceImpl(null);
            } else if (model.endsWith("api")) {
                tc.setController(null);
                tc.setEntity(null);
                tc.setServiceImpl(null);
                tc.setMapper(null);
                tc.setXml(null);
            }  else if (model.endsWith("service")) {
                tc.setController(null);
                tc.setService(null);
                tc.setMapper(null);
                tc.setEntity(null);
            } else if (model.endsWith("web")) {
                tc.setMapper(null);
                tc.setXml(null);
                tc.setService(null);
                tc.setServiceImpl(null);
                tc.setEntity(null);
            }
        }
        return tc;
    }


    /**
     * 將點替換為斜杠
     * @param name
     * @return
     */
    private static String replace(String name) {
        return name.replace(".","/");
    }

    /**
     * 判斷文件是否存在
     * @param path 路徑
     * @return
     */
    private static boolean isExists(String path) {
        File file = new File(path);
        return file.exists();
    }

    /**
     * 根據駝峰命名,首字母大寫
     * @param tabName 原名
     * @return 返回生成后的名字
     *  例如:user_info 返回 UserInfo
     */
    public static String getName(String tabName, String reChar) {
        String[] arr = tabName.split(reChar);
        String str = "";
        for (int i = 0; i < arr.length; i++ ) {
            String startChar = arr[i].substring(0,1).toUpperCase();
            String lastChar = arr[i].substring(1, arr[i].length());
            String newStr = startChar + lastChar;
            str += newStr;
        }
        return str;
    }
}
解釋

最前面的代碼是獲取數據庫的配置,我是寫在properties文件中的,當然你也可以直接寫在代碼中:

generator.jdbc.driver=com.mysql.cj.jdbc.Driver
generator.jdbc.url=jdbc:mysql://127.0.0.1:3307/test?characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&allowMultiQueries=true&serverTimezone=Asia/Shanghai
generator.jdbc.username=root
generator.jdbc.password=root

以上注釋已經夠詳細了,我就不做過多說明,下來我說下我遇到的問題:

第一個問題

錯誤信息

java.sql.SQLException: The server time zone value '?й???????' is unrecognized or represents more than one time zone. You must configure either the server or JDBC driver (via the serverTimezone configuration property) to use a more specifc time zone value if you want to utilize time zone support.

剛開始我以為是編碼問題,改了成utf-8,發現不行,然后上網查資料,發現是時區的問題,解決方法是在mysql url中加入如下參數:

zeroDateTimeBehavior=convertToNull&allowMultiQueries=true&serverTimezone=Asia/Shanghai

第二個問題

錯誤信息

java.sql.SQLSyntaxErrorException: Unknown error 1146

找了很多資料沒有找到解決方法,然后在代碼中找到如下設置:

dsc.setDbType(DbType.ORACLE);

因為我是mysql數據庫,所以要改成:

dsc.setDbType(DbType.MYSQL);

如果你用的是orcale,記得改成orcale

結語

如果沒有報錯,那你的代碼模板已經完成了,如果你還想進一步定制完善,好好去研究 Velocity ,編寫更符合自己需求的代碼模板吧!

不知道各位小伙伴感覺怎么樣,但我是真實地感受到用mybatis-plus構建項目的便利性,而且如果的模板夠完善,生成基於單表的增刪改查基本接口是沒有什么壓力的。好了,今天就到這里吧,我要去學習了😂


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM