Spring Data REST - почему hibernate извлекает дочернюю сущность с помощью FetchType.LAZY? - PullRequest
2 голосов
/ 27 мая 2020

У меня есть простой вариант использования, когда я создал 2 объекта Account и AccountDetail.

Эти объекты имеют отношение один-к-одному, как показано ниже: отношение таблиц

Объект Account:

@Entity
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Setter
@Getter
@EqualsAndHashCode
public class Account {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String username;

    private String password;

    @OneToOne(mappedBy = "account", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
    private AccountDetail accountDetail;

}

Объект AccountDetail:

@Entity
@Builder
@AllArgsConstructor
@NoArgsConstructor
@Setter
@Getter
@EqualsAndHashCode
public class AccountDetail {
    @Id
    private Long id;

    @OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL) @MapsId
    private Account account;

    private String firstName;

    private String lastName;
}

Я использую Spring Boot (v2.3.0) со следующими зависимостями: - Spring Data JPA: для сохранения сущностей с использованием Hibernate - Rest Repositories: для предоставления репозиториев через конечные точки REST

Проблема в том, что Hibernate извлекает сущность AccountDetail, хотя я указал fetch = FetchType.LAZY.

Вы можете увидеть в журнале 2 sql операторов: журнал гибернации


То, что я уже пробовал:

Использование проекции:

@Projection(types = {Account.class})
public interface AccountProjection {
    String getUsername();
}

При следующем запросе GET http://localhost: 8080 / accounts / 1? Projection = accountProjection я получаю:

   {
      "username": "warrior24",
      "_links": {
        "self": {
          "href": "http://localhost:8080/accounts/1"
        },
        "account": {
          "href": "http://localhost:8080/accounts/1{?projection}",
          "templated": true
        },
        "accountDetail": {
          "href": "http://localhost:8080/accounts/1/accountDetail"
        }
      }
    }

К сожалению, с этим решением он по-прежнему выполняет 2 sql запросов.

Использование Hibernate Bytecode Enhancement wit h @LazyToOne(LazyToOneOption.NO_PROXY)

