java - Spring Boot JPA Hibernate CRUD 存储库中的查找表

标签 java hibernate jpa spring-boot spring-data-jpa

我正在尝试将 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 用户必须跟踪此 key ,并且我们的系统不会输出它。恕我直言,它确实有点违背了查找表的目的。我宁愿让用户简单地传递:

{
  "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?

请注意,我省略了一些类以节省空间,但 entire example project可以在 GitHub 上找到。

最佳答案

不幸的是,由于未提供 key ,您将必须进行查找。

但是您可以通过使用 L2 缓存配置来消除性能问题。鉴于您的 Status 实体很少发生变化,因此它们是 L2 缓存存储的理想候选者。这可以防止网络和数据库查找成本。

关于java - Spring Boot JPA Hibernate CRUD 存储库中的查找表,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/47514229/

相关文章:

java - 有什么方法可以在 Android 上更快地压缩和保存图像?

java - 一对多关系的唯一约束违规

java.lang.IllegalStateException : No transactional EntityManager available

java - 无法反序列化],根本原因是 java.io.EOFException : null

java - 为kafka主题配置ACL

Java jTable 来自 ArrayList 对象的数据并通过其他方法更新

java - 如何将名称列表扫描到数组中?

java - 何时使用 Hibernate 缓存(二级)?

java - 在 Java 中存储和检索 copmosit 对象

hibernate - 消除编译时错误的问题-Springs MVC