Проблема со спящим режимом: INSERT вместо UPDATE при использовании Inheritance:SINGLE_TABLE и SecondaryTables

Я реализовал иерархию наследования, используя SINGLE_TABLE с SecondaryTables.

В противном случае это работает, но когда поля моей вторичной таблицы пусты (= ноль в Oracle), следующее обновление объекта завершается ошибкой, поскольку Hibernate считает, что он должен ВСТАВИТЬ в таблицу, когда он должен ОБНОВИТЬ. Пример:

CREATE TABLE TASK 
(
   ID NUMBER(10) NOT NULL 
   , TYPE NUMBER(1) NOT NULL 
   , STATUS NUMBER(1) NOT NULL 
, CONSTRAINT TASK_PK PRIMARY KEY  (ID) ENABLE);

CREATE TABLE SUB_TASK 
(
  ID NUMBER(10) NOT NULL 
, TEXT VARCHAR2(4000 CHAR) 
, CONSTRAINT SUB_TASK_PK PRIMARY KEY 
(ID) ENABLE);

ALTER TABLE SUB_TASK
ADD CONSTRAINT SUB_TASK_FK1 FOREIGN KEY
(ID)REFERENCES TASK(ID)ENABLE;

Задача.java:

@Entity
@Table(name="TASK")
@Inheritance(strategy=InheritanceType.SINGLE_TABLE)
@DiscriminatorColumn(name="TYPE",discriminatorType=DiscriminatorType.INTEGER)
public abstract class Task implements Serializable, Comparable<Task> {

@Id
@Column(nullable = false)
@GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "TASK_GEN")
@SequenceGenerator(name = "TASK_GEN", sequenceName = "SEQ_TASK", allocationSize = 1)
private Long id;

@Column(name = "TYPE", nullable = false, insertable=false, updatable=false)
private int typeCode;

Подзадача.java

@Entity
@DiscriminatorValue("7")
@SecondaryTable(name = "SUB_TASK", pkJoinColumns = {@PrimaryKeyJoinColumn(name = "id", referencedColumnName = "id")})
public class SubTask extends Task implements Serializable {

@Column(name="TEXT", length = 4000, table="SUB_TASK")
@Size(max = 4000)
private String text;

public String getText() {
    return text;
}

public void setText(String text) {
    this.text = text;
}

}

Исключение при сохранении объекта, который ранее имел null SubTask.text:

Вызвано: org.hibernate.exception.ConstraintViolationException: ORA-00001: yksikäsitteistä rajoitetta (SUB_TASK_PK) loukattu

at org.hibernate.exception.internal.SQLStateConversionDelegate.convert(SQLStateConversionDelegate.java:128)
at org.hibernate.exception.internal.StandardSQLExceptionConverter.convert(StandardSQLExceptionConverter.java:49)
at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:125)
at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:110)
at org.hibernate.engine.jdbc.internal.proxy.AbstractStatementProxyHandler.continueInvocation(AbstractStatementProxyHandler.java:129)
at org.hibernate.engine.jdbc.internal.proxy.AbstractProxyHandler.invoke(AbstractProxyHandler.java:81)
at $Proxy75.executeUpdate(Unknown Source)
at org.hibernate.persister.entity.AbstractEntityPersister.insert(AbstractEntityPersister.java:2965)
at org.hibernate.persister.entity.AbstractEntityPersister.updateOrInsert(AbstractEntityPersister.java:3028)
at org.hibernate.persister.entity.AbstractEntityPersister.update(AbstractEntityPersister.java:3350)
at org.hibernate.action.internal.EntityUpdateAction.execute(EntityUpdateAction.java:140)

Я отладил соответствующий код Hibernate, AbstractEntityPersister.java:

    if ( !isInverseTable( j ) ) {

        final boolean isRowToUpdate;
        if ( isNullableTable( j ) && oldFields != null && isAllNull( oldFields, j ) ) {
            //don't bother trying to update, we know there is no row there yet
            isRowToUpdate = false;
        }
        else if ( isNullableTable( j ) && isAllNull( fields, j ) ) {
            //if all fields are null, we might need to delete existing row
            isRowToUpdate = true;
            delete( id, oldVersion, j, object, getSQLDeleteStrings()[j], session, null );
        }
        else {
            //there is probably a row there, so try to update
            //if no rows were updated, we will find out
            isRowToUpdate = update( id, fields, oldFields, rowId, includeProperty, j, oldVersion, object, sql, session );
        }

        if ( !isRowToUpdate && !isAllNull( fields, j ) ) {
            // assume that the row was not there since it previously had only null
            // values, so do an INSERT instead
            //TODO: does not respect dynamic-insert
            insert( id, fields, getPropertyInsertability(), j, getSQLInsertStrings()[j], object, session );
        }

    }

и похоже, что первый if ("не утруждайте себя попыткой обновления") выполнен и isRowToUpdate = false, а последний if (if ( !isRowToUpdate && !isAllNull( fields, j ) ) также выполнен, так что вставка выполняется.

Я думаю, я мог бы обойти это, добавив фиктивное ненулевое поле в таблицу SUB_TASK, но это мой единственный выбор?

Использование Hibernate 4.17.final

РЕДАКТИРОВАТЬ: чтобы, надеюсь, прояснить, что я делаю:

SubTask s = taskRepository.findOne(42l);
s.setText("dasdsa");
taskRepository.save(s); // OK
s.setText(null); 
taskRepository.save(s); // OK
s.setText("update after null value");
taskRepository.save(s); // exception 

Я использую Spring Data для сохранения, но не похоже, что проблема связана с ним (и я предполагаю, что то же самое происходит при использовании vanilla JPA)


person Janne Mattila    schedule 04.02.2013    source источник
comment
Пожалуйста, уточните, что вы делаете. Все, что вы говорите, это исключение при сохранении объекта, который ранее имел нулевое значение SubTask.value. Покажите нам код, который вы выполняете, и полную трассировку атаки исключения. SubTask даже не имеет поля значения. Так что это не имеет особого смысла.   -  person JB Nizet    schedule 04.02.2013
comment
В конец добавлено краткое описание. Фиксированное SubTask.value => SubTask.text   -  person Janne Mattila    schedule 04.02.2013


Ответы (2)


У меня была похожая проблема с отношением @ManyToOne, определенным во вторичной таблице. Когда во вторичной таблице была строка, ссылающаяся на объект в первичной таблице, столбец отношения был нулевым и пытался установить значение, Hibernate пытался вставить новую строку вместо обновления существующей, выдавая исключение нарушения ограничения.

Решение состояло в том, чтобы использовать аннотацию @org.hibernate.annotations.Table(appliesTo="secondary_table", optional=false), которая заставляет Hibernate обновлять строку.

Я не уверен в поведении, когда строка во вторичной таблице не существует, но, возможно, это решает вашу проблему.

person dampudia    schedule 07.03.2013
comment
комментируя оба ответа: когда и если hibernate жалуется, что не может найти такую ​​таблицу, убедитесь, что вы пытаетесь указать имя таблицы в нижнем регистре в значении атрибута applyTo (даже если вы могли объявить его в верхнем регистре в соответствующая аннотация javax.persistence.Table) - кажется, это имеет значение - person hello_earth; 25.06.2021

Использование решения @dampudia сработало для меня.

Обязательно добавьте следующее в основную таблицу, а не в дополнительную. Также обратите внимание, что аннотация @Table, используемая здесь, взята из спящего режима, а не из java.persistence.

@org.hibernate.annotations.Table(appliesTo="secondary_table", необязательно = false)

person Dudi Patimer    schedule 21.03.2017