Ошибка: отсоединенная сущность передана для сохранения - попробуйте сохранить сложные данные (Play-Framework) - PullRequest
10 голосов
/ 03 ноября 2011

У меня проблема с сохранением данных через play-framework.Может быть, добиться такого результата невозможно, но было бы очень хорошо, если бы это сработало.

Простой : у меня есть сложная модель (магазин с адресами), и я хочу сразу изменить магазин с адресами и сохранить их таким же образом (shop.save ()).Но ошибка detached entity passed to persist происходит.

История обновлений 05.11

  • 05.11

    • update Модель Shop с атрибутом mappedBy="shop"
    • обновить ссылку на группу пользователей Google
  • 09.11

    • найти обходной путь, но он не является общим
  • 16.11

    • пример обновления HTML-формы, благодаря @ Pavel
    • обновление обходного пути (обновление 09.11) для универсального метода, благодаря @ mericano1
  • 21.11
    • Я бросил пытаться найти решение и ждал игры 2.0 ...

Dateil : я пытаюсь сократить проблему до минимума:
Модель :

@Entity
public class Shop extends Model {

    @Required(message = "Shopname is required")
    public String shopname;

    @OneToMany(cascade=CascadeType.ALL, fetch=FetchType.EAGER, mappedBy="shop")
    public List<Address> addresses;

}


@Entity
public class Address extends Model {

    @Required
    public String location;

    @ManyToOne
    public Shop shop;
}

теперь мой Frontendcode :

#{extends 'main.html' /}

#{form @save(shop?.id)}

    <input type="hidden" name="shop.id" value="${shop?.id}"/>

    #{field 'shop.shopname'}
        <label for="shopName">Shop name:</label>
        <input type="text" name="${field.name}" 
            value="${shop?.shopname}" class="${field.errorClass}" />
    #{/field}

    <legend>Addressen</legend>
    #{list items: shop.addresses, as: "address"}
        <input type="hidden" name="shop.addresses[${address_index - 1}].id" value="${address.id}"/>
        <label>Location</label>
        <input name="shop.addresses[${address_index - 1}].location" type="text" value="${address.location}"/>
    #{/list}

     <input type="submit" class="btn primary" value="Save changes" />
#{/form}

У меня есть только Id из самого магазина и название магазина для доставки через POST, например: ?shop.shopname=foo

Интересная часть - список адресови там у меня есть идентификатор и местоположение с адресаd результат будет примерно таким: ?shop.shopname=foo&shop.addresses[0].id=1&shop.addresses[0].location=bar.

Теперь Контроллер часть для данных:

