⭐⭐⭐ Spring Boot 项目实战 ⭐⭐⭐ Spring Cloud 项目实战
《Dubbo 实现原理与源码解析 —— 精品合集》 《Netty 实现原理与源码解析 —— 精品合集》
《Spring 实现原理与源码解析 —— 精品合集》 《MyBatis 实现原理与源码解析 —— 精品合集》
《Spring MVC 实现原理与源码解析 —— 精品合集》 《数据库实体设计合集》
《Spring Boot 实现原理与源码解析 —— 精品合集》 《Java 面试题 + Java 学习指南》

摘要: 原创出处 juejin.cn/post/7294908459002429475 「xuejm」欢迎转载,保留摘要,谢谢!


🙂🙂🙂关注微信公众号:【芋道源码】有福利:

  1. RocketMQ / MyCAT / Sharding-JDBC 所有源码分析文章列表
  2. RocketMQ / MyCAT / Sharding-JDBC 中文注释源码 GitHub 地址
  3. 您对于源码的疑问每条留言都将得到认真回复。甚至不知道如何读源码也可以请教噢。
  4. 新的源码解析文章实时收到通知。每周更新一篇左右。
  5. 认真的源码交流微信群。

背景

转java后的几年时间里面一直在寻找一个类似.net的orm,不需要很特别的功能,仅希望90%的场景都可以通过强类型语法来编写符合直觉的sql,来操作数据库编写业务。

但是一直没有找到,Mybatis-Plus的单表让我在最初的时间段内看到了希望,不过随着使用的深入越发的觉得Mybatis-Plus只是一个残缺的orm,因为大部分场景不支持表达式或者强类型会导致它本身的很多特性都无法使用,比如你配置了软删除,那么如果你遇到了join不好意思软删除你需要自己处理,很多配置会随着手写sql的加入变的那么的不智能,甚至表现得和sql helper没区别。

别说Mybatis-Plus-Join了,这玩意更逆天,如果一个orm想写出符合自己的sql,需要不断地调试尝试来“拼接”出想要的语句,那么他就称不上一个ORM,连sql builder也算不上。Mybatis-Plus-Join就是这样。

所以在4-5年后我终于忍受不了了,决定自研一款orm。参考现有.net生态十分完整的orm代码和几乎完美符合扩展性和语义性的链式表达式,让.net的orm带到java中。

查询

查询第一条数据

Topic topic = easyQuery.queryable(Topic.class)
.where(o -> o.eq(Topic::getId, "123"))
.firstOrNull();

==> Preparing: SELECT `id`,`stars`,`title`,`create_time` FROM `t_topic` WHERE `id` = ? LIMIT 1
==> Parameters: 123(String)
<== Time Elapsed: 2(ms)
<== Total: 0

查询并断言至多一条数据

Topic topic = easyQuery.queryable(Topic.class)
.where(o -> o.eq(Topic::getId, "123"))
.singleOrNull();

==> Preparing: SELECT `id`,`stars`,`title`,`create_time` FROM `t_topic` WHERE `id` = ?
==> Parameters: 123(String)
<== Time Elapsed: 2(ms)
<== Total: 0

查询多条数据

List<Topic> topics = easyQuery.queryable(Topic.class)
.where(o -> o.eq(Topic::getId, "123"))
.toList();

==> Preparing: SELECT `id`,`stars`,`title`,`create_time` FROM `t_topic` WHERE `id` = ?
==> Parameters: 123(String)
<== Time Elapsed: 2(ms)
<== Total: 0

查询自定义列

Topic topic = easyQuery.queryable(Topic.class)
.where(o -> o.eq(Topic::getId, "1"))
.select(o->o.column(Topic::getId).column(Topic::getTitle))
.firstOrNull();

==> Preparing: SELECT `id`,`title` FROM `t_topic` WHERE `id` = ? LIMIT 1
==> Parameters: 1(String)
<== Time Elapsed: 2(ms)
<== Total: 1

分页查询

 EasyPageResult<Topic> topicPageResult = easyQuery
.queryable(Topic.class)
.where(o -> o.isNotNull(Topic::getId))
.toPageResult(1, 20);

==> Preparing: SELECT COUNT(1) FROM t_topic t WHERE t.`id` IS NOT NULL
<== Total: 1
==> Preparing: SELECT t.`id`,t.`stars`,t.`title`,t.`create_time` FROM t_topic t WHERE t.`id` IS NOT NULL LIMIT 20
<== Total: 20

将表达式转成匿名表嵌套查询

//  SELECT `id`,`title` FROM `t_topic` WHERE `id` = ? 
Queryable<Topic> query = easyQuery.queryable(Topic.class)
.where(o -> o.eq(Topic::getId, "1"))
.select(Topic.class, o -> o.column(Topic::getId).column(Topic::getTitle));

List<Topic> list = query.leftJoin(Topic.class, (t, t1) -> t.eq(t1, Topic::getId, Topic::getId))
.where((t, t1) -> {
t1.eq(Topic::getId, "123");
t.eq(Topic::getId, "456");
}).toList();

