开发背景
为了追求开发效率和代码的美观干净,采用了jpa作为ORM框架。
问题描述
1、jpa在对多表查询支持不是很友好,虽然jpa提供了ManyToOne这种注解来实现多表关联,这样会导致N+1问题,一条主查询sql下出现非常多的子sql,这极大影响查询效率,
2、使用@Lazy也不能避免啊,但用到@Lazy标注的字段时也会在发出一条查询语句,问过数据库连接池的感受吗。
3、@EntityGraph能将实体下标注ManyToOne等注解的属性统一成一条关联查询,可是也有问题:
1.查询出来的字段和表太多了,有时候只需要实体的其中一个标记@ManyToOne的属性,结果把其他的@ManyToMone表也关联上了,说到底细粒度的可控性不好,我明白@NamedAttributeNode可以指定这一整条sql具体关联那些表,但业务不是这么单一,下次需要查其中两个标记@ManyToOne的属性呢。
2.实体下标记@ManyToOne的属性实体A中的属性也标记了@ManyToOne的属性B,这时候使用EntityGraph会继续使用一条sql去把B属性实体找出来,这又回到了N+1问题,这个属性B我不想查,设置@Lazy也避免不要它再查一次
4.实体A下使用了ManyToOne标记属性B以后并设置@Lazy, 当一条sql只需要找到这个B属性的id,那么程序只能获取属性B在查找B中的ID,这会触发懒查询,发出一条sql把B的所有属性全部找出来,我只要这个外键ID啊,不标记@ManyToOne,不是一条sql就搞定了。
我的看法
为了查询高效,没有必要用ManyToOne这种难以控制的注解,直接用@Query的原生sql查找List<Object[]>的结果集,由于jdk8的语法,可以非常高效的转换这个结果集,代码如下:
DTO转换使用Function
@Data
@Accessors(chain = true)
public class ArticleDTO {
/**
* 文章ID
*/
private Integer articleId;
/**
* 文章标题
*/
private String articleTitle;
/**
* 文章发布时间
*/
private Date postTime;
/**
* 知识库ID
*/
private Integer knowledgeId;
/**
* 知识库名字
*/
private String knowledgeName;
/**
* 知识库描述
*/
private String knowledgeDesc;
/**
* 知识库封面URL路径
*/
private String knowledgeCoverUrl;
public static Function<Object[],ArticleDTO> recommendKnowledge = data -> new ArticleDTO()
.setArticleTitle(String.valueOf(data[0])).setArticleId(Integer.valueOf(String.valueOf(data[1])))
.setPostTime(DateUtil.transformStrToDate(String.valueOf(data[2]), "yyyy-MM-dd HH:mm:ss"))
.setKnowledgeId(Integer.valueOf(String.valueOf(data[3]))).setKnowledgeName(String.valueOf(data[4]))
.setKnowledgeDesc(String.valueOf(data[5])).setKnowledgeCoverUrl(String.valueOf(data[6]));
}
调用转换代码ArticleDTO.recommendKnowledge.apply(data)
Map<Integer, List<ArticleDTO>> articleDTOS = dataList.stream()
.map(data -> ArticleDTO.recommendKnowledge.apply(data))
.collect(Collectors.groupingBy(ArticleDTO::getKnowledgeId));
在涉及到多表查询,老老实实写原生sql。不知道各位有什么看法,在平时项目中怎么解决我上面的问题的,jpa实战经验少,自己摸索思考,难免有理解不到位的地方,请各位指点
@Query
写 SQL