public class Shops extends CRUD {

public static void form(Long id) {

    if (id != null) {
        Shop shop = Shop.findById(id);
        render(shop);
    }
    render();
}

public static void save(Long id, Shop shop) {

    // set owner manually (dont edit from FE)
    User user = User.find("byEmail", Security.connected()).first();
    shop.owner = user;

    // Validate
    validation.valid(shop);
    if (validation.hasErrors()) 
        render("@form", shop);

    shop.save();
    index();
}

Теперь проблема : Когда я изменяю данные адреса, код достигает shop.save(); Магазин объектов заполнен всеми данными, и все выглядит нормально, но когда Hibernate пытается сохранить данные, возникает ошибка detached entity passed to persist :(

Я попытался изменить режим выборки, каскадный тип и ятакже попытался:

Shop shop1 = shop.merge();
shop1.save();

К сожалению, ничего не сработало, либо произошла ошибка, либо адресные данные не будут сохранены. Есть ли способ сохранить данные таким образом?

Если естьчто-то не ясно, пожалуйста, напишите мне, я был бы рад предоставить как можно больше информации.

Обновление 1 Я также поставил проблему в группу пользователей Google

Обновление 2 + 3 С помощью группы пользователей (спасибо Брайану) и Ответа от mericano1 здесь я нашел общий способ.

Сначала вынеобходимо удалить cascade=CascadeType.ALL из атрибута addresses в shop.class. Tтогда вы должны изменить метод save в shops.class.

public static void save(Long id, Shop shop) {

    // set owner manually (dont edit from FE)
    User user = User.find("byEmail", Security.connected()).first();
    shop.owner = user;

    // store complex data within shop
    storeData(shop.addresses, "shop.addresses");
    storeData(shop.links, "shop.links");

    // Validate
    validation.valid(shop);
    if (validation.hasErrors()) 
        render("@form", shop);

    shop.save();
    index();
}

общий способ хранения данных выглядит следующим образом:

private static <T extends Model> void  storeData(List<T> list, String parameterName) {
    for(int i=0; i<list.size(); i++) {
        T relation = list.get(i);

        if (relation == null)
            continue;

        if (relation.id != null) {
            relation = (T)Model.Manager.factoryFor(relation.getClass()).findById(relation.id);
            StringBuffer buf = new StringBuffer(parameterName);
            buf.append('[').append(i).append(']');
            Binder.bind(relation, buf.toString(), request.params.all());
        }

        // try to set bidiritional relation (you need an interface or smth)
        //relation.shop = shop;
        relation.save();
    }
}

Я добавил в Shop.class список Ссылки , но я выиграл 't обновить другие фрагменты кода, поэтому будьте осторожны, если произойдут ошибки компиляции.

Ответы [ 4 ]

6 голосов
/ 17 ноября 2011

Когда вы обновляете сложный экземпляр в Hibernate, вы должны убедиться, что он поступает из базы данных (сначала извлеките его, а затем обновите тот же экземпляр), чтобы избежать этой проблемы «отдельного экземпляра».

Обычно япредпочитаю всегда выбирать сначала, а затем обновлять только определенные поля, которые я ожидаю от пользовательского интерфейса.

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

(T)Model.Manager.factoryFor(relation.getClass()).findById(relation.id);
2 голосов
/ 20 ноября 2011

Это не сделает тебя счастливым.Я споткнулся из-за той же ошибки, собирался задать тот же вопрос на SO и увидел ваш.Эта вещь не работает, это серьезная ошибка.В документах, которые я нашел, «поскольку было бы утомительно явно вызывать save () для большого графа объектов, вызов save () автоматически каскадно связывается с аннотированным атрибутом cascade = CascadeType.ALL».но это просто не работает.

Я даже отлаживал SQL, что связано с вашими (и моими) ассоциациями в том, что они удаляются и повторно связываются с родителем, но они никогда не обновляютсяс новыми ценностями.Это что-то вроде этого:

//Here's it's updating a LaundryList where I've modified the address and one of the laundry items
//it first updates the simple values on the LaundryList:
update LaundryList set address=?, washDate=? where id=?
binding parameter [1] as [VARCHAR] - 123 bong st2
binding parameter [2] as [DATE] - Fri Mar 11 00:00:00 CET 2011
binding parameter [3] as [BIGINT] - 413

//then it deletes the older LaundryItem:
delete from LaundryList_LaundryItem where LaundryList_id=?
binding parameter [1] as [BIGINT] - 413
binding parameter [2] as [BIGINT] - 407

//here it's associating the laundry list to a different laundry item
insert into LaundryList_LaundryItem (LaundryList_id, laundryItems_id) values (?, ?)
binding parameter [1] as [BIGINT] - 413
binding parameter [2] as [BIGINT] - 408

//so where did you issue the SQL that adds the updated values to the associated LaundryItem?? 

Я видел ваш обходной путь, и я искренне ценю ваши усилия и тот факт, что вы постарались опубликовать его (так как это поможет людям, которые застряли с этим), но этопобеждает цель быстрого развития и ORM.Если я вынужден выполнять некоторые операции, которые обычно должны быть автоматическими (или даже не необходимыми, почему он удаляет старую ассоциацию и ассоциирует родительскую с новой, а не просто обновляет эту ассоциацию за один раз?), Тогда это не совсем так.«быстрое» или недействительное «объектно-реляционное отображение».

Насколько я понимаю, они взяли идеально работающую среду (JPA) и превратили ее во что-то бесполезное, изменив его поведение.В этом случае это относится к тому факту, что вызов JPA merge больше не делает то, что он должен, они говорят, что добавили свой собственный метод с именем "save", который помогает с этим (почему ???)и эта штука не работает так, как это описано в документации и примерах на их сайте (подробнее об этом в этом вопросе, который я отправил.

ОБНОВЛЕНИЕ:

Ну, вот и мой обходной путь:

Теперь я просто игнорирую, чтобы отправить идентификаторы обновленных ассоциаций в контроллер, таким образом, Play будет думать, что они новые сущности, которые будут добавлены в БД, ипри вызове merge (...) и save () для их родительской сущности все daya будут сохранены корректно, однако это вызывает у вас еще одну ошибку: с тех пор каждый раз, когда вы изменяете некоторые ассоциации и сохраняете родителя, эти ассоциации обрабатываются как новыесущности, которые будут созданы (они имеют id = null), и, таким образом, старые сохраняются от своих родителей при сохранении всего этого, что либо оставляет большую кучку осиротевших, бесполезныхсущностей в БД или заставляет вас писать еще более обходной код, чтобы очистить те осиротевшие связанные сущности в родительской сущности, которую вы собираетесь сохранить.

ОБНОВЛЕНИЕ 2:

На данный момент, я думаю, лучше подождать Play 2.0, который в настоящее время находится в бета-версии и будет запущен в ближайшее время.Это не слишком круто, так как, по словам мсье Борта (из памяти), «вы не сможете перенести свои проекты Play 1.x в Play 2 напрямую, но для переноса их будет достаточно легкого копирования и копирования».Скопируйте / вставьте свой код для победы!И подумать, сколько времени другие производители фреймворков тратят на обратную совместимость своей новой версии продукта!В любом случае, в своей статье , представляющей дорожную карту Play 2.0, они говорят, что они заменят Hibernate / JPA на какую-то другую инфраструктуру ORM, и в то же время признают, что взломали стандартную реализацию JPAсделано Hibernate для достижения ... ну, мы все видели, что это достигнуто.Вот цитата:

"Сегодня предпочтительным способом доступа приложения Java к приложению Play Java к базе данных SQL является библиотека Play Model, работающая на Hibernate. Но так как она раздражает в веб-среде без сохранения состояния, такой как Play, для управленияСостояния с состоянием, такие как те, которые определены в официальной спецификации JPA, мы предоставили особую разновидность JPA, позволяющую сохранять вещи как можно без состояний. Это заставило нас взломать Hibernate таким образом, который, вероятно, не будет устойчивым в долгосрочной перспективе.мы планируем перейти к существующей реализации JPA без сохранения состояния, которая называется EBean. "

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

2 голосов
/ 03 ноября 2011

Не уверен, что это ответ, потому что я не знаю о Play, но двунаправленная ассоциация Shop-Address неверна: сторона магазина должна быть помечена как обратная сторона другой стороны, используя @OneToMany(mappedBy="shop", ...).

Кроме того, если save и merge соответствуют Session.save и Session.merge соответственно, сохранение после слияния не имеет смысла.Сохранить используется для вставки нового, временного объекта в сеанс.Если слияние было вызвано, оно уже сохраняется в момент вызова save.

1 голос
/ 15 ноября 2011

Согласно документации Play, вы должны предоставить строку запроса, подобную следующей:

?shop.addresses[0].id=123
&shop.addresses[1].id=456
&shop.addresses[2].id=789

Я не уверен, правильно ли вы ее предоставили.Попробуйте это:

<input type="hidden" name="shop.addresses[${address_index - 1}].id" value="${address.id}"/>
Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...