Hibernate +Spring Boot JPA 利用@Where或@Filter过滤软删的子对象

目录

  • 场景
  • 数据
  • @Where
    • Entity:
    • Repository
    • Service
    • Controller
    • 测试
  • @Filter
    • Entity
    • Repository
    • 测试

场景

一个老师(Teacher)有很多个学生(Student)和有很多课本(book),一个学生有很多选修课程(Course)。查询老师列表的时候会把老师名下的学生和课本罗列出来,但是不需要展示被删除掉或者状态不对的数据。Teacher和Student通过关键字关联,Teacher和Book通过中间关联表关联,Student和Course通过关键字关联。

数据

Teacher:

1
2
3
4
5
 uuid
------
 t1
 t2
(2 rows)

Student:

1
2
3
4
 uuid | deleted |  status  | teacher
------+---------+----------+---------
 s1   |         | active   | t1
 s2   | deleted | inactive | t1

Course:

1
2
3
4
5
 uuid | deleted |  status  | student
------+---------+----------+---------
 c1   |         | active   | s1
 c2   | deleted | inactive | s1
(2 rows)

Book:

1
2
3
4
5
 uuid | deleted |  status  
------+---------+----------
 b1   |         | active
 b2   | deleted | inactive
(2 rows)

teacher_book

1
2
3
4
5
 teacher_id | book_id
------------+---------
 t1         | b1
 t1         | b2
(2 rows)

@Where

利用@Where注解过滤掉软删的学生、书、课程。

Entity:

Teacher:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Data
@Entity
@Table(name = "teacher")
@EqualsAndHashCode(of = {"id"})
public class Teacher {

    @Column(name = "uuid")
    @Id
    private String id;
   
    @ManyToMany(fetch = FetchType.LAZY)
    @JoinTable(name = "teacher_book",
    joinColumns = { @JoinColumn(name = "teacher_id") },
    inverseJoinColumns = { @JoinColumn(name = "book_id") })
    @Where(clause = " deleted is null ")
    private Set<Book> books;
   
    @OneToMany(mappedBy = "teacher", fetch = FetchType.LAZY)
    @Where(clause = " deleted is null ")
    private Set<Student> students;
}

Student:

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
@Data
@Entity
@Table(name = "student")
@EqualsAndHashCode(of = {"id"})
public class Student {

    @Column(name = "uuid")
    @Id
    private String id;
   
    @Column(name = "deleted")
    private String deleted;
   
    @Column(name = "status")
    private String status;
   
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "teacher")
    @JsonIgnore
    private Teacher teacher;
   
    @OneToMany(mappedBy = "student", fetch = FetchType.LAZY)
    @Where(clause = " deleted is null ")
    private Set<Course> courses;
}

Course:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Data
@Entity
@Table(name = "course")
@EqualsAndHashCode(of = {"id"})
public class Course {

    @Column(name = "uuid")
    @Id
    private String id;
   
    @Column(name = "deleted")
    private String deleted;
   
    @Column(name = "status")
    private String status;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "student")
    @JsonIgnore
    private Student student;
}

Book:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
@Data
@Entity
@Table(name = "book")
@EqualsAndHashCode(of = {"id"})
public class Book {

    @Column(name = "uuid")
    @Id
    private String id;
   
    @Column(name = "status")
    private String status;
   
    @Column(name = "deleted")
    private String deleted;
}

Repository

1
2
3
4
5
6
7
8
9
@Repository
public interface TeacherRepository extends CrudRepository<Teacher, String>{

    @Query("select t from Teacher t "
            + "left join fetch t.students s "
            + "left join fetch s.courses c "
            + "left join fetch t.books b")
    public Set<Teacher> findTeachersU();
}

Service

1
2
3
4
5
6
7
8
9
10
@Service
public class TeacherService {

    @Autowired
    TeacherRepository teacherRepository;
   
    public Set<Teacher> findTeachers(){
        return teacherRepository.findTeachersU();
    }
}

Controller

1
2
3
4
5
6
7
8
9
10
11
@RestController
public class TeacherController {

    @Autowired
    TeacherService teacherService;
   
    @GetMapping("/teachers")
    public Set<Teacher> findTeachers() String status){
        return teacherService.findTeachers();
    }
}

测试

