OpenJPA
  1. OpenJPA
  2. OPENJPA-1371

Insert is called instead of Update when merge() with derived Identity

    Details

    • Type: Bug Bug
    • Status: Closed
    • Priority: Major Major
    • Resolution: Fixed
    • Affects Version/s: 2.0.0-M3
    • Fix Version/s: 2.0.0-M3
    • Component/s: kernel
    • Labels:
      None

      Description

      Insert is called instead of Update when merge() with derived Identity:

        Activity

        Hide
        Fay Wang added a comment -

        During merge, openjpa needs to retrieve id from the entity to decide whether this is a new entity or a detached one. If it thinks it is a new entity, an INSERT will be perform. Otherwise, an Update statement will be executed.
        If (1) an entity has the compound primary key using IdClass, and
        (2) some field in the IdClass is a derived identity from a OneToOne/ManyToOne field,
        (3) that value in that field has not yet become managed (i.e., does not have the StateManagerImpl yet),
        this field in the IdClass will not be populated, as the primary key of the OneToOne/ManyToOne field usually can not be fetched back without an associated StateManagerImpl, unless it has a single-value primary key.

        Currently openjpa returns the null value for this field in the IdClass regardlessly in the above situation. The patch provides a relief when this derived identity-relation field has a single value primary key. It should fix the problem reported by Constantine. However, the limitation for nested/multi-level compound primary (e.g. Stock entity has compound primary key using IdClass, and some value in the idClass is a derived-identity relation field, ...) during merge remains.

        Show
        Fay Wang added a comment - During merge, openjpa needs to retrieve id from the entity to decide whether this is a new entity or a detached one. If it thinks it is a new entity, an INSERT will be perform. Otherwise, an Update statement will be executed. If (1) an entity has the compound primary key using IdClass, and (2) some field in the IdClass is a derived identity from a OneToOne/ManyToOne field, (3) that value in that field has not yet become managed (i.e., does not have the StateManagerImpl yet), this field in the IdClass will not be populated, as the primary key of the OneToOne/ManyToOne field usually can not be fetched back without an associated StateManagerImpl, unless it has a single-value primary key. Currently openjpa returns the null value for this field in the IdClass regardlessly in the above situation. The patch provides a relief when this derived identity-relation field has a single value primary key. It should fix the problem reported by Constantine. However, the limitation for nested/multi-level compound primary (e.g. Stock entity has compound primary key using IdClass, and some value in the idClass is a derived-identity relation field, ...) during merge remains.
        Hide
        Fay Wang added a comment -

        The following test scenario is provided by Constantine Kulak <code@mail.by>:

        There are three entities where PrognosisEntry has derived identity:

                • Source for Prognosis.java:

        @Entity(name = "Prognosis")
        @Table(name = "PROGNOSIS")
        @IdClass(Prognosis.PrognosisId.class)
        @Inheritance(strategy = InheritanceType.JOINED)
        public class Prognosis {

        protected List<PrognosisEntry> entries;
        protected String station;
        protected String type;

        @OneToMany(targetEntity = PrognosisEntry.class, cascade =

        {CascadeType.MERGE}

        , mappedBy="prognosis", fetch=FetchType.EAGER)
        public List<PrognosisEntry> getEntries() {
        if (entries == null)

        { entries = new ArrayList<PrognosisEntry>(); }

        return this.entries;
        }

        public void setEntries(List<PrognosisEntry> entries)

        { this.entries = entries; }

        @Id
        @Column(name = "STATION")
        public String getStation()

        { return station; }

        public void setStation(String value)

        { this.station = value; }

        @Id
        @Column(name = "TYPE_")
        public String getType()

        { return type; }

        public void setType(String value)

        { this.type = value; }

        public boolean equals(Object object)

        { ... }
        public int hashCode() { ... }

        public static class PrognosisId {
        protected String station;
        protected String type;

        public String getStation()

        { return station; }

        public void setStation(String value)

        { this.station = value; }

        public String getType()

        { return type; }

        public void setType(String value)

        { this.type = value; }

        public boolean equals(Object object)

        { ... }
        public int hashCode() { ... }

        }
        }

                • Source for PrognosisEntry.java:

        @Entity(name = "PrognosisEntry")
        @Table(name = "PROGNOSISENTRY")
        @Inheritance(strategy = InheritanceType.JOINED)
        @IdClass(PrognosisEntry.PrognosisEntryId.class)
        public class PrognosisEntry {

        protected String timestamp;
        protected String localState;
        protected Prognosis prognosis;

        protected Stock stock;

        @Id
        @ManyToOne(targetEntity = Stock.class, cascade =

        { CascadeType.MERGE }, fetch = FetchType.EAGER)
        public Stock getStock() { return stock; }

        public void setStock(Stock stock) { this.stock = stock; }

        @Id
        @ManyToOne(targetEntity = Prognosis.class, cascade = { CascadeType.MERGE }

        , fetch = FetchType.EAGER)
        public Prognosis getPrognosis()

        { return prognosis; }

        public void setPrognosis(Prognosis prognosis)

        { this.prognosis = prognosis; }

        @Column(name = "TIMESTAMP_", length = 255)
        public String getTimestamp()

        { return timestamp; }

        public void setTimestamp(String value)

        { this.timestamp = value; }

        @Basic
        @Column(name = "LOCALSTATE", length = 255)
        public String getLocalState()

        { return localState; }

        public void setLocalState(String value)

        { this.localState = value; }

        public boolean equals(Object object)

        { ... }
        public int hashCode() { ... }

        public static class PrognosisEntryId {

        protected Prognosis.PrognosisId prognosis;
        protected String stock;

        public String getStock()

        { return stock; }

        public void setStock(String stock)

        { this.stock = stock; }

        public Prognosis.PrognosisId getPrognosis()

        { return prognosis; }

        public void setPrognosis(Prognosis.PrognosisId prognosis)

        { this.prognosis = prognosis; }

        public boolean equals(Object object)

        { ... }
        public int hashCode() { ... }

        }
        }

                • Source for Stock.java:

        @Entity(name = "Stock")
        @Table(name = "STOCK")
        @Inheritance(strategy = InheritanceType.JOINED)
        public class Stock {
        protected String index;
        protected String length;

        @Id
        @Column(name = "INDEX_")
        public String getIndex()

        { return index; }

        public void setIndex(String value)

        { this.index = value; }

        @Basic
        @Column(name = "LENGTH_", length = 255)
        public String getLength()

        { return length; }

        public void setLength(String value)

        { this.length = value; }

        public boolean equals(Object object)

        { ... }
        public int hashCode() { ... }

        }

        The test scenario:
        (1) create a Prognosis entity with a list of PrognosisEntry.
        (2) call em.merge(newEntity) and commit
        (3) call em.clear()
        (4) call em.merge(newEntity)

        Step (4) generate insert statement to insert PrognosisEntry again, resulting in unique constraint violation from the database.

        It appears that the ApplicationIds.create(pc, meta) where pc is PrognosisEntry, the oid does not have complete id values.

        Show
        Fay Wang added a comment - The following test scenario is provided by Constantine Kulak <code@mail.by>: There are three entities where PrognosisEntry has derived identity: Source for Prognosis.java: @Entity(name = "Prognosis") @Table(name = "PROGNOSIS") @IdClass(Prognosis.PrognosisId.class) @Inheritance(strategy = InheritanceType.JOINED) public class Prognosis { protected List<PrognosisEntry> entries; protected String station; protected String type; @OneToMany(targetEntity = PrognosisEntry.class, cascade = {CascadeType.MERGE} , mappedBy="prognosis", fetch=FetchType.EAGER) public List<PrognosisEntry> getEntries() { if (entries == null) { entries = new ArrayList<PrognosisEntry>(); } return this.entries; } public void setEntries(List<PrognosisEntry> entries) { this.entries = entries; } @Id @Column(name = "STATION") public String getStation() { return station; } public void setStation(String value) { this.station = value; } @Id @Column(name = "TYPE_") public String getType() { return type; } public void setType(String value) { this.type = value; } public boolean equals(Object object) { ... } public int hashCode() { ... } public static class PrognosisId { protected String station; protected String type; public String getStation() { return station; } public void setStation(String value) { this.station = value; } public String getType() { return type; } public void setType(String value) { this.type = value; } public boolean equals(Object object) { ... } public int hashCode() { ... } } } Source for PrognosisEntry.java: @Entity(name = "PrognosisEntry") @Table(name = "PROGNOSISENTRY") @Inheritance(strategy = InheritanceType.JOINED) @IdClass(PrognosisEntry.PrognosisEntryId.class) public class PrognosisEntry { protected String timestamp; protected String localState; protected Prognosis prognosis; protected Stock stock; @Id @ManyToOne(targetEntity = Stock.class, cascade = { CascadeType.MERGE }, fetch = FetchType.EAGER) public Stock getStock() { return stock; } public void setStock(Stock stock) { this.stock = stock; } @Id @ManyToOne(targetEntity = Prognosis.class, cascade = { CascadeType.MERGE } , fetch = FetchType.EAGER) public Prognosis getPrognosis() { return prognosis; } public void setPrognosis(Prognosis prognosis) { this.prognosis = prognosis; } @Column(name = "TIMESTAMP_", length = 255) public String getTimestamp() { return timestamp; } public void setTimestamp(String value) { this.timestamp = value; } @Basic @Column(name = "LOCALSTATE", length = 255) public String getLocalState() { return localState; } public void setLocalState(String value) { this.localState = value; } public boolean equals(Object object) { ... } public int hashCode() { ... } public static class PrognosisEntryId { protected Prognosis.PrognosisId prognosis; protected String stock; public String getStock() { return stock; } public void setStock(String stock) { this.stock = stock; } public Prognosis.PrognosisId getPrognosis() { return prognosis; } public void setPrognosis(Prognosis.PrognosisId prognosis) { this.prognosis = prognosis; } public boolean equals(Object object) { ... } public int hashCode() { ... } } } Source for Stock.java: @Entity(name = "Stock") @Table(name = "STOCK") @Inheritance(strategy = InheritanceType.JOINED) public class Stock { protected String index; protected String length; @Id @Column(name = "INDEX_") public String getIndex() { return index; } public void setIndex(String value) { this.index = value; } @Basic @Column(name = "LENGTH_", length = 255) public String getLength() { return length; } public void setLength(String value) { this.length = value; } public boolean equals(Object object) { ... } public int hashCode() { ... } } The test scenario: (1) create a Prognosis entity with a list of PrognosisEntry. (2) call em.merge(newEntity) and commit (3) call em.clear() (4) call em.merge(newEntity) Step (4) generate insert statement to insert PrognosisEntry again, resulting in unique constraint violation from the database. It appears that the ApplicationIds.create(pc, meta) where pc is PrognosisEntry, the oid does not have complete id values.

          People

          • Assignee:
            Unassigned
            Reporter:
            Fay Wang
          • Votes:
            0 Vote for this issue
            Watchers:
            0 Start watching this issue

            Dates

            • Created:
              Updated:
              Resolved:

              Development