2017-11-27 11 views
0

JPA自動設定CRUDリポジトリ、Hibernate、およびMySQLでSpringブートを使用しようとしています。私は、私が期待していた方法で作業しているルックアップテーブルを得るのにいくつかの問題を抱えています。SpringブートのルックアップテーブルJPA Hibernate CRUDリポジトリ

Userエンティティには、という名前のプロパティがあり、現在のところenabledまたはdisabledのいずれかです。しかし、これらの値は再コンパイルなしで変更可能でなければならないため、これらの値をハードコーディングすることはできません。だから私は参照テーブルがUserモデルで多対1の関係として表現されるstatusの可能な値を含んでいると考えます。 statusテーブルには、問題のステータスの自動生成された主キーを参照する外部キー列を含めることができます。私はこれがORM以外のSQLコーディングではかなり標準的なものだと感じています。ここではJPAでこれを行うには、私の試みは次のとおりです。

ユーザモデルクラス、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:

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

ここに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" 
    } 
} 

これは機能しますが、欠陥があります。具体的には、ステータスのIDを渡しています。この値はシステム内部で設定されています。 GoogleのAPIユーザーがこのキーを把握する必要はなく、システムからは出力されません。それは実際にルックアップテーブルの目的を敗北させます。

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

彼らはただその代わり、"status":"disable"を渡すと、自動的にルックアップテーブルに解決することを持っていることができれば、私はもっと幸せになる:私は、ユーザーが簡単に通過させることを好むだろう。

{ 
    "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を作成し、明示的な検索を行うが、これはすべて1回のリポジトリの呼び出しでこれを行うよりも遅く、少ないエレガントだろうことができます。

複数のリポジトリを呼び出さずに新しいユーザーを作成できるように、ユーザーが明示的にIDを渡す必要がない、注釈やその他の変更は何ですか。

私はスペースを節約するためにいくつかのクラスを省略しましたが、entire example projectはGitHubにあります。

+0

「Status.name」列IDを作成できる場合は、それが可能です。 – 11thdimension

+0

@ 11th Dimension、それは聞いて良いです。どのように動作するかの例に向けて私を指摘できますか? –

+0

'Status'クラスの' name'カラムが一意の場合、これを '@ Id'でマークし、' Long id'フィールドを削除することができます。このように '{" name ":" disabled "}'を送信すると、JPAはそれ自身をフェッチすることができます。 – 11thdimension

答えて

0

残念ながら、キーが提供されていないため、ルックアップを行う必要があります。

L2キャッシュ構成を使用することで、パフォーマンス上の懸念を打ち消すことができます。 Statusエンティティが変更されることはめったにないので、L2キャッシュストレージの理想的な候補です。これにより、ネットワークとデータベースのルックアップコストが回避されます。

+0

私はルックアップテーブルでやろうとしているのと同じことを達成するためにJPAの適切な方法がいくつかありますか? JPAがこれを行う簡単な方法を持っていないことは非常に奇妙なようです。 –

+0

「奇妙な」とはどういう意味なのでしょうか。エンティティを別のエンティティに関連付ける場合は、識別子が必要です。ユーザーを作成し、ステータス(PKではない)だけを渡すときは残念ながらステータスを名前で検索し、エンティティを取得して作成前にそのユーザーに関連付ける必要があります。セキュリティ目的のためにフロントエンドのユーザーからステータスPKを非表示にする場合は、間接的なオブジェクト参照を使用できます。 – Rentius2407

関連する問題