Updating values in an Embeddable class

embeddable spring boot
jpa embedded collection
embedded and @embeddable in hibernate
hibernate partial update
jpa embedded id
hibernate update only non null values
hibernate update only modified columns
embeddable definition

Here's what I'm trying to do...I have a Person

@Entity
@Table(name = "PERSON", 
   uniqueConstraints = {
       @UniqueConstraint(columnNames = {"SSN"})
   }
)
@DynamicInsert(true)
@DynamicUpdate(true)
@SelectBeforeUpdate(true)
public class Person implements java.io.Serializable {

    private static final long serialVersionUID = 6732775093033061190L;

    @Version
    @Column(name = "OBJ_VERSION")
    private Timestamp version;

    @Id
    @Column(name = "SSN", length = 12, nullable = false, insertable = true, updatable = true)
    private String ssn;

    @Column(name = "LAST_NAME", length = 50, nullable = false, insertable = true, updatable = true)
    private String lastName;

    @Column(name = "FIRST_NAME", length = 30, nullable = false, insertable = true, updatable = true)
    private String firstName;

    @Column(name = "MIDDLE_NAME", length = 30, nullable = true, insertable = true, updatable = true)
    private String middleName;

    @OneToOne(fetch = FetchType.LAZY, mappedBy = "person", cascade = CascadeType.ALL)
    private Passport passport;

    @OneToMany(fetch = FetchType.EAGER, mappedBy = "person", cascade = CascadeType.ALL, orphanRemoval = true)
    private Set<Citizenship> citizenship = new HashSet<>();

// Getters and setters left out for brevity

and each person can have one Passport

@Entity
@Table(name = "PASSPORT", 
   uniqueConstraints = {
       @UniqueConstraint(columnNames = {"SSN", "PASSPORT_NUMBER"})
   }
)
@DynamicInsert(true)
@DynamicUpdate(true)
@SelectBeforeUpdate(true)
public class Passport implements java.io.Serializable {

    private static final long serialVersionUID = 6732775093033061190L;

    @Version
    @Column(name = "OBJ_VERSION")
    private Timestamp version;

    @Id
    @Column(name = "SSN", length = 12, nullable = false, insertable = true, updatable = true)
    private String ssn;

    @OneToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "SSN")
    @MapsId
    private Person person;    

    @Column(name = "EXPIRATION_DATE", nullable = true, insertable = true, updatable = false)
    private GregorianCalendar expirationDate;

    @Column(name = "ISSUING_COUNTRY", nullable = true, insertable = true, updatable = false)
    private String issuingCountry;

    @Column(name = "PASSPORT_NUMBER", nullable = false, insertable = true, updatable = false)
    private String passportNumber;

// Getters and setters left out for brevity

This works, each person can have one Passport and the Passport.ssn is assigned the value of the Person.ssn. This is being done because SSN is a unique identifier and it avoids the need for link tables.

Each person can also have a Citizenship

@Entity
@Table(name = "CITIZENSHIP")
@DynamicInsert(true)
@DynamicUpdate(true)
@SelectBeforeUpdate(true)
public class Citizenship implements java.io.Serializable {

    private static final long serialVersionUID = 6732775093033061190L;

    @Version
    @Column(name = "OBJ_VERSION")
    private Timestamp version;   

    @EmbeddedId
    private CitizenshipId citizenshipId;

    @Column(name = "DATE_OF_CITIZENSHIP")
    private GregorianCalendar dateOfCitizenship;     

    @ManyToOne(fetch = FetchType.EAGER)
    @JoinColumn(name = "SSN")
    @MapsId("ssn")
    private Person person; 

// Getters and setters left out for brevity

I have successfully added a person with a passport and a person without a passport. I have added a third person with a passport and dual citizenship with

   // This person has a passport and is a dual citizen.
    person = new Person();
    person.setSsn("654-89-7531");
    person.setFirstName("Lois");
    person.setLastName("Lane");
    passport = new Passport();
    passport.setExpirationDate(new GregorianCalendar());
    passport.setIssuingCountry("USA");
    passport.setPassportNumber("987654");

    Set<Citizenship> citizenshipSet = new HashSet<>();

    CitizenshipId citizenshipId = new CitizenshipId();
    citizenshipId.setCountry("USA");

