1

The problem:

I have two entities, SomeEntity and SomeEntityInfo in a bi-directional one-to-one relation with only CascadeType.REMOVE cascading set.

If SomeEntity.someEntityInfo is changed, and SomeEntity is (already existing) saved -> there shouldn't happen a cascading database update to the SomeEntityInfo table / object.

But instead, the related entity is updated too

edit / update In other words: I want the SomeEntityInfo to be "(somewhat-)immutable". It should be created when SomeEntity is created, but not updated / version checked - optimistic locking - if SomeEntity is re-saved.

what i did so far

  • Returning a copy of SomeEntityInfo in the getter of SomeEntity results in

    a new object was found through a relationship that was not marked cascade PERSIST [..]

  • (desperately)annotating with

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

    is related to the ID of the foreign key, not to the data inside the referenced object

Example - DB schema (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);

Entity Code

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]
    }

Used test scenario

    // 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();

Environment

EclipseLink, version: Eclipse Persistence Services - 2.5.2.v20140319-9ad6abd

Additional Information

The specification (http://wiki.eclipse.org/Introduction_to_EclipseLink_JPA_(ELUG)#.40OneToOne) sais:

cascade – By default, JPA does not cascade any persistence operations to the target of the association.

that is not the case (changes are cascaded).. what am I missing here?

3 Answers3

1

SomeEntityInfo instance is managed, meaning any changes to it will be saved, unrelated to cascading. Take a look at this answer for more details.

Community
  • 1
  • 1
Predrag Maric
  • 23,938
  • 5
  • 52
  • 68
  • okay, so.. is there a way to "unmanage" the referenced entity in this scenario? (i tried getter giving a copy of the entity - "immutable", with the effect 'a new object was found through a relationship that was not marked cascade PERSIST' – arkanoid256 Nov 30 '14 at 18:43
0

Changes aren't actually cascaded. What CascadeType.REMOVE means is essentially ON DELETE CASCADE, i.e. remove any orphaned rows. While there is a ON UPDATE CASCADE it is less used and only affects the other end of the foreign key.

If you make changes to an object that is handled by the ORM, the changes will be persisted. However it has nothing to do with cascading.

So if you don't want to update SomeEntityInfo in the database, don't update it in your code. EclipseLink is doing its job perfectly fine here.

Kayaman
  • 72,141
  • 5
  • 83
  • 121
  • So, did I get it right: there is no way to prevent updates/value changes on the object graph except "do not change the related object"? – arkanoid256 Nov 27 '14 at 12:01
  • 1
    @arkanoid256 You've told the ORM that "Hey, you handle these objects. If they change, write the changes to the database". That's what's happening, so there's nothing surprising about it. Why are you modifying SomeEntityInfo anyways? – Kayaman Nov 27 '14 at 12:04
  • hi, sry for the late response: i dont want to cascade any updates to the "SomeEntityInfo" object referenced by "SomeEntity" (and in the long run, no version change / optimistic locking - "SomeEntityInfo" can be changed during the programs lifecycle, but it should not be saved while persiting "SomeEntity" -> only when deleting "SomeEntity".. makes the "SomeEntityInfo" obsolete.. delete it too) – arkanoid256 Nov 30 '14 at 18:28
  • Well that's impossible. If you change `SomeEntityInfo`, it will be persisted. There might be hacks to prevent it (like detaching it from the session or whatever), but the main point still is: If you don't want to persist changes, don't make changes. It's your code that's doing the changes, so that's where your problem is. – Kayaman Nov 30 '14 at 20:47
0

The answers below are correct, I just wanted to add that the em.persist() isn't doing anything - it is a no-op because the loadedSe instance is a managed entity. If it were not a managed entity, the JPA specification requires that calling persist on an existing, unmanaged entity instance result in an exception - either on the persist or flush call, or when the transaction tries to commit.

All changes to managed entities are synchronized to the database when flush is called or the transaction commits. All entities accessed from an EntityManager are managed by that EntityManager context until they are cleared. If you called em.clear before making the changes, the changes would not have been picked up by the commit. Another alternative is to use the read-only query hint on the find operation: http://wiki.eclipse.org/EclipseLink/UserGuide/JPA/Basic_JPA_Development/Querying/Query_Hints#Read_Only

Chris
  • 20,138
  • 2
  • 29
  • 43