JPA EntityManger выполняет вставку базы данных при слиянии?

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

У меня есть данные из системы, которые мне нужно поместить в другую систему. Для этого я читаю данные, затем создаю новые объекты ORM на основе этих данных, а затем хочу сохранить объекты ORM в базе данных. Теперь, когда база данных пуста, программа работает без проблем с простым вызовом em.persist(object). Однако я хочу иметь возможность запускать этот процесс, когда в базе данных есть данные, добавляя новые данные и обновляя старые данные по мере необходимости, вот где у меня возникают проблемы.

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

ERROR JDBCExceptionReporter - Violation of UNIQUE KEY constraint 'UK-SubStuff-StuffId-SubStuffNumber'. Cannot insert duplicate key in object 'SubStuff'.

Мне кажется странным, что вызов em.merge() пытается вставить вместо обновления (я подтвердил это с помощью журнала SQL).

Hibernate: insert into SubStuff (SubStuffNumber, StuffId, Name, TypeId) values (?, ?, ?, ?)

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

Ниже приведен мой код, извините за грубое состояние, я попытался задокументировать здесь несколько обходных путей.

private void storeData(Collection<Stuff> Stuffs) {

    for (Stuff stuff : Stuffs) {
        //I think this first block can be safely ignored, as I am having no issues with it
        //  Left it in just in case someone more experianced then I sees the root of the issue here.
        Collection<SubStuff> subStuffs = stuff.getSubStuffCollection();
        for (SubStuff s : subStuffs) {
            //Persist SubStuff Type, which DOES NOT cascade,
            //  due to it not having an internal SubStuff collection
            Query q = em.createNamedQuery("SubStuffType.findByType");
            q.setParameter("type", f.getTypeId().getType());
            try {
                SubStuffType sst = (SubStuffType) q.getSingleResult();
                s.setTypeId(sst);
            } catch (NoResultException ex) {
                if (logger.isDebugEnabled()) logger.debug("SubStuff Type not found, persisting");
                em.persist(s.getTypeId());
            }
        }

        if (em.find(Stuff.class, stuff.getId()) == null) {
            //Persist on Stuffs will cascade to SubStuffs
            em.persist(stuff);
        } else {
            //  Failing to merge SubStuff, tries to insert duplicate
            //  Merge SubStuff first
            // The block below is my attempt to merge the SubStuff Collection before merging Stuff,
            //  it creates the same isuse as a straight merge of Stuff.
            Collection<SubStuff> mergedSubStuffs = new ArrayList<SubStuff>(SubStuffs.size());
            for (SubStuff s : SubStuffs) {
                Query q = em.createNamedQuery("SubStuff.findBySubStuffNumberStuffId");
                q.setParameter("SubStuffNumber", s.getSubStuffNumber());
                q.setParameter("StuffId", stuff.getId());
                try {
                    SubStuff subStuff = (SubStuff) q.getSingleResult();
        // -----> Merge fails, with an duplicate insert error
                    SubStuff mergedSubStuff = em.merge(s);
                    mergedSubStuffs.add(mergedSubStuff);
                } catch (NoResultException ex) {
                    throw ex;
                }
            }
            stuff.setSubStuffCollection(mergedSubStuffs);

        // -----> This will fails with same error as above, if I remove the attempt
            //  to merge the sub objects
            em.merge(stuff);
        }
    }
}

Если кто-нибудь с опытом JPA может мне помочь, я был бы очень признателен. Различия между saveOrUpdate() в Hibernate и merge() в JPA явно сбивают меня с толку, но, несмотря на то, что я прочитал несколько статей о слиянии EnityManger, я до сих пор не могу понять, что здесь происходит.

Спасибо за ваше время.


person James McMahon    schedule 29.04.2009    source источник
comment
Можете ли вы опубликовать, как вы сопоставили ассоциацию с «stuff» на «subStuffCollection»?   -  person zmf    schedule 29.04.2009
comment
Вещи - это один ко многим, чтобы заменить их. Аннотация: @OneToMany(cascade = CascadeType.ALL, mappedBy = stuffId, fetch = FetchType.LAZY).   -  person James McMahon    schedule 29.04.2009


Ответы (2)


Проклятие StackOverflow снова сработало. Поработав над этой проблемой около дня, я решил опубликовать этот вопрос, в течение 20 минут у меня был момент озарения, и я решил его. Отчасти потому, что я достаточно прояснил свои мысли, чтобы опубликовать вопрос. Размышляя о том, какая информация может иметь отношение к вопросу, я понял, что виноваты мои автоматически сгенерированные ключи (или, правильно, мое глупое отсутствие обработки их при слиянии).

Моя проблема в том, что SubStuff (наихудшая схема именования заменителей, извините за это) имеет автоматически сгенерированный искусственный первичный ключ. Поэтому при слиянии мне нужно было сделать;

SubStuff subStuff = (SubStuff) q.getSingleResult();
//++++++++
s.setId(subStuff.getId());
//++++++++

//The following code can be removed.
//--- SubStuff mergedSubStuff = em.merge(f);
//--- mergedSubStuffs.add(mergedSubStuff);

Это устанавливает первичный ключ для строки, которая уже существует в базе данных, и при первоначальном тестировании кажется, что она работает нормально.

Вызов слияния может быть упрощен до простого вызова em.merge(stuff), так как это приведет к каскадированию подобъектов, а коллекцию mergedsubstuff можно будет удалить целиком, так как мы больше не выполняем слияние внутри этого цикла.

Спасибо всем, кто прочитал мой смехотворно длинный вопрос, надеюсь, мой вопрос будет кому-то полезен в будущем.

person James McMahon    schedule 29.04.2009
comment
Я рад, что ты это понял. - person topchef; 30.04.2009

Ваша проблема связана со следующей парой строк:

Collection<SubStuff> mergedSubStuffs = new ArrayList<SubStuff>(SubStuffs.size());
...
stuff.setSubStuffCollection(mergedSubStuffs);

JPA будет рассматривать новую коллекцию как полный новый набор подсущностей и всегда будет вставлять их как таковые. Продолжайте работать с оригинальной коллекцией SubStuff внутри Stuff, и все будет в порядке.

person topchef    schedule 29.04.2009
comment
Там даже до второй строчки дело не доходит. С SubStuff не работает mergedSubStuff = em.merge(f); (желательно, чтобы SO поддерживал номера строк кода). Ваш совет, возможно, пригодится в будущем, спасибо - person James McMahon; 29.04.2009
comment
Коллекцию mergedSubStuff можно вообще убрать из кода, она надменная. Импорт заключается в том, что моему SubStuff назначается правильный первичный ключ, после чего он будет корректно объединен путем каскадирования, выполненного с вызовом em.persist(stuff). - person James McMahon; 29.04.2009