    Citizenship c = new Citizenship();
    c.setDateOfCitizenship(new GregorianCalendar());
    c.setCitizenshipId(citizenshipId);
    c.setPerson(person);
    citizenshipSet.add(c);

    citizenshipId = new CitizenshipId();
    citizenshipId.setCountry("CAN");
    c = new Citizenship();
    c.setDateOfCitizenship(new GregorianCalendar());
    c.setCitizenshipId(citizenshipId);   
    c.setPerson(person);
    citizenshipSet.add(c);

    person.setPassport(passport);
    passport.setPerson(person);

    session.saveOrUpdate(person);

    for(Citizenship citizen : citizenshipSet) {
        session.saveOrUpdate(citizen);            
    }
    session.flush();
    session.clear();

This looks weird/inefficient to me, but it does work (tips for improvement would be appreciated). But as desired, the Person.ssn is carried into the Citizenship. Here's the problem:

The Person with dual Citizenship currently has citizenship in USA and Canada. Let's assume this is wrong and the Person has citizenship in USA and Mexico, which means the CitizenshipId.country needs to change from "CAN" to "MEX". I have tried a bunch of variations of code like

        Criteria citCriteria = session.createCriteria(Citizenship.class);    
        citCriteria.add(Restrictions.eq("citizenshipId.ssn", "654-89-7531"));
        List<Citizenship> citizenship = citCriteria.list();

        for(Citizenship c : citizenship) {
            if("CAN".equalsIgnoreCase(c.getCitizenshipId().getCountry())) {
                session.evict(c);

                c.getCitizenshipId().setCountry("MEX");
                session.saveOrUpdate(c);
                session.flush();
                session.clear();
            }
        }

With "show_sql" on, this doesn't perform an update, even though I can see the values change when debugging. I did try an evict(), then set the country, then saveOrUpdate, which made a new entry (I figured it would).

Phew...the question is: How can the values in an Embeddable class be updated when that class is being used as an EmbeddedId? I feel like I'm close but just missing one thing...

Thanks.

Adding CitizenshipID for reference

@Embeddable
public class CitizenshipId implements Serializable {

    private static final long serialVersionUID = 6732775093033061190L;

