Двунаправленный @OneToOne с ТОЛЬКО CascadeType.REMOVE сохраняет изменения: это не нужно

1

Проблема:

У меня есть два объекта: SomeEntity и SomeEntityInfo в двунаправленном взаимно однозначном отношении только с каскадным набором CascadeType.REMOVE.

Если SomeEntity.someEntityInfo изменен, а SomeEntity (уже существующий) сохранен → не должно быть каскадного обновления базы данных в таблице/объекте SomeEntityInfo.

Но вместо этого связанный объект также обновляется

edit/update Другими словами: Я хочу, чтобы SomeEntityInfo был "(somewhat-) неизменным". Он должен быть создан, когда SomeEntity создан, но не обновлен/проверен версией - оптимистическая блокировка - если SomeEntity повторно сохраняется.

что я сделал до сих пор

  • Возврат копии SomeEntityInfo в getter of SomeEntity приводит к

    новый объект был найден через связь, которая не была отмечена каскадом PERSIST [..]

  • (отчаянно), аннотируя

    @OneToOne(cascade = { CascadeType.REMOVE })
    @JoinColumn(name = "someentityinfo_id", updatable = false, insertable = true)
    private SomeEntityInfo someEntityInfo;
    

    связан с идентификатором внешнего ключа, а не с данными внутри ссылочного объекта

Пример - схема DB (mysql db)

    CREATE TABLE someentity (
      id INT(11) UNSIGNED NOT NULL AUTO_INCREMENT,
      version INT(11) NULL DEFAULT NULL,
      someentityinfo_id INT(11) UNSIGNED NULL DEFAULT NULL,
      PRIMARY KEY (id)
    )
    COLLATE='utf8_general_ci'
    ENGINE=InnoDB;

    CREATE TABLE someentityinfo (
      id INT(11) UNSIGNED NOT NULL AUTO_INCREMENT,
      version INT(11) NULL DEFAULT NULL,
      status varchar(255) DEFAULT NULL,
      PRIMARY KEY (id)
    )
    COLLATE='utf8_general_ci'
    ENGINE=InnoDB;

    ALTER TABLE someentity
      ADD INDEX FK_someentityinfo_id (someentityinfo_id);

    ALTER TABLE someentity
      ADD CONSTRAINT FK_someentityinfo_id FOREIGN KEY (someentityinfo_id) REFERENCES someentityinfo (id);

Код сущности

SomeEntity

    import javax.persistence.CascadeType;
    import javax.persistence.Entity;
    import javax.persistence.GeneratedValue;
    import javax.persistence.GenerationType;
    import javax.persistence.Id;
    import javax.persistence.JoinColumn;
    import javax.persistence.OneToOne;
    import javax.persistence.Table;
    import javax.persistence.Version;

    @Entity
    @Table(name = "someentity")
    public class SomeEntity {

        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Integer id;

        @Version
        private Integer version;

        @OneToOne(cascade = { CascadeType.REMOVE })
        @JoinColumn(name = "someentityinfo_id")
        private SomeEntityInfo someEntityInfo;
        [getter/setter]
    }

SomeEntityInfo

    import javax.persistence.Entity;
    import javax.persistence.GeneratedValue;
    import javax.persistence.GenerationType;
    import javax.persistence.Id;
    import javax.persistence.OneToOne;
    import javax.persistence.Table;
    import javax.persistence.Version;

    @Entity
    @Table(name = "someentityinfo")
    public class SomeEntityInfo {

        @Id
        @GeneratedValue(strategy = GenerationType.IDENTITY)
        private Integer id;

        @Version
        private Integer version;

        private String status;

        @OneToOne(mappedBy = "someEntityInfo")
        private SomeEntity someEntity;
        [getter/setter]
    }

Используемый тестовый сценарий

    // create and persist entity and its info object
    em.getTransaction().begin();
    SomeEntity se = new SomeEntity();
    SomeEntityInfo seInfo = new SomeEntityInfo();
    se.setSomeEntityInfo(seInfo);
    seInfo.setSomeEntity(se);

    seInfo.setStatus("status 1");

    em.persist(se);
    em.persist(se.getSomeEntityInfo());
    em.getTransaction().commit();

    // load created entity, modify its info and expect
    // to NOT update the info object while saving the entity again
    em.getTransaction().begin();
    SomeEntity loadedSe = em.find(SomeEntity.class, Integer.valueOf(se.getId()));

    loadedSe.getSomeEntityInfo().setStatus("do not cascade update");

    // as Chris said below, not necessary to explicit save managed entity again
    // em.persist(loadedSe);

    em.getTransaction().commit();

Окружающая среда

EclipseLink, версия: Eclipse Persistence Services - 2.5.2.v20140319-9ad6abd

Дополнительная информация

Спецификация (http://wiki.eclipse.org/Introduction_to_EclipseLink_JPA_(ELUG)#.40OneToOne):

Каскад. По умолчанию JPA не каскадирует любые операции персистентности до цели ассоциации.

это не так (изменения каскадируются). Что мне здесь не хватает?

Теги:
orm
jpa
eclipselink

3 ответа

0
Лучший ответ

Изменения фактически не каскадируются. Что означает CascadeType.REMOVE по сути, используется ON DELETE CASCADE, то есть удалять любые осиротевшие строки. Хотя есть ON UPDATE CASCADE он менее используется и влияет только на другой конец внешнего ключа.

Если вы вносите изменения в объект, который обрабатывается ORM, изменения будут сохранены. Однако это не имеет никакого отношения к каскадированию.

Поэтому, если вы не хотите обновлять SomeEntityInfo в базе данных, не обновляйте его в своем коде. EclipseLink отлично справляется с этой задачей.

  • 0
    Итак, правильно ли я понял: нет способа предотвратить обновления / изменения значений на графе объектов, кроме «не изменять связанный объект»?
  • 1
    @ arkanoid256 Вы сказали ORM, что «Эй, вы обрабатываете эти объекты. Если они изменяются, запишите изменения в базу данных». Вот что происходит, так что в этом нет ничего удивительного. В любом случае, почему вы изменяете SomeEntityInfo?
Показать ещё 2 комментария
1

SomeEntityInfo управляется, то есть любые изменения в нем будут сохранены, не связанные с каскадом. Взгляните на этот ответ для более подробной информации.

  • 0
    хорошо, так .. есть ли способ "неуправлять" сущностью, на которую ссылаются, в этом сценарии? (Я попытался получить, предоставив копию объекта - «неизменяемый», с эффектом «новый объект был найден в отношениях, которые не были помечены как каскад PERSIST»).
0

Ниже приведены правильные ответы. Я просто хотел добавить, что em.persist() ничего не делает - это не-op, потому что экземпляр loadedSe является управляемым объектом. Если это не управляемый объект, спецификация JPA требует, чтобы вызов сохранялся в существующем, неуправляемом экземпляре объекта, в результате был исключение - либо на постоянном, либо на флеш-вызове, или когда транзакция пытается совершить транзакцию.

Все изменения в управляемых объектах синхронизируются с базой данных при вызове flush или транзакции. Все сущности, получаемые из EntityManager, управляются этим контентом EntityManager до тех пор, пока они не будут очищены. Если вы вызвали em.clear перед внесением изменений, изменения не были бы захвачены фиксацией. Другой альтернативой является использование подсказки для чтения только для операции поиска: http://wiki.eclipse.org/EclipseLink/UserGuide/JPA/Basic_JPA_Development/Querying/Query_Hints#Read_Only

Ещё вопросы

Сообщество Overcoder
Наверх
Меню