Hibernate 4.3.11
У меня проблема с сохранением следующего графа объектов в hibernate.Работодатель сохраняется с помощью метода merge ().
Employer
|_ List<EmployerProducts> employerProductsList;
|_ List<EmployerProductsPlan> employerProductsPlan;
Для продуктов Employer & EmployerProducts автоматически создается pk.План EmployerProductsPlan представляет собой составной ключ, состоящий из идентификатора EmployerProducts и строки с кодом плана.
Ошибка возникает, когда в списке EmployerProducts есть временный объект, каскадный к которому List<EmployerProductsPlan>
.Первой ошибкой, с которой я столкнулся, я пытался обойти, был внутренний спящий NPE.Этот пост здесь прекрасно описывает проблему, которая вызывает у меня нулевой указатель Hibernate NullPointer на INSERTED id при сохранении трех уровней с использованием @Embeddable и cascade
ОП оставил комментарий, указав, что ониЯ сделал, чтобы решить, но я в конечном итоге с другой ошибкой при переходе на предлагаемое сопоставление.После изменения сопоставления я получаю
org.hibernate.NonUniqueObjectException: A different object with the same identifier value was already associated with the session : [com.webexchange.model.EmployerProductsPlan#com.webexchange.model.EmployerProductsPlanId@c733f9bd]
. Из-за других зависимостей библиотеки я не могу в настоящее время обновиться до версии 4.3.x.Этот проект использует spring-boot-starter-data-jpa 1.3.3.Никакая другая работа не выполняется в сеансе, кроме вызова merge () и передачи объекта работодателя.
Ниже приведены сопоставления для каждого класса:
Работодатель
@Entity
@Table(name = "employer")
@lombok.Getter
@lombok.Setter
@lombok.EqualsAndHashCode(of = {"employerNo"})
public class Employer implements java.io.Serializable {
@Id
@GeneratedValue(strategy = IDENTITY)
@Column(name = "EMPLOYER_NO", unique = true, nullable = false)
private Long employerNo;
.....
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "employer", orphanRemoval = true)
private List<EmployerProducts> employerProductsList = new ArrayList<>(0);
}
EmployerProducts
@Entity
@Table(name = "employer_products")
@Accessors(chain = true) // has to come before @Getter and @Setter
@lombok.Getter
@lombok.Setter
@lombok.EqualsAndHashCode(of = {"employerProductsNo"})
public class EmployerProducts implements Serializable {
@Id
@GeneratedValue(strategy = IDENTITY)
@Column(name = "employer_products_no", unique = true, nullable = false)
private Long employerProductsNo;
@ManyToOne
@JoinColumn(name = "employer_no", nullable = false)
private Employer employer;
......
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "employerProducts", orphanRemoval = true)
private List<EmployerProductsPlan> employerProductsPlanList = new ArrayList<>(0);
}
EmployerProductsPlan
@Accessors(chain = true) // has to come before @Getter and @Setter
@lombok.Getter
@lombok.Setter
@lombok.EqualsAndHashCode(of = {"id"})
@Entity
@Table(name="employer_products_plan")
public class EmployerProductsPlan implements Serializable {
@EmbeddedId
@AttributeOverrides({ @AttributeOverride(name = "plan", column = @Column(name = "epp_plan", nullable = false)),
@AttributeOverride(name = "employerProductsNo", column = @Column(name = "employer_products_no", nullable = false)) })
private EmployerProductsPlanId id;
@ManyToOne
@JoinColumn(name = "employer_products_no")
@MapsId("employerProductsNo")
private EmployerProducts employerProducts;
}
Я заполняю вышеуказанные продукты работодателя с помощьютот же экземпляр объекта EmployerProducts, который сохраняется.Он временный и не имеет идентификатора, заполненного, поскольку он еще не существует в БД.
EmployerProductsPlanId
@Accessors(chain = true) // has to come before @Getter and @Setter
@lombok.Getter
@lombok.Setter
@lombok.EqualsAndHashCode(of = {"plan", "employerProductsNo"})
@Embeddable
public class EmployerProductsPlanId implements Serializable {
private String plan;
private Long employerProductsNo;
// This was my previous mapping that was causing the internal NPE in hibernate
/* @ManyToOne
@JoinColumn(name = "employer_products_no")
private EmployerProducts employerProducts;*/
}
ОБНОВЛЕНИЕ: Показывает распорки контроллера и дао.Объект Employer никогда не загружается из БД до сохранения.Struts создает весь этот граф объектов из параметров запроса Http.
Контроллер Struts 2.5
@lombok.Getter
@lombok.Setter
public class EditEmployers extends ActionHelper implements Preparable {
@Autowired
@lombok.Getter(AccessLevel.NONE)
@lombok.Setter(AccessLevel.NONE)
private IEmployerDao employerDao;
private Employer entity;
....
public String save() {
beforeSave();
boolean newRecord = getEntity().getEmployerNo() == null || getEntity().getEmployerNo() == 0;
Employer savedEmployer = newRecord ?
employerDao.create(getEntity()) :
employerDao.update(getEntity());
setEntity(savedEmployer);
return "success";
}
private void beforeSave() {
Employer emp = getEntity();
// associate this employer record with any products attached
for (EmployerProducts employerProduct : emp.getEmployerProductsList()) {
employerProduct.setEmployer(emp);
employerProduct.getEmployerProductsPlanList().forEach(x ->
x.setEmployerProducts(employerProduct));
}
// check to see if branding needs to be NULL. It will create the object from the select parameter with no id
// if a branding record has not been selected
if (emp.getBranding() != null && emp.getBranding().getBrandingNo() == null) {
emp.setBranding(null);
}
}
}
Работодатель DAO
@Repository
@Transactional
@Service
@Log4j
public class EmployerDao extends WebexchangeBaseDao implements IEmployerDao {
private Criteria criteria() {
return getCurrentSession().createCriteria(Employer.class);
}
@Override
@Transactional(readOnly = true)
public Employer read(Serializable id) {
return (Employer)getCurrentSession().load(Employer.class, id);
}
@Override
public Employer create(Employer employer) {
getCurrentSession().persist(employer);
return employer;
}
@Override
public Employer update(Employer employer) {
getCurrentSession().merge(employer);
return employer;
}
}