вставка нового объекта, имеющего однонаправленную связь «многие ко многим», генерирует каскадное сохранение вместо каскадного слияния

#java #hibernate #eclipselink

#java #спящий режим #eclipselink

Вопрос:

У меня есть две сущности Role и Grant со следующим сопоставлением:

 public class Role extends BaseBean {
    private static final long            serialVersionUID   = 1L;
    private String                     name;
    private Set<Grant> grants = new HashSet<Grant>();
// get set
}

public class Grant implements Serializable {
    private static final long   serialVersionUID    = 1L;
    private String            id;
    private String            data;
}
  

отображение orm:

  <entity name="q2role" class="tn.waycon.alquasar2.adm.model.Role">
        <attributes>
            <basic name="name">
                <column length="800" nullable="false" unique="true"/>
            </basic>
            <many-to-many name="grants" fetch="EAGER">
                <join-table name="role_grant">
                    <join-column name="role_id"/>
                    <inverse-join-column name="grant_id"/>
                </join-table>
                <cascade>
                    <cascade-all/>
                </cascade>
            </many-to-many>
        </attributes>
    </entity>

<entity name="q2grant" class="tn.waycon.alquasar2.adm.model.Grant">
        <attributes>
            <id name="id">
                <column name="id_g"/>
                <generated-value stategy="IDENTITY" generator="SEQ_GEN"/>
            </id>
            <basic name="data"></basic>
        </attributes>
</entity>
  

Теперь, когда я пытаюсь вставить новую роль, содержащую существующие гранты, транзакция завершится неудачно, потому что eclipselink пытается вставить уже существующие гранты. Почему eclipselink ведет себя так странно? Я устанавливаю cascade-all, и eclipselink должен быть достаточно умен, чтобы разделять каскадное сохранение и каскадное слияние.

 Main {
Role role = new Role();
List<Grant> grants = grantRepository.getGrantsBydata(List<String> datas);
role.setGrants(grants);
roleRepository.save(role);
}
  

Журнал:

ПРЕДУПРЕЖДЕНИЕ [http-nio-8080-exec-2] org.springframework.remoting.support.RemoteInvocationTraceInterceptor.invoke Обработка удаленного вызова HttpInvokerServiceExporter привела к фатальному исключению tn.waycon.alquasar2.adm.service.api.IAdminService.createRole org.springframework.transaction.Исключение TransactionSystemException: не удалось зафиксировать JPA транзакции; вложенным исключением является javax.persistence.Исключение RollbackException: Исключение [EclipseLink-4002] (службы сохранения Eclipse — 2.5.2.v20140319-9ad6abd) org.eclipse.persistence.exceptions.Исключение DatabaseException Внутреннее исключение: java.sql.BatchUpdateException: нарушение ПЕРВИЧНОГО КЛЮЧА «PK__Q2GRANT__9DB7D2FA15DA3E5D». Не удается вставить дубликат ключа в object ‘dbo.Q2GRANT «. дублирующее значение ключа (13969). Код ошибки: 2627

Комментарии:

1. Ваш метод сохранения использует слияние или сохраняется?

2. Я использую spring data jpa, в интерфейсе JpaRepository есть только один метод сохранения, в зависимости от того, является первичный ключ нулевым или нет, он решит использовать слияние или сохранение.

Ответ №1:

Вызов Persist заставляет JPA вставлять корневую сущность, но он также каскадирует вызов persist по отношению, отмеченному типом cascade persist . Это косвенно означает, что вы вызываете persist для своего отдельного объекта, который спецификация JPA требует, чтобы поставщики создавали исключение либо немедленно, либо когда транзакция синхронизируется с базой данных (оператор insert).

Параметры Persist и cascade persist предназначены только для использования, когда вы собираетесь вставлять новый граф объектов, поэтому вам может потребоваться пересмотреть, где вы вставляете параметр cascade persist — это имеет последствия.

Параметры

  1. Чтение существующих объектов перед добавлением их в новый объект и использованием управляемого экземпляра. Поскольку сохранение в управляемом объекте не выполняется, это решит вашу проблему.
  2. Вместо этого используйте слияние. Слияние позволяет поставщику просматривать экземпляр объекта, чтобы решить, является ли он новым или обновленным, и это каскадируется в график, как указано. Затем Merge возьмет ваш отдельный объект и обработает его соответствующим образом, обновив его.

Ответ №2:

Вы должны удалить, поскольку гранты управляются отдельно от ролей. Использование означает, что при вставке роли все ее права доступа также вставляются, а при ее удалении права доступа также удаляются. В вашем случае я вижу, что гранты уже существуют при создании роли, поэтому вы не захотите, чтобы они, например, были удалены, когда эта роль будет удалена!