2017-11-27 2 views
0

JPA 자동 구성 CRUD 저장소, 최대 절전 모드 및 MySQL에서 스프링 부트를 사용하려고합니다. 내가 기대하는 방식으로 작동하는 조회를 얻는 데 어려움을 겪고 있습니다.스프링 부트 JPA 하이버 네이트 CRUD 저장소의 찾아보기 테이블

User 엔티티는 현재 enabled 또는 disabled 인 속성이 status입니다. 그러나 이러한 값은 다시 컴파일하지 않고 변경 가능해야하기 때문에이 값을 하드 코딩 할 수 없습니다. 그래서 나는 룩업 테이블이 모델에서 many-to-one 관계로 표현 된 status의 가능한 값을 포함하고 있다고 생각합니다. status 테이블에는 해당 상태의 자동 생성 기본 키를 참조하는 외래 키 열이있을 수 있습니다. 나는 이것이 ORM이 아닌 SQL 코딩에서 상당히 표준적인 것이라고 생각한다.

package com.example.model; 

import javax.persistence.Entity; 
import javax.persistence.Id; 

import com.fasterxml.jackson.annotation.JsonIgnore; 

@Entity 
public class Status { 

    @Id 
    @GeneratedValue(strategy = GenerationType.AUTO) 
    @JsonIgnore 
    private Long id; 

    private String name; 

    public Status() { 
    } 

    public Status(final String name) { 
     this.name = name; 
    } 

    @Override 
    public String toString() { 
     return String.format("Status[id='%d', name='%s', description='%s']", id, name); 
    } 

    @Override 
    public boolean equals(final Object obj) { 
     if (obj == null || !(obj instanceof Status)) { return false; } 
     final Status rhs = (Status) obj; 
     return new EqualsBuilder().append(name, rhs.getName()).build(); 
    } 

    @Override 
    public int hashCode() { 
     return new HashCodeBuilder().append(name).build(); 
    } 

    ...getters and setters... 

} 

및 UserRepository.java

:

사용자 모델 클래스, User.java :

package com.example.model; 

import javax.persistence.Column; 
import javax.persistence.Entity; 
import javax.persistence.GeneratedValue; 
import javax.persistence.GenerationType; 
import javax.persistence.Id; 
import javax.persistence.JoinColumn; 
import javax.persistence.ManyToOne; 

import org.apache.commons.lang3.builder.EqualsBuilder; 
import org.apache.commons.lang3.builder.HashCodeBuilder; 

import com.fasterxml.jackson.annotation.JsonIgnore; 

@Entity 
public class User { 

    @Id 
    @GeneratedValue(strategy = GenerationType.AUTO) 
    @JsonIgnore 
    private Long id; 

    @Column(nullable = false, updatable = false) 
    private String guid; 

    @ManyToOne 
    @JoinColumn(name = "status", nullable = false, updatable = false) 
    private Status status; 

    private String description; 

    public User() { 
    } 

    public User(final String guid) { 
     this.guid = guid; 
    } 

    @Override 
    public String toString() { 
     return String.format("User[id='%d', guid='%s', description='%s']", id, guid, description); 
    } 

    @Override 
    public boolean equals(final Object obj) { 
     if (obj == null || !(obj instanceof User)) { return false; } 
     final User rhs = (User) obj; 
     return new EqualsBuilder().append(guid, rhs.getGuid()).build(); 
    } 

    @Override 
    public int hashCode() { 
     return new HashCodeBuilder().append(guid).build(); 
    } 

    ...getters and setters... 

} 

및 중첩 모델 Status.java 여기 JPA 함께 할 내 시도이다

다음은 SQL 스키마입니다.

CREATE TABLE `status` (
    `id` bigint(20) NOT NULL AUTO_INCREMENT, 
    `name` varchar(255) DEFAULT NULL, 
    PRIMARY KEY (`id`) 
) ENGINE=InnoDB DEFAULT CHARSET=utf8; 


