Повторяющееся значение ключа нарушает ограничение уникальности для первичного ключа - PullRequest
0 голосов
/ 21 июня 2019

У меня проблема, где я получаю duplicate key value violates unique constraint "my_obj_pk". Где my_obj_pk - это первичный ключ таблицы, сопоставленной с сущностью Spring и основанной на id:

@Entity
@Table(name = "MY_TABLE")
@SequenceGenerator(name = "MY_OBJ_SEQ", allocationSize = 500)
public class MyObject {

   @Id
   @GeneratedValue(generator = "MY_OBJ_SEQ", strategy = GenerationType.TABLE)
   private Long id;
...
}

Исключение составляет:

Internal Exception: java.sql.BatchUpdateException: Batch entry 1 INSERT INTO MY_TABLE (ID, ...) VALUES (257521, ...) was aborted: ERROR: duplicate key value violates unique constraint "my_obj_pk"
  Detail: Key (id)=(257521) already exists.  Call getNextException to see other errors in the batch.
Error Code: 0
Call: INSERT INTO MY_TABLE (ID, ...) VALUES (?, ...)
    bind => [8 parameters bound]
    at org.springframework.orm.jpa.JpaTransactionManager.doCommit(JpaTransactionManager.java:524)
    at org.springframework.transaction.support.AbstractPlatformTransactionManager.processCommit(AbstractPlatformTransactionManager.java:757)
    at org.springframework.transaction.support.AbstractPlatformTransactionManager.commit(AbstractPlatformTransactionManager.java:726)
    at org.springframework.transaction.interceptor.TransactionAspectSupport.commitTransactionAfterReturning(TransactionAspectSupport.java:478)
    at org.springframework.transaction.interceptor.TransactionAspectSupport.invokeWithinTransaction(TransactionAspectSupport.java:272)
    at org.springframework.transaction.interceptor.TransactionInterceptor.invoke(TransactionInterceptor.java:95)
    at org.springframework.aop.framework.ReflectiveMethodInvocation.proceed(ReflectiveMethodInvocation.java:179)
    at org.springframework.aop.framework.JdkDynamicAopProxy.invoke(JdkDynamicAopProxy.java:207)
    at com.sun.proxy.$Proxy341.saveMyObject(Unknown Source)
    at MyWorker.doWork(MyWorker.java:172)
    at java.util.concurrent.CompletableFuture$AsyncRun.run$$$capture(CompletableFuture.java:1626)
    ... 4 more
Caused by: javax.persistence.RollbackException: Exception [EclipseLink-4002] (Eclipse Persistence Services - 2.7.3.v20180807-4be1041): org.eclipse.persistence.exceptions.DatabaseException
Internal Exception: java.sql.BatchUpdateException: Batch entry 1 INSERT INTO MY_TABLE (ID, ...) VALUES (257521, ...) was aborted: ERROR: duplicate key value violates unique constraint "my_obj_pk"
  Detail: Key (id)=(257521) already exists.  Call getNextException to see other errors in the batch.
Error Code: 0

Как видите, реализация JPA - Eclipse Persistence Services - 2.7.3.v20180807-4be1041. БД - PostgreSQl 9.4.0.

MyWorker - это Spring-компонент, который раз в 10 минут порождает 35 CompletableFuture объектов, которые запускаются через исполнителя, имеющего 20 потоков. Каждый такой Future несколько раз вызывает различные функции из Spring Service MyServiceImpl, где каждая функция в сервисе помечена Transactional и PermitAll. Эти функции протягивают через различные Repository различные объекты, скажем X и Collection<Y>, Часть этих объектов создается, если еще не существует, однако не сохраняется явно.

X имеет следующее:

@OneToMany(mappedBy = "xObj", fetch = FetchType.LAZY, cascade = CascadeType.REMOVE)
List<MyObject> myObjects;

Y имеет следующее:

@OneToMany(mappedBy = "yObj", fetch = FetchType.LAZY, cascade = CascadeType.REMOVE)
List<MyObject> myObjects;

MyObject имеет:

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "X_ID")
private X xObj;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "Y_ID")
private Y yObj;

Итак, в основном, X и Y имеют список MyObject, которые имеют X и Y внутри

В конце каждый Future звонит saveMyObject() с X и Collection<Y> с одного и того же MyServiceImpl:

@Override
@PermitAll
@Transactional
public void saveMyObject(X x, Collection<Y> colY) {
    xRepository.save(x);
    yRepository.save(colY);
    final List<MyObject> myObjects = x.getMyObjects() == null ?
            new ArrayList<>(colY.size()) : x.getMyObjects();
    colY.forEach(p -> {
        MyObject o = new MyObject();
        myObjects.add(o);
        List<MyObject> pobj = p.getMyObjects();
        if (pobj == null) {
           pobj = new ArrayList<>();
           p.setMyObjects(pobj);
        }
        pobj.add(o);
    });
    x.setMyObjects(myObjects);
    myObjectsRepository.save(myObjects);
}

НИЧЕГО не назначено вручную. На данный момент я не понимаю, как PK MyObject может дублироваться. Я предпочитаю, если возможно, не трогать свойства CASCADE, так как эти классы существуют в течение длительного времени и широко используются. Спасибо!

Редактировать: я должен добавить, что разные Future могут работать на тех же Y и MyObject, поэтому, возможно, дизайн не совсем правильный

...