Ich versuche Spring Boot mit JPA-autokonfigurierten CRUD-Repositorys, Hibernate und MySQL zu verwenden. Ich habe ein paar Probleme damit, dass das Lookup-Tab so funktioniert, wie ich es erwarte.Nachschlagetabellen in Spring Boot JPA Hibernate CRUD-Repositorys
Die User
Entität hat eine Eigenschaft namens status
, die derzeit entweder enabled
oder disabled
ist. Ich kann diese Werte jedoch nicht hart codieren, da sie ohne Neukompilierung änderbar sein müssen. So stelle ich fest, dass eine Nachschlagetabelle die möglichen Werte von status
enthält, dargestellt als eine Viele-zu-Eins-Beziehung auf dem Modell User
. Die Tabelle status
kann eine Fremdschlüsselspalte enthalten, die auf den automatisch generierten Primärschlüssel des betreffenden Status verweist. Ich habe das Gefühl, dass dies ziemlich Standard in der Nicht-ORM-SQL-Codierung ist. Hier ist mein Versuch, dies mit JPA zu tun:
der Benutzer Modellklasse, 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...
}
und verschachteltes Modell 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...
}
und UserRepository.java
package com.example.repository;
import org.springframework.data.repository.CrudRepository;
import com.example.model.User;
public interface UserRepository extends CrudRepository<User, Long> {
boolean existsByGuid(String guid);
User findByGuid(String guid);
boolean deleteByGuid(String guid);
}
Und hier ist das SQL-Schema:
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;
Ich habe einige Testzeilen in die Datenbank eingefügt, um die Lesefunktion des CRUD-Repositorys zu überprüfen. Ich kann sehen, dass die Nachschlagetabelle richtig referenziert wird.
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');
Hier ist die JSON-Zeichenfolge Ausgabe:
{
"users": [
{
"guid": "rick",
"status": {
"name": "enabled"
},
"description": null
},
{
"guid": "morty",
"status": {
"name": "disabled"
},
"description": null
}
],
}
Das Problem kommt, wenn wir zu POST JSON wollen einen neuen Benutzer zu erstellen. Ich kann einen JSON-Körper wie den folgenden verwenden:
{
"guid": "jerry",
"status": {
"id": 2,
"name": "disabled"
}
}
Das funktioniert, aber es hat einen Fehler. Es gibt speziell die ID des Status weiter. Dieser Wert ist intern in unserem System. Wir möchten nicht, dass unsere API-Benutzer diesen Schlüssel im Auge behalten müssen, und unser System gibt ihn nicht aus. Es verleugnet irgendwie den Zweck der Nachschlagetabelle, imho. Ich würde es vorziehen, der Benutzer einfach passieren zu lassen:
{
"guid": "jerry",
"status": {
"name": "disabled"
}
}
ich wäre noch glücklicher, wenn sie nur "status":"disable"
passieren könnten, statt und haben das automatisch in die Lookup-Tabelle zu beheben.
{
"guid": "jerry",
"status": "disabled"
}
jedoch mit meinen aktuellen Konfigurationen, JPA nicht verstehen, dass es eine vorhandene Zeile in der Lookup-Tabelle mit dem disabled
Namen verwenden soll, wenn der Primärschlüssel nicht explizit übergeben wird.
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]
Als Abhilfe kann, kann ich eine StatusRepository schaffen (die CrudRepository erstreckt) und eine explizite Lookup tun, aber dies wäre langsamer und weniger elegant als das alles in einer Repository Aufruf zu tun.
Bitte, was sind die Annotation (en) und/oder welche anderen Änderungen, die mir erlauben, einen neuen Benutzer ohne mehrere Repository-Aufrufe zu erstellen, und ohne dass der Benutzer die ID explizit übergeben muss?
Bitte beachten Sie, dass ich einige Klassen weggelassen habe, um Platz zu sparen, aber die entire example project kann auf GitHub gefunden werden.
Wenn Sie die Spaltennummer "Status.name" dann machen können, wäre es möglich. – 11thdimension
@ 11.Dimension, das ist gut zu hören. Können Sie mir ein Beispiel dafür geben, wie das funktionieren würde? –
Wenn die Spalte 'name' in der Klasse' Status' eindeutig ist, können Sie dies mit '@ Id' markieren und das Feld' Lange ID' entfernen. Auf diese Weise, wenn Sie den '{" name ":" disabled "}' JPA senden, wäre es in der Lage, es selbst zu holen. – 11thdimension