2017-11-27 3 views
0

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.

+0

Wenn Sie die Spaltennummer "Status.name" dann machen können, wäre es möglich. – 11thdimension

+0

@ 11.Dimension, das ist gut zu hören. Können Sie mir ein Beispiel dafür geben, wie das funktionieren würde? –

+0

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

Antwort

0

Leider müssen Sie die Suche durchführen, da der Schlüssel nicht zur Verfügung gestellt wird.

Aber Sie können den Leistungsbedenken entgegenwirken, die Sie haben, indem Sie eine L2-Cache-Konfiguration verwenden. Da sich Ihre Status Entitäten selten ändern, sind sie ein idealer Kandidat für den L2-Cache-Speicher. Dies verhindert die Netzwerk- und Datenbanksuchkosten.

+0

Gibt es noch etwas mehr JPA, um das gleiche zu erreichen, was ich mit meiner Lookup-Tabelle versuche? Es scheint sehr merkwürdig, dass JPA keinen einfachen Weg dazu hätte. –

+0

Nicht sicher, was "seltsam" bedeutet. Wenn Sie eine Entität mit einer anderen verknüpfen möchten, müssen Sie eine Kennung haben. Wenn Sie einen Benutzer erstellen und nur den Namen (der nicht die PK ist) des Status übergeben, müssen Sie leider Status über den Namen suchen, die Entität holen und sie dem Benutzer vor der Erstellung zuordnen. Wenn Sie aus Sicherheitsgründen den Status PK vom Benutzer im Front-End ausblenden möchten, können Sie die indirekte Objektreferenz verwenden. – Rentius2407

Verwandte Themen