    String ssn;
    String country;

// Omitted getters, setters, constructors, hashcode, and equals

Have you tried:

if("CAN".equalsIgnoreCase(c.getCitizenshipId().getCountry())) {
                session.evict(c);

                c.getCitizenshipId().setCountry("MEX");
                c.getPerson().getCitizenship().add(c);  // TRY ADDING THIS
                session.saveOrUpdate(c);
                session.flush();
                session.clear();
}

How to update only a subset of entity attributes using JPA and , This will automatically create/update the database tables whenever we update the corresponding entity class in our application. Note that, You'll  An entity may have references of other non-entity classes. Such non-entity classes are called embeddable classes. All fields of an embeddable class are mapped to the owner entity table. @Embeddable annotation is used on the non-entity class. This is the class which is to be embedded in an entity class. @Embedded is used in the entity class. This annotation is placed on the field/property referring to the embeddable class.

if("CAN".equalsIgnoreCase(c.getCitizenshipId().getCountry())) {

                // TRY ADDING THIS ------------------------
                //session.evict(c);
                CitizenshipId cid = new CitizenshipId();
                cid.setSsn(c.getCitizenshipId().getSsn();
                cid.setCountry("MEX");
                c.setCitizenshipId(cid); // references new CID -- should issue update
                // ----------------------------------------- 
                session.saveOrUpdate(c);
                session.flush();
                session.clear();
}  

I removed the .evict due to the description in the API:

Remove this instance from the session cache. Changes to the instance will not be synchronized with the database. This operation cascades to associated instances if the association is mapped with cascade="evict".

JPA / Hibernate @Embeddable and @Embedded Example with , JPA provides the @Embeddable annotation to declare that a class will be embedded by other entities. Let's define a class to abstract out the  Embeddable classes are used to represent the state of an entity but don’t have a persistent identity of their own, unlike entity classes. Instances of an embeddable class share the identity of the entity that owns it. Embeddable classes exist only as the state of another entity.

Topic How to update values associated with Primary Key in Spring-JPA is inline with what Dan posted above about creating a new object with the old object Id. However, topic Hibernate - update the primary key 'id' column in the table does state that Hibernate doesn't allow updates to primary keys.

The objective here was to create a Person with a(n) SSN, possibly with a Passport, and a Citizenship. SSN is intended to be the primary key so I mapped Person to Passport and Citizenship and used SSN as the JoinColumn.

Person to Passport is a one to one relationship, so that wasn't a problem.

Person to Citizenship is a one to many relationship. This relationship means I had to create an embeddable ID. To make each Citizenship unique the embeddable class CitizenshipId was created with SSN and Country.

Using the accepted answer for Hibernate - update the primary key 'id' column in the table I changed the variations of

    Criteria citCriteria = session.createCriteria(Citizenship.class);    
    citCriteria.add(Restrictions.eq("citizenshipId.ssn", "654-89-7531"));
    List<Citizenship> citizenship = citCriteria.list();

    for(Citizenship c : citizenship) {
        if("CAN".equalsIgnoreCase(c.getCitizenshipId().getCountry())) {
            session.evict(c);

            c.getCitizenshipId().setCountry("MEX");
            session.saveOrUpdate(c);
            session.flush();
            session.clear();
        }
    }

to

Query query=session.createQuery("update Citizenship set country = :country1 where ssn = :ssn and country = :country2")
    .setString("country1", "MEX").setString("ssn", "654-89-7531").setString("country2", "CAN");
query.executeUpdate();

And an update did occur. Being unable to make an update via typical code (use criteria to get data, update it, then call saveOrUpdate) but being able to make an update via a query doesn't make a whole lot of sense to me. I know that key management is more times than not best left to the database, but when a unique value such as SSN is being used there is no need for another key. If an ID is identified within the code without a generation strategy it stands to reason that the IDs can be updated...JMHO.

Thanks to Dan for his ideas. I hope this topic and its references helps others.

JPA @Embedded And @Embeddable, If we need to update value type we just create a new instance of value @​Embeddable public class PhoneNumber implements Serializable  Let’s first define the embeddable types that will be embedded in the User model. We’ll create a package named model inside com.example.jpa package and add all the model classes in this package. Embeddable Types. We use JPA’s @Embeddable annotation to declare that a class is meant to be embedded by other entities. 1. Name

Introduction to Hibernate embeddable types, @Embeddable public class AddressLine { private String value; Person column: active (should be mapped with insert="false" update="false"). JPA Tutorial - JPA Embeddable Example « Previous; Next » The following sections demonstrate how to embed an entity into another entity. First we create a Embeddable entity by marking the class with @Embeddable annotation. @Embeddable @Access(AccessType.FIELD) public class Address {

Map me if you can - Advanced embeddable mappings, Updating JPA Entity Objects. Modifying Employee.class, 1); em. The entity object is physically updated in the database when the transaction is committed. Is applied to a persistent field or property of an entity class or mapped superclass to denote a composite primary key that is an embeddable class. The embeddable class must be annotated as Embeddable. Let us directly look into JPA @EmbeddedId example.

Using JPA to update Java entity objects in the database, Fields of persistable user defined classes (entity classes, embeddable classes and their values are never stored in the database (similar to transient fields in  Introduction. In a previous article, I explained how you could audit entity modifications using the JPA @EntityListeners for embeddable types.. Since Hibernate ORM 5.2.17 now allows you to use the @PrePersist and @PreUpdate JPA entity listeners, we can simplify the previous example, as you will see in this article.

Comments
  • try removing session.evict(c);
  • Have you tried updating the Person, setting the citizenship property to the newly updated version?
  • @Dan - I tried with and without the evict(). And yes, I tried updating the Person as well. No update.
  • Able to figure it out?
  • Not yet...I put the update code into a try catch block and it's catching an unhandled exception "A different object with the same identifier value was already associated with the session". This indicates I may need to deep copy all objects associated with Citizenship...which seems stupid so I'm probably wrong...
  • As expected...this added a third citizenship rather than updating one.
  • I know this looks exactly like what you're doing, however Hibernate is a strange beast and I've found little things like this sometimes do work
  • This added a third citizenship rather than updating one. Yes, it can be a little quirky.
  • What properties are encompassed in CitizenshipId ?
  • Whoops, I meant to comment out session.evict(c)' when I posted this code.
  • CitizenshipId added to OP.