使用 Cascade ALL 处理 OneToMany 关系的子实体时出现问题
Problem manipulating child entity of a OneToMany relation with Cascade ALL
我在使用双向关系 (OneToMany - ManyToOne) 时遇到性能问题。
我有一个实体 Interaction
可以有很多 ClassifiedGroup
(请参阅下面的 类 了解更多详细信息)但我有一个特定的用例,我需要删除几个 ClassifiedGroups 和添加其他一些。
问题是,当我使用 InteractionRepository.java
本身(一个简单的 JpaRepository)来保存相关 ClassifiedGroup
的所有修改时,代码将不得不从与一个链接的数据库中获取所有组相互作用。它还将获取 Interaction
的许多其他属性,这些属性也可能很昂贵。我不想将 Interaction
实体操作为 delete/create new ClassifiedGroup
但我也希望将级联类型设置为 ALL
因为我在交互时创建了第一个组已创建,如果我删除交互本身,则应删除所有组。
我试图为 ClassifiedGroup
实体创建一个 JpaRepository
并直接操作它,但我在尝试保存它时遇到错误:
代码:
List<ClassifiedGroup> toAdd = generate(); // generate list to add
toAdd.forEach(g -> g.setInteraction(interaction));
this.classifiedGroupRepository.saveAll(toAdd);
错误:
org.springframework.beans.ConversionNotSupportedException: Failed to convert property value of type 'java.util.UUID' to required type 'br.com.stilingue.smartcare.entities.Interaction' for property 'interaction'; nested exception is java.lang.IllegalStateException: Cannot convert value of type 'java.util.UUID' to required type 'br.com.stilingue.smartcare.entities.Interaction' for property 'interaction': no matching editors or conversion strategy found
at org.springframework.beans.AbstractNestablePropertyAccessor.convertIfNecessary(AbstractNestablePropertyAccessor.java:595)
at org.springframework.beans.AbstractNestablePropertyAccessor.convertForProperty(AbstractNestablePropertyAccessor.java:609)
at org.springframework.beans.AbstractNestablePropertyAccessor.processLocalProperty(AbstractNestablePropertyAccessor.java:458)
at org.springframework.beans.AbstractNestablePropertyAccessor.setPropertyValue(AbstractNestablePropertyAccessor.java:278)
at org.springframework.beans.AbstractNestablePropertyAccessor.setPropertyValue(AbstractNestablePropertyAccessor.java:246)
at org.springframework.data.util.DirectFieldAccessFallbackBeanWrapper.setPropertyValue(DirectFieldAccessFallbackBeanWrapper.java:75)
at org.springframework.data.jpa.repository.support.JpaMetamodelEntityInformation$IdentifierDerivingDirectFieldAccessFallbackBeanWrapper.setPropertyValue(JpaMetamodelEntityInformation.java:367)
at org.springframework.data.jpa.repository.support.JpaMetamodelEntityInformation.getId(JpaMetamodelEntityInformation.java:175)
at org.springframework.data.repository.core.support.AbstractEntityInformation.isNew(AbstractEntityInformation.java:46)
at org.springframework.data.jpa.repository.support.JpaMetamodelEntityInformation.isNew(JpaMetamodelEntityInformation.java:246)
at org.springframework.data.jpa.repository.support.SimpleJpaRepository.save(SimpleJpaRepository.java:596)
at org.springframework.data.jpa.repository.support.SimpleJpaRepository.saveAll(SimpleJpaRepository.java:631)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:564)
at org.springframework.data.repository.core.support.RepositoryMethodInvoker$RepositoryFragmentMethodInvoker.lambda$new[=11=](RepositoryMethodInvoker.java:289)
at org.springframework.data.repository.core.support.RepositoryMethodInvoker.doInvoke(RepositoryMethodInvoker.java:137)
at org.springframework.data.repository.core.support.RepositoryMethodInvoker.invoke(RepositoryMethodInvoker.java:121)
at org.springframework.data.repository.core.support.RepositoryComposition$RepositoryFragments.invoke(RepositoryComposition.java:529)
at org.springframework.data.repository.core.support.RepositoryComposition.invoke(RepositoryComposition.java:285)
at org.springframework.data.repository.core.support.RepositoryFactorySupport$ImplementationMethodExecutionInterceptor.invoke(RepositoryFactorySupport.java:599)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.doInvoke(QueryExecutorMethodInterceptor.java:163)
at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.invoke(QueryExecutorMethodInterceptor.java:138)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor.invoke(DefaultMethodInvokingMethodInterceptor.java:80)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.transaction.interceptor.TransactionInterceptor.proceedWithInvocation(TransactionInterceptor.java:123)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:388)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:137)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:174)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:97)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:215)
at com.sun.proxy.$Proxy197.saveAll(Unknown Source)
at br.com.stilingue.smartcare.event.interaction.update.strategy.impl.InteractionStilingueArrayUpdateStrategy.updateInteraction(InteractionStilingueArrayUpdateStrategy.java:78)
at br.com.stilingue.smartcare.event.interaction.update.strategy.impl.InteractionStilingueArrayUpdateStrategy$$FastClassBySpringCGLIB$$b5a01170.invoke(<generated>)
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:779)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:750)
at org.springframework.transaction.interceptor.TransactionInterceptor.proceedWithInvocation(TransactionInterceptor.java:123)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:388)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:750)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:692)
at br.com.stilingue.smartcare.event.interaction.update.strategy.impl.InteractionStilingueArrayUpdateStrategy$$EnhancerBySpringCGLIB$a4de66a.updateInteraction(<generated>)
at br.com.stilingue.smartcare.event.interaction.update.strategy.impl.InteractionStilingueArrayUpdateStrategyIntegrationTest.testShouldAddNewGroups(InteractionStilingueArrayUpdateStrategyIntegrationTest.java:114)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:564)
at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:688)
at org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60)
at org.junit.jupiter.engine.execution.InvocationInterceptorChain$ValidatingInvocation.proceed(InvocationInterceptorChain.java:131)
at org.junit.jupiter.engine.extension.TimeoutExtension.intercept(TimeoutExtension.java:149)
at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestableMethod(TimeoutExtension.java:140)
at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestMethod(TimeoutExtension.java:84)
at org.junit.jupiter.engine.execution.ExecutableInvoker$ReflectiveInterceptorCall.lambda$ofVoidMethod[=11=](ExecutableInvoker.java:115)
at org.junit.jupiter.engine.execution.ExecutableInvoker.lambda$invoke[=11=](ExecutableInvoker.java:105)
at org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:106)
at org.junit.jupiter.engine.execution.InvocationInterceptorChain.proceed(InvocationInterceptorChain.java:64)
at org.junit.jupiter.engine.execution.InvocationInterceptorChain.chainAndInvoke(InvocationInterceptorChain.java:45)
at org.junit.jupiter.engine.execution.InvocationInterceptorChain.invoke(InvocationInterceptorChain.java:37)
at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:104)
at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:98)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod(TestMethodTestDescriptor.java:210)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:206)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:131)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:65)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively(NodeTestTask.java:139)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively(NodeTestTask.java:129)
at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively(NodeTestTask.java:127)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:126)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:84)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively(NodeTestTask.java:143)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively(NodeTestTask.java:129)
at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively(NodeTestTask.java:127)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:126)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:84)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively(NodeTestTask.java:143)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively(NodeTestTask.java:129)
at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively(NodeTestTask.java:127)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:126)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:84)
at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:32)
at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57)
at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:51)
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:108)
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:88)
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.lambda$execute[=11=](EngineExecutionOrchestrator.java:54)
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.withInterceptedStreams(EngineExecutionOrchestrator.java:67)
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:52)
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:96)
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:75)
at com.intellij.junit5.JUnit5IdeaTestRunner.startRunnerWithArgs(JUnit5IdeaTestRunner.java:71)
at com.intellij.rt.junit.IdeaTestRunner$Repeater.execute(IdeaTestRunner.java:38)
at com.intellij.rt.execution.junit.TestsRepeater.repeat(TestsRepeater.java:11)
at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:35)
at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:235)
at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:54)
Caused by: java.lang.IllegalStateException: Cannot convert value of type 'java.util.UUID' to required type 'br.com.stilingue.smartcare.entities.Interaction' for property 'interaction': no matching editors or conversion strategy found
at org.springframework.beans.TypeConverterDelegate.convertIfNecessary(TypeConverterDelegate.java:262)
at org.springframework.beans.AbstractNestablePropertyAccessor.convertIfNecessary(AbstractNestablePropertyAccessor.java:590)
... 120 more
我发现了一些其他问题,如果我在父实体中有 CascadeType.PERSIST
,我将无法直接操作子实体,因为父实体将是子实体的“所有者”子实体。
但是我要问:有什么办法可以避免这个性能瓶颈吗?我的交互实体还有其他一些 OneToOne
、ManyToMany
、ManyToOne
和 OneToMany
关系,每次我使用 InteractionRepository#save
时,所有关系都从数据库中获取。我真的需要自己 create/delete ClassifiedGroup
个实体。
我的类:
互动
package br.com.stilingue.smartcare.entities;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.google.cloud.spanner.hibernate.types.SpannerJsonType;
import java.sql.Timestamp;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import lombok.experimental.Accessors;
import org.hibernate.annotations.Type;
import org.hibernate.annotations.TypeDef;
@Data
@Entity
@Accessors(chain = true)
@Table(
name = "interactions",
uniqueConstraints = {@UniqueConstraint(columnNames = {"pid", "channel", "universe_id"})})
public class Interaction {
@Id
@GeneratedValue
@Type(type = "uuid-char")
@Column(name = "interaction_id", nullable = false)
private UUID interactionId;
@Column(name = "pid", nullable = false)
private String pid;
@Enumerated(EnumType.STRING)
@Column(name = "channel", nullable = false)
private Channel channel;
@Column(name = "universe_id", nullable = false)
private Long universeId;
@Column(name = "post_date", nullable = false)
private Timestamp postDate;
@ToString.Exclude
@EqualsAndHashCode.Exclude
@OneToMany(
mappedBy = "interaction",
cascade = CascadeType.ALL,
fetch = FetchType.LAZY,
orphanRemoval = true)
private Set<ClassifiedGroup> groups;
// many other relations/attributes not relevant for the issue, but they also are loaded when I use InteractionRepository#save method
// method when the Interaction is created the first time
public void setRelations() {
if (this.groups != null) {
this.groups.forEach(group -> group.setInteraction(this));
}
}
}
分类组
package br.com.stilingue.smartcare.entities;
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.FetchType;
import javax.persistence.Id;
import javax.persistence.IdClass;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.ToString;
import lombok.experimental.Accessors;
@Data
@Entity
@NoArgsConstructor
@Accessors(chain = true)
@Table(name = "classified_groups")
@IdClass(ClassifiedGroup.PrimaryKeys.class)
public class ClassifiedGroup {
@Data
public static class PrimaryKeys implements Serializable {
private Interaction interaction;
private String descriptor;
private String value;
}
public ClassifiedGroup(String group, ClassificationType type, String descriptor) {
this.value = group;
this.type = type;
this.descriptor = descriptor;
}
public ClassifiedGroup(String group, ClassificationType type) {
this(group, type, "");
}
@Id
@ToString.Exclude
@EqualsAndHashCode.Exclude
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(
name = "interaction_id",
referencedColumnName = "interaction_id",
insertable = false,
updatable = false)
private Interaction interaction;
@Enumerated(EnumType.STRING)
@Column(name = "type", nullable = false)
private ClassificationType type;
@Id
@Column(name = "value", nullable = false)
private String value;
@Id
@Column(name = "descriptor", nullable = false)
private String descriptor = "";
}
ClassifiedGroupRepository
package br.com.stilingue.smartcare.repositories;
import br.com.stilingue.smartcare.entities.ClassifiedGroup;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface ClassifiedGroupRepository
extends JpaRepository<ClassifiedGroup, ClassifiedGroup.PrimaryKeys> {}
InteractionRepository
package br.com.stilingue.smartcare.repositories;
import java.util.Optional;
import java.util.UUID;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface InteractionRepository extends JpaRepository<Interaction, UUID>{}
我目前正在使用 Hibernate 5.6.5.Final
。
您的映射不正确 - 您需要查看有关如何映射派生 ID 的 JPA 教程,但该错误会告诉您哪里出了问题,指向您的 ClassifiedGroup.interaction 映射。 PK class 必须使用交互的 ID(UUID),而不是交互实例本身。
public static class PrimaryKeys implements Serializable {
private UUID interaction;
private String descriptor;
private String value;
}
由于 JPA 需要将值插入定义的“interaction_id”fk,您在 ClassifiedGroup 中的映射应 可写,类似于:
@Id
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(
name = "interaction_id",
referencedColumnName = "interaction_id")
private Interaction interaction;
关于你的其他问题 - 是的,有很多方法可以处理你想要的东西,但这取决于你想要投入多少工作。让关系变得懒惰可以避免它们被不必要地获取,但是你然后必须考虑通过其他机制进行序列化和对分离实体的更改。我不相信 Spring/JPA 提供商在关系中收到空值时自动知道我想要做什么,因为可能是数据未序列化,或者可能是更改。因此,在关注性能的地方,我编写了自己的合并方法来处理接收序列化数据(比如来自 REST 接口)并确定它应该如何合并到托管实例中(如果存在)。
另请注意,CascadeType.ALL 可能不是您真正想要的。你所说的只是你想要的是对 Interaction.groups 列表的更改被拾取并且引用的 ClassifiedGroups 与交互一起被删除,并且作为一种说服力,你可以在一次调用中保留一个交互及其组列表。那只是 cascade={PERSIST, REMOVE}
。 ALL 包括合并、刷新和分离,它们会产生性能开销,如果您真的不需要它们,可能会让您感到悲伤。特别是合并 - 当您在其组列表中传递带有更改和分类组的交互时,这些分类组中与数据库中内容的任何差异都将被合并。这可能会导致过时数据覆盖等后果,当然会产生很大的开销,因为合并也会级联 ClassifiedGroup 引用。不要轻易使用级联选项。
我在使用双向关系 (OneToMany - ManyToOne) 时遇到性能问题。
我有一个实体 Interaction
可以有很多 ClassifiedGroup
(请参阅下面的 类 了解更多详细信息)但我有一个特定的用例,我需要删除几个 ClassifiedGroups 和添加其他一些。
问题是,当我使用 InteractionRepository.java
本身(一个简单的 JpaRepository)来保存相关 ClassifiedGroup
的所有修改时,代码将不得不从与一个链接的数据库中获取所有组相互作用。它还将获取 Interaction
的许多其他属性,这些属性也可能很昂贵。我不想将 Interaction
实体操作为 delete/create new ClassifiedGroup
但我也希望将级联类型设置为 ALL
因为我在交互时创建了第一个组已创建,如果我删除交互本身,则应删除所有组。
我试图为 ClassifiedGroup
实体创建一个 JpaRepository
并直接操作它,但我在尝试保存它时遇到错误:
代码:
List<ClassifiedGroup> toAdd = generate(); // generate list to add
toAdd.forEach(g -> g.setInteraction(interaction));
this.classifiedGroupRepository.saveAll(toAdd);
错误:
org.springframework.beans.ConversionNotSupportedException: Failed to convert property value of type 'java.util.UUID' to required type 'br.com.stilingue.smartcare.entities.Interaction' for property 'interaction'; nested exception is java.lang.IllegalStateException: Cannot convert value of type 'java.util.UUID' to required type 'br.com.stilingue.smartcare.entities.Interaction' for property 'interaction': no matching editors or conversion strategy found
at org.springframework.beans.AbstractNestablePropertyAccessor.convertIfNecessary(AbstractNestablePropertyAccessor.java:595)
at org.springframework.beans.AbstractNestablePropertyAccessor.convertForProperty(AbstractNestablePropertyAccessor.java:609)
at org.springframework.beans.AbstractNestablePropertyAccessor.processLocalProperty(AbstractNestablePropertyAccessor.java:458)
at org.springframework.beans.AbstractNestablePropertyAccessor.setPropertyValue(AbstractNestablePropertyAccessor.java:278)
at org.springframework.beans.AbstractNestablePropertyAccessor.setPropertyValue(AbstractNestablePropertyAccessor.java:246)
at org.springframework.data.util.DirectFieldAccessFallbackBeanWrapper.setPropertyValue(DirectFieldAccessFallbackBeanWrapper.java:75)
at org.springframework.data.jpa.repository.support.JpaMetamodelEntityInformation$IdentifierDerivingDirectFieldAccessFallbackBeanWrapper.setPropertyValue(JpaMetamodelEntityInformation.java:367)
at org.springframework.data.jpa.repository.support.JpaMetamodelEntityInformation.getId(JpaMetamodelEntityInformation.java:175)
at org.springframework.data.repository.core.support.AbstractEntityInformation.isNew(AbstractEntityInformation.java:46)
at org.springframework.data.jpa.repository.support.JpaMetamodelEntityInformation.isNew(JpaMetamodelEntityInformation.java:246)
at org.springframework.data.jpa.repository.support.SimpleJpaRepository.save(SimpleJpaRepository.java:596)
at org.springframework.data.jpa.repository.support.SimpleJpaRepository.saveAll(SimpleJpaRepository.java:631)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:564)
at org.springframework.data.repository.core.support.RepositoryMethodInvoker$RepositoryFragmentMethodInvoker.lambda$new[=11=](RepositoryMethodInvoker.java:289)
at org.springframework.data.repository.core.support.RepositoryMethodInvoker.doInvoke(RepositoryMethodInvoker.java:137)
at org.springframework.data.repository.core.support.RepositoryMethodInvoker.invoke(RepositoryMethodInvoker.java:121)
at org.springframework.data.repository.core.support.RepositoryComposition$RepositoryFragments.invoke(RepositoryComposition.java:529)
at org.springframework.data.repository.core.support.RepositoryComposition.invoke(RepositoryComposition.java:285)
at org.springframework.data.repository.core.support.RepositoryFactorySupport$ImplementationMethodExecutionInterceptor.invoke(RepositoryFactorySupport.java:599)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.doInvoke(QueryExecutorMethodInterceptor.java:163)
at org.springframework.data.repository.core.support.QueryExecutorMethodInterceptor.invoke(QueryExecutorMethodInterceptor.java:138)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.data.projection.DefaultMethodInvokingMethodInterceptor.invoke(DefaultMethodInvokingMethodInterceptor.java:80)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.transaction.interceptor.TransactionInterceptor.proceedWithInvocation(TransactionInterceptor.java:123)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:388)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.dao.support.PersistenceExceptionTranslationInterceptor.invoke(PersistenceExceptionTranslationInterceptor.java:137)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.data.jpa.repository.support.CrudMethodMetadataPostProcessor$CrudMethodMetadataPopulatingMethodInterceptor.invoke(CrudMethodMetadataPostProcessor.java:174)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.interceptor.ExposeInvocationInterceptor.invoke(ExposeInvocationInterceptor.java:97)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:215)
at com.sun.proxy.$Proxy197.saveAll(Unknown Source)
at br.com.stilingue.smartcare.event.interaction.update.strategy.impl.InteractionStilingueArrayUpdateStrategy.updateInteraction(InteractionStilingueArrayUpdateStrategy.java:78)
at br.com.stilingue.smartcare.event.interaction.update.strategy.impl.InteractionStilingueArrayUpdateStrategy$$FastClassBySpringCGLIB$$b5a01170.invoke(<generated>)
at org.springframework.cglib.proxy.MethodProxy.invoke(MethodProxy.java:218)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.invokeJoinpoint(CglibAopProxy.java:779)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:163)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:750)
at org.springframework.transaction.interceptor.TransactionInterceptor.proceedWithInvocation(TransactionInterceptor.java:123)
at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:388)
at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:119)
at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:186)
at org.springframework.aop.framework.CglibAopProxy$CglibMethodInvocation.proceed(CglibAopProxy.java:750)
at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:692)
at br.com.stilingue.smartcare.event.interaction.update.strategy.impl.InteractionStilingueArrayUpdateStrategy$$EnhancerBySpringCGLIB$a4de66a.updateInteraction(<generated>)
at br.com.stilingue.smartcare.event.interaction.update.strategy.impl.InteractionStilingueArrayUpdateStrategyIntegrationTest.testShouldAddNewGroups(InteractionStilingueArrayUpdateStrategyIntegrationTest.java:114)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:564)
at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:688)
at org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60)
at org.junit.jupiter.engine.execution.InvocationInterceptorChain$ValidatingInvocation.proceed(InvocationInterceptorChain.java:131)
at org.junit.jupiter.engine.extension.TimeoutExtension.intercept(TimeoutExtension.java:149)
at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestableMethod(TimeoutExtension.java:140)
at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestMethod(TimeoutExtension.java:84)
at org.junit.jupiter.engine.execution.ExecutableInvoker$ReflectiveInterceptorCall.lambda$ofVoidMethod[=11=](ExecutableInvoker.java:115)
at org.junit.jupiter.engine.execution.ExecutableInvoker.lambda$invoke[=11=](ExecutableInvoker.java:105)
at org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:106)
at org.junit.jupiter.engine.execution.InvocationInterceptorChain.proceed(InvocationInterceptorChain.java:64)
at org.junit.jupiter.engine.execution.InvocationInterceptorChain.chainAndInvoke(InvocationInterceptorChain.java:45)
at org.junit.jupiter.engine.execution.InvocationInterceptorChain.invoke(InvocationInterceptorChain.java:37)
at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:104)
at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:98)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod(TestMethodTestDescriptor.java:210)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:206)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:131)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:65)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively(NodeTestTask.java:139)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively(NodeTestTask.java:129)
at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively(NodeTestTask.java:127)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:126)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:84)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively(NodeTestTask.java:143)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively(NodeTestTask.java:129)
at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively(NodeTestTask.java:127)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:126)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:84)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1511)
at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:38)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively(NodeTestTask.java:143)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively(NodeTestTask.java:129)
at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively(NodeTestTask.java:127)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:126)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:84)
at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:32)
at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57)
at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:51)
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:108)
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:88)
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.lambda$execute[=11=](EngineExecutionOrchestrator.java:54)
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.withInterceptedStreams(EngineExecutionOrchestrator.java:67)
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:52)
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:96)
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:75)
at com.intellij.junit5.JUnit5IdeaTestRunner.startRunnerWithArgs(JUnit5IdeaTestRunner.java:71)
at com.intellij.rt.junit.IdeaTestRunner$Repeater.execute(IdeaTestRunner.java:38)
at com.intellij.rt.execution.junit.TestsRepeater.repeat(TestsRepeater.java:11)
at com.intellij.rt.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:35)
at com.intellij.rt.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:235)
at com.intellij.rt.junit.JUnitStarter.main(JUnitStarter.java:54)
Caused by: java.lang.IllegalStateException: Cannot convert value of type 'java.util.UUID' to required type 'br.com.stilingue.smartcare.entities.Interaction' for property 'interaction': no matching editors or conversion strategy found
at org.springframework.beans.TypeConverterDelegate.convertIfNecessary(TypeConverterDelegate.java:262)
at org.springframework.beans.AbstractNestablePropertyAccessor.convertIfNecessary(AbstractNestablePropertyAccessor.java:590)
... 120 more
我发现了一些其他问题,如果我在父实体中有 CascadeType.PERSIST
,我将无法直接操作子实体,因为父实体将是子实体的“所有者”子实体。
但是我要问:有什么办法可以避免这个性能瓶颈吗?我的交互实体还有其他一些 OneToOne
、ManyToMany
、ManyToOne
和 OneToMany
关系,每次我使用 InteractionRepository#save
时,所有关系都从数据库中获取。我真的需要自己 create/delete ClassifiedGroup
个实体。
我的类:
互动
package br.com.stilingue.smartcare.entities;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.google.cloud.spanner.hibernate.types.SpannerJsonType;
import java.sql.Timestamp;
import java.util.List;
import java.util.Set;
import java.util.UUID;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.UniqueConstraint;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import lombok.experimental.Accessors;
import org.hibernate.annotations.Type;
import org.hibernate.annotations.TypeDef;
@Data
@Entity
@Accessors(chain = true)
@Table(
name = "interactions",
uniqueConstraints = {@UniqueConstraint(columnNames = {"pid", "channel", "universe_id"})})
public class Interaction {
@Id
@GeneratedValue
@Type(type = "uuid-char")
@Column(name = "interaction_id", nullable = false)
private UUID interactionId;
@Column(name = "pid", nullable = false)
private String pid;
@Enumerated(EnumType.STRING)
@Column(name = "channel", nullable = false)
private Channel channel;
@Column(name = "universe_id", nullable = false)
private Long universeId;
@Column(name = "post_date", nullable = false)
private Timestamp postDate;
@ToString.Exclude
@EqualsAndHashCode.Exclude
@OneToMany(
mappedBy = "interaction",
cascade = CascadeType.ALL,
fetch = FetchType.LAZY,
orphanRemoval = true)
private Set<ClassifiedGroup> groups;
// many other relations/attributes not relevant for the issue, but they also are loaded when I use InteractionRepository#save method
// method when the Interaction is created the first time
public void setRelations() {
if (this.groups != null) {
this.groups.forEach(group -> group.setInteraction(this));
}
}
}
分类组
package br.com.stilingue.smartcare.entities;
import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.FetchType;
import javax.persistence.Id;
import javax.persistence.IdClass;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.NoArgsConstructor;
import lombok.ToString;
import lombok.experimental.Accessors;
@Data
@Entity
@NoArgsConstructor
@Accessors(chain = true)
@Table(name = "classified_groups")
@IdClass(ClassifiedGroup.PrimaryKeys.class)
public class ClassifiedGroup {
@Data
public static class PrimaryKeys implements Serializable {
private Interaction interaction;
private String descriptor;
private String value;
}
public ClassifiedGroup(String group, ClassificationType type, String descriptor) {
this.value = group;
this.type = type;
this.descriptor = descriptor;
}
public ClassifiedGroup(String group, ClassificationType type) {
this(group, type, "");
}
@Id
@ToString.Exclude
@EqualsAndHashCode.Exclude
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(
name = "interaction_id",
referencedColumnName = "interaction_id",
insertable = false,
updatable = false)
private Interaction interaction;
@Enumerated(EnumType.STRING)
@Column(name = "type", nullable = false)
private ClassificationType type;
@Id
@Column(name = "value", nullable = false)
private String value;
@Id
@Column(name = "descriptor", nullable = false)
private String descriptor = "";
}
ClassifiedGroupRepository
package br.com.stilingue.smartcare.repositories;
import br.com.stilingue.smartcare.entities.ClassifiedGroup;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface ClassifiedGroupRepository
extends JpaRepository<ClassifiedGroup, ClassifiedGroup.PrimaryKeys> {}
InteractionRepository
package br.com.stilingue.smartcare.repositories;
import java.util.Optional;
import java.util.UUID;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
@Repository
public interface InteractionRepository extends JpaRepository<Interaction, UUID>{}
我目前正在使用 Hibernate 5.6.5.Final
。
您的映射不正确 - 您需要查看有关如何映射派生 ID 的 JPA 教程,但该错误会告诉您哪里出了问题,指向您的 ClassifiedGroup.interaction 映射。 PK class 必须使用交互的 ID(UUID),而不是交互实例本身。
public static class PrimaryKeys implements Serializable {
private UUID interaction;
private String descriptor;
private String value;
}
由于 JPA 需要将值插入定义的“interaction_id”fk,您在 ClassifiedGroup 中的映射应 可写,类似于:
@Id
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(
name = "interaction_id",
referencedColumnName = "interaction_id")
private Interaction interaction;
关于你的其他问题 - 是的,有很多方法可以处理你想要的东西,但这取决于你想要投入多少工作。让关系变得懒惰可以避免它们被不必要地获取,但是你然后必须考虑通过其他机制进行序列化和对分离实体的更改。我不相信 Spring/JPA 提供商在关系中收到空值时自动知道我想要做什么,因为可能是数据未序列化,或者可能是更改。因此,在关注性能的地方,我编写了自己的合并方法来处理接收序列化数据(比如来自 REST 接口)并确定它应该如何合并到托管实例中(如果存在)。
另请注意,CascadeType.ALL 可能不是您真正想要的。你所说的只是你想要的是对 Interaction.groups 列表的更改被拾取并且引用的 ClassifiedGroups 与交互一起被删除,并且作为一种说服力,你可以在一次调用中保留一个交互及其组列表。那只是 cascade={PERSIST, REMOVE}
。 ALL 包括合并、刷新和分离,它们会产生性能开销,如果您真的不需要它们,可能会让您感到悲伤。特别是合并 - 当您在其组列表中传递带有更改和分类组的交互时,这些分类组中与数据库中内容的任何差异都将被合并。这可能会导致过时数据覆盖等后果,当然会产生很大的开销,因为合并也会级联 ClassifiedGroup 引用。不要轻易使用级联选项。