1.简介
首先声明这是基于Java后端开发的SSM框架,在开发过程中,编码阶段第一步就是实现对各表的增删改查操作,也就是俗称的CURD,一大半的时间都是把以前的代码拿来抄抄改改,即使是使用了Mybatis的逆向工程,service
、controller
的代码量依然很大,而且还要实现的诸如分页查询、多参数查询等功能,Mybatis逆向工程提供的模板并不能完全适用。
在这里,我依据我的编码习惯,基于EasyCode插件,修改完成了我的一套完全生成dao
、service
、controller
代码的宏定义模板,我想重点不是我的模板适不适你的场景,而是知道如何利用这个宏定义模板让我们告别繁杂的CURD重复代码编写。
idea插件市场的下载不好用的话可以到gitee上去下载:https://gitee.com/makejava/EasyCode
2.使用EasyCode
下载好之后进行离线安装:
然后创建一个数据库,添加一个user表:
CREATE TABLE `user` (
`id` int(20) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(16) DEFAULT NULL COMMENT '姓名',
`pwd` varchar(16) DEFAULT NULL COMMENT '密码',
`type` int(1) DEFAULT NULL COMMENT '0-管理员 1-普通用户',
PRIMARY KEY (`id`)
)
构建一个SpringBoot项目,然后在idea中打开database添加mysql并连接上创建的数据库:
在表上右键选择EasyCode->General code,然后选择包和生成的文件即可生成代码:
3.自定义模板
3.1 如何自定义
使用作者预先设定的模板可以完成dao和service的大部分代码,由于适用的普遍性,初始模板没有包含过多具体的代码,不过我们可以自定义:
File -> Settings 搜索 Easy Code
,选择Template即可看到宏定义模板,我们可以在此处进行修改或者替换:
3.2 实例
接下来介绍针对CURD,我自己修改实现的包含分页查询、多条件查询等的RestFul风格、前后端Json数据交互的CURD接口服务。
mapper.xml:
##引入mybatis支持
$!mybatisSupport
##设置保存名称与保存位置
$!callback.setFileName($tool.append($!{tableInfo.name}, "Dao.xml"))
$!callback.setSavePath($tool.append($modulePath, "/src/main/resources/mapper"))
##拿到主键
#if(!$tableInfo.pkColumn.isEmpty())
#set($pk = $tableInfo.pkColumn.get(0))
#end
<?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="$!{tableInfo.savePackageName}.dao.$!{tableInfo.name}Dao">
<resultMap type="$!{tableInfo.savePackageName}.entity.$!{tableInfo.name}" id="$!{tableInfo.name}Map">
#foreach($column in $tableInfo.fullColumn)
<result property="$!column.name" column="$!column.obj.name" jdbcType="$!column.ext.jdbcType"/>
#end
</resultMap>
<!--查询单个-->
<select id="queryById" resultMap="$!{tableInfo.name}Map">
select
#allSqlColumn()
from $!{tableInfo.obj.parent.name}.$!tableInfo.obj.name
where $!pk.obj.name = #{$!pk.name}
</select>
<!--条件查询(带分页)-->
<select id="selectList" parameterType="map" resultMap="$!{tableInfo.name}Map">
select
#allSqlColumn()
from $!{tableInfo.obj.parent.name}.$!tableInfo.obj.name
<where>
#foreach($column in $tableInfo.fullColumn)
<if test="$!column.name != null#if($column.type.equals("java.lang.String")) and $!column.name != ''#end">
and $!column.obj.name = #{$!column.name}
</if>
#end
</where>
<if test="offset != null and limit !=null">
limit ${offset},${limit}
</if>
</select>
<!--符合条件总数查询-->
<select id="selectListSize" parameterType="map" resultType="java.lang.Integer">
select
count(*)
from $!{tableInfo.obj.parent.name}.$!tableInfo.obj.name
<where>
#foreach($column in $tableInfo.fullColumn)
<if test="$!column.name != null#if($column.type.equals("java.lang.String")) and $!column.name != ''#end">
and $!column.obj.name = #{$!column.name}
</if>
#end
</where>
</select>
<!--新增所有列-->
<insert id="insert" keyProperty="$!pk.name" useGeneratedKeys="true">
insert into $!{tableInfo.obj.parent.name}.$!{tableInfo.obj.name}(#foreach($column in $tableInfo.otherColumn)$!column.obj.name#if($velocityHasNext), #end#end)
values (#foreach($column in $tableInfo.otherColumn)#{$!{column.name}}#if($velocityHasNext), #end#end)
</insert>
<!--通过主键修改数据-->
<update id="update">
update $!{tableInfo.obj.parent.name}.$!{tableInfo.obj.name}
<set>
#foreach($column in $tableInfo.otherColumn)
<if test="$!column.name != null#if($column.type.equals("java.lang.String")) and $!column.name != ''#end">
$!column.obj.name = #{$!column.name},
</if>
#end
</set>
where $!pk.obj.name = #{$!pk.name}
</update>
<!--通过主键删除-->
<delete id="deleteById">
delete from $!{tableInfo.obj.parent.name}.$!{tableInfo.obj.name} where $!pk.obj.name = #{$!pk.name}
</delete>
</mapper>
dao.java:
##定义初始变量
#set($tableName = $tool.append($tableInfo.name, "Dao"))
##设置回调
$!callback.setFileName($tool.append($tableName, ".java"))
$!callback.setSavePath($tool.append($tableInfo.savePath, "/dao"))
##拿到主键
#if(!$tableInfo.pkColumn.isEmpty())
#set($pk = $tableInfo.pkColumn.get(0))
#end
#if($tableInfo.savePackageName)package $!{tableInfo.savePackageName}.#{end}dao;
import $!{tableInfo.savePackageName}.entity.$!{tableInfo.name};
import org.apache.ibatis.annotations.Param;
import java.util.List;
import java.util.Map;
/**
* $!{tableInfo.comment}($!{tableInfo.name})表数据库访问层
*
* @author $!author
* @since $!time.currTime()
*/
public interface $!{tableName} {
/**
* 通过ID查询单条数据
*
* @param $!pk.name 主键
* @return 实例对象
*/
$!{tableInfo.name} queryById($!pk.shortType $!pk.name);
/**
* 条件查询(带分页)
*
* @param params 实例对象
* @return 对象列表
*/
List<$!{tableInfo.name}> selectList(Map<String,String> params);
/**
* 符合条件总数查询
*
* @param params 实例对象
* @return 对象列表
*/
int selectListSize(Map<String,String> params);
/**
* 新增数据
*
* @param $!tool.firstLowerCase($!{tableInfo.name}) 实例对象
* @return 影响行数
*/
int insert($!{tableInfo.name} $!tool.firstLowerCase($!{tableInfo.name}));
/**
* 修改数据
*
* @param $!tool.firstLowerCase($!{tableInfo.name}) 实例对象
* @return 影响行数
*/
int update($!{tableInfo.name} $!tool.firstLowerCase($!{tableInfo.name}));
/**
* 通过主键删除数据
*
* @param $!pk.name 主键
* @return 影响行数
*/
int deleteById($!pk.shortType $!pk.name);
}
entity.java:
##引入宏定义
$!define
##使用宏定义设置回调(保存位置与文件后缀)
#save("/entity", ".java")
##使用宏定义设置包后缀
#setPackageSuffix("entity")
##使用全局变量实现默认包导入
$!autoImport
import java.io.Serializable;
##使用宏定义实现类注释信息
#tableComment("实体类")
public class $!{tableInfo.name} implements Serializable {
private static final long serialVersionUID = $!tool.serial();
#foreach($column in $tableInfo.fullColumn)
#if(${column.comment})/**
* ${column.comment}
*/#end
private $!{tool.getClsNameByFullName($column.type)} $!{column.name};
#end
#foreach($column in $tableInfo.fullColumn)
##使用宏定义实现get,set方法
#getSetMethod($column)
#end
}
serviceImpl.java
##定义初始变量
#set($tableName = $tool.append($tableInfo.name, "ServiceImpl"))
##设置回调
$!callback.setFileName($tool.append($tableName, ".java"))
$!callback.setSavePath($tool.append($tableInfo.savePath, "/service/impl"))
##拿到主键
#if(!$tableInfo.pkColumn.isEmpty())
#set($pk = $tableInfo.pkColumn.get(0))
#end
#if($tableInfo.savePackageName)package $!{tableInfo.savePackageName}.#{end}service.impl;
import $!{tableInfo.savePackageName}.entity.$!{tableInfo.name};
import $!{tableInfo.savePackageName}.dao.$!{tableInfo.name}Dao;
import $!{tableInfo.savePackageName}.service.$!{tableInfo.name}Service;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import javax.annotation.Resource;
import java.security.InvalidParameterException;
import java.util.List;
import java.util.Map;
/**
* $!{tableInfo.comment}($!{tableInfo.name})表服务实现类
*
* @author $!author
* @since $!time.currTime()
*/
@Service("$!tool.firstLowerCase($!{tableInfo.name})Service")
public class $!{tableName} implements $!{tableInfo.name}Service {
@Resource
private $!{tableInfo.name}Dao $!tool.firstLowerCase($!{tableInfo.name})Dao;
/**
* 通过ID查询单条数据
*
* @param $!pk.name 主键
* @return 实例对象
*/
@Override
public $!{tableInfo.name} queryById($!pk.shortType $!pk.name) {
return this.$!{tool.firstLowerCase($!{tableInfo.name})}Dao.queryById($!pk.name);
}
/**
* 条件查询(带分页)
*
* @param params 传入参数
* @return 对象列表
*/
@Override
public List<$!{tableInfo.name}> selectList(Map<String,String> params) {
try {
if (params.containsKey("page") && params.containsKey("limit")) {
int offset = (Integer.parseInt(params.get("page")) - 1) * Integer.parseInt(params.get("limit"));
if (offset < 0) throw new InvalidParameterException("请传入正确的分页参数");
params.put("offset", offset + "");
}
}catch (Exception e){
throw new InvalidParameterException("分页参数非法:"+e.getMessage());
}
return this.$!{tool.firstLowerCase($!{tableInfo.name})}Dao.selectList(params);
}
/**
* 符合条件总数查询
*
* @param params 传入参数
* @return 对象列表
*/
public int selectListSize(Map<String, String> params) {
return this.$!{tool.firstLowerCase($!{tableInfo.name})}Dao.selectListSize(params);
}
/**
* 新增数据
*
* @param $!tool.firstLowerCase($!{tableInfo.name}) 实例对象
* @return 是否成功
*/
@Override
public boolean insert($!{tableInfo.name} $!tool.firstLowerCase($!{tableInfo.name})) {
return this.$!{tool.firstLowerCase($!{tableInfo.name})}Dao.insert($!tool.firstLowerCase($!{tableInfo.name})) > 0;
}
/**
* 修改数据
*
* @param $!tool.firstLowerCase($!{tableInfo.name}) 实例对象
* @return 是否成功
*/
@Override
public boolean update($!{tableInfo.name} $!tool.firstLowerCase($!{tableInfo.name})) {
if(StringUtils.isEmpty($!tool.append($!tool.firstLowerCase($!{tableInfo.name}),".get","$!tool.firstUpperCase($!pk.name)()")))
throw new InvalidParameterException("请传入更新数据的主键");
return this.$!{tool.firstLowerCase($!{tableInfo.name})}Dao.update($!tool.firstLowerCase($!{tableInfo.name})) > 0;
}
/**
* 通过主键删除数据
*
* @param $!pk.name 主键
* @return 是否成功
*/
@Override
public boolean deleteById($!pk.shortType $!pk.name) {
return this.$!{tool.firstLowerCase($!{tableInfo.name})}Dao.deleteById($!pk.name) > 0;
}
}
service.java
##定义初始变量
#set($tableName = $tool.append($tableInfo.name, "Service"))
##设置回调
$!callback.setFileName($tool.append($tableName, ".java"))
$!callback.setSavePath($tool.append($tableInfo.savePath, "/service"))
##拿到主键
#if(!$tableInfo.pkColumn.isEmpty())
#set($pk = $tableInfo.pkColumn.get(0))
#end
#if($tableInfo.savePackageName)package $!{tableInfo.savePackageName}.#{end}service;
import $!{tableInfo.savePackageName}.entity.$!{tableInfo.name};
import java.util.List;
import java.util.Map;
/**
* $!{tableInfo.comment}($!{tableInfo.name})表服务接口
*
* @author $!author
* @since $!time.currTime()
*/
public interface $!{tableName} {
/**
* 通过ID查询单条数据
*
* @param $!pk.name 主键
* @return 实例对象
*/
$!{tableInfo.name} queryById($!pk.shortType $!pk.name);
/**
* 条件查询(带分页)
*
* @param params 传入参数
* @return 对象列表
*/
List<$!{tableInfo.name}> selectList(Map<String,String> params);
/**
* 符合条件总数查询
*
* @param params 传入参数
* @return 对象列表
*/
int selectListSize(Map<String,String> params);
/**
* 新增数据
*
* @param $!tool.firstLowerCase($!{tableInfo.name}) 实例对象
* @return 是否成功
*/
boolean insert($!{tableInfo.name} $!tool.firstLowerCase($!{tableInfo.name}));
/**
* 修改数据
*
* @param $!tool.firstLowerCase($!{tableInfo.name}) 实例对象
* @return 是否成功
*/
boolean update($!{tableInfo.name} $!tool.firstLowerCase($!{tableInfo.name}));
/**
* 通过主键删除数据
*
* @param $!pk.name 主键
* @return 是否成功
*/
boolean deleteById($!pk.shortType $!pk.name);
}
controller.java:
##定义初始变量
#set($tableName = $tool.append($tableInfo.name, "Controller"))
##设置回调
$!callback.setFileName($tool.append($tableName, ".java"))
$!callback.setSavePath($tool.append($tableInfo.savePath, "/controller"))
##拿到主键
#if(!$tableInfo.pkColumn.isEmpty())
#set($pk = $tableInfo.pkColumn.get(0))
#end
#if($tableInfo.savePackageName)package $!{tableInfo.savePackageName}.#{end}controller;
import $!{tableInfo.savePackageName}.entity.$!{tableInfo.name};
import $!{tableInfo.savePackageName}.service.$!{tableInfo.name}Service;
import $!{tableInfo.savePackageName}.util.JsonResp;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import java.util.List;
import java.util.Map;
/**
* $!{tableInfo.comment}($!{tableInfo.name})表控制层
*
* @author $!author
* @since $!time.currTime()
*/
@RestController
@RequestMapping("$!tool.firstLowerCase($tableInfo.name)")
public class $!{tableName} {
/**
* 服务对象
*/
@Resource
private $!{tableInfo.name}Service $!tool.firstLowerCase($tableInfo.name)Service;
/**
* 通过主键查询单条数据
*
* @param id 主键
* @return resp
* @throws Exception
*/
@GetMapping("/{id}")
public JsonResp queryById(@PathVariable $!pk.shortType id) throws Exception{
$!{tableInfo.name} $!tool.firstLowerCase($!{tableInfo.name}) = this.$!{tool.firstLowerCase($tableInfo.name)}Service.queryById(id);
JsonResp resp = new JsonResp(JsonResp.STATE_OK,JsonResp.CODE_OK);
resp.setObj($!tool.firstLowerCase($!{tableInfo.name}));
return resp;
}
/**
* 条件查询(带分页),参数名和实体类一致,另规定:
* page 表示页数
* limit 表示每页显示数据条数
*
* @param params 传入参数,K-V
* @return resp
* @throws Exception
*/
@GetMapping("/list")
public JsonResp list(@RequestParam Map<String,String> params) throws Exception{
JsonResp resp = new JsonResp(JsonResp.STATE_OK, JsonResp.CODE_OK);
List<$!{tableInfo.name}> list = this.$!{tool.firstLowerCase($tableInfo.name)}Service.selectList(params);
resp.setData(list);
resp.setCount(this.$!{tool.firstLowerCase($tableInfo.name)}Service.selectListSize(params));
return resp;
}
/**
* 新增数据
*
* @param $!tool.firstLowerCase($!{tableInfo.name}) 实例对象
* @return resp
* @throws Exception
*/
@PostMapping
public JsonResp insert($!{tableInfo.name} $!tool.firstLowerCase($!{tableInfo.name})) throws Exception {
boolean flag = this.$!{tool.firstLowerCase($tableInfo.name)}Service.insert($!tool.firstLowerCase($!{tableInfo.name}));
JsonResp resp = null;
if(flag){
resp = new JsonResp(JsonResp.STATE_OK,JsonResp.CODE_OK);
}else{
resp = new JsonResp(JsonResp.STATE_ERR,JsonResp.CODE_ERR);
}
return resp;
}
/**
* 修改数据
*
* @param $!tool.firstLowerCase($!{tableInfo.name}) 实例对象
* @return resp
* @throws Exception
*/
@PutMapping
public JsonResp update($!{tableInfo.name} $!tool.firstLowerCase($!{tableInfo.name})) throws Exception {
boolean flag = this.$!{tool.firstLowerCase($tableInfo.name)}Service.update($!tool.firstLowerCase($!{tableInfo.name}));
JsonResp resp = null;
if(flag){
resp = new JsonResp(JsonResp.STATE_OK,JsonResp.CODE_OK);
}else{
resp = new JsonResp(JsonResp.STATE_ERR,JsonResp.CODE_ERR);
}
return resp;
}
/**
* 通过主键删除数据
*
* @param $!pk.name 主键
* @return resp
* @throws Exception
*/
@DeleteMapping("/{id}")
public JsonResp deleteById(@PathVariable $!pk.shortType id) throws Exception {
boolean flag = this.$!{tool.firstLowerCase($tableInfo.name)}Service.deleteById(id);
JsonResp resp = null;
if(flag){
resp = new JsonResp(JsonResp.STATE_OK,JsonResp.CODE_OK);
}else{
resp = new JsonResp(JsonResp.STATE_ERR,JsonResp.CODE_ERR);
}
return resp;
}
}
模板中的注释写得比较详细,将这些模板代码替换进去之后,直接生成代码:
然后加上JsonResp返回值封装类,放在util包里面:
package com.dsy.codetst.util;
import org.springframework.util.StringUtils;
import java.util.List;
public class JsonResp {
// 成功
public final static String STATE_OK = "ok";
public final static Integer CODE_OK = 0;
// 系统错误
public final static String STATE_ERR = "error";
public final static Integer CODE_ERR = 1;
// 业务错误
public final static String STATE_WARN = "warn";
/**
* @Fields state: 请求结果状态
*/
private String state;
/**
* @Fields errMsg: 错误信息
*/
private String msg;
/**
* @Fields exceptionDetails: 异常细节,针对非自定义类异常中信息
*/
private String exceptionDetails;
/**
* @Fields exceptionStackTrace: 异常堆栈,针对非自定义类异常中信息
*/
private String exceptionStackTrace;
/**
* @Fields data: 结果集合
*/
private List data;
/**
* 结果集条数
*/
private Integer count;
/**
* 单个对象
*/
private Object obj;
/**
* 内部定义标识:0成功,其他错误
*/
private Integer code;
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public Integer getCount() {
return count;
}
public void setCount(Integer count) {
this.count = count;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
public List getData() {
return data;
}
public void setData(List data) {
this.data = data;
}
public Object getObj() {
return obj;
}
public void setObj(Object obj) {
this.obj = obj;
}
public JsonResp() {
}
public JsonResp(String state) throws Exception {
this.state = state;
}
public JsonResp(String state, Integer code) throws Exception {
this.state = state;
this.code = code;
}
public String getState() {
return state;
}
public void setState(String state) {
this.state = state;
}
public String getExceptionDetails() {
return exceptionDetails;
}
public void setExceptionDetails(String exceptionDetails) {
this.exceptionDetails = exceptionDetails;
}
public String getExceptionStackTrace() {
return exceptionStackTrace;
}
public void setExceptionStackTrace(String exceptionStackTrace) {
this.exceptionStackTrace = exceptionStackTrace;
}
}
在启动类上添加注解 @MapperScan("com.dsy.codetst.dao")
扫描dao接口
配置文件中添加数据库信息、mapper映射文件路径:
application.properties
server.port=8080
spring.datasource.name=codetest
spring.datasource.url=jdbc:mysql://localhost:3333/codetest?characterEncoding=UTF-8&serverTimezone=GMT%2B8
spring.datasource.username=root
spring.datasource.password=
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
#mybatis.type-aliases-package=com.dag.dao.entity
mybatis.mapperLocations=classpath:mapper/*Dao.xml
到这里,基于user的CURD就完成了,直接启动项目就可以使用。
3.3 接口测试
新增数据
POST http://localhost:8080/user?name=测试&password=123456&type=1
返回:
{
"state": "ok",
"msg": null,
"exceptionDetails": null,
"exceptionStackTrace": null,
"data": null,
"count": null,
"obj": null,
"code": 0
}
再插入一条
POST http://localhost:8080/user?name=测试2&password=123456&type=1
主键查询
GET http://localhost:8080/user/1
返回:
{
"state": "ok",
"msg": null,
"exceptionDetails": null,
"exceptionStackTrace": null,
"data": null,
"count": null,
"obj": {
"id": "1",
"name": "测试",
"pwd": null,
"type": 1
},
"code": 0
}
条件+分页查询(page表示页数,limit表示每页的数据条数)
GET http://localhost:8080/user/list?page=1&limit=3
返回:
{
"state": "ok",
"msg": null,
"exceptionDetails": null,
"exceptionStackTrace": null,
"data": [{
"id": "1",
"name": "测试",
"pwd": null,
"type": 1
},
{
"id": "2",
"name": "测试2",
"pwd": null,
"type": 1
}
],
"count": 2,
"obj": null,
"code": 0
}
修改数据
PUT http://localhost:8080/user?id=1&name=测试修改&pwd=123456
返回:
......(和查询一样,回调ok)
删除数据
DELETE http://localhost:8080/user/2
返回:
......(同上,回调ok)
再次查询验证数据
GET http://localhost:8080/user/list?page=1&limit=3
{
"state": "ok",
"msg": null,
"exceptionDetails": null,
"exceptionStackTrace": null,
"data": [{
"id": "1",
"name": "测试修改",
"pwd": "123456",
"type": 1
}],
"count": 1,
"obj": null,
"code": 0
}
最后,结合SpringMVC的异常处理机制,会更好用,可以捕获到抛出的异常,并仍然以JsonResp封装返回错误信息:
package com.dsy.codetst.handler;
import com.dsy.codetst.util.JsonResp;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.RestController;
import java.security.InvalidParameterException;
/**
* 统一异常处理类
*
*/
@ControllerAdvice
@RestController
public class GlobalExceptionHandler {
/**
* 捕获InvalidParameterException异常(参数只能是Throwable及其子类)
* @return
*/
@ExceptionHandler({InvalidParameterException.class})
public JsonResp handlerArithmeticException(InvalidParameterException e) throws Exception {
JsonResp resp = new JsonResp(JsonResp.STATE_ERR);
e.printStackTrace();
resp.setMsg("参数错误: "+e.getMessage());
return resp;
}
@ExceptionHandler({Exception.class})
public JsonResp handlerArithmeticException(Exception e) throws Exception {
JsonResp resp = new JsonResp(JsonResp.STATE_ERR);
e.printStackTrace();
resp.setMsg("发生错误: "+e.getMessage());
return resp;
}
}
一些说明:
1.在条件查询的设计当中,为了结合分页查询和数据封装的简便,使用了HashMap作为参数,使用了page和limit分别做页数和条数,如果你的数据表中要使用page、limit做字段,应当修改此处的参数名。2.分页的limit子句中,使用了${param}取值,但不必担心sql注入风险,service层对参数做了校验,如果非数字,会直接抛出异常。