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にあります。
「Status.name」列IDを作成できる場合は、それが可能です。 – 11thdimension
@ 11th Dimension、それは聞いて良いです。どのように動作するかの例に向けて私を指摘できますか? –
'Status'クラスの' name'カラムが一意の場合、これを '@ Id'でマークし、' Long id'フィールドを削除することができます。このように '{" name ":" disabled "}'を送信すると、JPAはそれ自身をフェッチすることができます。 – 11thdimension