专栏名称: SegmentFault思否
SegmentFault (www.sf.gg)开发者社区,是中国年轻开发者喜爱的极客社区,我们为开发者提供最纯粹的技术交流和分享平台。
目录
相关文章推荐
程序员小灰  ·  3个令人惊艳的DeepSeek项目,诞生了! ·  13 小时前  
OSC开源社区  ·  宇树王兴兴早年创业分享引围观 ·  2 天前  
程序猿  ·  “我真的受够了Ubuntu!” ·  2 天前  
程序猿  ·  “未来 3 年内,Python 在 AI ... ·  3 天前  
程序员小灰  ·  DeepSeek做AI代写,彻底爆了! ·  3 天前  
51好读  ›  专栏  ›  SegmentFault思否

hibernate 查询时指定查询字段、级联表的一种方式

SegmentFault思否  · 公众号  · 程序员  · 2020-03-10 11:45

正文

本文转载于 SegmentFault 社区
社区专栏:河北工业大学梦云智软件开发团队
作者:myskies

最近在进行数据统计查询时屡次遇到慢查询事件,最终发现问题发生在 hibernate 的查询操作上。 hibernate @ManyToOne 注解上的 FetchType 默认值为 FetchType.EAGER ,在进行查询操作时, hibernate 会自动的发起关联表的 join 查询。一旦关联的表太多则会大幅地影响查询效率。

在简单的数据查询中,上述查询机制并无可厚非:此机制能够在查询某个实体时,自动关联查询相关实体,这使得程序开发变得异常简单。但正是由于此方法会关联查询出过多的信息,使得在进行大量的数据操作时给数据库带来了过多的压力,数据库不堪重负,随之带来慢查询。

解决由于关联查询造成的慢查询问题的方法有几个:比如牺牲部分便利性为 @ManyToOne 注解添加 fetch = FetchType.LAZY 属性;再比如可以用综合查询专门的创建一个视图,并在综合查询中调用视图中的数据;再比如还可以为综合查询专门建立一个返回值类型。

本文给出一种通过代码来定义返回的字段、自动去除无用的关联查询的方法。



情景设置


假设有以下 4 张表,分别为学生、班级、教师、学校。每个表中均有两个字段,分别为 id 及 name。er 图如下:



数据表间的关系均为 n:1 ,示例实体如下:
@Entity
public class Student {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY ➊)
private Long id;

private String name;

@ManyToOne(cascade = CascadeType.PERSIST ➋)
private Clazz clazz;

// 省略空构造函数★及setter/getter
}
  • 设置为自增
  • 设置为级联保存
  • 空构造函数很重要,必须有

[success] 班级、教师、学校三个实体的代码均参考上述代码完成。

public interface StudentRepository extends CrudRepository<Student, Long>, JpaSpecificationExecutor {
}


测试



查询测试:
@SpringBootTest
class StudentRepositoryTest {
@Autowired
StudentRepository studentRepository;

@Autowired
private EntityManager entityManager; ➊

Student student;

@BeforeEach
public void beforeEach() {
School school = new School();
school.setName("测试学校");
Teacher teacher = new Teacher();
teacher.setName("测试教师");
Clazz clazz = new Clazz();
clazz.setName("测试班级");
this.student = new Student();
student.setName("测试学生");
teacher.setSchool(school);
clazz.setTeacher(teacher);
student.setClazz(clazz);
this.studentRepository.save(student);
}

@Test
public void find() {
this.studentRepository.findById(student.getId()).get();
}
}
  • 备用
  • 老的版本中使用的是 @Befor e ,具体请参数本文给出的 github 链接

