Visibility
Previous documents have discussed in detail the dynamic and immutable properties of Jimmer entities. This introduces a new feature: visibility.
Entangled Properties
For Jimmer entities, although properties are mostly independent, in some cases multiple properties share private data and thus affect each other. We call this entanglement between properties.
These cases include:
-
Java/Kotlin-based calculated properties (review details)
- Java
- Kotlin
Author.javapackage com.example.model;
import org.babyfish.jimmer.sql.*;
@Entity
public interface Author {
String firstName();
String lastName();
@Formula(dependencies = {"firstName", "lastName"})
default String fullName() {
return firstName() + ' ' + lastName();
}
...Omit other properties...
}Author.ktpackage com.example.model
import org.babyfish.jimmer.sql.*
@Entity
interface Author {
val firstName: String
val lastName: String
@Formula(dependencies = ["firstName", "lastName"])
val fullName: String
get() = "$firstName $lastName"
...Omit other properties...
}Author.fullName
has no private data, it depends onAuthor.firstName
andAuthor.lastName
.Although
Author.fullName
is defined as a calculated property, it can also be considered a view property from another perspective.If treating
Author.firstName
andAuthor.lastName
as original properties, thenAuthor.fullName
can be considered a view property based on them. -
@IdView view properties (review details)
- Java
- Kotlin
Book.javapackage com.example.model;
import org.babyfish.jimmer.sql.*;
import org.jetbrains.annotations.Nullable;
@Entity
public interface Book {
...Omit other properties...
@ManyToOne
@Nullable
BookStore store();
@ManyToMany
@JoinTable(
name = "BOOK_AUTHOR_MAPPING",
joinColumnName = "BOOK_ID",
inverseJoinColumnName = "AUTHOR_id"
)
List<Author> authors();
@IdView // View of associated object store's id
Long storeId();
// View of ids of all objects in associated collection authors
@IdView("authors")
List<Long> authorIds();
}Book.ktpackage com.example.model
import org.babyfish.jimmer.sql.*
@Entity
interface Book {
...Omit other properties...
@ManyToOne
val store: BookStore?
@ManyToMany
@JoinTable(
name = "BOOK_AUTHOR_MAPPING",
joinColumnName = "BOOK_ID",
inverseJoinColumnName = "AUTHOR_id"
)
val authors: List<Author>
@IdView // View of associated object store's id
val storeId: Long?
// View of ids of all objects in associated collection authors
@IdView("authors")
val authorIds: List<Long>
}-
Book.store
is the original property,Book.storeId
is the view property based on it -
Book.authors
is the original property,Book.authorIds
is the view property based on it
-
@ManyToManyView view properties (review details)
- Java
- Kotlin
Student.java@Entity
public interface Student {
// In step 1, already declared one-to-many association `learningLinks`
@OneToMany(mappedBy = "student")
List<LearningLink> learningLinks();
@ManyToManyView(
prop = "learningLinks",
deeperProp = "course"
)
List<Course> courses();
...Omit other code...
}Student.kt@Entity
interface Student {
@OneToMany(mappedBy = "student")
val learningLinks: List<LearningLink>
@ManyToManyView(
prop = "learningLinks",
deeperProp = "course"
)
val courses: List<Course>
...Omit other code...
}Student.learningLinks
is the original property,Student.courses
is the view property based on it.
The commonality in the examples above is that there are original properties and view properties.
The original properties have their own private data, while the view properties have none. The view properties just observe the values of the original properties from a different perspective.
From an internal implementation perspective, a view property actually shares private data with the original property. This means knowing one value necessarily reveals partial information about the other's value. Hence they can be metaphorically described as entangled properties.
Object Fetchers and Entangled Properties
We introduced entangled properties, with original properties and view properties. The real data is held by the original properties, while the view properties only observe.
When using an object fetcher to fetch a view property, the internal logic will translate it into fetching the original property, for example:
-
Fetching
Author.fullName
is translated internally into fetchingAuthor.firstName
andAuthor.lastName
-
Fetching
Book.storeId
is translated internally into fetchingBook.store
-
Fetching
Book.authorIds
is translated internally into fetchingBook.authors
-
Fetching
Student.courses
is translated internally into fetchingStudent.learningLinks
Let's take Book.authorIds
and Book.authors
to demonstrate how object fetchers handle original properties and view properties differently:
-
Fetch the original property
- Java
- Kotlin
Book book = sqlClient.findById(
Fetchers.BOOK_FETCHER
.allScalarFields()
.authors(), // Associated objects with only id
1L
);
System.out.println(book);val book = sqlClient.findById(
newFetcher(Book::class).by {
allScalarFields()
authors() // Associated objects with only id
},
1L
)
println(book)The
authors()
in the fetcher has no arguments, indicating it fetches a collection of author objects with only id properties. The result is (manually formatted for readability):{
"id":1,
"name":"Learning GraphQL",
"edition":1,
"price":50,
"authors":[
{ "id":2 },
{ "id":1 }
]
} -
Fetch the view property
- Java
- Kotlin
Book book = sqlClient.findById(
Fetchers.BOOK_FETCHER
.allScalarFields()
.authorIds(), // Associated ids, not objects
1L
);
System.out.println(book);val book = sqlClient.findById(
newFetcher(Book::class).by {
allScalarFields()
authorIds() // Associated ids, not objects
},
1L
)
println(book)This time the result is (manually formatted for readability):
{
"id":1,
"name":"Learning GraphQL",
"edition":1,
"price":50,
"authorIds":[
2, 1
]
}
Although the returned data is equivalent, the formats are completely different.
We said earlier that when an object fetcher fetches a view property, it translates internally into fetching the original property.
Since this is the case, the underlying logic should be exactly the same. Where does this difference come from?
Property Visibility
The question above is, with identical underlying logic, why do two queries with the same logic return data in different formats?
Jimmer can control the visibility of each property, making it shown or hidden.
Unlike dynamic where a property can be loaded or unloaded, visibility is an orthogonal feature, completely unrelated to dynamism.
Visibility only affects Jackson serialization of objects (including their own toString
behavior), deciding whether a property is serialized. Other than that, it does not impact any other behavior of the object.
So the previous examples can be easily explained:
-
First query:
Book.authors
is shown,Book.authorIds
is hidden -
Second query:
Book.authors
is hidden,Book.authorIds
is shown
Only when both conditions below are met will a property participate in Jackson serialization
- Dynamism: the property is set
- Visibility: the property is shown
See tool methods for how to control visibility of Jimmer object properties yourself