CREATE TABLE `user` (
    `id` bigint(20) NOT NULL AUTO_INCREMENT, 
    `description` varchar(255) DEFAULT NULL, 
    `guid` varchar(255) NOT NULL, 
    `status` bigint(20) NOT NULL, 
    PRIMARY KEY (`id`), 
    KEY `status_id` (`status`), 
    FOREIGN KEY (`status`) REFERENCES `status` (`id`) 
) ENGINE=InnoDB DEFAULT CHARSET=utf8; 

일부 테스트 행을 데이터베이스에 삽입하여 CRUD 저장소의 읽기 기능을 확인했습니다. 조회 테이블이 제대로 참조되고 있음을 알 수 있습니다. 우리는 새로운 사용자를 생성 POST JSON을 할 때

{ 
    "users": [ 
     { 
      "guid": "rick", 
      "status": { 
       "name": "enabled" 
      }, 
      "description": null 
     }, 
     { 
      "guid": "morty", 
      "status": { 
       "name": "disabled" 
      }, 
      "description": null 
     } 
    ], 
} 

문제는 온다 :

INSERT INTO `status` (`name`) VALUES 
    ('enabled'), 
    ('disabled'); 

INSERT INTO `user` (`guid`, `status`) 
    SELECT 'rick', `status`.`id` FROM `status` WHERE `status`.`name` = 'enabled'; 

INSERT INTO `user` (`guid`, `status`) 
    (SELECT 'morty', `status`.`id` FROM `status` WHERE `status`.`name` = 'disabled'); 

다음은 JSON 캐릭터 라인 출력합니다. 이 작품

{ 
    "guid": "jerry", 
    "status": { 
    "id": 2, 
    "name": "disabled" 
    } 
} 

하지만 결함이있다 : 나는 다음과 같은 JSON 본체를 사용할 수 있습니다. 구체적으로 상태의 ID를 전달합니다. 이 값은 시스템 내부에 있습니다. Google API 사용자가이 키를 추적하지 않아도되고 시스템에서 결과를 출력하지 못하도록합니다. 정말 조회 테이블, imho의 목적을 패배. 나는 사용자가 간단하게 전달할 수 있도록하는 것을 선호 : 그들은 단지 대신 "status":"disable"을 통과하고 자동으로 조회 테이블로 해결 할 수있는 경우

{ 
    "guid": "jerry", 
    "status": { 
    "name": "disabled" 
    } 
} 

나는 더 행복 할 것입니다.

{ 
    "guid": "jerry", 
    "status": "disabled" 
} 

하지만, 내 현재의 구성으로, JPA는 기본 키가 명시 적으로 전달되지 않은 이름 disabled으로 룩업 테이블의 기존 행을 사용한다는 것을 이해하지 못합니다.