SELECT t1.`id`,t1.`title`
FROM (SELECT t.`id`,t.`title` FROM `t_topic` t WHERE t.`id` = ?) t1
LEFT JOIN `t_topic` t2 ON t1.`id` = t2.`id` WHERE t2.`id` = ? AND t1.`id` = ?

==> Preparing: SELECT t1.`id`,t1.`title` FROM (SELECT t.`id`,t.`title` FROM `t_topic` t WHERE t.`id` = ?) t1 LEFT JOIN `t_topic` t2 ON t1.`id` = t2.`id` WHERE t2.`id` = ? AND t1.`id` = ?
==> Parameters: 1(String),123(String),456(String)
<== Time Elapsed: 5(ms)
<== Total: 0

子查询

//SELECT * FROM `t_blog` t1 WHERE t1.`deleted` = ? AND t1.`id` = ?
Queryable<BlogEntity> subQueryable = easyQuery.queryable(BlogEntity.class)
.where(o -> o.eq(BlogEntity::getId, "1"));


List<Topic> x = easyQuery
.queryable(Topic.class).where(o -> o.exists(subQueryable.where(q -> q.eq(o, BlogEntity::getId, Topic::getId)))).toList();


==> Preparing: SELECT t.`id`,t.`stars`,t.`title`,t.`create_time` FROM `t_topic` t WHERE EXISTS (SELECT 1 FROM `t_blog` t1 WHERE t1.`deleted` = ? AND t1.`id` = ? AND t1.`id` = t.`id`)
==> Parameters: false(Boolean),1(String)
<== Time Elapsed: 3(ms)
<== Total: 1

多表join查询

Topic topic = easyQuery
.queryable(Topic.class)
.leftJoin(BlogEntity.class, (t, t1) -> t.eq(t1, Topic::getId, BlogEntity::getId))
.where(o -> o.eq(Topic::getId, "3"))
.firstOrNull();

==> Preparing: SELECT t.`id`,t.`stars`,t.`title`,t.`create_time` FROM t_topic t LEFT JOIN t_blog t1 ON t1.`deleted` = ? AND t.`id` = t1.`id` WHERE t.`id` = ? LIMIT 1
==> Parameters: false(Boolean),3(String)
<== Total: 1

流式结果大数据迭代返回

try(JdbcStreamResult<BlogEntity> streamResult = easyQuery.queryable(BlogEntity.class).where(o -> o.le(BlogEntity::getStar, 100)).orderByAsc(o -> o.column(BlogEntity::getCreateTime)).toStreamResult()){

LocalDateTime begin = LocalDateTime.of(2020, 1, 1, 1, 1, 1);
int i = 0;
for (BlogEntity blog : streamResult.getStreamIterable()) {
String indexStr = String.valueOf(i);
Assert.assertEquals(indexStr, blog.getId());
Assert.assertEquals(indexStr, blog.getCreateBy());
Assert.assertEquals(begin.plusDays(i), blog.getCreateTime());
Assert.assertEquals(indexStr, blog.getUpdateBy());
Assert.assertEquals(begin.plusDays(i), blog.getUpdateTime());
Assert.assertEquals("title" + indexStr, blog.getTitle());
// Assert.assertEquals("content" + indexStr, blog.getContent());
Assert.assertEquals("http://blog.easy-query.com/" + indexStr, blog.getUrl());
Assert.assertEquals(i, (int) blog.getStar());
Assert.assertEquals(0, new BigDecimal("1.2").compareTo(blog.getScore()));
Assert.assertEquals(i % 3 == 0 ? 0 : 1, (int) blog.getStatus());
Assert.assertEquals(0, new BigDecimal("1.2").multiply(BigDecimal.valueOf(i)).compareTo(blog.getOrder()));
Assert.assertEquals(i % 2 == 0, blog.getIsTop());
Assert.assertEquals(i % 2 == 0, blog.getTop());
Assert.assertEquals(false, blog.getDeleted());
i++;
}
} catch (SQLException e) {
throw new RuntimeException(e);
}

==> Preparing: SELECT `id`,`create_time`,`update_time`,`create_by`,`update_by`,`deleted`,`title`,`content`,`url`,`star`,`publish_time`,`score`,`status`,`order`,`is_top`,`top` FROM `t_blog` WHERE `deleted` = ? AND `star` <= ? ORDER BY `create_time` ASC
==> Parameters: false(Boolean),100(Integer)
<== Time Elapsed: 6(ms)

自定义VO返回