发送/teachers可得到以下结果:

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
[
    {
        "id": "t1",
        "books": [
            {
                "id": "b1",
                "status": "active",
                "deleted": null
            },
            {
                "id": "b2",
                "status": "inactive",
                "deleted": "deleted"
            }
        ],
        "students": [
            {
                "id": "s1",
                "deleted": null,
                "status": "active",
                "courses": [
                    {
                        "id": "c1",
                        "deleted": null,
                        "status": "active"
                    }
                ]
            }
        ]
    },
    {
        "id": "t2",
        "books": [],
        "students": []
    }
]

从结果可以看出软删掉的student和student名下的course已经被过滤掉了,而teacher名下的book并没有过滤掉。所以有中间表关联的子集使用@Where无法达到过滤的作用。

1
2
3
4
5
6
7
Hibernate: select teacher0_.uuid as uuid1_25_0_, students1_.uuid as uuid1_24_1_, courses2_.uuid as uuid1_5_2_, book4_.uuid as uuid1_3_3_, students1_.deleted as deleted2_24_1_, students1_.status as status3_24_1_, students1_.teacher as teacher4_24_1_, students1_.teacher as teacher4_24_0__, students1_.uuid as uuid1_24_0__, courses2_.deleted as deleted2_5_2_, courses2_.status as status3_5_2_, courses2_.student as student4_5_2_, courses2_.student as student4_5_1__, courses2_.uuid as uuid1_5_1__, book4_.deleted as deleted2_3_3_, book4_.status as status3_3_3_, books3_.teacher_id as teacher_1_26_2__, books3_.book_id as book_id2_26_2__ from teacher teacher0_
           left outer join student students1_ on teacher0_.uuid=students1_.teacher and (  students1_.deleted is null )
           left outer join course courses2_ on students1_.uuid=courses2_.student and (  courses2_.deleted is null )
           left outer join teacher_book books3_ on teacher0_.uuid=books3_.teacher_id
           left outer join book book4_ on books3_.book_id=book4_.uuid and (  book4_.deleted is null )

Hibernate: select book0_.uuid as uuid1_3_0_, book0_.deleted as deleted2_3_0_, book0_.status as status3_3_0_ from book book0_ where book0_.uuid=?

从sql语句中可以看到Hibernate将@Where的条件添加到了left join 的on条件而进行了过滤。

利用@Where进行的过滤是静态的,如果是变量的话就得利用@Filter了。

@Filter

利用@FilterDef定义变量的名称
@FilterDef(name = “过滤器的名字”, parameters = {@ParamDef(name=“sql中变量的名字”,type=“变量类型”)})

在需要过滤的字段添加@Filter进行过滤
@Filter(name = “过滤器的名字”, condition = " 过滤器中定义的变量=数据库表的字段 ")

Entity

Teacher

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Data
@Entity
@Table(name = "teacher")
@EqualsAndHashCode(of = {"id"})
public class Teacher {

    @Column(name = "uuid")
    @Id
    private String id;
   
    @ManyToMany(fetch = FetchType.LAZY)
    @JoinTable(name = "teacher_book",
    joinColumns = { @JoinColumn(name = "teacher_id") },
    inverseJoinColumns = { @JoinColumn(name = "book_id") })
    @Filter(name = "BookStatusFilter", condition = " :status=status ")
    private Set<Book> books;
   
    @OneToMany(mappedBy = "teacher", fetch = FetchType.LAZY)
    @Filter(name = "StudentStatusFilter", condition = " :status=status ")
    private Set<Student> students;
}

Student:

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
@Data
@Entity
@Table(name = "student")
@EqualsAndHashCode(of = {"id"})
@FilterDef(name = "StudentStatusFilter", parameters = {@ParamDef(name="status",type="string")})
public class Student {

    @Column(name = "uuid")
    @Id
    private String id;
   
    @Column(name = "deleted")
    private String deleted;
   
    @Column(name = "status")
    private String status;
   
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "teacher")
    @JsonIgnore
    private Teacher teacher;
   
    @OneToMany(mappedBy = "student", fetch = FetchType.LAZY)
    @Where(clause = " deleted is null ")
    @Filter(name = "CourseStatusFilter", condition = " :status=status ")
    private Set<Course> courses;
}

Course:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
@Data
@Entity
@Table(name = "course")
@EqualsAndHashCode(of = {"id"})
@FilterDef(name = "CourseStatusFilter", parameters = {@ParamDef(name="status",type="string")})
public class Course {

    @Column(name = "uuid")
    @Id
    private String id;
   
    @Column(name = "deleted")
    private String deleted;
   
    @Column(name = "status")
    private String status;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "student")
    @JsonIgnore
    private Student student;
}

