DoytoQuery中关于N+1查询问题解决方案详解
作者:f0rb 发布时间:2022-06-14 12:03:07
1. 背景
Java Persistence with Hibernate 在12.2.1小节使用如下例子描述 n+1查询问题:
List<Item> items = em.createQuery("select i from Item i").getResultList();
// select * from ITEM
for (Item item : items) {
assertTrue(item.getBids().size() > 0);
// select * from BID where ITEM_ID = ?
}
在这个例子中,每个bids集合的加载都需要执行一条额外的查询语句,当item有N条记录,一共就会执行N+1条查询语句:
SELECT * FROM item;
SELECT * FROM bid WHERE item_id = ?;
SELECT * FROM bid WHERE item_id = ?;
SELECT * FROM bid WHERE item_id = ?;
SELECT * FROM bid WHERE item_id = ?;
2. SQL层的解决方案
在本方案中,首先通过两个步骤对SQL语句加以改造,从SQL层面上解决这个问题。
使用关键字
UNION ALL
将N条查询语句合为一条语句,便将N+1次查询转化为了1+1次查询。由于第二次查询的所有记录被一次性返回,而我们需要将
Bid
实体关联到相关的Item
实体上,因此我们需要添加一个额外的item_id
列以便进行实体关联。
以下是改造后的两条查询语句。
SELECT * FROM item;
SELECT ? AS item_id, b.* FROM bid b WHERE item_id = ? UNION ALL
SELECT ? AS item_id, b.* FROM bid b WHERE item_id = ? UNION ALL
SELECT ? AS item_id, b.* FROM bid b WHERE item_id = ? UNION ALL
SELECT ? AS item_id, b.* FROM bid b WHERE item_id = ?;
When we want to query a bid list and every bid entity to carry its item, we can execute two query statements as follows:
SELECT * FROM bid;
SELECT ? AS bid_id, i.* FROM item i WHERE id IN (SELECT item_id FROM bid WHERE id = ?) UNION ALL
SELECT ? AS bid_id, i.* FROM item i WHERE id IN (SELECT item_id FROM bid WHERE id = ?) UNION ALL
SELECT ? AS bid_id, i.* FROM item i WHERE id IN (SELECT item_id FROM bid WHERE id = ?) UNION ALL
SELECT ? AS bid_id, i.* FROM item i WHERE id IN (SELECT item_id FROM bid WHERE id = ?);
Item
和Bid
之间的关系是典型的一对多/多对一关系。以上这一解决方案也可用于多对多关系。
3. ORM应用层的解决方案
对于ORM层,我们需要想办法从表结构的信息中映射到第二条查询语句,在Java中开发中我们常用注解的方式来进行配置。
上面的SQL语句中只有四个要素,两个表名item
和bid
,表bid
中的外键列item_id
和表item
中的引用列id
。 其中,查询实体的表名是已知的,于是便只剩下三个要素。 DoytoQuery定义了一个注解@DomainPath
来配置这三个要素,用以映射第二条查询语句。
@Target(FIELD)
@Retention(RUNTIME)
public @interface DomainPath {
String[] value();
String localField() default "id";
String foreignField() default "id";
}
由于第二条查询语句中附加的id
列仅用于实体赋值,因此我们将附加列的别名统一命名为 MAIN_ENTITY_ID
。Item
和Bid
的类定义如下:
@Getter
@Setter
public class ItemView extends AbstractPersistable<Integer> {
// other fields in Item
// one-to-many
// SELECT ? AS MAIN_ENTITY_ID, b.*
// FROM bid b WHERE item_id = ? [UNION ALL ...]
@DomainPath(value = "bid", foreignField = "item_id")
private List<BidView> bids;
}
@Getter
@Setter
public class BidView extends AbstractPersistable<Integer> {
// other fields in Bid
// many-to-one
// SELECT ? AS MAIN_ENTITY_ID, i.*
// FROM item i WHERE id IN (SELECT item_id FROM bid WHERE id = ?) [UNION ALL ...]
@DomainPath(value = "item", foreignField = "id", localField = "item_id")
private ItemView item;
}
假设Item
和Category
之间的多对多关系存放于中间表CATEGORY_ITEM
中,我们可以使用@DomainPath
来定义如下实体,用以映射第二条查询语句:
@Getter
@Setter
public class ItemView extends AbstractPersistable<Integer> {
// other fields in Item
// many-to-many
@DomainPath({"item", "~", "category"})
private List<ItemView> items;
}
@Getter
@Setter
public class CategoryView extends AbstractPersistable<Integer> {
// other fields in Category
// many-to-many
@DomainPath({"category", "item"})
private List<ItemView> items;
}
4. 小结
在本文中,我们介绍了DoytoQuery
中的一种可以避免n+1查询问题的关联查询方案。并且我们只需要通过一个注解@DomainPath
便可管理ERM中定义的四种实体关系,更多关于DoytoQuery N+1查询问题的资料请关注脚本之家其它相关文章!
来源:https://juejin.cn/post/7181089504529219621


