项目开发过程中, 遇到了一个文章列表查询速度非常慢的问题。这到底是怎么回事呢?

一路追查下来, 发现竟然是spring-data-jpa设计上的问题, 问题稍后说明, 博主先简评一下spring-data-jpa, spring-data-jpa很多时候真的很令人劝退, 各种奇奇怪怪的BUG, 功能达不到项目所需, 关联查询灵活性低 , 一些注解失效或难用, 但它简便的开发方式又令人不忍割舍, 正向生成数据库, 通过命名方法对数据库操作,分页排序查询等确实特别好用。

总之, spring-data-jpa像个爱耍小脾气的漂亮女生, 一开始接触她, 你能享受到各种快乐优雅的开发模式, 觉得她就是我最想要的人, 被她迷得神魂颠倒, 接触久了, 便发现其中的各种毛病, 她不仅满足不了自己, 还可能会给你带来各种烦恼, 让你不禁会问: 为什么你的操作总是令我头秃 ? 她会说: 你去翻遍我所有的文档, 你会发现, 我就是这样不讲理的。

这时候, 无奈的你则需要其他框架的辅助, JdbcTemplateMybatis Plus能够帮帮你解决问题。spring-data-jpa优势很明显, 缺点也多, 希望spring-data-jpa能日趋成熟, 以后做个善解人意的女人, 加大开发灵活性, 满足更多开发者的诉求, 这样博主也会服服帖帖地倒在她的石榴裙下o( ̄▽ ̄)o~嗷~ 。

问题

看代码

Article实体类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import com.google.common.collect.Sets;
import lombok.Getter;
import lombok.Setter;
import org.hibernate.annotations.NotFound;
import org.hibernate.annotations.NotFoundAction;
import org.hibernate.annotations.Where;
import org.hibernate.validator.constraints.Range;
import javax.persistence.*;
import javax.validation.constraints.NotNull;
import java.io.Serializable;
import java.time.LocalDate;
import java.util.Set;

/**
* @author mission
* @date 2019/1/24 0024-15:39
*/
//jpa 注解
@Entity
@Table(name = "article")
//Lombok
@Getter
@Setter
@Where(clause = "ar_status = 1")
public class Article implements Serializable {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long arId;

@NotNull(message = "文章标题arTitle不能为空")
@Column(nullable = false)
private String arTitle;

@Column(columnDefinition = "longtext ")
private String arContent;

@Column(nullable = false,columnDefinition = "datetime DEFAULT CURRENT_TIMESTAMP")
private LocalDate arDate;//时间 默认当前系统时间

@Range(min=-1,max=1,message = "逻辑状态arStatus超出范围")
@Column(length = 2)
private Integer arStatus; // 状态 (0回收站删除 ,1显示)


@OrderBy("ac_id asc ")
@JsonIgnoreProperties("articles")
@NotFound(action= NotFoundAction.IGNORE)
@ManyToMany(cascade = {CascadeType.PERSIST,CascadeType.MERGE,CascadeType.REFRESH},fetch = FetchType.LAZY)
@JoinTable(name = "relation_article_cate",joinColumns = @JoinColumn(name = "ar_id"),inverseJoinColumns = @JoinColumn(name = "arc_id"))
private Set<ArticleCategory> articleCategories= Sets.newHashSet();

@NotFound(action= NotFoundAction.IGNORE)
@OneToMany(cascade = {CascadeType.ALL},fetch = FetchType.LAZY)
@JoinColumn(name = "ar_id")
private Set<FileResource> fileResources = Sets.newHashSet();


@NotFound(action= NotFoundAction.IGNORE)
@OneToMany(cascade = {CascadeType.ALL},fetch = FetchType.LAZY)
@JoinColumn(name = "ar_id")
private Set<Video> videos= Sets.newHashSet();

}

Article投影

主要关注这里

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
import com.fasterxml.jackson.annotation.JsonInclude;
import java.time.LocalDate;
import java.util.Set;