Book:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
@Data
@Entity
@Table(name = "book")
@EqualsAndHashCode(of = {"id"})
@FilterDef(name = "BookStatusFilter", parameters = {@ParamDef(name="status",type="string")})
public class Book {

    @Column(name = "uuid")
    @Id
    private String id;
   
    @Column(name = "status")
    private String status;
   
    @Column(name = "deleted")
    private String deleted;
}

Repository

1
2
3
4
5
6
7
8
9
@Repository
public interface TeacherRepository extends CrudRepository<Teacher, String>{

    @Query("select t from Teacher t "
            + "left join fetch t.students s "
            + "left join fetch s.courses c "
            + "left join fetch t.books b")
    public Set<Teacher> findTeachersU();
}

Service:
JPA中启用Filter

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
@Service
public class TeacherService {
   
    @Autowired
    @PersistenceContext()
    EntityManager entityManager;

    @Autowired
    TeacherRepository teacherRepository;
   
    public Set<Teacher> findTeachers(){
        return teacherRepository.findTeachersU();
    }
   
    public Set<Teacher> findTeachersByFilter(String status){
        Session session = entityManager.unwrap(Session.class);
        session.enableFilter("StudentStatusFilter").setParameter("status", status);
        session.enableFilter("CourseStatusFilter").setParameter("status", status);
        session.enableFilter("BookStatusFilter").setParameter("status", status);
        Set<Teacher> reSet = teacherRepository.findTeachersU();
        session.disableFilter("StudentStatusFilter");
        session.disableFilter("CourseStatusFilter");
        session.disableFilter("BookStatusFilter");
        return reSet;
    }
}

Controller

1
2
3
4
5
6
7
8
9
10
11
@RestController
public class TeacherController {

    @Autowired
    TeacherService teacherService;
   
    @GetMapping("/teachers")
    public Set<Teacher> findTeachers(@RequestParam(value = "status", required = false) String status){
        return teacherService.findTeachersByFilter(status);
    }
}

测试

利用/teachers?status=active动态传输过滤变量过滤。可以得到下列结果:

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
[
    {
        "id": "t1",
        "books": [
            {
                "id": "b1",
                "status": "active",
                "deleted": null
            },
            {
                "id": "b2",
                "status": "inactive",
                "deleted": "deleted"
            }
        ],
        "students": [
            {
                "id": "s1",
                "deleted": null,
                "status": "active",
                "courses": [
                    {
                        "id": "c1",
                        "deleted": null,
                        "status": "active"
                    }
                ]
            }
        ]
    },
    {
        "id": "t2",
        "books": [],
        "students": []
    }
]

可以看到status为inactive的student和course已经被过滤掉了,而inactive的book并没有被过滤掉。所以,与@Where一样,通过中间表关联的数据利用@Filter达不到过滤的效果。
查询的sql:

1
2
3
4
5
6
7
Hibernate: select teacher0_.uuid as uuid1_25_0_, students1_.uuid as uuid1_24_1_, courses2_.uuid as uuid1_5_2_, book4_.uuid as uuid1_3_3_, students1_.deleted as deleted2_24_1_, students1_.status as status3_24_1_, students1_.teacher as teacher4_24_1_, students1_.teacher as teacher4_24_0__, students1_.uuid as uuid1_24_0__, courses2_.deleted as deleted2_5_2_, courses2_.status as status3_5_2_, courses2_.student as student4_5_2_, courses2_.student as student4_5_1__, courses2_.uuid as uuid1_5_1__, book4_.deleted as deleted2_3_3_, book4_.status as status3_3_3_, books3_.teacher_id as teacher_1_26_2__, books3_.book_id as book_id2_26_2__ from teacher teacher0_
           left outer join student students1_ on teacher0_.uuid=students1_.teacher and  ?=students1_.status
           left outer join course courses2_ on students1_.uuid=courses2_.student and  ?=courses2_.status  and (  courses2_.deleted is null )
           left outer join teacher_book books3_ on teacher0_.uuid=books3_.teacher_id
           left outer join book book4_ on books3_.book_id=book4_.uuid and  ?=book4_.status

Hibernate: select book0_.uuid as uuid1_3_0_, book0_.deleted as deleted2_3_0_, book0_.status as status3_3_0_ from book book0_ where book0_.uuid=?

达到过滤功能的原理和@Where一样。

参考:
https://www.baeldung.com/hibernate-dynamic-mapping
https://stackoverflow.com/questions/49752429/using-a-hibernate-filter-with-spring-boot-jpa