List<QueryVO> list = easyQuery
.queryable(Topic.class)
//第一个join采用双参数,参数1表示第一张表Topic 参数2表示第二张表 BlogEntity
.leftJoin(BlogEntity.class, (t, t1) -> t.eq(t1, Topic::getId, BlogEntity::getId))
//第二个join采用三参数,参数1表示第一张表Topic 参数2表示第二张表 BlogEntity 第三个参数表示第三张表 SysUser
.leftJoin(SysUser.class, (t, t1, t2) -> t.eq(t2, Topic::getId, SysUser::getId))
.where(o -> o.eq(Topic::getId, "123"))//单个条件where参数为主表Topic
//支持单个参数或者全参数,全参数个数为主表+join表个数 链式写法期间可以通过then来切换操作表
.where((t, t1, t2) -> t.eq(Topic::getId, "123").then(t1).like(BlogEntity::getTitle, "456")
.then(t2).eq(BaseEntity::getCreateTime, LocalDateTime.now()))
//如果不想用链式的then来切换也可以通过lambda 大括号方式执行顺序就是代码顺序,默认采用and链接
.where((t, t1, t2) -> {
t.eq(Topic::getId, "123");
t1.like(BlogEntity::getTitle, "456");
t1.eq(BaseEntity::getCreateTime, LocalDateTime.now());
})
.select(QueryVO.class, (t, t1, t2) ->
//将第一张表的所有属性的列映射到vo的列名上,第一张表也可以通过columnAll将全部字段映射上去
// ,如果后续可以通过ignore方法来取消掉之前的映射关系
t.column(Topic::getId)
.then(t1)
//将第二张表的title字段映射到VO的field1字段上
.columnAs(BlogEntity::getTitle, QueryVO::getField1)
.then(t2)
//将第三张表的id字段映射到VO的field2字段上
.columnAs(SysUser::getId, QueryVO::getField2)
).toList();

表单条件动态查询

BlogQuery2Request query = new BlogQuery2Request();
query.setContent("标题");
query.setPublishTimeEnd(LocalDateTime.now());
query.setStatusList(Arrays.asList(1,2));

List<BlogEntity> queryable = easyQuery.queryable(BlogEntity.class)
.whereObject(query).toList();


==> Preparing: SELECT `id`,`create_time`,`update_time`,`create_by`,`update_by`,`deleted`,`title`,`content`,`url`,`star`,`publish_time`,`score`,`status`,`order`,`is_top`,`top` FROM `t_blog` WHERE `deleted` = ? AND `content` LIKE ? AND `publish_time` <= ? AND `status` IN (?,?)
==> Parameters: false(Boolean),%标题%(String),2023-07-14T22:37:47.880(LocalDateTime),1(Integer),2(Integer)
<== Time Elapsed: 2(ms)
<== Total: 0

基本类型结果返回

List<String> list = easyQuery.queryable(Topic.class)
.where(o -> o.eq(Topic::getId, "1"))
.select(String.class, o -> o.column(Topic::getId))
.toList();

==> Preparing: SELECT t.`id` FROM `t_topic` t WHERE t.`id` = ?
==> Parameters: 1(String)
<== Time Elapsed: 2(ms)
<== Total: 1

分组查询

List<TopicGroupTestDTO> topicGroupTestDTOS = easyQuery.queryable(Topic.class)
.where(o -> o.eq(Topic::getId, "3"))
.groupBy(o->o.column(Topic::getId))
.select(TopicGroupTestDTO.class, o->o.columnAs(Topic::getId,TopicGroupTestDTO::getId).columnCount(Topic::getId,TopicGroupTestDTO::getIdCount))
.toList();


==> Preparing: SELECT t.`id` AS `id`,COUNT(t.`id`) AS `idCount` FROM t_topic t WHERE t.`id` = ? GROUP BY t.`id`
==> Parameters: 3(String)
<== Total: 1

//groupKeysAs快速选择并且给别名
List<TopicGroupTestDTO> topicGroupTestDTOS = easyQuery.queryable(Topic.class)
.where(o -> o.eq(Topic::getId, "3"))
.groupBy(o->o.column(Topic::getId))
.select(TopicGroupTestDTO.class, o->o.groupKeysAs(0, TopicGroupTestDTO::getId).columnCount(Topic::getId,TopicGroupTestDTO::getIdCount))
.toList();


==> Preparing: SELECT t.`id` AS `id`,COUNT(t.`id`) AS `idCount` FROM t_topic t WHERE t.`id` = ? GROUP BY t.`id`
==> Parameters: 3(String)
<== Total: 1

原生sql片段

String sql = easyQuery.queryable(H2BookTest.class)
.where(o -> o.sqlNativeSegment("regexp_like({0},{1})", it -> it.expression(H2BookTest::getPrice)
.value("^Ste(v|ph)en$")))
.select(o -> o.columnAll()).toSQL();

SELECT id,name,edition,price,store_id FROM t_book_test WHERE regexp_like(price,?)

数据库函数列

用户存储的数据是base64结果,但是内存中是普通的字符串或者其他数据,easy-query提供了无感的使用,譬如pgsql的geo等地理相关数据

数据库函数列:

❝

https://xuejm.gitee.io/easy-query-doc/guide/adv/column-sql-func-auto.html

❞

支持like的高性能加密解密

用来实现支持like模式的高性能加密解密,支持emoji和非emoji两种用户可以自行选择

更多功能比如数据追踪差异更新,数据原子更新,分库分表(老行当了肯定要支持),一款本无依赖双语(java/kotlin)都支持的高性能orm

github地址:

https://github.com/dromara/easy-query

gitee地址:

https://gitee.com/xuejm/easy-query

文章目录
  1. 1. 背景
  2. 2. 查询
  3. 3. 数据库函数列
  4. 4. 支持like的高性能加密解密