简体   繁体   中英

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

I have a bidirectional relation between Product and Feature, user will add the product with some features and need to save them in same transaction. While updating I need to delete all the old features and save the newly added if there

@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;

    ...
}

And below is the service

@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);
        ...
    }

}

But when I am trying to add a new product or update it occasionally getting the below error (somtimes working fine !)

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)

There are possibly multiple problems here.

First of all, you are using the same @SequenceGenerator(name = "mySeq") on both entities. These generators have global scope, they are not limited to the class where they are defined. I actually am surprised that you don't get an error because you are defining a generator with the same name twice. But you are surely using the same generator for both entities.

Then, I would strongly suggest that you set the mapped_by value for the @OneToMany , because this is a derived mapping. There is some default heuristic by Hibernate to figure that out, but it's not always what you think it is.

Third, if you really are deleting the old Features and recreating them on each Product update, then this code is missing in your example. This could also be a source of errors, when you think you deleted an entity, but it's actually still there and then you try to create a new one with the same ID.

And finally, I would also set an explicit cascading strategy, on the @OneToMany, just to make it more obvious.

If you want to have ability to persist child entities via parent entity collection, you need to set orphanRemoval=true and instead of calling clear() you can do setFeatures(null) and then setFeatures(productDTO.getFeatures())

However, I recommend you to rewrite your logic so that your features would be removed or updated directly through the EntityManager. You can compare ids of incoming features with existed, then remove those which don't match and create those which don't have id and update those which have.

The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM