【发布时间】:2018-07-05 17:40:53
【问题描述】:
这里是 Spring Boot/Hibernate/JPA/MySQL。我有以下两个 JPA 实体:
@MappedSuperclass
public abstract class BaseEntity {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String refId;
}
@MappedSuperclass
public abstract class BaseLookup extends BaseEntity {
@JsonIgnore
@NotNull
private String name;
@NotNull
private String label;
@JsonIgnore
@NotNull
private String description;
}
@Entity
@Table(name = "device_systems")
@AttributeOverrides({
@AttributeOverride(name = "id", column=@Column(name="device_system_id")),
@AttributeOverride(name = "refId", column=@Column(name="device_system_ref_id")),
@AttributeOverride(name = "name", column=@Column(name="device_system_name")),
@AttributeOverride(name = "label", column=@Column(name="device_system_label")),
@AttributeOverride(name = "description", column=@Column(name="device_system_description"))
})
public class DeviceSystem extends BaseLookup {
}
@Entity
@Table(name = "devices")
@AttributeOverrides({
@AttributeOverride(name = "id", column=@Column(name="device_id")),
@AttributeOverride(name = "refId", column=@Column(name="device_ref_id"))
})
@JsonDeserialize(using = DeviceDeserializer.class)
class Device extends BaseEntity {
@Column(name = "device_app_version")
private String appVersion;
@OneToOne(fetch = FetchType.EAGER, cascade = [CascadeType.PERSIST, CascadeType.MERGE])
@JoinColumn(name = "device_system_id", referencedColumnName = "device_system_id")
@NotNull
@Valid
private DeviceSystem system;
@Column(name = "device_system_version")
private String systemVersion;
@Column(name = "device_model")
private String model;
}
下面是CrudRepository"s 为他们:
public interface DevicePersistor extends CrudRepository<Device,Long> {
}
public interface DeviceSystemPersistor extends CrudRepository<DeviceSystem,Long> {
@Query("FROM DeviceSystem WHERE label = 'ANDROID'")
public DeviceSystem android();
@Query("FROM DeviceSystem WHERE label = 'iOS'")
public DeviceSystem iOS();
@Query("FROM DeviceSystem WHERE name = :name")
public DeviceSystem findByName(@Param(value = "name") String name);
}
这是我运行select * from device_systems;(这是 MySQL)时的结果:
mysql> select * from device_systems;
+------------------+--------------------------------------+--------------------+---------------------+-------------------------------+
| device_system_id | device_system_ref_id | device_system_name | device_system_label | device_system_description |
+------------------+--------------------------------------+--------------------+---------------------+-------------------------------+
| 1 | 5e2bc70b-d570-43c7-b420-9add794f7c76 | Android | ANDROID | Google/Android based devices. |
| 2 | 312d82fa-b0db-4c9a-a356-4e2610373f3f | iOS | iOS | Apple/iOS based devices. |
device_systems 表包含静态/查找/参考数据,因此该表中应该有两条且只有两条(曾经)记录。在我的应用中,用户可以使用以下 curl 命令创建新设备:
curl -k -i -H "Content-Type: application/json" -X POST \
-d '{ "appVersion" : "0.0.1", "system" : "Android", "systemVersion" : "7.0", "model" : "Samsung Galaxy S7 Edge" }' \
https://localhost:9200/v1/devices
DeviceDeserializer 看起来像:
public class DeviceDeserializer extends JsonDeserializer<Device> {
@Autowired
private DeviceSystemPersistor deviceSystemPersistor;
@Override
public Device deserialize(JsonParser jsonParser, DeserializationContext ctxt) throws IOException, JsonProcessingException {
JsonNode deviceNode = jsonParser.readValueAsTree();
String appVersion = deviceNode.get("appVersion").asText();
// Lookup the DeviceSystem record/entity/instance by the name provided in the JSON.
// This is because the user can only specify 'Android' or 'iOS'; no other values allowed!
DeviceSystem deviceSystem = deviceSystemPersistor.findByName(deviceNode.get("system").asText());
String systemVersion = deviceNode.get("systemVersion").asText();
String model = deviceNode.get("model").asText();
return new Device(appVersion, deviceSystem, systemVersion, model);
}
}
处理该 POST 的 DeviceController 方法如下所示:
@PostMapping
public void saveDevice(@RequestBody Device device) {
devicePersistor.save(device);
}
在运行时,当我运行该 curl 命令时,出现以下异常:
nested exception is org.hibernate.PersistentObjectException: detached entity passed to persist: com.myapp.entities.DeviceSystem
at org.springframework.orm.jpa.vendor.HibernateJpaDialect.convertHibernateAccessException(HibernateJpaDialect.java:299)
at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:244)
at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.translateExceptionIfPossible(AbstractEntityManagerFactoryBean.java:488)
at org.springframework.dao.support.ChainedPersistenceExceptionTranslator.translateExceptionIfPossible(ChainedPersistenceExceptionTranslator.java:59)
at org.springframework.dao.support.DataAccessUtils.translateIfNecessary(DataAccessUtils.java:213)
at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:147)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:133)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:92)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
堆栈跟踪很大,但表明 devicePersistor.save(device) 调用是 PersistentObjectException 的来源...
它听起来像 JPA/Hibernate 不喜欢这样一个事实,即我试图坚持一个 Device 与现有的 DeviceSystem 相关联。需要明确的是,我不是试图创建一个新的DeviceSystem 实例,我只是试图保存我的新Device 实例以与一个现有的关联DeviceSystem。 如何以正确的“JPA”方式执行此操作?
【问题讨论】:
标签: mysql hibernate jpa spring-boot spring-data-jpa