生成的 sql 语句如下:
select
student0_.id as id1_2_0_, student0_.clazz_id as clazz_id3_2_0_, student0_.name as name2_2_0_,
clazz1_.id as id1_0_1_, clazz1_.name as name2_0_1_, clazz1_.teacher_id as teacher_3_0_1_,
teacher2_.id as id1_3_2_, teacher2_.name as name2_3_2_, teacher2_.school_id as school_i3_3_2_,
school3_.id as id1_1_3_, school3_.name as name2_1_3_
from student student0_
left outer join clazz clazz1_ on student0_.clazz_id=clazz1_.id
left outer join teacher teacher2_ on clazz1_.teacher_id=teacher2_.id
left outer join school school3_ on teacher2_.school_id=school3_.id
where student0_.id=1
如上所示 hibernate 在查询学生时,会关联查询学生实体中通过 @ManyToOne 注解的字段,并且它还会聪明的依次累推关联查询班级实体中的教师字段以及教师实体对应的学校字段。



Selection



Hiberante 在综合中提供了 Selection 来解决查询时冗余字段与冗余关联的问题,在使用 Selection 来进行查询时需要先在实体类中建立对应的构造函数,假设当前仅需要查询出学生的 id,name 信息。则首先需要建立以下构造函数:
public Student(Long id, String name) {
this.id = id;
this.name = name;
System.out.println("student construct");
}
示例代码如下:
@Test
public void findByColumn() {
CriteriaBuilder criteriaBuilder = this.entityManager.getCriteriaBuilder(); ➊
CriteriaQuery criteriaQuery = criteriaBuilder.createQuery(Student.class); ➊
Root root = criteriaQuery.from(Student.class); ➊

criteriaQuery
.multiselect(root.get("id"), root.get("name")) ➋
.where(criteriaBuilder.equal(root.get("id").as(Long.class), student.getId().toString())); ➌
TypedQuery query = this.entityManager.createQuery(criteriaQuery); ➍

List students = query.getResultList(); ➎
}
}
  • 创建用于综合查询的 criteriaBuilder criteriaQuery root
  • 创建本次查询的输出字段为 student 实体的 id name 字段。
  • 设置查询条件
  • 生成预查询
  • 执行查询

执行测试控制台相关信息如下
select student0_.id as col_0_0_, student0_.name as col_1_0_
from student student0_
where student0_.id=1

student construct
如上所示,在综合查询中使用了 multiselect 指定输出字段后, hibernate 进行查询时在进行 select 时只选择了规定字段 student.id、student.name ,并且在查询中并没有关联其它表。在查询出数据后,调用了 Student 实体中的构造函数。



关联查询



在需要进行关联查询时仍可按上述的步骤:先建立对应的构造函数,再设置相应的选择条件。比如需要查询出班级 id 及教师 id 的信息,代码如下:
public Student(Long id, String name, Long clazzId, Long teacherId) {
this.id = id;
this.name = name;
this.clazz = new Clazz();
this.clazz.setId(clazzId);
this.clazz.setTeacher(new Teacher());
this.clazz.getTeacher().setId(teacherId);
System.out.println("student construct invoked");
}
查询代码如下:
@Test
public void findByColumnWithJoin() {
CriteriaBuilder criteriaBuilder = this.entityManager.getCriteriaBuilder();
CriteriaQuery criteriaQuery = criteriaBuilder.createQuery(Student.class);
Root root = criteriaQuery.from(Student.class);

criteriaQuery
.multiselect(root.get("id"),
root.get("name"),
root.get("clazz").get("id"),
root.get("clazz").get("teacher").get("id"))
.where(criteriaBuilder.equal(root.get("id").as(Long.class), student.getId().toString()));

TypedQuery query = this.entityManager.createQuery(criteriaQuery);

List students = query.getResultList();
}
执行日志如下:
select student0_.id as col_0_0_, student0_.name as col_1_0_, student0_.clazz_id as col_2_0_,
clazz1_.teacher_id as col_3_0_
from student student0_
cross join clazz clazz1_
where student0_.clazz_id=clazz1_.id and student0_.id=1

student construct invoked
如上所示 hibrenate 自动构建了有需要级联 sql 语句。



Selection< Tuple >



如果不想使用添加构造函数的方法来进行查询,还可以使用 Selection 。仍与上述查询为例:使用 Sel ection 进行查询的代码如下:
@Test






请到「今天看啥」查看全文