我正在尝试将Spring Boot与JPA自动配置的CRUD存储库,Hibernate和MySQL一起使用。我在查找表格方面遇到了一些麻烦,我希望这样做。
User
实体有一个名为status
的属性,该属性目前为enabled
或disabled
。但是,我不能对这些值进行硬编码,因为它们必须可以在不重新编译的情况下进行更改。所以我认为查找表包含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用户必须跟踪此密钥,我们的系统不会输出它。它真的有点挫败了查找表的目的,imho。我宁愿让用户简单地通过:
{
"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上找到entire example project。
答案 0 :(得分:0)
不幸的是,由于没有提供密钥,您将不得不进行查找。
但您可以通过使用L2缓存配置来抵消您所遇到的性能问题。鉴于您的Status
实体很少更改,它们是L2缓存存储的理想候选者。这可以防止网络和数据库查找成本。
答案 1 :(得分:0)
据我所知,有几种选择。
user.setStatus(new Status(1, "disabled"))
。因此,您可以自己缓存值。并在您自己的缓存中查找它们。