/**
* @author mission
* @date 2019/1/27 0027-9:05
*/
public interface ArticleSimpleProjection {

Long getArId();

String getArTitle();

LocalDate getArDate();//时间 默认当前系统时间

@JsonInclude(JsonInclude.Include.NON_EMPTY)
Set<ArticleCategoryProjection> getArticleCategories();
}

Article投影查询方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
import com.wteam.gdousd.entity.Article;
import com.wteam.gdousd.entity.projection.ArticleSimpleProjection;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;



/**
* (Article)表数据库JPA储存库
*
* @author makejava
* @since 2019-01-25 15:21:13
*/

public interface ArticleRepository extends JpaSpecificationExecutor<Article>,JpaRepository<Article,Long>{

Page<ArticleSimpleProjection> findByArStatusAndArTitleLike(Integer status,String arTitle, Pageable pageable);

}

结果

当执行方法以后

1
Hibernate: select article0_.ar_id as ar_id1_1_, article0_.ar_content as ar_conte2_1_, article0_.ar_date as ar_date3_1_, article0_.ar_status as ar_statu4_1_, article0_.ar_title as ar_title5_1_ from article article0_ where ( article0_.ar_status = 1) and article0_.ar_status=? and (article0_.ar_title like ?) order by article0_.ar_date desc limit ?

可以看到,article0_.ar_content , article0_.ar_status 等在ArticleSimpleProjection 接口中定义方法没有对应上的数据库字段竟然也被jpa查出 ,其中ar_content是文章内容,属于大文本字段, 查询多个记录而且还查询该字段的话,查询速度就比原来慢了好几倍。 这不是博主想要的结果, 必须优化!

原因

投影接口中存在返回集合的定义方法, 使得spring-data-jpa会自动查询所有字段, 目前博主也不知道什么原因(博主没有看过源码)。

1
2
@JsonInclude(JsonInclude.Include.NON_EMPTY)
Set<ArticleCategoryProjection> getArticleCategories();

因为在投影中存在集合,在ArticleSimpleProjection 接口中就存在了返回Set集合的定义方法getArticleCategories(), 所以查询投影就会将所有字段查询,这是spring-data-jpa的设计所致

解决

Article投影

去掉Set集合

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
package com.wteam.gdousd.entity.projection;

import com.fasterxml.jackson.annotation.JsonInclude;

import java.time.LocalDate;

/**
* @author mission
* @date 2019/1/27 0027-9:05
*/
public interface ArticleSimpleProjection {

Long getArId();

String getArTitle();

LocalDate getArDate();//时间 默认当前系统时间

@JsonInclude(JsonInclude.Include.NON_EMPTY)
ArticleCategoryProjection getArticleCategories();
}

结果

1
Hibernate: select article0_.ar_id as col_0_0_, article0_.ar_title as col_1_0_, article0_.ar_date as col_2_0_, articlecat2_.ac_id as col_3_0_, articlecat2_.ac_id as ac_id1_2_, articlecat2_.ac_name as ac_name2_2_, articlecat2_.sub_classify as sub_clas3_2_ from article article0_ left outer join relation_article_cate articlecat1_ on article0_.ar_id=articlecat1_.ar_id left outer join article_category articlecat2_ on articlecat1_.arc_id=articlecat2_.ac_id where ( article0_.ar_status = 1) and article0_.ar_status=? and (article0_.ar_title like ?) order by article0_.ar_date desc limit ?

从结果可见,只要不用set集合,就不会发生jpa查询所有字段的事情。
有时候, 非要把查询结果放在用Set集合中, 就自定义查询吧! 博主认为, 这种情况, 用其他框架 JdbcTemplateMybatis Plus实现更好。

结语

目前对数据库操作的框架, 博主使用的是spring-data-jpa +JdbcTemplateMybatis Plus, 基本满足小项目的开发。可是, 各个框架都有优有劣, 博主至今很难做出最佳选择, 真的想全都要呀!可是这样项目体积会变得很庞大哎~~ 如果读者有更好的对数据库操作的框架推荐 或 对本章内容有什么意见建议, 可以在下方评论留言,你的每一言都会让这个无聊博主的人生添上不一样的色彩哦~ヾ( ̄▽ ̄)Bye~Bye~