我们在开发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给加上了
为了使用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
这里和官网的有点不同,因为官网的采用了MAVEN的多模块,所以有继承,这边把继承相关的给copy过来
如parent官网的不同,
dependencyManagement,官网把其放在父pom.xml中,
copy过来后,就不会依赖父pom了,直接运行即可.
org.springframework.boot
spring-boot-starter-parent
2.1.3.RELEASE
4.0.0
mybatis-plus-sample-quickstart
UTF-8
UTF-8
1.8
3.0.8.4-SNAPSHOT
com.baomidou
mybatis-plus-boot-starter
${mybatisplus.version}
com.baomidou
mybatis-plus
${mybatisplus.version}
com.baomidou
mybatis-plus-generator
${mybatisplus.version}
org.springframework.boot
spring-boot-starter
org.springframework.boot
spring-boot-starter-test
test
org.projectlombok
lombok
provided
com.h2database
h2
runtime
com.baomidou
mybatis-plus-boot-starter
org.springframework.boot
spring-boot-maven-plugin
例子中没有用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)
);
就只配置了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
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 {
}
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 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类时,也就能直接使用了!!
官网也有生成代码的例子,不过过于简单,所以这里重新写一下.
实际上,这个生成工具是完全独立的,其不需要和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文件夹
采用了mysql,所以要有一个mysql数据库
*pom.xml
4.0.0
org.springframework.boot
spring-boot-starter-parent
2.1.3.RELEASE
com.example
demo
0.0.1-SNAPSHOT
demo
Demo project for Spring Boot
1.8
com.baomidou
mybatis-plus-boot-starter
3.1.0
org.springframework.boot
spring-boot-starter-web
org.springframework.boot
spring-boot-starter-test
test
mysql
mysql-connector-java
runtime
com.alibaba
druid-spring-boot-starter
1.1.9
com.baomidou
mybatis-plus-generator
3.1.0
com.ibeetl
beetl
2.9.3
org.springframework.boot
spring-boot-maven-plugin
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() {
});
<% if(enableCache){ %>
<% } %>
<% if(baseResultMap){ %>
<% for(field in table.fields){ %>
<% /** 生成主键排在第一位 **/ %>
<% if(field.keyFlag){ %>
<% } %>
<% } %>
<% for(field in table.commonFields){ %>
<% /** 生成公共字段 **/ %>
<% } %>
<% for(field in table.fields){ %>
<% /** 生成普通字段 **/ %>
<% if(!field.keyFlag){ %>
<% } %>
<% } %>
<% } %>
<% if(baseColumnList){ %>
<% for(field in table.commonFields){ %>
${field.name},
<% } %>
${table.fieldNames}
<% } %>
*核心类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 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 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,即可生成
上面的是生成逻辑,生成了文件后,我们就可以启动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 userList = userMapper.selectList(null);
userList.forEach(System.out::println);
}
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
默认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
/**
* 初始化${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的。
对于减少工作量来说,最好是复用,其次才是生成,同时文档说明全面,自己写了一堆的模板,哪些对哪些要搞清楚,