前言
最近公司有一个小的项目让我来开发,因为整个后端只有我一个人,所以自由度还是很高的,所以在搭建项目的时候就在想不完全按照公司其他项目的架构来搭建(其他项目都是分布式架构)以求进行一个快速的开发。
说道快速开发,springboot就不得不提了,用的也是比较熟悉了,单就springboot的话实际上更多的是对一个项目配置的简化,而内些service、dao层的操作实际上还是想以前ssm一样的。
所以在经过一些探索之后发现了三个开发神器:
1、hutool
2、lombok
3、mybatis-plus
下面以此介绍一下上面三个“神器”
一、hutool
Hutool是一个Java工具包,也只是一个工具包,它帮助我们简化每一行代码,减少每一个方法,让Java语言也可以“甜甜的”。Hutool最初是我项目中“util”包的一个整理,后来慢慢积累并加入更多非业务相关功能,并广泛学习其它开源项目精髓,经过自己整理修改,最终形成丰富的开源工具集。
上面这个一段对于Hutool的简介,它确实就是一个工具包,是一个想到一个什么功能,一搜他就有的工具包,在这个项目中我需要做很多的文件操作,包括对图片对excel的操作,完全不用自己去写,简直了。
我的建议是可以打开编译器从git上clone下来hutool的代码,当成一个词典一样看,有没有需要都可以看看,真的挺好的。
具体的可以去看一下 https://github.com/looly/hutool
二、lombok
简单的来说lombok就是为javabean生成getset方法的工具,他简化了冗余的代码,也都重写了toString,hash等方法。当然如果要使用的时候还需要在编译器安装一个插件。
用了lombok之后我的一个实体类大概就长这样
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.io.Serializable;
@Data
@TableName("pd_prj_info")
public class PdPrjInfo implements Serializable {
private static final long serialVersionUID = 1L;
//项目id
@TableId
private String prjId;
//项目名称
private String prjName;
//资产包类型
private String prjType;
//资产明细表存放地址
private String filePath;
//影像文件保存地址
private String folderPath;
//创建人
private String creator;
//创建时间
private String crtTime;
//修改时间
private String lastOptTime;
}
是不是有点简洁啊,当然他还有很多灵活的配置,这里就不一一列举了。
三、mybatis-plus
重点来啦!!!
本篇的重点其实就是mybatis-plus了,下面简单介绍一下。
MyBatis-Plus(简称 MP)是一个 MyBatis 的增强工具,在 MyBatis 的基础上只做增强不做改变,为简化开发、提高效率而生。
上面这一段是来自于mybatis-plus的官网 https://mp.baomidou.com/guide/#特性 里的一段描述,我的理解是mybatis和mybatis-plus是完全兼容的,mybatis-plus是在mybatis的基础上做了一些增强,包括使用起来非常简便的条件构造器、分页插件、SERVICE和MAPPER层的CRUD接口定义,当然还有一些别的扩展插件。
插播一条mybatis-plus的思维导图
下面简单的介绍一下mybatis-plus的使用,详细的代码已经push到我的github上了,里面会有很详细的mybatis-plus和mybatis的用法(如果没有那就是我还没来得及写呢,一定会写的!!!)
https://github.com/jszmt/mybatis-plus-test
1.依赖引用:
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>mybatis-plus-boot-starter</artifactId>
<version>3.0.6</version>
</dependency>
2.添加配置
mybatis-plus:
# 如果是放在src/main/java目录下 classpath:/com/yourpackage/*/mapper/*Mapper.xml
# 如果是放在resource目录 classpath:/mapper/*Mapper.xml
mapper-locations: classpath:mybatis/mappers/*Mapper.xml
#实体扫描,多个package用逗号或者分号分隔
typeAliasesPackage: cn.saytime.model
global-config:
#主键类型 0:"数据库ID自增", 1:"用户输入ID",2:"全局唯一ID (数字类型唯一ID)", 3:"全局唯一ID UUID";
id-type: 0
#字段策略 0:"忽略判断",1:"非 NULL 判断"),2:"非空判断"
field-strategy: 1
#驼峰下划线转换
#db-column-underline: true
#刷新mapper 调试神器
#refresh-mapper: true
#数据库大写下划线转换
#capital-mode: true
# Sequence序列接口实现类配置
#key-generator: com.baomidou.mybatisplus.incrementer.OracleKeyGenerator
#逻辑删除配置(下面3个配置)
#logic-delete-value: 1
#logic-not-delete-value: 0
#sql-injector: com.baomidou.mybatisplus.mapper.LogicSqlInjector
#自定义填充策略接口实现
#meta-object-handler: com.baomidou.springboot.MyMetaObjectHandler
configuration:
map-underscore-to-camel-case: true
cache-enabled: false
#配置JdbcTypeForNull
jdbc-type-for-null: 'null'
# 配置slq打印日志
log-impl: org.apache.ibatis.logging.stdout.StdOutImpl
3.DAO层扫描
可以在每个dao层的接口上添加@Mapper注解,也可以在配置类或者启动类添加@MapperScan(“com.wugz.app.dao”)
4.service层继承通用service实现(可忽略)
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.wugz.app.dao.PdPrjInfoDao;
import com.wugz.app.domain.PdPrjInfo;
import org.springframework.stereotype.Service;
@Service
public class PdPrjInfoService extends ServiceImpl<PdPrjInfoDao,PdPrjInfo> {
}
在service层中如果没有其他的需要,不需要注入Dao层如果需要使用在父类ServiceImpl中定义了baseMapper代表了上面代码中的PdPrjInfoDao例如下面的用法
@Service
public class OpTaskInfoService extends ServiceImpl<OpTaskInfoDao,OpTaskInfo> {
public OpTaskInfo selectOneByObject(OpTaskInfo opTaskInfo){
return baseMapper.selectOne(new LambdaQueryWrapper(opTaskInfo));
}
}
5.dao层继承BaseMapper
public interface PdPrjInfoDao extends BaseMapper<PdPrjInfo> {
}
这样基本就完成了一个基本操作的写法,可以看到甚至不需要dao层的xml文件,而使用的话他提供的条件表达式也很方便,下面是我的git中的一个demo例子。
package com.wugz.app.controller;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.toolkit.support.SFunction;
import com.wugz.app.domain.PdPrjInfo;
import com.wugz.app.service.PdPrjInfoService;
import com.wugz.app.service.TdTaskInfoService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
/**
* @ClassName LambdaQueryWrapperTestController
* @Description LambdaQueryWrapper测试
* @Author wugz
* @Date 2018/12/28 14:07
* @Version 1.0
*/
@RestController
@RequestMapping("/lambda")
public class LambdaQueryWrapperTestController {
@Autowired
private PdPrjInfoService pdPrjInfoService;
@Autowired
private TdTaskInfoService tdTaskInfoService;
/***
* @Author wugz
* @Description 初始化一个lambda查询表达式
* @Date 2018/12/28 15:52
* @Param []
* @return java.lang.Object
**/
@RequestMapping("/")
public Object init(){
/**
* 初始化LambdaQueryWrapper时若使用带实体类参数的构造器,则默认查询条件为属性不为空的条件
* 以listMaps为例,构造的sql为:
*<script>
* SELECT <choose>
* <when test="ew != null and ew.sqlSelect != null">
* ${ew.sqlSelect}
* </when>
* <otherwise>prj_id,prj_name,prj_type,file_path,folder_path,creator,crt_time,last_opt_time</otherwise>
* </choose> FROM pd_prj_info
* <if test="ew != null">
* <where>
* <if test="ew.entity != null">
* <if test="ew.entity.prjId != null">prj_id=#{ew.entity.prjId}</if>
* <if test="ew.entity.prjName != null"> AND prj_name=#{ew.entity.prjName}</if>
* <if test="ew.entity.prjType != null"> AND prj_type=#{ew.entity.prjType}</if>
* <if test="ew.entity.filePath != null"> AND file_path=#{ew.entity.filePath}</if>
* <if test="ew.entity.folderPath != null"> AND folder_path=#{ew.entity.folderPath}</if>
* <if test="ew.entity.creator != null"> AND creator=#{ew.entity.creator}</if>
* <if test="ew.entity.crtTime != null"> AND crt_time=#{ew.entity.crtTime}</if>
* <if test="ew.entity.lastOptTime != null"> AND last_opt_time=#{ew.entity.lastOptTime}</if>
* </if>
* <if test="ew.sqlSegment != null and ew.sqlSegment != '' and ew.nonEmptyOfWhere">
* <if test="ew.nonEmptyOfEntity and ew.nonEmptyOfNormal"> AND</if> ${ew.sqlSegment}
* </if>
* </where>
* <if test="ew.sqlSegment != null and ew.sqlSegment != '' and ew.emptyOfWhere">
* ${ew.sqlSegment}
* </if>
* </if>
* </script>
*/
PdPrjInfo prjInfo = new PdPrjInfo();
prjInfo.setPrjId("1");
prjInfo.setPrjName("查询项目1");
LambdaQueryWrapper<PdPrjInfo> wrapper = new LambdaQueryWrapper<>(prjInfo);
/***
* 当然还可以使用无参构造器
*/
LambdaQueryWrapper<PdPrjInfo> wrapper1 = new LambdaQueryWrapper<>();
System.out.println(wrapper.getSqlSegment());
return pdPrjInfoService.listMaps(wrapper1);
}
/**
* @Author wugz
* @Description 选择执行的查询列,若不选择则默认全部查询
* @Date 2018/12/28 15:28
* @Param []
* @return java.lang.Object
**/
@RequestMapping("/select")
public Object select(){
LambdaQueryWrapper<PdPrjInfo> wrapper = new LambdaQueryWrapper<>();
/***
* 指定字段查询:prj_name 和 prj_id 两列
*/
//wrapper.select(PdPrjInfo::getPrjName,PdPrjInfo::getPrjId);
/**
* 条件查询,条件是查询字段以pjt开头的 其中i 代表 TableFieldInfo 数据库表字段反射信息
*/
wrapper.select(PdPrjInfo.class,i -> i.getProperty().startsWith("pjt"));
System.out.println(wrapper.getSqlSegment());
return pdPrjInfoService.listMaps(wrapper);
}
/**
* @Author wugz
* @Description 全部eq(或个别isNull)
* @Date 2018/12/29 11:13
* @Param []
* @return java.lang.Object
**/
@RequestMapping("/allEq")
public Object allEq(){
LambdaQueryWrapper<PdPrjInfo> wrapper = new LambdaQueryWrapper<>();
Map<SFunction<PdPrjInfo,?>,String> param = new HashMap<>();
param.put(PdPrjInfo::getPrjName,"测试项目");
param.put(PdPrjInfo::getPrjId,null);
/**
* SELECT prj_id,prj_name,prj_type,file_path,folder_path,creator,crt_time,last_opt_time FROM pd_prj_info WHERE prj_name = ?
*/
//wrapper.allEq(param,false);
/**
* SELECT prj_id,prj_name,prj_type,file_path,folder_path,creator,crt_time,last_opt_time FROM pd_prj_info WHERE prj_name = ? AND prj_id IS NULL
*/
//第二个boolean属性就是把map中为null的 是不是转换成is null
wrapper.allEq(param,true);
return pdPrjInfoService.list(wrapper);
}
/**
* @Author wugz
* @Description 相等
* @Date 2018/12/29 13:12
* @Param []
* @return java.lang.Object
**/
@RequestMapping("/eq")
public Object eq(){
LambdaQueryWrapper<PdPrjInfo> wrapper = new LambdaQueryWrapper<>();
//设置为false则不使用该条件
//wrapper.eq(false,PdPrjInfo::getPrjName,"测试项目1");
wrapper.eq(true,PdPrjInfo::getPrjName,"测试项目1");
System.out.println(wrapper.getSqlSegment());//SELECT prj_id,prj_name,prj_type,file_path,folder_path,creator,crt_time,last_opt_time FROM pd_prj_info WHERE prj_name = ?
return pdPrjInfoService.list(wrapper);
}
/**
* @Author wugz
* @Description 不相等
* @Date 2018/12/29 13:12
* @Param []
* @return java.lang.Object
**/
@RequestMapping("/ne")
public Object ne(){
LambdaQueryWrapper<PdPrjInfo> wrapper = new LambdaQueryWrapper<>();
//设置为false则不使用该条件
//wrapper.ne(false,PdPrjInfo::getPrjName,"测试项目1");
wrapper.ne(true,PdPrjInfo::getPrjName,"测试项目1");
System.out.println(wrapper.getSqlSegment());//SELECT prj_id,prj_name,prj_type,file_path,folder_path,creator,crt_time,last_opt_time FROM pd_prj_info WHERE prj_name <> ?
return pdPrjInfoService.list(wrapper);
}
/***
* gt(大于)
* ge(大于等于)
* lt(小于)
* le(小于等于)
*/
/**
* @Author wugz
* @Description 不相等
* @Date 2018/12/29 13:12
* @Param []
* @return java.lang.Object
**/
@RequestMapping("/between")
public Object between(){
LambdaQueryWrapper<PdPrjInfo> wrapper = new LambdaQueryWrapper<>();
wrapper.between(PdPrjInfo::getPrjType,"1","2");
System.out.println(wrapper.getSqlSegment());//SELECT prj_id,prj_name,prj_type,file_path,folder_path,creator,crt_time,last_opt_time FROM pd_prj_info WHERE prj_name BETWEEN ? AND ?
return pdPrjInfoService.list(wrapper);
}
/***
* notBetween NOT BETWEEN 值1 AND 值2 例: notBetween("age", 18, 30)--->age not between 18 and 30
*
* like LIKE '%值%' like("name", "王")--->name like '%王%'
*
* notLike NOT LIKE '%值%' 例: notLike("name", "王")--->name not like '%王%'
*
* likeLeft LIKE '%值' 例: likeLeft("name", "王")--->name like '%王'
*
* likeRight LIKE '值%' 例: likeRight("name", "王")--->name like '王%'
*
* isNull 字段 IS NULL 例: isNull("name")--->name is null
*
* isNotNull 字段 IS NOT NULL 例: isNotNull("name")--->name is not null
*
*/
/**
* @Author wugz
* @Description 字段 IN (value.get(0), value.get(1), ...) 例: in("age",{1,2,3})--->age in (1,2,3)
* @Date 2018/12/29 14:04
* @Param []
* @return java.lang.Object
**/
@RequestMapping("/in")
public Object in(){
LambdaQueryWrapper<PdPrjInfo> wrapper = new LambdaQueryWrapper<>();
//wrapper.in(PdPrjInfo::getPrjType,"1","2");
String[] a = {"1","2"};
wrapper.in(PdPrjInfo::getPrjType,Arrays.asList(a));
System.out.println(wrapper.getSqlSegment());//SELECT prj_id,prj_name,prj_type,file_path,folder_path,creator,crt_time,last_opt_time FROM pd_prj_info WHERE prj_type IN (?,?)
return pdPrjInfoService.list(wrapper);
}
/***
* notIn 例: notIn("age",{1,2,3})--->age not in (1,2,3)
*/
@RequestMapping("/inSql")
public Object inSql(){
LambdaQueryWrapper<PdPrjInfo> wrapper = new LambdaQueryWrapper<>();
wrapper.inSql(PdPrjInfo::getPrjType,"select prj_type from pd_prj_info");
System.out.println(wrapper.getSqlSegment());//SELECT prj_id,prj_name,prj_type,file_path,folder_path,creator,crt_time,last_opt_time FROM pd_prj_info WHERE prj_type IN (select prj_type from pd_prj_info)
return pdPrjInfoService.list(wrapper);
}
/***
* notInSql notInSql("id", "select id from table where id < 3")--->age not in (select id from table where id < 3)
*
* groupBy 例: groupBy("id", "name")--->group by id,name
*
* orderByAsc 例: orderByAsc("id", "name")--->order by id ASC,name ASC
*
* orderByDesc 例: orderByDesc("id", "name")--->order by id DESC,name DESC
*
* orderBy orderBy(true, true, "id", "name")--->order by id ASC,name ASC
*
*/
@RequestMapping("/having")
public Object having(){
LambdaQueryWrapper<PdPrjInfo> wrapper = new LambdaQueryWrapper<>();
wrapper.ne(PdPrjInfo::getPrjName,"1231231");//随便输的条件
wrapper.groupBy(PdPrjInfo::getPrjType);
wrapper.having("max(prj_type) > 0");
//SELECT prj_id,prj_name,prj_type,file_path,folder_path,creator,crt_time,last_opt_time FROM pd_prj_info WHERE prj_name <> ? GROUP BY prj_type HAVING max(prj_type) > 0
System.out.println(wrapper.getSqlSegment());
return pdPrjInfoService.list(wrapper);
}
/**
* @Author wugz
* @Description 主动调用or表示紧接着下一个方法不是用and连接!(不调用or则默认为使用and连接)
* @Date 2018/12/29 15:35
* @Param []
* @return java.lang.Object
**/
@RequestMapping("/or")
public Object or(){
LambdaQueryWrapper<PdPrjInfo> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(PdPrjInfo::getPrjType,"1").or().eq(PdPrjInfo::getPrjName,"123");
// SELECT prj_id,prj_name,prj_type,file_path,folder_path,creator,crt_time,last_opt_time FROM pd_prj_info WHERE prj_type = ? OR prj_name = ?
System.out.println(wrapper.getSqlSegment());
return pdPrjInfoService.list(wrapper);
}
/**
* @Author wugz
* @Description 拼接sql 该方法可用于数据库函数 动态入参的params对应前面sqlHaving内部的{index}部分.这样是不会有sql注入风险的,反之会有!
* @Date 2018/12/29 15:37
* @Param []
* @return java.lang.Object
**/
@RequestMapping("/apply")
public Object apply(){
LambdaQueryWrapper<PdPrjInfo> wrapper = new LambdaQueryWrapper<>();
//wrapper.apply("prj_id=1 and prj_name = {1}","1","测试项目");
wrapper.apply("prj_id={0} and prj_name = {1}","1","测试项目");
//Preparing: SELECT prj_id,prj_name,prj_type,file_path,folder_path,creator,crt_time,last_opt_time FROM pd_prj_info WHERE prj_id=? and prj_name = ?
//Parameters: 1(String), 测试项目(String)
System.out.println(wrapper.getSqlSegment());
return pdPrjInfoService.list(wrapper);
}
/**
* @Author wugz
* @Description 拼接 EXISTS ( sql语句 )
* @Date 2018/12/29 15:58
* @Param []
* @return java.lang.Object
**/
@RequestMapping("/exists")
public Object exists(){
LambdaQueryWrapper<PdPrjInfo> wrapper = new LambdaQueryWrapper<>();
wrapper.exists("select prj_type FROM pd_prj_info");
//SELECT prj_id,prj_name,prj_type,file_path,folder_path,creator,crt_time,last_opt_time FROM pd_prj_info WHERE EXISTS (select prj_type FROM pd_prj_info)
System.out.println(wrapper.getSqlSegment());
return pdPrjInfoService.list(wrapper);
}
/***
* last 无视优化规则直接拼接到 sql 的最后 例: last("limit 1")
*
* notExists 拼接 NOT EXISTS ( sql语句 )
*
*/
/**
* @Author wugz
* @Description 嵌套条件,相当于将多个条件使用()括起来
* @Date 2018/12/29 16:05
* @Param []
* @return java.lang.Object
**/
@RequestMapping("/nested")
public Object nested(){
LambdaQueryWrapper<PdPrjInfo> wrapper = new LambdaQueryWrapper<>();
wrapper.nested(i -> i.eq(PdPrjInfo::getPrjName,"测试项目").eq(PdPrjInfo::getPrjType,"1"));
//SELECT prj_id,prj_name,prj_type,file_path,folder_path,creator,crt_time,last_opt_time FROM pd_prj_info WHERE ( prj_name = ? AND prj_type = ? )
System.out.println(wrapper.getSqlSegment());
return pdPrjInfoService.list(wrapper);
}
}
其他的一些例子还是参考github中的demo就可以了。
四、总结
照搬官网的话说,mybatis-plus的三个特性:
润物无声:只做增强不做改变,引入它不会对现有工程产生影响,如丝般顺滑。
效率至上:只需简单配置,即可快速进行 CRUD 操作,从而节省大量时间。
丰富功能:热加载、代码生成、分页、性能分析等功能一应俱全。
可能很多人还是觉得在xml中写sql比较好,在我看来mybatis-plus的优势不是对一些复杂sql做简化的,他的核心就是提高开发效率,对于常规的CRUD操作进行封装,方便快速使用,也确实达到了这个目的,他并不会影响我们在一些复杂的情况使用手写sql的操作,只在需要简化的地方简化其他的地方就如常就好了啊。
其实推荐mybatis-plus的原因很重要的是真的讨厌增加一个sql操作就要在service接口、service实现类、dao接口、dao.xml中都对应添加方法了,实际上可能我就是想对一个字段排序啊。