簡體   English   中英

保存一對多 JPA 時出錯,違反主鍵約束

[英]Error while saving one to many JPA, Violation of PRIMARY KEY constraint

我在產品和功能之間有雙向關系,用戶將添加具有某些功能的產品並需要將它們保存在同一個事務中。 更新時,我需要刪除所有舊功能並保存新添加的功能(如果有)

@Entity
@Table(name = "product")
public class Product{

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "mySeq")
    @SequenceGenerator(name = "mySeq")
    private Long id;


    @OneToMany(cascade = CascadeType.ALL)
    @JoinColumn(name = "product_id")
    private List<Feature> features = new ArrayList<Feature>();

    ...

    public Product addFeatures(Feature feature) {
        this.features.add(feature);
        feature.setProduct(this);
        return this;
    }
}

@Entity
@Table(name = "feature")
public class Feature{

    @Id
    @GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "mySeq")
    @SequenceGenerator(name = "mySeq")
    private Long id;


    @ManyToOne
    @JoinColumn(name="product_id")
    @JsonIgnoreProperties("features")
    private Product product;

    ...
}

下面是服務

@Service
@Transactional
public class ProductService {
    ...
    public ProductDTO addNew(ProductDTO productDTO){
        Product product = productMapper.toEntity(productDTO);
        product.getFeatures().clear();
        for(FeatureDTO featureDTO : productDTO.getFeatures()){
            Feature feature = featureMapper.toEntity(featureDTO);
            product.addFeatures(feature);

        }
        entityManager.persist(product);
        ...
    }


    public ProductDTO update(ProductDTO productDTO){
        Product product = productMapper.toEntity(productDTO);
        //Need to delete all old features and save the newly added
        featureRepository.deleteByProductId(productDTO.getId());
        product.getFeatures().clear();
        for(FeatureDTO featureDTO : productDTO.getFeatures()){
            Feature feature = featureMapper.toEntity(featureDTO);
            product.addFeatures(feature);
        }
        featureRepository.save(product);
        ...
    }

}

但是當我嘗試添加新產品或更新它時,偶爾會出現以下錯誤(有時工作正常!)

org.springframework.dao.DataIntegrityViolationException: could not execute statement; SQL [n/a]; constraint [null]; nested exception is org.hibernate.exception.ConstraintViolationException: could not execute statement
    at org.springframework.orm.jpa.vendor.HibernateJpaDialect.convertHibernateAccessException(HibernateJpaDialect.java:257)
    at org.springframework.orm.jpa.vendor.HibernateJpaDialect.translateExceptionIfPossible(HibernateJpaDialect.java:223)
    at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:540)
    at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:746)
    at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:714)
    at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:532)
    at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:304)
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:98)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:185)
    at org.springframework.aop.framework.CglibAopProxy$DynamicAdvisedInterceptor.intercept(CglibAopProxy.java:688)

...
Caused by: org.hibernate.exception.ConstraintViolationException: could not execute statement
    at org.hibernate.exception.internal.SQLStateConversionDelegate.convert(SQLStateConversionDelegate.java:112)
    at org.hibernate.exception.internal.StandardSQLExceptionConverter.convert(StandardSQLExceptionConverter.java:42)
    at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:111)
    at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:97)
    at org.hibernate.engine.jdbc.internal.ResultSetReturnImpl.executeUpdate(ResultSetReturnImpl.java:178)
    at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:3032)
    at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:3547)
    at org.hibernate.action.internal.EntityInsertAction.execute(EntityInsertAction.java:89)
    at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:600)
    at org.hibernate.engine.spi.ActionQueue.executeActions(ActionQueue.java:474)
    at org.hibernate.event.internal.AbstractFlushingEventListener.performExecutions(AbstractFlushingEventListener.java:337)
    at org.hibernate.event.internal.DefaultFlushEventListener.onFlush(DefaultFlushEventListener.java:39)
    at org.hibernate.internal.SessionImpl.doFlush(SessionImpl.java:1437)
    at org.hibernate.internal.SessionImpl.managedFlush(SessionImpl.java:494)
    at org.hibernate.internal.SessionImpl.flushBeforeTransactionCompletion(SessionImpl.java:3245)
    at org.hibernate.internal.SessionImpl.beforeTransactionCompletion(SessionImpl.java:2451)
    at org.hibernate.engine.jdbc.internal.JdbcCoordinatorImpl.beforeTransactionCompletion(JdbcCoordinatorImpl.java:473)
    at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.beforeCompletionCallback(JdbcResourceLocalTransactionCoordinatorImpl.java:156)
    at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl.access$100(JdbcResourceLocalTransactionCoordinatorImpl.java:38)
    at org.hibernate.resource.transaction.backend.jdbc.internal.JdbcResourceLocalTransactionCoordinatorImpl$TransactionDriverControlImpl.commit(JdbcResourceLocalTransactionCoordinatorImpl.java:231)
    at org.hibernate.engine.transaction.internal.TransactionImpl.commit(TransactionImpl.java:68)
    at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:536)
    ... 120 common frames omitted
Caused by: com.microsoft.sqlserver.jdbc.SQLServerException: Violation of PRIMARY KEY constraint 'PK_FEATURE'. Cannot insert duplicate key in object 'dbo.feature'. The duplicate key value is (1451).
    at com.microsoft.sqlserver.jdbc.SQLServerException.makeFromDatabaseError(SQLServerException.java:254)
    at com.microsoft.sqlserver.jdbc.SQLServerStatement.getNextResult(SQLServerStatement.java:1608)
    at com.microsoft.sqlserver.jdbc.SQLServerPreparedStatement.doExecutePreparedStatement(SQLServerPreparedStatement.java:578)
    at com.microsoft.sqlserver.jdbc.SQLServerPreparedStatement$PrepStmtExecCmd.doExecute(SQLServerPreparedStatement.java:508)
    at com.microsoft.sqlserver.jdbc.TDSCommand.execute(IOBuffer.java:7240)

這里可能存在多個問題。

首先,您在兩個實體上使用相同的@SequenceGenerator(name = "mySeq") 這些生成器具有全局 scope,它們不限於定義它們的 class。 實際上,我很驚訝您沒有收到錯誤,因為您定義了兩次具有相同名稱的生成器。 但是您肯定對兩個實體使用相同的生成器。

然后,我強烈建議您為@OneToMany設置 mapped_by 值,因為這是派生映射。 Hibernate 有一些默認的啟發式方法來解決這個問題,但它並不總是你認為的那樣。

第三,如果您確實要刪除舊功能並在每次產品更新時重新創建它們,那么您的示例中缺少此代碼。 這也可能是錯誤的來源,當您認為您刪除了一個實體,但它實際上仍然存在,然后您嘗試創建一個具有相同 ID 的新實體。

最后,我還將在@OneToMany 上設置一個明確的級聯策略,以使其更加明顯。

如果您希望能夠通過父實體集合保留子實體,則需要設置 orphanRemoval=true 而不是調用clear()您可以執行setFeatures(null)然后setFeatures(productDTO.getFeatures())

但是,我建議您重寫邏輯,以便直接通過 EntityManager 刪除或更新您的功能。 您可以將傳入特征的 id 與現有特征進行比較,然后刪除不匹配的特征並創建沒有 id 的特征並更新具有的特征。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM