簡體   English   中英

Spring Boot JPA Hibernate CRUD 存儲庫中的查找表

[英]Lookup tables in Spring Boot JPA Hibernate CRUD repositories

我正在嘗試將 Spring Boot 與 JPA 自動配置的 CRUD 存儲庫、Hibernate 和 MySQL 一起使用。 我在按照我期望的方式進行查找時遇到了一些麻煩。

User實體有一個名為status的屬性,它當前是enableddisabled 但是,我無法對這些值進行硬編碼,因為它們必須可以在不重新編譯的情況下進行更改。 所以我想一個查找表包含status的可能值,在User模型上表示為多對一的關系。 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

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);

}

這是 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 存儲庫的讀取功能。 我可以看到查找表被正確引用。

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 字符串化的輸出:

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

當我們想要 POST JSON 來創建一個新用戶時,問題就出現了。 我可以使用如下的 JSON 正文:

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

這有效,但它有一個缺陷。 它專門傳遞狀態的ID。 這個值是我們系統的內部值。 我們不希望我們的 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]

作為一種解決方法,我可以創建一個 StatusRepository(它擴展 CrudRepository)並進行顯式查找,但這比在一個存儲庫調用中完成所有這些操作要慢且不優雅。

請問,注釋和/或任何其他更改是什么,它可以讓我在沒有多次存儲庫調用的情況下創建一個新用戶,並且用戶不必顯式傳遞 ID?

請注意,為了節省空間,我省略了一些類,但可以在 GitHub 上找到整個示例項目

不幸的是,由於未提供密鑰,您將不得不進行查找。

但是您可以通過使用 L2 緩存配置來抵消性能問題。 鑒於您的Status實體很少更改,它們是 L2 緩存存儲的理想候選者。 這可以防止網絡和數據庫查找成本。

據我所知,有幾種選擇。

  1. 使用 spring 數據 jpa getOne方法。 或者它在休眠中 對應
  2. 使用“字符串名稱”作為@Id 或@NaturalId。
  3. Hibernate 支持子實體的持久化,即使它是一個全新的實體但帶有 id,即user.setStatus(new Status(1, "disabled")) 因此,您可以自己緩存這些值。 並在您自己的緩存中查找它們。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM