我这样写代码,比直接使用 MyBatis 效率提高了100倍!
来源:https://juejin.cn/post/7027733039299952676
对一个 Java 后端程序员来说,mybatis
、hibernate
、data-jdbc
等都是我们常用的 ORM 框架。它们有时候很好用,比如简单的 CRUD,事务的支持都非常棒。但有时候用起来也非常繁琐,比如接下来我们要聊到的一个常见的开发需求,最后本文会给出一个比直接使用这些 ORM 开发效率至少会提高 100 倍的方法(绝无夸张)。
| 首先数据库有两张表
用户表(user):(简单起见,假设只有 4 个字段)
字段名 | 类型 | 含义 |
---|---|---|
id | bitint | 用户 ID |
name | varchar(45) | 用户名 |
age | int | 年龄 |
role_id | int | 角色 ID |
角色表(role):(简单起见,假设只有 2 个字段)
字段名 | 类型 | 含义 |
---|---|---|
id | int | 角色 ID |
name | varchar(45) | 角色名 |
| 接下来我们要实现一个用户查询的功能
这个查询有点复杂,它的要求如下:
可按
用户名
字段查询,要求:可精确匹配(等于某个值)
可全模糊匹配(包含给定的值)
可后模糊查询(以...开头)
可前模糊查询(以.. 结尾)
可指定以上四种匹配是否可以忽略大小写
可按
年龄
字段查询,要求:可精确匹配(等于某个年龄)
可大于匹配(大于某个值)
可小于匹配(小于某个值)
可区间匹配(某个区间范围)
可按
角色ID
查询,要求:精确匹配可按
用户ID
查询,要求:同年龄
字段可指定只输出哪些列(例如,只查询
ID
与用户名
列)支持分页(每次查询后,页面都要显示满足条件的用户总数)
查询时可选择按
ID
、用户名
、年
龄
等任意字段排序
| 后端接口该怎么写呢?
试想一下,对于这种要求的查询,后端接口里的代码如果用 mybatis
、hibernate
、data-jdbc
直接来写的话,100 行代码 能实现吗?
反正我是没这个信心,算了,我还是直接坦白,面对这种需求后端如何 只用一行代码搞定 吧(有兴趣的同学可以 mybatis 等写个试试,最后可以对比一下)
| 手把手:只一行代码实现以上需求
首先,重点人物出场啦:Bean Searcher, 它就是专门来对付这种列表检索的,无论简单的还是复杂的,统统一行代码搞定!而且它还非常轻量,Jar 包体积仅不到 100KB,无第三方依赖。
假设我们项目使用的框架是 Spring Boot(当然 Bean Searcher 对框架没有要求,但在 Spring Boot 中使用更加方便)
添加依赖
Maven :
<dependency><groupId>com.ejlchina</groupId><artifactId>bean-searcher-boot-starter</artifactId><version>3.0.1</version>
</dependency>
Gradle :
implementation 'com.ejlchina:bean-searcher-boot-starter:3.0.1'
然后写个实体类来承载查询的结果
@SearchBean(tables="user u, role r", joinCond="u.role_id = r.id", autoMapTo="u")
public class User {private Long id; // 用户ID(u.id)private String name; // 用户名(u.name)private int age; // 年龄(u.age)private int roleId; // 角色ID(u.role_id)@DbField("r.name") // 指明这个属性来自 role 表的 name 字段private int role; // 角色名(r.name)// Getter and Setter ...
}
接着就可以写用户查询接口了
接口路径就叫 /user/index 吧:
@RestController
@RequestMapping("/user")
public class UserController {@Autowiredprivate MapSearcher mapSearcher; // 注入检索器(由 bean-searcher-boot-starter 提供)@GetMapping("/index")public SearchResult<Map<String, Object>> index(HttpServletRequest request) {// 这里咱们只写一行代码return mapSearcher.search(User.class, MapUtils.flat(request.getParameterMap()));}}
上述代码中的
MapUtils
是 Bean Searcher 提供的一个工具类,MapUtils.flat(request.getParameterMap())
只是为了把前端传来的请求参数统一收集起来,然后剩下的,就全部交给MapSearcher
检索器了。
| 这样就完了?那我们来测一下这个接口,看看效果吧
(1)无参请求
GET /user/index
返回结果:
{"dataList": [ // 用户列表,默认返回第 0 页,默认分页大小为 15 (可配置){ "id": 1, "name": "Jack", "age": 25, "roleId": 1, "role": "普通用户" },{ "id": 2, "name": "Tom", "age": 26, "roleId": 1, "role": "普通用户" },...],"totalCount": 100 // 用户总数
}
(2)分页请求(page | size)
GET /user/index? page = 2 & size = 10
返回结果:结构同 (1)(只是每页 10 条,返回第 2 页)
参数名
size
和page
可自定义,page
默认从0
开始,同样可自定义,并且可与其它参数组合使用
(3)数据排序(sort | order)
GET /user/index? sort = age & order = desc
返回结果:结构同 (1)(只是 dataList 数据列表以 age 字段降序输出)
参数名
sort
和order
可自定义,可与其它参数组合使用
(4)指定(排除)字段(onlySelect | selectExclude)
GET /user/index? onlySelect = id,name,role
GET /user/index? selectExclude = age,roleId
返回结果:( 列表只含 id,name 与 role 三个字段)
{"dataList": [ // 用户列表,默认返回第 0 页(只包含 id,name,role 字段){ "id": 1, "name": "Jack", "role": "普通用户" },{ "id": 2, "name": "Tom", "role": "普通用户" },...],"totalCount": 100 // 用户总数
}
参数名
onlySelect
和selectExclude
可自定义,可与其它参数组合使用
(5)字段过滤(op = eq)
GET /user/index? age=20
GET /user/index? age=20 & age-op=eq
返回结果:结构同 (1)(但只返回 age = 20 的数据)
参数
age-op = eq
表示age
的 字段运算符 是eq
(Equal
的缩写),表示参数age
与参数值20
之间的关系是Equal
,由于Equal
是一个默认的关系,所以age-op = eq
也可以省略
参数名 age-op
的后缀 -op
可自定义,且可与其它字段参数 和 上文所列的参数(分页、排序、指定字段)组合使用,下文所列的字段参数也是一样,不再复述。
(6)字段过滤(op = ne)
GET /user/index? age=20 & age-op=ne
返回结果:结构同 (1)(但只返回 age != 20 的数据,
ne
是NotEqual
的缩写)
(7)字段过滤(op = ge)
GET /user/index? age=20 & age-op=ge
返回结果:结构同 (1)(但只返回 age >= 20 的数据,
ge
是GreateEqual
的缩写)
(8)字段过滤(op = le)
GET /user/index? age=20 & age-op=le
返回结果:结构同 (1)(但只返回 age <= 20 的数据,
le
是LessEqual
的缩写)
(9)字段过滤(op = gt)
GET /user/index? age=20 & age-op=gt
返回结果:结构同 (1)(但只返回 age > 20 的数据,
gt
是GreateThan
的缩写)
(10)字段过滤(op = lt)
GET /user/index? age=20 & age-op=lt
返回结果:结构同 (1)(但只返回 age < 20 的数据,
lt
是LessThan
的缩写)
(11)字段过滤(op = bt)
GET /user/index? age-0=20 & age-1=30 & age-op=bt
返回结果:结构同 (1)(但只返回 20 <= age <= 30 的数据,
bt
是Between
的缩写)
参数
age-0 = 20
表示age
的第 0 个参数值是20
。上述提到的age = 20
实际上是age-0 = 20
的简写形式。另:参数名age-0
与age-1
中的连字符-
可自定义。
(12)字段过滤(op = mv)
GET /user/index? age-0=20 & age-1=30 & age-2=40 & age-op=mv
返回结果:结构同 (1)(但只返回 age in (20, 30, 40) 的数据,
mv
是MultiValue
的缩写,表示有多个值的意思)
(13)字段过滤(op = in)
GET /user/index? name=Jack & name-op=in
返回结果:结构同 (1)(但只返回 name 包含 Jack 的数据,
in
是Include
的缩写)
(14)字段过滤(op = sw)
GET /user/index? name=Jack & name-op=sw
返回结果:结构同 (1)(但只返回 name 以 Jack 开头的数据,
sw
是StartWith
的缩写)
(15)字段过滤(op = ew)
GET /user/index? name=Jack & name-op=ew
返回结果:结构同 (1)(但只返回 name 以 Jack 结尾的数据,
ew
是EndWith
的缩写)
(16)字段过滤(op = ey)
GET /user/index? name-op=ey
返回结果:结构同 (1)(但只返回 name 为空 或为 null 的数据,
ey
是Empty
的缩写)
(17)字段过滤(op = ny)
GET /user/index? name-op=ny
返回结果:结构同 (1)(但只返回 name 非空 的数据,
ny
是NotEmpty
的缩写)
(18)忽略大小写(ic = true)
GET /user/index? name=Jack & name-ic=true
返回结果:结构同 (1)(但只返回 name 等于 Jack (忽略大小写) 的数据,
ic
是IgnoreCase
的缩写)
参数名
name-ic
中的后缀-ic
可自定义,该参数可与其它的参数组合使用,比如这里检索的是 name 等于 Jack 时忽略大小写,但同样适用于检索 name 以 Jack 开头或结尾时忽略大小写。
当然,以上各种条件都可以组合,例如
查询 name 以 Jack (忽略大小写) 开头,且 roleId = 1,结果以 id 字段排序,每页加载 10 条,查询第 2 页:
GET /user/index? name=Jack & name-op=sw & name-ic=true & roleId=1 & sort=id & size=10 & page=2
返回结果:结构同 (1)
OK,效果看完了,/user/index
接口里我们确实只写了一行代码,它便可以支持这么多种的检索方式,有没有觉得现在 你写的一行代码 就可以 干过别人的一百行 呢?
| Bean Searcher
本例中,我们只使用了 Bean Searcher 提供的 MapSearcher
检索器的一个 search
方法,其实,它有很多 search
方法。
检索方法
searchCount(Class<T> beanClass, Map<String, Object> params)
查询指定条件下的数据 总条数searchSum(Class<T> beanClass, Map<String, Object> params, String field)
查询指定条件下的 某字段 的 统计值searchSum(Class<T> beanClass, Map<String, Object> params, String[] fields)
查询指定条件下的 多字段 的 统计值search(Class<T> beanClass, Map<String, Object> params)
分页 查询指定条件下数据 列表 与 总条数search(Class<T> beanClass, Map<String, Object> params, String[] summaryFields)
同上 + 多字段 统计searchFirst(Class<T> beanClass, Map<String, Object> params)
查询指定条件下的 第一条 数据searchList(Class<T> beanClass, Map<String, Object> params)
分页 查询指定条件下数据 列表searchAll(Class<T> beanClass, Map<String, Object> params)
查询指定条件下 所有 数据 列表
MapSearcher 与 BeanSearcher
另外,Bean Searcher 除了提供了 MapSearcher
检索器外,还提供了 BeanSearcher
检索器,它同样拥有 MapSearcher
拥有的方法,只是它返回的单条数据不是 Map
,而是一个 泛型 对象。
参数构建工具
另外,如果你是在 Service 里使用 Bean Searcher,那么直接使用 Map<String, Object>
类型的参数可能不太优雅,为此, Bean Searcher 特意提供了一个参数构建工具。
例如,同样查询 name 以 Jack (忽略大小写) 开头,且 roleId = 1,结果以 id 字段排序,每页加载 10 条,加载第 2 页,使用参数构建器,代码可以这么写:
Map<String, Object> params = MapUtils.builder().field(User::getName, "Jack").op(Operator.StartWith).ic().field(User::getRoleId, 1).orderBy(User::getId, "asc").page(2, 10).build()
List<User> users = beanSearcher.searchList(User.class, params);
这里使用的是
BeanSearcher
检索器,以及它的searchList(Class<T> beanClass, Map<String Object> params)
方法。
运算符约束
上文我们看到,Bean Searcher 对实体类中的每一个字段,都直接支持了很多的检索方式。
但某同学:哎呀!检索方式太多了,我根本不需要这么多,我的数据量几十个亿呀,用户名字段的前模糊查询方式利用不到索引,万一把我的数据库查崩了怎么办呀?
好办,Bean Searcher 支持运算符的约束,实体类的用户名 name
字段只需要注解一下即可:
@SearchBean(tables="user u, role r", joinCond="u.role_id = r.id", autoMapTo="u")
public class User {@DbField(onlyOn = {Operator.Equal, Operator.StartWith})private String name;// 为减少篇幅,省略其它字段...
}
如上,通过 @DbField
注解的 onlyOn
属性,指定这个用户名 name
只能适用与 精确匹配 和 后模糊查询,其它检索方式它将直接忽略。
上面的代码是限制了 name
只能有两种检索方式,如果再严格一点,只允许 精确匹配,那其实有两种写法。
(1)还是使用运算符约束:
@SearchBean(tables="user u, role r", joinCond="u.role_id = r.id", autoMapTo="u")
public class User {@DbField(onlyOn = Operator.Equal)private String name;// 为减少篇幅,省略其它字段...
}
(2)在 Controller 的接口方法里把运算符参数覆盖:
@GetMapping("/index")
public SearchResult<Map<String, Object>> index(HttpServletRequest request) {Map<String, Object> params = MapUtils.flatBuilder(request) // 收集前端传来的参数 .field(User::getName).op(Operator.Equal) // 把 name 字段的运算符直接覆盖为 Equal.build()return mapSearcher.search(User.class, params);
}
条件约束
该同学又:哎呀!我的数据量还是很大,age 字段没有索引,根本不能参与 where 条件,一查就是一条 慢 SQL 啊!
不急,Bean Searcher 还支持条件的约束,让这个字段直接不能作为条件:
@SearchBean(tables="user u, role r", joinCond="u.role_id = r.id", autoMapTo="u")
public class User {@DbField(conditional = false)private int age;// 为减少篇幅,省略其它字段...
}
如上,通过 @DbField
注解的 conditional
属性, 就直接不允许 age
字段参与条件了,无论前端怎么传参,Bean Searcher 都不搭理。
参数过滤器
该同学仍:哎呀!哎呀 ...
别怕! Bean Searcher 还支持配置全局参数过滤器,可自定义任何参数过滤规则,在 Spring Boot 项目中,只需要配置一个 Bean:
@Bean
public ParamFilter myParamFilter() {return new ParamFilter() {@Overridepublic <T> Map<String, Object> doFilter(BeanMeta<T> beanMeta, Map<String, Object> paraMap) {// beanMeta 是正在检索的实体类的元信息, paraMap 是当前的检索参数// TODO: 这里可以添加一下自定义的参数过滤规则return paraMap; // 返回过滤后的检索参数}};
}
| 某同学问
参数咋这么怪,这么多呢,和前端有仇呀
参数名是否奇怪,这其实看个人喜好,如果你不喜欢中划线
-
,不喜欢op
、
ic
后缀,完全可以自定义,参考这篇文档:https://searcher.ejlchina.com/guide/latest/params.html#字段参数参数个数的多少,其实是和需求的复杂程度相关,如果需求很简单,其实很多参数没必要让前端传,后端直接塞进去就好,比如:
name
只要求后模糊匹配,age
只要求区间匹配,那可以这样:
@GetMapping("/index")
public SearchResult<Map<String, Object>> index(HttpServletRequest request) {Map<String, Object> params = MapUtils.flatBuilder(request).field(User::getName).op(Operator.StartWith).field(User::getAge).op(Operator.Between).build()return mapSearcher.search(User.class, params);
}
这样前端就不用传 name-op
与 age-op
这两个参数了。
其实还有一种更简单的方法,那就是 运算符约束(当约束存在时,运算符默认就是 onlyOn
属性中指定的第一个值,前端可以省略不传):
@SearchBean(tables="user u, role r", joinCond="u.role_id = r.id", autoMapTo="u")
public class User {@DbField(onlyOn = Operator.StartWith)private String name;@DbField(onlyOn = Operator.Between)private String age;// 为减少篇幅,省略其它字段...
}
入参是 request,我 swagger 文档不好渲染了呀
其实,Bean Searcher 的检索器只是需要一个 Map<String, Object>
类型的参数,至于这个参数是怎么来的,和 Bean Searcher 并没有直接关系。前文之所以从 request
里取,只是因为这样代码看起来简洁,如果你喜欢声明参数,完全可以把代码写成这样:
@GetMapping("/index")
public SearchResult<Map<String, Object>> index(Integer page, Integer size, String sort, String order, String name, Integer roleId,@RequestParam(value = "name-op", required = false) String name_op,@RequestParam(value = "name-ic", required = false) Boolean name_ic,@RequestParam(value = "age-0", required = false) Integer age_0,@RequestParam(value = "age-1", required = false) Integer age_1,@RequestParam(value = "age-op", required = false) String age_op) {Map<String, Object> params = MapUtils.builder().field(Employee::getName, name).op(name_op).ic(name_ic).field(Employee::getAge, age_0, age_1).op(age_op).field(Employee::getRoleId, roleId).orderBy(sort, order).page(page, size).build();return mapSearcher.search(User.class, params);
}
字段参数之间的关系都是 “且” 呀,那 “或” 呢?“且” “或” 任意组合呢?
上文所述的字段参数之间确是都是 "且" 的关系,至于 “或”,虽然这种使用场景不太多,但 Bean Searcher 也是支持的,详细可以参考这篇文章:
https://github.com/ejlchina/bean-searcher/issues/8
这里就不再复述了。
| 结语
本文介绍了 Bean Searcher 在复杂列表检索领域的超强能力,它之所以可以极大提高这类需求的研发效率,根本上归功于它 独创 的 动态字段运算符 与 多表映射机制,这是传统 ORM 框架所没有的。但由于篇幅所限,本文所述仍只是冰山一角,比如它还:
支持 嵌入参数
支持 字段转换器
支持 Sql 拦截器
支持 多数据源
支持 自定义注解
等等
| 项目 GitHub 地址
https://github.com/ejlchina/bean-searcher
我这样写代码,比直接使用 MyBatis 效率提高了100倍!相关推荐
- 我这样写代码,比直接使用 MyBatis 效率提高了 100 倍
今日推荐 扔掉 Postman,一个工具全部搞定,真香!为啥查询那么慢?还在直接用JWT做鉴权?JJWT真香推荐 15 款常用开发工具干掉 navicat:这款 DB 管理工具才是y(永)y(远)d( ...
- python提升运行速度-一行代码让你的python运行速度提高100倍!Python真强!
原标题:一行代码让你的python运行速度提高100倍!Python真强! Python用的好,猪也能飞起来. 今天,带大家学习如何让Python飞起来的方法,干货满满哦! python一直被病垢运行 ...
- 一行代码让你的python运行速度提高100倍
python一直被病垢运行速度太慢,但是实际上python的执行效率并不慢,慢的是python用的解释器Cpython运行效率太差. "一行代码让python的运行速度提高100倍" ...
- 为了提高python代码运行速度和进行_一行代码让你的python运行速度提高100倍
Python用的好,猪也能飞起来.今天,带大家学习如何让Python飞起来的方法,干货满满哦! python一直被病垢运行速度太慢,但是实际上python的执行效率并不慢,慢的是python用的解释器 ...
- python运行非常慢的解决-一行代码让你的python运行速度提高100倍
python一直被病垢运行速度太慢,但是实际上python的执行效率并不慢,慢的是python用的解释器Cpython运行效率太差. "一行代码让python的运行速度提高100倍" ...
- 加一行代码,让你的Python的运算速度加快100倍
一行代码让你的python运行速度提高100倍 python一直被病垢运行速度太慢,但是实际上python的执行效率并不慢,慢的是python用的解释器Cpython运行效率太差. "一行代 ...
- 骚操作 !IDEA 防止写代码沉迷插件 !
点击上方蓝色"方志朋",选择"设为星标" 回复"666"获取独家整理的学习资料! 原文地址:juejin.cn/post/6914083 ...
- 肝一波 ~ 手写一个简易版的Mybatis,带你深入领略它的魅力!
零.准备工作 <dependencies><dependency><groupId>mysql</groupId><artifactId>m ...
- 【手写系列】透彻理解MyBatis设计思想之手写实现
前言 MyBatis,曾经给我的感觉是一个很神奇的东西,我们只需要按照规范写好XXXMapper.xml以及XXXMapper.java接口.要知道我们并没有提供XXXMapper.java的实现类, ...
最新文章
- oracle 分页写法
- 使用RabbitMQ做数据接收和处理时,自动关闭
- EcologyEvolution|微生物功能多样性从概念到应用
- Retrofit2.0
- c#命名法 【转】
- Flutter开发之爬坑集合(五)
- Hive之 hive-1.2.1 + hadoop 2.7.4 集群安装
- 真实版删库跑路,宕机36小时市值蒸发9亿!
- php采集网页,phpQuery采集网页实现代码实例
- 基于DotNet构件技术的企业级敏捷软件开发平台 - AgileEAS.NET - 文章汇总及学习指南...
- 云炬随笔20161224
- javafx中的tree_JavaFX中的塔防(2)
- Apache20、lighttpd、nginx的比较[zt]
- 参加第六届中国制造业MES应用年会
- matlab java错误_求助:matlab load mat文件出错!java exception occurred:
- 蒟蒻的HNOI2017滚粗记
- 在线class文件反编译java
- HFSS - WLAN双频单极子天线设计与仿真
- Win10(winser2019)关闭驱动数字签名方法
- [电路]5-电压源、电流源的串联和并联
热门文章
- python3查找文件中指定字符串_Python3在指定路径下递归定位文件中出现的字符串...
- 串口 能 按位传输吗_六类网线能传输多少米?家装六类网线有必要吗?
- 使用ajax追加样式没,ajax-动态添加内容后,jQuery Mobile不应用样式
- 小程序语音识别+php,微信小程序之语音识别(附小程序+服务器源码)
- HDU1874(Dijstra算法)
- PTA基础编程题目集-6-8 简单阶乘计算
- 模板 - 最小斯坦纳树
- 曼哈顿距离和切比雪夫距离链接
- 邻接表存储(链式前向星)
- php 尾递归,关于尾递归的使用详解