猜你喜欢
- 一、关键字分类C语言一共多少个关键字呢?一般的书上,都是32个(包括本书),但是这个都是C90(C89)的标准。其实C99后又新增了5个关键
- 使用了Android的系统API实现了多点触控功能,多点触控对设备的硬件有一定的要求,目前市面上的手机几乎都能实现多点触控了。实现多点触控最
- 如何下载并配置JDK 15进入官网下载JDK 15。官网地址:https://www.oracle.com/index.html脚本之家下载
- 今天给大家带来一个向右滑动销毁Activity的效果,Activtiy随着手指的移动而移动,该效果在Android应用中还是比较少见的,在I
- 说明:1、集合类型参数化;2、可根据集合中的对象的各个属性进行排序,传入属性名称即可;注:属性必须实现了IComparable接口,C#中i
- 使用C#在不借助第三方插件的情况下将Excel中的数据转换成DataSet/// <summary>
- 在用C++来开发Windows程序时,经常看到下面的判断情况:HRESULT hr = ::RegCreateKeyEx(hk, szKey
- springboot前端传参date类型后台处理先说结论建议大家直接使用@JsonFormat,原因如下: 1、针对json格式
- 1. 运行环境 Enviroment当 MyBatis 与不同的应用结合时,需要不同的事务管理机制。与 Spring 结合时,由 Sprin
- “深入浅出,人人都是程序员”开发过android手机的同学都知道在eclipse中可以直接查找到SHA1值,但是使用intellij ide
- 本文实例为大家分享了Android实现滑动屏幕切换图片的具体代码,供大家参考,具体内容如下activity_main.xml 文件代码:&l
- 应用场景我们开发的控制台应用,在运行阶段很有可能被用户Ctrl+C终止或是被用户直接关闭。如果我们不希望用户通过Ctrl+C终止我们的程序,
- 最近项目上产品经理提了个需求,要求关闭语言国际化,不管手机系统设置那个国家的语言,都要显示汉语,好吧,既然有需求,那就做吧。但是项目中已经有
- Android 中下拉菜单,即如html中的<select>,关键在于调用setDropDownViewResource方法,以
- 最近项目中用到了service进行计时,在连接USB的情况下一切正常,但是拔掉USB后发现,手机进入休眠后service停止了工作。最后通过
- 1.servlet:定义:接口2.配置servlet:public class HelloServlet extends HttpServl
- java中 String和StringBuffer的区别实例详解String: &
- Spring发布了一个新工具Spring Native Beta,用于将现有的Spring Boot应用程序(用Java或Kotlin编写)
- activiti使用的时候,通常需要跟业务紧密的结合在一起,有些业务非常的复杂,比如一个简单的采购流程:流程如下: 供应商上新商品
- 一、IDEA自带打包插件内容:此种方式可以自己选择制作胖包或者瘦包,但推荐此种方式制作瘦包。输出:输出目录在out目录下流程步骤:第一步: