Проблема:
У меня есть два объекта: 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 не каскадирует любые операции персистентности до цели ассоциации.
это не так (изменения каскадируются). Что мне здесь не хватает?
Изменения фактически не каскадируются. Что означает CascadeType.REMOVE
по сути, используется ON DELETE CASCADE
, то есть удалять любые осиротевшие строки. Хотя есть ON UPDATE CASCADE
он менее используется и влияет только на другой конец внешнего ключа.
Если вы вносите изменения в объект, который обрабатывается ORM, изменения будут сохранены. Однако это не имеет никакого отношения к каскадированию.
Поэтому, если вы не хотите обновлять SomeEntityInfo
в базе данных, не обновляйте его в своем коде. EclipseLink отлично справляется с этой задачей.
SomeEntityInfo
управляется, то есть любые изменения в нем будут сохранены, не связанные с каскадом. Взгляните на этот ответ для более подробной информации.
Ниже приведены правильные ответы. Я просто хотел добавить, что 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