Краткая справка
У меня есть два метода в API, созданном в Spring Boot для извлечения данных из базы данных mySQL (через Hibernate и JpaRepository). Существует метод, который возвращает все вхождения в таблице с именем test , а другой метод возвращает test , соответствующий идентификатору, переданному в качестве параметра в вызове GET. Обе точки входа API (посредством сервисов и репозиториев) в итоге вызывают два метода JpaRepository ( findAll () и getOne () соответственно).
Описание проблемы
Проблема, которую я наблюдаю, состоит в том, что в методе findAll () JpaRepository ведет себя иначе, чем getOne () , когда дело доходит до возврата списков внутренних объектов, соответствующих @ Многие-многие отношения в модели. Класс test содержит список обязательных объектов, соответствующих другому объекту модели. Оба класса следующие:
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>();
Здесь приведен фрагмент JSON, возвращенный отображением GET, которое возвращает все тесты. Можно заметить, что первый тестовый объект, объект с idtest 9, имеет коллекцию из 5 реквизитов объектов со свойством реквизиты, Второй тест (с идентификатором 10), напротив, показывает только полный обязательный объект в том, который не существует в предыдущем, показывая только значение идентификатора в другие:
[
{
"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": ""
}
...
]
А вот и результат вызова метода GET для одного объекта через его идентификатор (в данном случае объект с идентификатором 10, который неверен в предыдущем 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": ""
}
Дополнительная информация
Я наблюдал следующее поведение:
- Если я связываю какой-либо реквизит только с одним тестом, он работает как задумано.
- Если я свяжу реквизит с двумя или более тестами, он вернет объект только в первом тесте и вернет только его значение индекса в следующих.
Из-за этих двух моментов я считаю, что это может показаться проблемой кеширования (я не включил ни кеш второго уровня, ни кеш уровня запросов). Я имею в виду, что похоже, что hibernate только получает данный реквизит из базы данных в первый раз, а для остальных обнаруживает, что реквизит с этим идентификатором был запрошен, и не запускает запрос. Конечно, это хорошо, но как я могу использовать кэшированные объекты в моих результатах в последующих тестах объектов, с которыми связан реквизит ?
Редактировать : Хорошо, я публикую это как ответ, потому что проблема, которую я описываю в моем оригинальном сообщении, кажется, исчезает. Как я уже говорил в комментариях к исходному сообщению, я удалил аннотацию @JsonIdentityInfo для класса Requisite. Я использовал его, чтобы избежать бесконечной рекурсии из-за отношений @ManyToMany между классами моделей.
Однако я не думаю, что это решение, и определенно не понимаю, как работает @JsonIdentityInfo. Я прочитаю документацию, но я был бы признателен за объяснение, если кто-нибудь сможет его предоставить.