@OneToOne(mappedBy = "account", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
@LazyToOne(LazyToOneOption.NO_PROXY)
private AccountDetail accountDetail;

Даже это решение не решает проблему.

Заключение

Я не уверен, что понимаю, почему спящий режим извлекает ребенка . На данный момент это не большая проблема, но если количество записей в базе данных станет большим, это может повлиять на производительность.

Есть ли у кого-нибудь похожая проблема и, возможно, знает, как ее решить?


Обновление: включая журнал отладки для запроса GET

2020-05-27 15:23:58.414 INFO 2119 --- [nio-8080-exec-1] o.a.c.c.C.[Tomcat-3].[localhost].[/] : Initializing Spring DispatcherServlet 'dispatcherServlet' 2020-05-27 15:23:58.414 INFO 2119 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Initializing Servlet 'dispatcherServlet' 2020-05-27 15:23:58.414 DEBUG 2119 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Detected StandardServletMultipartResolver 2020-05-27 15:23:58.415 DEBUG 2119 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : enableLoggingRequestDetails='false': request parameters and headers will be masked to prevent unsafe logging of potentially sensitive data 2020-05-27 15:23:58.415 INFO 2119 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Completed initialization in 1 ms 2020-05-27 15:23:58.415 DEBUG 2119 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : GET "/accounts/1", parameters={} 2020-05-27 15:23:58.416 DEBUG 2119 --- [nio-8080-exec-1] o.s.d.r.w.RepositoryRestHandlerMapping : Mapped to org.springframework.data.rest.webmvc.RepositoryEntityController#getItemResource(RootResourceInformation, Serializable, PersistentEntityResourceAssembler, HttpHeaders) 2020-05-27 15:23:58.416 DEBUG 2119 --- [nio-8080-exec-1] o.j.s.OpenEntityManagerInViewInterceptor : Opening JPA EntityManager in OpenEntityManagerInViewInterceptor 2020-05-27 15:23:58.417 DEBUG 2119 --- [nio-8080-exec-1] stomAnnotationTransactionAttributeSource : Adding transactional method 'findById' with attribute: PROPAGATION_REQUIRED,ISOLATION_DEFAULT,readOnly 2020-05-27 15:23:58.417 DEBUG 2119 --- [nio-8080-exec-1] o.s.orm.jpa.JpaTransactionManager : Found thread-bound EntityManager [SessionImpl(1137877934<open>)] for JPA transaction 2020-05-27 15:23:58.417 DEBUG 2119 --- [nio-8080-exec-1] o.s.orm.jpa.JpaTransactionManager : Creating new transaction with name [org.springframework.data.jpa.repository.support.SimpleJpaRepository.findById]: PROPAGATION_REQUIRED,ISOLATION_DEFAULT,readOnly 2020-05-27 15:23:58.417 DEBUG 2119 --- [nio-8080-exec-1] o.s.jdbc.datasource.DataSourceUtils : Setting JDBC Connection [HikariProxyConnection@646683728 wrapping conn193: url=jdbc:h2:mem:f0df200a-97ea-4e5b-9f80-050ff9a550f7 user=SA] read-only 2020-05-27 15:23:58.417 DEBUG 2119 --- [nio-8080-exec-1] o.s.orm.jpa.JpaTransactionManager : Exposing JPA transaction as JDBC [org.springframework.orm.jpa.vendor.HibernateJpaDialect$HibernateConnectionHandle@3a65c273] 2020-05-27 15:23:58.417 DEBUG 2119 --- [nio-8080-exec-1] org.hibernate.SQL : select account0_.id as id1_0_0_, account0_.password as password2_0_0_, account0_.username as username3_0_0_ from account account0_ where account0_.id=? 2020-05-27 15:23:58.417 TRACE 2119 --- [nio-8080-exec-1] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [BIGINT] - [1] 2020-05-27 15:23:58.418 DEBUG 2119 --- [nio-8080-exec-1] org.hibernate.SQL : select accountdet0_.account_id as account_3_1_0_, accountdet0_.first_name as first_na1_1_0_, accountdet0_.last_name as last_nam2_1_0_ from account_detail accountdet0_ where accountdet0_.account_id=? 2020-05-27 15:23:58.418 TRACE 2119 --- [nio-8080-exec-1] o.h.type.descriptor.sql.BasicBinder : binding parameter [1] as [BIGINT] - [1] 2020-05-27 15:23:58.418 DEBUG 2119 --- [nio-8080-exec-1] o.s.orm.jpa.JpaTransactionManager : Initiating transaction commit 2020-05-27 15:23:58.418 DEBUG 2119 --- [nio-8080-exec-1] o.s.orm.jpa.JpaTransactionManager : Committing JPA transaction on EntityManager [SessionImpl(1137877934<open>)] 2020-05-27 15:23:58.418 DEBUG 2119 --- [nio-8080-exec-1] o.s.jdbc.datasource.DataSourceUtils : Resetting read-only flag of JDBC Connection [HikariProxyConnection@646683728 wrapping conn193: url=jdbc:h2:mem:f0df200a-97ea-4e5b-9f80-050ff9a550f7 user=SA] 2020-05-27 15:23:58.418 DEBUG 2119 --- [nio-8080-exec-1] o.s.orm.jpa.JpaTransactionManager : Not closing pre-bound JPA EntityManager after transaction 2020-05-27 15:23:58.421 DEBUG 2119 --- [nio-8080-exec-1] o.s.w.s.m.m.a.HttpEntityMethodProcessor : Using 'application/hal+json', given [*/*] and supported [application/hal+json] 2020-05-27 15:23:58.421 DEBUG 2119 --- [nio-8080-exec-1] o.s.w.s.m.m.a.HttpEntityMethodProcessor : Writing [Resource { content: Account(id=1, username=warrior24, password=1234), links: [<http://localhost:8080 (truncated)...] 2020-05-27 15:23:58.421 DEBUG 2119 --- [nio-8080-exec-1] s.d.r.w.j.PersistentEntityJackson2Module : Serializing PersistentEntity org.springframework.data.jpa.mapping.JpaPersistentEntityImpl@45b7be72. 2020-05-27 15:23:58.423 DEBUG 2119 --- [nio-8080-exec-1] .s.ReloadableResourceBundleMessageSource : No properties file found for [classpath:rest-messages] - neither plain properties nor XML 2020-05-27 15:23:58.424 DEBUG 2119 --- [nio-8080-exec-1] .s.ReloadableResourceBundleMessageSource : No properties file found for [classpath:rest-messages_en] - neither plain properties nor XML 2020-05-27 15:23:58.424 DEBUG 2119 --- [nio-8080-exec-1] .s.ReloadableResourceBundleMessageSource : No properties file found for [classpath:rest-messages_en_GB] - neither plain properties nor XML 2020-05-27 15:23:58.426 DEBUG 2119 --- [nio-8080-exec-1] o.j.s.OpenEntityManagerInViewInterceptor : Closing JPA EntityManager in OpenEntityManagerInViewInterceptor 2020-05-27 15:23:58.426 DEBUG 2119 --- [nio-8080-exec-1] o.s.web.servlet.DispatcherServlet : Completed 200 OK

Ответы [ 3 ]

0 голосов
/ 27 мая 2020

Попробуйте поставить аннотацию @EqualsAndHashCode.Exclude, чтобы исключить accountDetail из методов equals и hashCode

@EqualsAndHashCode.Exclude    
 @OneToOne(mappedBy = "account", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
                private AccountDetail accountDetail;
0 голосов
/ 02 июня 2020

РЕШЕНИЕ (вроде)

Хорошо, я думаю, что нашел решение, но, к сожалению, оно несовместимо с Spring Data Rest.

Добавив optional = false, вы можете сообщить спящему режиму, что вы гарантирует, что дочерняя ссылка никогда не будет нулевой (в моем случае AccountDetail).

Следовательно, ему не нужно будет извлекать дочерний элемент, чтобы проверить, должен ли он создавать прокси или назначать NULL.

@OneToOne(mappedBy = "account", fetch = FetchType.LAZY, cascade = CascadeType.ALL, optional = false)
@LazyToOne(LazyToOneOption.NO_PROXY)
private AccountDetail accountDetail;

Проблема с этим решением заключается в том, что вы не можете создать новый ресурс Account, используя конечную точку POST, предоставленную с аннотацией @RepositoryRestResource. Поскольку теперь он ожидает, что AccountDetail не будет нулевым, вам необходимо предоставить ссылку на этот ресурс в JSON (который еще не существует). И вы не можете создать ресурс AccountDetail, потому что он использует в качестве первичного ключа внешний ключ из таблицы Account.

0 голосов
/ 27 мая 2020

Попробуйте использовать аннотацию @JsonIgnore, которая указывает, что аннотированный метод или поле должны игнорироваться функциями сериализации и десериализации на основе интроспекции.

@JsonIgnore
@OneToOne(mappedBy = "account", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
    private AccountDetail accountDetail;
...