Обновление значений во встраиваемом классе

Вот что я пытаюсь сделать ... У меня есть человек

@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

и у каждого человека может быть один паспорт

@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

Это работает, у каждого человека может быть один паспорт, а Passport.ssn присваивается значение Person.ssn. Это делается потому, что SSN является уникальным идентификатором и позволяет избежать необходимости в таблицах ссылок.

Каждый человек также может иметь гражданство.

@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

Я успешно добавил человека с паспортом и человека без паспорта. Я добавил третьего лица с паспортом и двойным гражданством с

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

Мне это кажется странным / неэффективным, но это действительно работает (мы будем благодарны за советы по улучшению). Но по желанию Person.ssn переносится в Гражданство. Вот в чем проблема:

Лицо с двойным гражданством в настоящее время имеет гражданство США и Канады. Предположим, это неверно, и у человека есть гражданство США и Мексики, что означает, что CitizenshipId.country необходимо изменить с CAN на MEX. Я пробовал несколько вариантов кода, например

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

При включенном "show_sql" обновление не выполняется, хотя я вижу, что значения меняются при отладке. Я попробовал evict (), затем установил страну, затем saveOrUpdate, который сделал новую запись (я полагал, что это будет).

Уф ... вопрос в том, как можно обновить значения в классе Embeddable, когда этот класс используется как EmbeddedId? Я чувствую, что близок, но просто упускаю одну вещь ...

Спасибо.

Добавление CitizenshipID для справки

@Embeddable
public class CitizenshipId implements Serializable {

    private static final long serialVersionUID = 6732775093033061190L;

    String ssn;
    String country;

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

попробуйте удалить session.evict(c);

Dan 31.07.2018 22:05

Вы пробовали обновить Person, установив свойство гражданства на недавно обновленную версию?

Dan 31.07.2018 22:07

@Dan - Я пробовал с evict () и без него. И да, я тоже пробовал обновить человека. Нет обновлений.

BigFish 31.07.2018 22:13

В состоянии разобраться?

Dan 31.07.2018 23:19

Еще нет ... Я помещаю код обновления в блок try catch, и он перехватывает необработанное исключение «Другой объект с таким же значением идентификатора уже был связан с сеансом». Это указывает на то, что мне может потребоваться глубокое копирование всех объектов, связанных с гражданством ... что кажется глупым, поэтому я, вероятно, ошибаюсь ...

BigFish 01.08.2018 16:05
Пользовательский скаляр GraphQL
Пользовательский скаляр GraphQL
Листовые узлы системы типов GraphQL называются скалярами. Достигнув скалярного типа, невозможно спуститься дальше по иерархии типов. Скалярный тип...
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
Как вычислять биты и понимать побитовые операторы в Java - объяснение с примерами
В компьютерном программировании биты играют важнейшую роль в представлении и манипулировании данными на двоичном уровне. Побитовые операции...
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Поднятие тревоги для долго выполняющихся методов в Spring Boot
Приходилось ли вам сталкиваться с требованиями, в которых вас могли попросить поднять тревогу или выдать ошибку, когда метод Java занимает больше...
Полный курс Java для разработчиков веб-сайтов и приложений
Полный курс Java для разработчиков веб-сайтов и приложений
Получите сертификат Java Web и Application Developer, используя наш курс.
0
5
665
3
Перейти к ответу Данный вопрос помечен как решенный

Ответы 3

Попытался ли ты:

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

Как и ожидалось ... это добавило третье гражданство, а не обновило его.

BigFish 31.07.2018 22:20
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();
}  

Я удалил .evict из-за описания в 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".

Я знаю, что это похоже на то, что вы делаете, однако Hibernate - странный зверь, и я обнаружил, что такие мелочи иногда работают.

Dan 31.07.2018 22:29

Это добавило третье гражданство, а не обновило его. Да, это может быть немного необычно.

BigFish 31.07.2018 22:32

Какие свойства заключены в CitizenshipId?

Dan 31.07.2018 22:35

Упс, я хотел закомментировать session.evict(c)', когда разместил этот код.

Dan 31.07.2018 22:38

CitizenshipId добавлен в OP.

BigFish 31.07.2018 22:42

Запуск обновленного ответа не вызывает обновления, это одна из двух сложных частей.

BigFish 31.07.2018 22:44

Хорошо, последнее изменение этого ответа ... Я обновил его, указав новый CitizenshipId. Если это не сработает, я в тупике.

Dan 31.07.2018 22:50
Ответ принят как подходящий

Тема Как обновить значения, связанные с первичным ключом в Spring-JPA соответствует тому, что Дэн написал выше о создании нового объекта со старым идентификатором объекта. Однако в разделе Hibernate - обновить столбец id первичного ключа в таблице говорится, что Hibernate не позволяет обновлять первичные ключи.

Целью здесь было создать человека с (n) SSN, возможно, с паспортом и гражданством. SSN должен быть первичным ключом, поэтому я сопоставил Person с Passport и Citizenship и использовал SSN как JoinColumn.

Человек к паспорту - это отношения один к одному, так что это не проблема.

Человек к гражданству - это отношение «один ко многим». Это отношение означает, что мне нужно было создать встраиваемый идентификатор. Чтобы сделать каждое гражданство уникальным, был создан встраиваемый класс CitizenshipId с SSN и Country.

Используя принятый ответ для Hibernate - обновить столбец id первичного ключа в таблице, я изменил варианты

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

к

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

И обновление действительно произошло. Невозможность выполнить обновление с помощью обычного кода (используйте критерии для получения данных, обновите их, затем вызовите saveOrUpdate), но возможность выполнить обновление с помощью запроса не имеет для меня большого смысла. Я знаю, что управление ключами лучше всего оставлять базе данных, но когда используется уникальное значение, такое как SSN, другой ключ не нужен. Если идентификатор идентифицируется в коде без стратегии генерации, логично предположить, что идентификаторы могут быть обновлены ... JMHO.

Спасибо Дэну за его идеи. Надеюсь, эта тема и ссылки на нее помогут другим.

Другие вопросы по теме