關於代碼生成器的說明
我們在開發mybatis時,涉及到xml,和bean,mapper等的書寫,copy改,花的時間多且會有Bug,考慮到這些代碼都是機械式的,用生成的方式比較靠譜
mybatis官方推薦有了相應的生成工具org.mybatis.generator,以maven插件的形式生成,會生成很多的example類,也比較方便.
不過這篇要講的是mybatis-plus的生成
一些資料
MyBatis-Plus(簡稱 MP)是一個 MyBatis 的增強工具
代碼生成器是其其中一個核心功能,利用模板化,
快速入門
https://mp.baomidou.com/guide/generator.html#添加依賴
各配置的說明
https://mp.baomidou.com/config/generator-config.html#tableprefix
各種sample
https://github.com/baomidou/mybatis-plus-samples.git
mybatis-plus的集成說明
把sqlsessionFactory中的Configure 給替換了 ,入口爲MybatisPlusAutoConfiguration
裏面可不寫xml,因爲:org.apache.ibatis.session.Configuration中的 mappedStatements 方法addMappedStatement中把statement給加上了
一個使用mybatic-plus的例子
爲了使用mybatis-plus的生成代碼,先要把其集成進來,這裏是一個官方的例子.
https://github.com/baomidou/mybatis-plus-samples.git
查看其目錄結構:
| pom.xml
|
+---src
| +---main
| | +---java
| | | \---com
| | | \---baomidou
| | | \---mybatisplus
| | | \---samples
| | | \---quickstart
| | | | QuickstartApplication.java
| | | |
| | | +---entity
| | | | User.java
| | | |
| | | \---mapper
| | | UserMapper.java
| | |
| | \---resources
| | | application.yml
| | |
| | \---db
| | data-h2.sql
| | schema-h2.sql
1,pom加入相應的內容
這裏和官網的有點不同,因爲官網的採用了MAVEN的多模塊,所以有繼承,這邊把繼承相關的給copy過來
如parent官網的不同,
dependencyManagement,官網把其放在父pom.xml中,
copy過來後,就不會依賴父pom了,直接運行即可.
<?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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>mybatis-plus-sample-quickstart</artifactId>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
<mybatisplus.version>3.0.8.4-SNAPSHOT</mybatisplus.version>
</properties>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>${mybatisplus.version}</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus</artifactId>
<version>${mybatisplus.version}</version>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>${mybatisplus.version}</version>
</dependency>
</dependencies>
</dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
2,把數據給準備好
例子中沒有用mysql,而是用了內存數據庫org.h2.Driver中
下面建表和數據語句,即運行前會執行
data-h2.sql:
DELETE FROM user;
INSERT INTO user (id, name, age, email) VALUES
(1, 'Jone', 18, '[email protected]'),
(2, 'Jack', 20, '[email protected]'),
(3, 'Tom', 28, '[email protected]'),
(4, 'Sandy', 21, '[email protected]'),
(5, 'Billie', 24, '[email protected]');
schema-h2.sql:
DROP TABLE IF EXISTS user;
CREATE TABLE user
(
id BIGINT(20) NOT NULL COMMENT '主鍵ID',
name VARCHAR(30) NULL DEFAULT NULL COMMENT '姓名',
age INT(11) NULL DEFAULT NULL COMMENT '年齡',
email VARCHAR(50) NULL DEFAULT NULL COMMENT '郵箱',
PRIMARY KEY (id)
);
3,yml配置文件寫好
就只配置了db鏈接
# DataSource Config
spring:
datasource:
driver-class-name: org.h2.Driver
schema: classpath:db/schema-h2.sql
data: classpath:db/data-h2.sql
url: jdbc:h2:mem:test
username: root
password: test
4,建立新的entity和mapper
User.java
原例子中用了lombok,這是簡化java寫法的插件,可用可不用,用的話要在ide中加上插件,比較麻煩,
所以我改成下下面:
public class User {
private Long id;
private String name;
private Integer age;
private String email;
...get/set
}
UserMapper.java
package com.baomidou.mybatisplus.samples.quickstart.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.samples.quickstart.entity.User;
public interface UserMapper extends BaseMapper<User> {
}
5,寫好main和測試
QuickstartApplication.java
package com.baomidou.mybatisplus.samples.quickstart;
import com.baomidou.mybatisplus.samples.quickstart.entity.User;
import com.baomidou.mybatisplus.samples.quickstart.mapper.UserMapper;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import javax.annotation.Resource;
import java.util.List;
@SpringBootApplication
@MapperScan("com.baomidou.mybatisplus.samples.quickstart.mapper")
public class QuickstartApplication {
@Resource
private UserMapper userMapper;
@Bean
public void ss() {
System.out.println(("----- selectAll method test ------"));
List<User> userList = userMapper.selectList(null);
userList.forEach(System.out::println);
}
public static void main(String[] args) {
SpringApplication.run(QuickstartApplication.class, args);
}
}
6,執行
直接執行main即可,運行流程上,其會去加載 @Bean下的內容,所以會打印出內容
7,總結
所以,安裝和使用非常的簡單,上面中,只要配置了mapper的路徑,其就能自動去掃,後面調用mapper類時,也就能直接使用了!!
集成generator後的邏輯
官網也有生成代碼的例子,不過過於簡單,所以這裏重新寫一下.
1,功能說明
實際上,這個生成工具是完全獨立的,其不需要和spring結合,原理上來說,其是通過對已有表的查詢,然後通過其 模板(beetl,velocity等) 渲染成對應的文
這個對應文件是一個文本文件.是.java,.html等後綴,其並不關心
系統自己有默認模板,我自己也寫了一些模板,一些效果:
原文件目錄:
| pom.xml
|
+---src
| +---main
| | +---java
| | | \---com
| | | \---example
| | | \---demo
| | | | DemoApplication.java
| | | |
| | | \---common
| | | BaseController.java
| | | BaseEntity.java
| | |
| | \---resources
| | | application.yml
| | |
| | \---templates
| | mapper.xml.btl
| | page_info.js.btl
| |
| \---test
| \---java
| \---com
| \---example
| \---demo
| CodeGeneratorTest.java
運行CodeGeneratorTest.java,生成後:
| pom.xml
|
+---src
| +---main
| | +---java
| | | \---com
| | | \---example
| | | \---demo
| | | | DemoApplication.java
| | | |
| | | +---common
| | | | BaseController.java
| | | | BaseEntity.java
| | | |
| | | \---mymodule
| | | +---controller
| | | | TUserController.java
| | | |
| | | +---entity
| | | | TUser.java
| | | |
| | | +---mapper
| | | | TUserMapper.java
| | | |
| | | \---service
| | | | ITUserService.java
| | | |
| | | \---impl
| | | TUserServiceImpl.java
| | |
| | \---resources
| | | application.yml
| | |
| | +---mapper
| | | \---mymodule
| | | TUserMapper.xml
| | |
| | +---templates
| | | mapper.xml.btl
| | | page_info.js.btl
| | |
| | \---web
| | \---mymodule
| | TUser.js
| |
| \---test
| \---java
| \---com
| \---example
| \---demo
| CodeGeneratorTest.java
說明,多了三個 mymodule文件夾
2,準備內容
採用了mysql,所以要有一個mysql數據庫
3,運行生成邏輯
*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 http://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.1.3.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.example</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<!-- https://mvnrepository.com/artifact/com.baomidou/mybatis-plus-boot-starter -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.1.0</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.1.9</version>
</dependency>
<!-- https://mvnrepository.com/artifact/com.baomidou/mybatis-plus-generator -->
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-generator</artifactId>
<version>3.1.0</version>
</dependency>
<dependency>
<groupId>com.ibeetl</groupId>
<artifactId>beetl</artifactId>
<version>2.9.3</version>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
-
BaseController和BaseEntity 是一個空的類,因爲其默認生成的controller等需要用到的父類
-
btl是模板 page_info.js.btl(這是個自己寫的模板,後面要指定的
/**
* 初始化${cfg.bizChName}詳情對話框
*/
var ${cfg.bizEnBigName}InfoDlg = {
${cfg.bizEnName}InfoData : {}
};
/**
* 清除數據
*/
${cfg.bizEnBigName}InfoDlg.clearData = function() {
this.${cfg.bizEnName}InfoData = {};
}
$(function() {
});
- btl是模板 mapper.xml.btl (這個和上面不同,這是默認的,代碼中不用指定)
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="${package.Mapper}.${table.mapperName}">
<% if(enableCache){ %>
<!-- 開啓二級緩存 -->
<cache type="org.mybatis.caches.ehcache.LoggingEhcache"/>
<% } %>
<% if(baseResultMap){ %>
<!-- 通用查詢映射結果 -->
<resultMap id="BaseResultMap" type="${package.Entity}.${entity}">
<% for(field in table.fields){ %>
<% /** 生成主鍵排在第一位 **/ %>
<% if(field.keyFlag){ %>
<id column="${field.name}" property="${field.propertyName}" />
<% } %>
<% } %>
<% for(field in table.commonFields){ %>
<% /** 生成公共字段 **/ %>
<result column="${field.name}" property="${field.propertyName}" />
<% } %>
<% for(field in table.fields){ %>
<% /** 生成普通字段 **/ %>
<% if(!field.keyFlag){ %>
<result column="${field.name}" property="${field.propertyName}" />
<% } %>
<% } %>
</resultMap>
<% } %>
<% if(baseColumnList){ %>
<!-- 通用查詢結果列 -->
<sql id="Base_Column_List">
<% for(field in table.commonFields){ %>
${field.name},
<% } %>
${table.fieldNames}
</sql>
<% } %>
</mapper>
*核心類CodeGeneratorTest
這個很重要
是在官方例子中改變過來的,加了模板,去除了不必要的內容.
package com.example.demo;
import com.baomidou.mybatisplus.core.exceptions.MybatisPlusException;
import com.baomidou.mybatisplus.core.toolkit.StringPool;
import com.baomidou.mybatisplus.core.toolkit.StringUtils;
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.NamingStrategy;
import com.baomidou.mybatisplus.generator.engine.BeetlTemplateEngine;
import java.util.*;
// 演示例子,執行 main 方法控制檯輸入模塊表名回車自動生成對應項目目錄中
public class CodeGeneratorTest {
public static void main(String[] args) {
String author = "myname";
String dbUrl = "jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf8&useSSL=false";
String dbDriver = "com.mysql.jdbc.Driver";
String uname = "root";
String pwd = "root";
String parentPackage = "com.example.demo";
String moduleName = "mymodule";
String tableName = "user";
// 代碼生成器
AutoGenerator mpg = new AutoGenerator();
// 全局配置
GlobalConfig gc = new GlobalConfig();
String projectPath = System.getProperty("user.dir");
gc.setOutputDir(projectPath + "/src/main/java"); //輸出 文件路徑
gc.setAuthor(author);
gc.setOpen(false);
mpg.setGlobalConfig(gc);
// 數據源配置
DataSourceConfig dsc = new DataSourceConfig();
dsc.setUrl(dbUrl);
// dsc.setSchemaName("public");
dsc.setDriverName(dbDriver);
dsc.setUsername(uname);
dsc.setPassword(pwd);
mpg.setDataSource(dsc);
// 包配置
PackageConfig pc = new PackageConfig();
pc.setModuleName(moduleName);
pc.setParent(parentPackage);
mpg.setPackageInfo(pc);
// 自定義配置
InjectionConfig cfg = new InjectionConfig() {
@Override
public void initMap() {
// to do nothing
Map<String, Object> map = new HashMap<>();
map.put("bizChName", "用戶");
map.put("bizEnBigName", "Tuser");
this.setMap(map);
}
};
// 如果模板引擎是 freemarker
// String templatePath = "/templates/mapper.xml.ftl";
// 如果模板引擎是 velocity
// String templatePath = "/templates/mapper.xml.vm";
// 如果模板引擎是 beetl
String templatePath = "/templates/mapper.xml.btl";
// 自定義輸出配置
List<FileOutConfig> focList = new ArrayList<>();
// 自定義配置會被優先輸出
focList.add(new FileOutConfig(templatePath) {
@Override
public String outputFile(TableInfo tableInfo) {
// 自定義輸出文件名 , 如果你 Entity 設置了前後綴、此處注意 xml 的名稱會跟着發生變化!!
return projectPath + "/src/main/resources/mapper/" + pc.getModuleName()
+ "/" + tableInfo.getEntityName() + "Mapper" + StringPool.DOT_XML;
}
});
focList.add(new FileOutConfig("/templates/page_info.js.btl") {
@Override
public String outputFile(TableInfo tableInfo) {
// 自定義輸出文件名 , 如果你 Entity 設置了前後綴、此處注意 xml 的名稱會跟着發生變化!!
return projectPath + "/src/main/resources/web/" + pc.getModuleName()
+ "/" + tableInfo.getEntityName() + ".js";
}
});
cfg.setFileOutConfigList(focList);
mpg.setCfg(cfg);
// 配置模板
TemplateConfig templateConfig = new TemplateConfig();
// // 配置自定義輸出模板
// //指定自定義模板路徑,注意不要帶上.ftl/.vm, 會根據使用的模板引擎自動識別
// // templateConfig.setEntity("templates/entity2.java");
// // templateConfig.setService();
// // templateConfig.setController();
templateConfig.setXml(null);
mpg.setTemplate(templateConfig);
// 策略配置
StrategyConfig strategy = new StrategyConfig();
strategy.setNaming(NamingStrategy.underline_to_camel);
strategy.setColumnNaming(NamingStrategy.underline_to_camel);
strategy.setSuperEntityClass("com.example.demo.common.BaseEntity");
strategy.setEntityLombokModel(false);
strategy.setEntityTableFieldAnnotationEnable(true);
strategy.setRestControllerStyle(true);
strategy.setSuperControllerClass("com.example.demo.common.BaseController");
strategy.setInclude(tableName); // 需要生成的表
strategy.setSuperEntityColumns("id");
strategy.setControllerMappingHyphenStyle(true);
strategy.setTablePrefix(pc.getModuleName() + "_");
mpg.setStrategy(strategy);
// mpg.setTemplateEngine(new FreemarkerTemplateEngine());
mpg.setTemplateEngine(new BeetlTemplateEngine());
mpg.execute();
}
}
*生成邏輯是不依賴spring的,直接執行上面的main,即可生成
4,spring的代碼
上面的是生成邏輯,生成了文件後,我們就可以啓動spring來測試了,下面是一springboot的一些配置
*application.yml
spring:
datasource:
driver-class-name: com.mysql.jdbc.Driver
url: jdbc:mysql://127.0.0.1:3306/test?useUnicode=true&characterEncoding=utf8&useSSL=false
username: root
password: root
type: com.alibaba.druid.pool.DruidDataSource
dbcp2.pool-prepared-statements: true
dbcp2.max-open-prepared-statements: 20
*DemoApplication.java
下面 TUserMapper即是 上面生成的代碼,所以可以先執行 上一步驟,再把這個文件加進來.
package com.example.demo;
import com.example.demo.mymodule.entity.TUser;
import com.example.demo.mymodule.mapper.TUserMapper;
import org.mybatis.spring.annotation.MapperScan;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
import java.util.List;
@SpringBootApplication
@MapperScan(basePackages = {"com.example.demo.mymodule.mapper"})
public class DemoApplication {
@Autowired
private TUserMapper userMapper;
@Bean
public void testSelect() {
System.out.println(("----- selectAll method test ------"));
List<TUser> userList = userMapper.selectList(null);
userList.forEach(System.out::println);
}
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
5.編輯模板與處理(部分個性化內容)
默認entity,mapper,service,執行時可以直接生成,不過service,controller,html,js等最好都用template來處理!
講一下模板,實際上就是實際要生成文件前的 模子
在模板裏面,其變量可以直接使用,我這裏是用了 beetl的模板引擎
其對象有 cfg,table等.直接使用即可,如:
/**
* 收集數據
*/
${cfg.bizEnBigName}InfoDlg.collectData = function() {
this
<% for(item in table.fields!){ %>
<% if(itemLP.last != true){ %>
.set('${item.propertyName}')
<% }else{ %>
.set('${item.propertyName}');
<% }} %>
}
ctf.bizEnBigName是自定義的,上面的 CodeGeneratorTest 裏有初始化
table.fields 是生成器自己的 ,名字表達 很明白了,就是找到 表的字段.
生成器的內置對象具體請查看 https://mp.baomidou.com/config/generator-config.html#tableprefix
附page_info.js.btl完整
/**
* 初始化${cfg.bizChName}詳情對話框
*/
var ${cfg.bizEnBigName}InfoDlg = {
${cfg.bizEnName}InfoData : {}
};
/**
* 清除數據
*/
${cfg.bizEnBigName}InfoDlg.clearData = function() {
this.${cfg.bizEnName}InfoData = {};
}
/**
* 設置對話框中的數據
*
* @param key 數據的名稱
* @param val 數據的具體值
*/
${cfg.bizEnBigName}InfoDlg.set = function(key, val) {
this.${cfg.bizEnName}InfoData[key] = (typeof val == "undefined") ? $("#" + key).val() : val;
return this;
}
/**
* 設置對話框中的數據
*
* @param key 數據的名稱
* @param val 數據的具體值
*/
${cfg.bizEnBigName}InfoDlg.get = function(key) {
return $("#" + key).val();
}
/**
* 關閉此對話框
*/
${cfg.bizEnBigName}InfoDlg.close = function() {
parent.layer.close(window.parent.${cfg.bizEnBigName}.layerIndex);
}
/**
* 收集數據
*/
${cfg.bizEnBigName}InfoDlg.collectData = function() {
this
<% for(item in table.fields!){ %>
<% if(itemLP.last != true){ %>
.set('${item.propertyName}')
<% }else{ %>
.set('${item.propertyName}');
<% }} %>
}
/**
* 提交添加
*/
${cfg.bizEnBigName}InfoDlg.addSubmit = function() {
this.clearData();
this.collectData();
//提交信息
var ajax = new $ax(Feng.ctxPath + "/${cfg.bizEnName}/add", function(data){
Feng.success("添加成功!");
window.parent.${cfg.bizEnBigName}.table.refresh();
${cfg.bizEnBigName}InfoDlg.close();
},function(data){
Feng.error("添加失敗!" + data.responseJSON.message + "!");
});
ajax.set(this.${cfg.bizEnName}InfoData);
ajax.start();
}
/**
* 提交修改
*/
${cfg.bizEnBigName}InfoDlg.editSubmit = function() {
this.clearData();
this.collectData();
//提交信息
var ajax = new $ax(Feng.ctxPath + "/${cfg.bizEnName}/update", function(data){
Feng.success("修改成功!");
window.parent.${cfg.bizEnBigName}.table.refresh();
${cfg.bizEnBigName}InfoDlg.close();
},function(data){
Feng.error("修改失敗!" + data.responseJSON.message + "!");
});
ajax.set(this.${cfg.bizEnName}InfoData);
ajax.start();
}
$(function() {
});
後記
生成代碼在後臺管理方面是運用非常廣的,但有很多細節要處理
有些後臺管理實現了 很複雜的生成功能,把代碼生成做了可編輯的,但這樣對擴展性限制 比較大,多生成幾次,自己都不知道怎麼開發了。所以我個人還是喜歡半自動化的,1是其它人好上手,同時好編輯,2是修改模板應該是常態,我們在做系統的過程中組件會一直增加,同時也會有重構的內容,
關於哪些模塊更需要用到生成?mybatis-plus的生成功能明顯是針對 mybatis的XML等去做的,但實際上java層面的生成交沒有那麼迫切,html和js等難以複用的模塊纔是要生成的主力模塊。
生成的一個問題在於修改時怎麼辦,現在只能在初始時生成,後面如果修改了,則不敢去生成了,因爲會覆蓋後修改的代碼,這也是關自動化的問題。不過金無足赤,總體來說還是OK的。
對於減少工作量來說,最好是複用,其次纔是生成,同時文檔說明全面,自己寫了一堆的模板,哪些對哪些要搞清楚,