【发布时间】:2020-05-26 14:01:51
【问题描述】:
我正在尝试在两个实体之间建立关系,即Subscription 和Delivery。两个实体都应该使用用户的 id 作为他们的主键(即两者都与User 存在一对一的关系,但Delivery 只使用也存在于Subscription 中的 id)。 Delivery 还使用属性 email 作为附加键(与用户 ID 一起建立复合主键)。虽然我将 eclipselink 用作 spring boot 的 jpa 后端,但在包含 jpa 存储库并显示以下错误消息时,我的应用程序在此定义中似乎没有问题。
错误信息:
Caused by: java.lang.IllegalArgumentException: Expected id attribute type [class com.tnt.entity.Subscription$DeliveryPK] on the existing id attribute [SingularAttributeImpl[EntityTypeImpl@482885994:Subscription [ javaType: class com.tnt.entity.Subscription descriptor: RelationalDescriptor(com.tnt.entity.Subscription --> [DatabaseTable(tbl_subscription)]), mappings: 6],org.eclipse.persistence.mappings.ManyToOneMapping[subscription]]] on the identifiable type [EntityTypeImpl@1615668218:Delivery [ javaType: class com.tnt.entity.Subscription$Delivery descriptor: RelationalDescriptor(com.tnt.entity.Subscription$Delivery --> [DatabaseTable(tbl_subscription_delivery)]), mappings: 2]] but found attribute type [class com.tnt.entity.Subscription].
at org.eclipse.persistence.internal.jpa.metamodel.IdentifiableTypeImpl.getId(IdentifiableTypeImpl.java:204) ~[org.eclipse.persistence.jpa-2.7.0.jar:na]
at org.springframework.data.jpa.repository.support.JpaMetamodelEntityInformation$IdMetadata.<init>(JpaMetamodelEntityInformation.java:262) ~[spring-data-jpa-2.2.3.RELEASE.jar:2.2.3.RELEASE]
at org.springframework.data.jpa.repository.support.JpaMetamodelEntityInformation.<init>(JpaMetamodelEntityInformation.java:88) ~[spring-data-jpa-2.2.3.RELEASE.jar:2.2.3.RELEASE]
at org.springframework.data.jpa.repository.support.JpaEntityInformationSupport.getEntityInformation(JpaEntityInformationSupport.java:66) ~[spring-data-jpa-2.2.3.RELEASE.jar:2.2.3.RELEASE]
at org.springframework.data.jpa.repository.support.JpaRepositoryFactory.getEntityInformation(JpaRepositoryFactory.java:211) ~[spring-data-jpa-2.2.3.RELEASE.jar:2.2.3.RELEASE]
at org.springframework.data.jpa.repository.support.JpaRepositoryFactory.getTargetRepository(JpaRepositoryFactory.java:161) ~[spring-data-jpa-2.2.3.RELEASE.jar:2.2.3.RELEASE]
at org.springframework.data.jpa.repository.support.JpaRepositoryFactory.getTargetRepository(JpaRepositoryFactory.java:144) ~[spring-data-jpa-2.2.3.RELEASE.jar:2.2.3.RELEASE]
at org.springframework.data.jpa.repository.support.JpaRepositoryFactory.getTargetRepository(JpaRepositoryFactory.java:69) ~[spring-data-jpa-2.2.3.RELEASE.jar:2.2.3.RELEASE]
at org.springframework.data.repository.core.support.RepositoryFactorySupport.getRepository(RepositoryFactorySupport.java:312) ~[spring-data-commons-2.2.3.RELEASE.jar:2.2.3.RELEASE]
at org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport.lambda$afterPropertiesSet$5(RepositoryFactoryBeanSupport.java:297) ~[spring-data-commons-2.2.3.RELEASE.jar:2.2.3.RELEASE]
at org.springframework.data.util.Lazy.getNullable(Lazy.java:212) ~[spring-data-commons-2.2.3.RELEASE.jar:2.2.3.RELEASE]
at org.springframework.data.util.Lazy.get(Lazy.java:94) ~[spring-data-commons-2.2.3.RELEASE.jar:2.2.3.RELEASE]
at org.springframework.data.repository.core.support.RepositoryFactoryBeanSupport.afterPropertiesSet(RepositoryFactoryBeanSupport.java:300) ~[spring-data-commons-2.2.3.RELEASE.jar:2.2.3.RELEASE]
at org.springframework.data.jpa.repository.support.JpaRepositoryFactoryBean.afterPropertiesSet(JpaRepositoryFactoryBean.java:121) ~[spring-data-jpa-2.2.3.RELEASE.jar:2.2.3.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1855) ~[spring-beans-5.2.2.RELEASE.jar:5.2.2.RELEASE]
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1792) ~[spring-beans-5.2.2.RELEASE.jar:5.2.2.RELEASE]
... 88 common frames omitted
实体:
package com.tnt.entity;
import com.fasterxml.jackson.annotation.JsonIgnore;
import javax.persistence.*;
import java.io.Serializable;
import java.util.Objects;
@Entity
@Table(name = "tbl_subscription")
public class Subscription {
@Column(name = "user_id")
@Id
private Long id;
@Column(name = "daily_report")
@Basic
private boolean receiveDailyReport;
@Column(name = "weekly_report")
@Basic
private boolean receiveWeeklyReport;
@Column(name = "monthly_report")
@Basic
private boolean receiveMonthlyReport;
@Column(name = "multi_report")
@Basic
private boolean multiReport;
@PrimaryKeyJoinColumn(name = "user_id")
@OneToOne(optional = false)
@JsonIgnore
private User user;
public Subscription() {
}
public Subscription(User user){
this.user = user;
this.receiveDailyReport = true;
}
@Entity
@Table(name = "tbl_subscription_delivery")
@IdClass(DeliveryPK.class)
public static class Delivery {
@JoinColumn(name = "subscription_id")
@ManyToOne
@Id
private Subscription subscription;
@Column(name = "email")
@Id
private String email;
public Delivery() {
}
public Delivery(Subscription subscription, String email){
this.subscription = subscription;
this.email = email;
}
public Subscription getSubscription() {
return subscription;
}
public void setSubscription(Subscription subscription) {
this.subscription = subscription;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
// ... equals, hashCode
}
public static class DeliveryPK implements Serializable {
private Long subscription;
private String email;
public DeliveryPK() {
}
public Long getSubscription() {
return subscription;
}
public void setSubscription(Long subscription) {
this.subscription = subscription;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
// ... equals, hashCode
}
// ... getter, setter, equals, hashCode
}
存储库接口:
interface DeliveryRepository : JpaRepository<Delivery, DeliveryPK> {
@Query("SELECT d FROM Delivery d WHERE d.subscription = :subscription AND d.email = :email")
fun findByEmail(subscription: Subscription, email: String): Optional<Delivery>
}
有人知道我做错了什么吗?
编辑:这个问题也可以用不同的方式提出——如果我在 SQL 中对此进行建模,它可能看起来像这样:
CREATE TABLE tbl_user (
id BIGINT NOT NULL PRIMARY KEY,
email VARCHAR(255) UNIQUE,
password VARCHAR(255),
salt VARCHAR(64)
);
CREATE TABLE tbl_subscription (
user_id BIGINT NOT NULL REFERENCES tbl_user(id),
daily_report BOOLEAN,
weekly_report BOOLEAN,
monthly_report BOOLEAN,
multi_report BOOLEAN,
PRIMARY KEY (user_id)
);
CREATE TABLE tbl_subscription_delivery (
subscription_id BIGINT NOT NULL REFERENCES tbl_subscription(user_id),
email VARCHAR(255),
PRIMARY KEY(email, subscription_id)
);
如何在 JPA 2.0 中对这种行为进行建模?
【问题讨论】:
-
您使用的是什么版本的 JPA?关系上的 PrimaryKeyJoinColumn 是 v1 要做的事情,它被 MapsId 注释 (eclipse.org/eclipselink/api/2.6/index.html?javax/persistence/…) 取代,这使得更清楚哪个映射控制该字段。您可以尝试将这些实体和 Pk 类放入它们自己的 java 类文件中,因为它会以某种方式混淆 PK 类和订阅类。
-
我使用的是 Eclipselink 2.7 版。我已经对 MapsId 进行了同样的尝试,但不幸的是收到了另一条错误消息:
java.lang.ClassCastException: org.eclipse.persistence.internal.jpa.metadata.accessors.mappings.IdAccessor cannot be cast to org.eclipse.persistence.internal.jpa.metadata.accessors.mappings.ObjectAccessor -
不管你信不信,但是将类分成单独的文件确实解决了我的问题
-
EclipseLink 中的注释处理出了点问题,以奇怪的不同方式出现,但通常围绕主键旋转。这是我的大部分评论,我只是将 mapsID 作为旁注加入 - 它确实会让你的代码更容易 IMO。使用 primarykeyjoin 列,我不知道有人真正意识到哪个映射实际设置了数据库中的值,并且在很多情况下它会导致混淆,因为 subscription.id != subscription.user.id 除非您自己修复问题.
-
Brian Vosburgh 的代码是我所建议的,应该对你有用(现在你已经将你的类分解成单独的文件)。如果不是,请发布另一个问题,其中包含正在使用的代码和异常。 MapsId 是 primarykeyjoincolumn hack 的直接替代品,允许您将 id 字段映射为基本字段并作为参考进行访问。我通常只是将关系标记为 ID 并取消基本映射,但在某些情况下我需要访问 ID 字符串并且不想强制获取整个引用对象。
标签: java jpa spring-data-jpa eclipselink