Вот что я пытаюсь сделать ... У меня есть человек
@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
Вы пробовали обновить Person, установив свойство гражданства на недавно обновленную версию?
@Dan - Я пробовал с evict () и без него. И да, я тоже пробовал обновить человека. Нет обновлений.
В состоянии разобраться?
Еще нет ... Я помещаю код обновления в блок try catch, и он перехватывает необработанное исключение «Другой объект с таким же значением идентификатора уже был связан с сеансом». Это указывает на то, что мне может потребоваться глубокое копирование всех объектов, связанных с гражданством ... что кажется глупым, поэтому я, вероятно, ошибаюсь ...




Попытался ли ты:
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();
}
Как и ожидалось ... это добавило третье гражданство, а не обновило его.
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 - странный зверь, и я обнаружил, что такие мелочи иногда работают.
Это добавило третье гражданство, а не обновило его. Да, это может быть немного необычно.
Какие свойства заключены в CitizenshipId?
Упс, я хотел закомментировать session.evict(c)', когда разместил этот код.
CitizenshipId добавлен в OP.
Запуск обновленного ответа не вызывает обновления, это одна из двух сложных частей.
Хорошо, последнее изменение этого ответа ... Я обновил его, указав новый CitizenshipId. Если это не сработает, я в тупике.
Тема Как обновить значения, связанные с первичным ключом в 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.
Спасибо Дэну за его идеи. Надеюсь, эта тема и ссылки на нее помогут другим.
попробуйте удалить
session.evict(c);