2017-11-26 22:21:57.174 WARN 3748 --- [nio-8080-exec-7] o.h.a.i.UnresolvedEntityInsertActions : HHH000437: Attempting to save one or more entities that have a non-nullable association with an unsaved transient entity. The unsaved transient entity must be saved in an operation prior to saving these dependent entities. 
    Unsaved transient entity: ([com.example.model.Status#<null>]) 
    Dependent entities: ([[com.example.model.User#<null>]]) 
    Non-nullable association(s): ([com.example.model.User.status]) 
2017-11-26 22:21:57.213 ERROR 3748 --- [nio-8080-exec-7] o.a.c.c.C.[.[.[/].[dispatcherServlet] : Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.dao.InvalidDataAccessApiUsageException: org.hibernate.TransientPropertyValueException: Not-null property references a transient value - transient instance must be saved before current operation : com.example.model.User.status -> com.example.model.Status; nested exception is java.lang.IllegalStateException: org.hibernate.TransientPropertyValueException: Not-null property references a transient value - transient instance must be saved before current operation : com.example.model.User.status -> com.example.model.Status] with root cause 

org.hibernate.TransientPropertyValueException: Not-null property references a transient value - transient instance must be saved before current operation : com.example.model.User.status -> com.example.model.Status 
    at org.hibernate.action.internal.UnresolvedEntityInsertActions.checkNoUnresolvedActionsAfterOperation(UnresolvedEntityInsertActions.java:123) ~[hibernate-core-5.0.12.Final.jar:5.0.12.Final] 
    at org.hibernate.engine.spi.ActionQueue.checkNoUnresolvedActionsAfterOperation(ActionQueue.java:414) ~[hibernate-core-5.0.12.Final.jar:5.0.12.Final] 
    at org.hibernate.internal.SessionImpl.checkNoUnresolvedActionsAfterOperation(SessionImpl.java:619) ~[hibernate-core-5.0.12.Final.jar:5.0.12.Final] 
    at org.hibernate.internal.SessionImpl.firePersist(SessionImpl.java:777) ~[hibernate-core-5.0.12.Final.jar:5.0.12.Final] 
    at org.hibernate.internal.SessionImpl.persist(SessionImpl.java:748) ~[hibernate-core-5.0.12.Final.jar:5.0.12.Final] 
    at org.hibernate.internal.SessionImpl.persist(SessionImpl.java:753) ~[hibernate-core-5.0.12.Final.jar:5.0.12.Final] 
    at org.hibernate.jpa.spi.AbstractEntityManagerImpl.persist(AbstractEntityManagerImpl.java:1146) ~[hibernate-entitymanager-5.0.12.Final.jar:5.0.12.Final] 

는이 문제를 해결 나는 (CrudRepository를 확장)를 StatusRepository을 작성하고 명시 적으로 조회를 할 수 있지만, 속도가 느린이 모두 하나의 저장소를 호출하는 것보다 덜 우아 될 수 있습니다.

여러 저장소 호출없이 새 사용자를 만들 수 있고 사용자가 명시 적으로 ID를 전달하지 않아도되는 특수 효과 및/또는 기타 변경 사항은 무엇입니까?

공간을 절약하기 위해 일부 수업을 생략했지만 entire example project은 GitHub에서 찾을 수 있습니다.

+0

'Status.name' 열 ID를 만들 수 있다면 가능합니다. – 11thdimension

+0

@ 11thdension, 듣기 좋은 곳입니다. 이것이 어떻게 효과가 있을지에 대한 예를 지적 해 주시겠습니까? –

+0

'Status' 클래스의'name' 컬럼이 유일하다면 이것을'@ Id'로 표시하고'Long id' 필드를 제거 할 수 있습니다. 이렇게하면'{ "name": "disabled"}'를 보내면 JPA가 자체적으로 가져올 수 있습니다. – 11thdimension

답변

0

불행히도 키가 제공되지 않으므로 조회를 수행해야합니다.

하지만 L2 캐시 구성을 사용하여 성능 문제를 해결할 수 있습니다. Status 엔티티가 거의 변경되지 않는다는 점을 감안할 때 L2 캐시 스토리지를위한 이상적인 후보입니다. 이렇게하면 네트워크 및 데이터베이스 조회 비용이 발생하지 않습니다.

+0

룩업 테이블에서 수행하려는 것과 동일한 것을 달성하기위한 JPA 적절한 방법이 있습니까? JPA가 이것을 쉽게 수행 할 수 없다는 것은 매우 이상하게 보입니다. –

+0

"이상한"의미하는 것이 확실하지 않습니다. 엔티티를 다른 엔티티와 연결하려면 식별자가 있어야합니다. 사용자를 생성하고 상태 (PK가 아닌) 만 전달할 때 불행하게도 이름을 통해 상태를 조회해야하며 엔티티를 가져 와서 생성 전에 사용자와 연결해야합니다. 보안 목적으로 상태 PK를 프론트 엔드의 사용자로부터 숨기려면 간접 객체 참조를 사용할 수 있습니다. – Rentius2407

관련 문제