跳到主要内容

视图属性

IdView

对于数据结构,前后端各自偏好有所不同,后端倾向于结构化数据,而前端倾向于平铺化数据。

对于IdOnly的对象,Jimmer提供了@IdView注解,可以很方便的将IdOnly对象展开成一个字段。

修改示例实体接口

在示例实体接口中,分别添加了两个@IdView的属性,这是一个视图属性,该属性的值将自动同步对应关联对象的值。

对于集合属性的IdView,需要手动写明其对应的是哪个关联属性。

@Entity
public interface Book {

...省略其他属性...

@Nullable
@ManyToOne
BookStore store();

@ManyToMany
List<Author> authors();

@IdView
Long storeId();

@IdView("authors")
List<Long> authorIds();
}

抓取@IdView属性

警告

视图属性需要手动抓取,allScalarFieldsallTableFields不会自动抓取视图属性。

BookTable book = Tables.BOOK_TABLE;

List<Book> bookWithIdViews = sqlClient
.createQuery(book)
.where(book.edition().eq(3))
.select(
book.fetch(
Fetchers.BOOK_FETCHER
.allScalarFields()
.storeId()
.authorIds()
)
)
.execute();
System.out.println(bookWithIdViews);

输出结果为:

[
{
"id": 3,
"name": "Learning GraphQL",
"edition": 3,
"price": 51.00,
"storeId": 1,
"authorIds": [1,2]
}
...省略其他对象
]

ManyToManyView

某些场景下,多对多关联的中间表,不再是纯粹的由双方id组成,而是夹杂了一些其他字段。

除双方id外还有其他字段的中间表无法直接映射为ManyToMany关联。我们不得不创建三个实体类型:两个原本的实体与一个中间实体,双方实体各持有一个指向中间实体的一对多关联属性,中间实体持有到双方实体的两个多对一关联属性。

幸运的是,Jimmer提供了@ManyToManyView,可以在这中三实体结构的基础上抽象出双方实体之间的多对多关联。

为了更好的举例,这里不在使用书籍、作者,而是更具真实含义的学生(student)、课程(course)、课程得分(learning_link)三张表为例。

数据准备

为了示例方便,这里使用的是H2数据库,首先使用以下sql建立表结构和初始化数据:

drop table course if exists;
drop table learning_link if exists;
drop table student if exists;

create table course(
id identity(100, 1) not null,
name varchar(50) not null,
academic_credit integer not null
);

create table student(
id identity(100, 1) not null,
name varchar(50) not null
);

create table learning_link(
id identity(100, 1) not null,
student_id int not null,
course_id int not null,
score int null
);

alter table learning_link
add constraint fk_learning_link__student
foreign key(student_id)
references student(id)
on delete cascade;

alter table learning_link
add constraint fk_learning_link__course
foreign key(course_id)
references course(id)
on delete cascade;

insert into student(id, name)
values (1, 'John'),
(2, 'Jane'),
(3, 'Jack'),
(4, 'Jill'),
(5, 'Joe');

insert into course(id, name, academic_credit)
values (1, 'Math', 100),
(2, 'English', 100),
(3, 'History', 100),
(4, 'Science', 100),
(5, 'Art', 100);

insert into learning_link(student_id, course_id, score)
values (1, 1, 100), (1, 2, 90), (1, 3, 80), (1, 4, 70), (1, 5, 60),
(2, 1, 90), (2, 2, 80), (2, 3, 70), (2, 4, 60), (2, 5, 100),
(3, 1, 80), (3, 2, 70), (3, 3, 60), (3, 4, 100), (3, 5, 90),
(4, 1, 70), (4, 2, 60), (4, 3, 100), (4, 4, 90), (4, 5, 80),
(5, 1, 60), (5, 2, 100), (5, 3, 90), (5, 4, 80), (5, 5, 70);

建立实体

  • Student

    @Entity
    public interface Student {

    @Id
    long id();

    String name();

    @OneToMany(mappedBy = "student")
    List<LearningLink> learningLinks();

    // 多对多视图属性
    // prop: 从当前实体指向中间实体的关联属性`learningLinks`
    // deeperProp: 从中间实体指向目标实体的关联属性`course`(可省略)
    @ManyToManyView(prop = "learningLinks", deeperProp = "course")
    List<Course> courses();
    }
  • Course

    @Entity
    public interface Course {

    @Id
    long id();

    String name();

    int academicCredit();

    @OneToMany(mappedBy = "course")
    List<LearningLink> learningLinks();

    // 多对多视图属性
    // prop: 从当前实体指向中间实体的关联属性`learningLinks`
    // deeperProp: 从中间实体指向目标实体的关联属性`student`(可省略)
    @ManyToManyView(prop = "learningLinks", deeperProp = "student")
    List<Student> students();
    }

  • 中间实体LearningLink

    @Entity
    public interface LearningLink {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    long id();

    // 多对一关联,指向学生实体
    @ManyToOne
    @OnDissociate(DissociateAction.DELETE)
    Student student();

    // 多对一关联,指向课程实体
    @ManyToOne
    @OnDissociate(DissociateAction.DELETE)
    Course course();

    Integer score();
    }

抓取@ManyToManyView属性

由于引入了中间实体,学生实体与课程实体不再直接互相关联,虽然可以通过Student->LearningLink->Course这样的链路间接关联,但是路径太长。

而且中间实体LearningLink只应该在需要读写考试分数时候被使用,其他情况下应该尽量隐藏中间实体。

可以在对象抓起其中使用@ManyToManyView属性 (对本例而言,就是Student.courses) 来减少调用链路长度。

例如以下查询:id为1的学生的所有课程名称

Student student = sqlClient
.findNullable(
1L,
Fetchers.STDUENT_FETCHER
.name()
.courses(
Fetchers.COURSE_FETCHER
.name()
)
)
System.out.println(student);

生成SQL为:

  1. 查询Student

    select 
    tb_1_.ID, tb_1_.NAME
    from STUDENT tb_1_
    where tb_1_.ID = ? /* 1 */
  2. 查询LearningLink

    select 
    tb_1_.STUDENT_ID,
    tb_1_.ID, tb_1_.COURSE_ID
    from LEARNING_LINK tb_1_
    where tb_1_.STUDENT_ID in (? /* 1 */)
  3. 查询Course

    select 
    tb_1_.ID, tb_1_.NAME
    from COURSE tb_1_
    where tb_1_.ID in (
    ? /* 1 */, ? /* 2 */, ? /* 3 */,
    ? /* 4 */, ? /* 5 */
    )

输出结果为:

{
"id": 1,
"name": "John",
"courses": [
{
"id": 1,
"name": "Math"
},
{
"id": 2,
"name": "English"
},
{
"id": 3,
"name": "History"
},
{
"id": 4,
"name": "Science"
},
{
"id": 5,
"name": "Art"
}
]
}

使用多对多视图,可以像没有引入中间实体时一样方便快捷的直接操作关联对象。