Hibernate 4.3 Каскадное объединение через несколько списков с внедренным идентификатором - PullRequest
0 голосов
/ 04 февраля 2019

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


}

1 Ответ

0 голосов
/ 04 февраля 2019

На данный момент мое решение состоит в том, чтобы пройтись по EmployerProducts и проверить наличие новых записей.Я вызвал persist для новых, прежде чем вызывать merge () для родительского Employer.Я также перенес логику, с которой связывал все ключи в дао, вместо того, чтобы использовать ее в своем действии Struts.Ниже показано, как мой метод update () в DAO работодателя теперь выглядит как

public Employer update(Employer employer) {


    // associate this employer record with any products attached
    for (EmployerProducts employerProduct : employer.getEmployerProductsList()) {
        employerProduct.setEmployer(employer);

        if (employerProduct.getEmployerProductsNo() == null) {
            // The cascade down to employerProductsPlanList has issues getting the employerProductsNo
            // automatically if the employerProduct does not exists yet.  Persist the new employer product
            // before we try to insert the new composite key in the plan
            // /11462270/hibernate-4-3-kaskadnoe-obedinenie-cherez-neskolko-spiskov-s-vnedrennym-identifikatorom
            List<EmployerProductsPlan> plansToBeSaved = employerProduct.getEmployerProductsPlanList();
            employerProduct.setEmployerProductsPlanList(new ArrayList<>());
            getCurrentSession().persist(employerProduct);

            // add the plans back in
            employerProduct.setEmployerProductsPlanList(plansToBeSaved);
        }

        // associate the plan with the employer product
        employerProduct.getEmployerProductsPlanList().forEach(x ->
                    x.getId().setEmployerProductsNo(employerProduct.getEmployerProductsNo())
        );

    }


    return (Employer)getCurrentSession().merge(employer);
}
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...