问题
Brief background
I have two methods in an API created on Spring Boot to retrieve data from a mySQL database (via Hibernate and JpaRepository). There is a method that returns all the ocurrences in a table called test, and another one that returns the test corresponding to the id passed as a parameter in the GET call. Both API entry points (by means of the services and repositories) end up calling two JpaRepository methods (findAll() and getOne() respectively).
Problem description
The problem I'm observing is that in the findAll() method, JpaRepository behaves differently from getOne() when it comes to returning lists of internal objects corresponding to a @ManyToMany relationship in the model. The test class has a list of requisite objects corresponding to a different entity of the model. Both classes are as follows:
Pectest.java
@Entity
@JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
@JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property="idtest")
public class Pectest {
@Id
@Column(name="idtest")
private long idtest;
private String testname;
private String testdescription;
[...]
// Other properties and fields
[...]
@ManyToMany(fetch = FetchType.LAZY
)
@JoinTable(name = "test_checks_requisite",
joinColumns = @JoinColumn(name = "test_idtest"),
inverseJoinColumns = @JoinColumn(name = "requisite_idrequisite")
)
@JsonProperty(access=Access.READ_ONLY)
private Set<Requisite> requisites = new HashSet<Requisite>();
[...]
Requisite.java
@Entity
@JsonIgnoreProperties({"hibernateLazyInitializer", "handler"})
@JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property="idrequisite")
public class Requisite {
@Id
@Column(name="idrequisite")
private long idrequisite;
private String Name;
private String Title;
private String Description;
@ManyToOne(cascade = {CascadeType.PERSIST}, fetch=FetchType.LAZY)
@JoinColumn(name="functionality_idfunctionality")
@JsonProperty(access=Access.WRITE_ONLY)
private Functionality functionality;
@ManyToMany(mappedBy="requisites")
private Set<Pectest> tests = new HashSet<Pectest>();
Here goes a fragment of the JSON returned by the GET mapping that returns all tests. It can be observed that the first test object, the one with idtest 9 has a collection of 5 requisite objects under the requisites property. The second test (with id 10) on the contrary, only shows the full requisite object in the one which doesn't exist in the previous one, showing only the id's value in the others:
[
{
"idtest": 9,
"testtype": {
"idTestType": 5,
"testTypeName": "Manual"
},
"datatype": null,
"requisites": [
{
"idrequisite": 7,
"name": "REQ-0006",
"description": "Campo para traducción de nombres, debe mostrar el nombre en el idioma seleccionado por el cliente.",
"title": "DisplayName"
},
{
"idrequisite": 4,
"name": "REQ-0003",
"description": "Se ofrece acceso a parámetros a través de este tipo de nodo",
"title": "Parameter Type"
},
{
"idrequisite": 5,
"name": "REQ-0004",
"description": "El BrowseName de las variables debe ser sólo el último campo de las variables exportadas por FW de BR (ItemName)",
"title": "BrowseName"
},
{
"idrequisite": 3,
"name": "REQ-0002",
"description": "Se ofrece acceso a variables analógicas a través de este tipo de nodo",
"title": "Analog Type"
},
{
"idrequisite": 2,
"name": "REQ-0001",
"description": "Se ofrece acceso a variables digitales a través de este tipo de nodo",
"title": "Digital Type"
}
],
"testDescription": "El servidor es capaz de devolver correctamente todos los tipos de dato.",
"lastUpdated": null,
"testName": "Lectura de DataTypes",
"dateCreated": null,
"testURL": ""
},
{
"idtest": 10,
"testtype": {
"idTestType": 5,
"testTypeName": "Manual"
},
"datatype": {
"idDataType": 2,
"dataTypeName": "Boolean"
},
"requisites": [
7,
5,
{
"idrequisite": 10,
"name": "REQ-0009",
"description": "Se generan a partir de los niveles de acceso de la variable. Se deben escalar los valores, de 256 a 1000.",
"title": "AccessLevel & UserAccessLevel"
},
2
],
"testDescription": "El servidor es capaz de admitir escrituras correctamente de todos los tipos de dato.",
"lastUpdated": null,
"testName": "Escritura de DataTypes",
"dateCreated": null,
"testURL": ""
}
...
]
And here goes the result of calling the GET method for a single object via its id (in this case the one with id 10, which is incorrect in the previous JSON):
{
"idtest": 10,
"testtype": {
"idTestType": 5,
"testTypeName": "Manual"
},
"datatype": {
"idDataType": 2,
"dataTypeName": "Boolean"
},
"requisites": [
{
"idrequisite": 7,
"name": "REQ-0006",
"description": "Campo para traducción de nombres, debe mostrar el nombre en el idioma seleccionado por el cliente.",
"title": "DisplayName"
},
{
"idrequisite": 2,
"name": "REQ-0001",
"description": "Se ofrece acceso a variables digitales a través de este tipo de nodo",
"title": "Digital Type"
},
{
"idrequisite": 5,
"name": "REQ-0004",
"description": "El BrowseName de las variables debe ser sólo el último campo de las variables exportadas por FW de BR (ItemName)",
"title": "BrowseName"
},
{
"idrequisite": 10,
"name": "REQ-0009",
"description": "Se generan a partir de los niveles de acceso de la variable. Se deben escalar los valores, de 256 a 1000.",
"title": "AccessLevel & UserAccessLevel"
}
],
"testDescription": "El servidor es capaz de admitir escrituras correctamente de todos los tipos de dato.",
"lastUpdated": null,
"testName": "Escritura de DataTypes",
"dateCreated": null,
"testURL": ""
}
Additional information
I have observed the following behavior:
- If I associate any requisite just to a single test, it works as intended.
- If I associate a requisite to two or more tests, it will only return the object on the first one, returning only its index value in the following ones.
Because of those two points, I believe that it may seem like some caching issue (I haven't enabled second level cache nor query level cache). I mean, it seems like hibernate only retrieves a given requisite from the database the first time, and for the rest of them detects that a requisite with that id has been requested and doesn't launch the query. Which is good, of course, but how can I have the cached objects in my results in the subsequent test objects that have that requisite associated?
Edit: Ok, I post this as an answer, because the problem I describe in my original post seems to disappear. As I was saying in the comments to the original post, I removed the @JsonIdentityInfo annotation on the Requisite class. I was using it to avoid infinite recursion due to @ManyToMany relationships between model classes.
However, I don't think this is a solution, and definitely I don't understand how @JsonIdentityInfo works. I'll read the documentation, but I'd appreciate an explanation if somebody can provide one.
来源:https://stackoverflow.com/questions/53046952/jpa-repository-and-jsonignoreproperties-return-the-same-object-differently-in-ge