Skip to main content

View Properties

IdView

For data structures, frontends and backends have different preferences. Backends prefer structured data, while frontends prefer flattened data.

For IdOnly objects, Jimmer provides the @IdView annotation to easily flatten IdOnly objects into a field.

Modify the Example Entity Interface

In the example entity interface, two @IdView properties are added, which are view properties. The value of these properties will be automatically synchronized with the value of the corresponding associated object.

For the IdView of a collection property, the corresponding association property needs to be specified manually.

@Entity
public interface Book {

...Other properties omitted...

@Nullable
@ManyToOne
BookStore store();

@ManyToMany
List<Author> authors();

@IdView
Long storeId();

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

Fetch @IdView Properties

caution

View properties need to be fetched manually. allScalarFields and allTableFields will not automatically fetch view properties.

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);

The output is:

[
{
"id": 3,
"name": "Learning GraphQL",
"edition": 3,
"price": 51.00,
"storeId": 1,
"authorIds": [1,2]
}
...Other objects omitted
]

ManyToManyView

In some scenarios, the join table of a many-to-many association contains not only the IDs of both sides, but also some other fields.

A join table with additional fields besides the two IDs cannot be directly mapped to a ManyToMany association. We have no choice but to create three entity types: the original two entities and an intermediate entity. Each side entity has a one-to-many association pointing to the intermediate entity, and the intermediate entity has two many-to-one associations pointing to the two side entities.

Fortunately, Jimmer provides @ManyToManyView to abstract a many-to-many association between the two side entities on top of this three-entity structure.

To better demonstrate, instead of using books and authors, a more realistic example with students, courses, and learning_link tables is used.

Prepare Data

For convenience, H2 database is used here. First create the table structure and initialize data with:

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);

Define Entities

  • Student

    @Entity
    public interface Student {

    @Id
    long id();

    String name();

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

    // ManyToMany view property
    // prop: One-to-many to intermediate entity `learningLinks`
    // deeperProp: Many-to-one from intermediate to target entity `course` (optional)
    @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();

    // ManyToMany view property
    // prop: One-to-many to intermediate entity `learningLinks`
    // deeperProp: Many-to-one from intermediate to target entity `student` (optional)
    @ManyToManyView(prop = "learningLinks", deeperProp = "student")
    List<Student> students();
    }

  • Intermediate Entity LearningLink

    @Entity
    public interface LearningLink {

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

    // Many-to-one, points to Student entity
    @ManyToOne
    @OnDissociate(DissociateAction.DELETE)
    Student student();

    // Many-to-one, points to Course entity
    @ManyToOne
    @OnDissociate(DissociateAction.DELETE)
    Course course();

    Integer score();
    }

Fetch @ManyToManyView Properties

With the introduction of the intermediate entity, Student and Course entities are no longer directly associated. Although they can be indirectly associated through a Student->LearningLink->Course path, the path is too long.

Besides, the intermediate LearningLink entity should be hidden as much as possible, only used when reading/writing scores.

@ManyToManyView properties (e.g. Student.courses in this example) can be used in object fetchers to reduce the path length.

For example, the following query gets all course names for the student with id 1:

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

The generated SQL is:

  1. Query Student

    select 
    tb_1_.ID, tb_1_.NAME
    from STUDENT tb_1_
    where tb_1_.ID = ? /* 1 */
  2. Query 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. Query Course

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

The output is:

{
"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"
}
]
}

With many-to-many view properties, associated objects can be operated as conveniently as if the intermediate entity did not exist at all.