Spring MVC, генерирующий объект поддержки объекта из запроса? - PullRequest
5 голосов
/ 30 марта 2009

Я использую Spring MVC 2.5 и пытаюсь получить объект формы JSTL для загрузки из запроса GET. Я использую Hibernate POJO в качестве объектов поддержки.

В запросе есть одна страница, указывающая на другую страницу с идентификатором класса (первичным ключом строки). Запрос выглядит как "newpage.htm? Name = RowId". Это идет на страницу с объектом поддержки формы,

Новая страница выше загружает поля объекта в редактируемые поля, заполненные существующими значениями строки. Идея состоит в том, что вы должны иметь возможность редактировать эти поля и затем сохранять их обратно в базу данных.

Вид этой страницы выглядит примерно так

<form:form commandName="thingie">
    <span>Name:</span>
    <span><form:input path="name" /></span>
    <br/>
    <span>Scheme:</span>
    <span><form:input path="scheme" /></span>
    <br/>
    <span>Url:</span>
    <span><form:input path="url" /></span>
    <br/>
    <span>Enabled:</span>
    <span><form:checkbox path="enabled"/></span>
    <br/>

    <input type="submit" value="Save Changes" />
</form:form>

Контроллер содержит это,

public class thingieDetailController extends SimpleFormController {

    public thingieDetailController() {    
        setCommandClass(Thingie.class);
        setCommandName("thingie");
    }

    @Override
    protected Object formBackingObject(HttpServletRequest request) throws Exception {
        Thingie thingieForm = (Thingie) super.formBackingObject(request);

        //This output is always null, as the ID is not being set properly
        logger.debug("thingieForm.getName(): [" + thingieForm.getName() + "]");
        //thingieForm.setName(request.getParameter("name"));
        SimpleDAO.loadThingie(thingieForm);

        return thingieForm;
    }

    @Override
    protected void doSubmitAction(Object command) throws Exception {            
        Thingie thingie = (Thingie) command;
        SimpleDAO.saveThingie(thingie);
    }
}

Как видно из прокомментированного кода, я попытался вручную установить идентификатор объекта (в данном случае это name) из запроса. Однако Hibernate жалуется на десинхронизацию объекта, когда я пытаюсь сохранить данные в форме.

org.hibernate.StaleObjectStateException: Row was updated or deleted by another transaction (or unsaved-value mapping was incorrect)

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

Если кто-нибудь, знакомый с Spring MVC, может помочь мне с этим или предложить обходной путь, я был бы очень признателен.

EDIT:
Заводской код сеанса.

private static final SessionFactory sessionFactory;
private static final Configuration configuration = new Configuration().configure();

static {
    try {
        // Create the SessionFactory from standard (hibernate.cfg.xml) 
        // config file.
        sessionFactory = new AnnotationConfiguration().configure().buildSessionFactory();
    } catch (Throwable ex) {
        // Log the exception. 
        System.err.println("Initial SessionFactory creation failed." + ex);
        throw new ExceptionInInitializerError(ex);
    }
}

public static SessionFactory getSessionFactory() {
    return sessionFactory;
}

Ответы [ 4 ]

6 голосов
/ 17 августа 2009

Одним из основных недостатков использования Spring MVC + hibernate является то, что естественным подходом является использование объекта домена hibernate в качестве вспомогательного объекта для формы. Spring будет связывать что-либо в запросе по имени DEFAULT. Это непреднамеренно включает в себя такие вещи, как идентификатор или имя (обычно первичный ключ) или другие настраиваемые управляемые свойства гибернации. Это также делает вас уязвимым для инъекций формы.

Для обеспечения безопасности в этом сценарии вы должны использовать что-то вроде:

protected void initBinder(HttpServletRequest request, ServletRequestDataBinder binder) 
throws Exception {
 String[] allowedFields = {"name", "birthday"}
 binder.setAllowedFields(allowedFields);
}

и ИСКЛЮЧИТЕЛЬНО установите поля РАЗРЕШЕННЫЕ на те поля, которые есть в вашей форме, и исключите первичный ключ, иначе вы получите беспорядок !!!

5 голосов
/ 30 марта 2009

Чтобы ответить на ваш ближайший вопрос, проблема, с которой вы столкнулись в Hibernate, связана со следующей последовательностью событий:

  1. Открывается один сеанс Hibernate (назовем его сеанс A) в formBackingObject
  2. Используя сеанс А, вы загружаете объект Thingie в formBackingObject
  3. Вы возвращаете объект Thingie в результате formBackingObject
  4. Когда вы возвращаете объект Thingie, сессия A закрывается, но Thingie все еще связана с ним
  5. Когда вызывается doSubmitAction, тот же экземпляр объекта поддержки Thingie передается как команда
  6. Открывается новый сеанс Hibernate (назовите его сеанс B)
  7. Вы пытаетесь сохранить объект Thingie (первоначально открытый с сеансом A), используя сеанс B

В данный момент Hibernate ничего не знает о сеансе A, потому что он закрыт, поэтому вы получаете сообщение об ошибке. Хорошей новостью является то, что вы не должны делать это таким образом, и правильный путь полностью обойдет эту ошибку.

Метод formBackingObject используется для заполнения объекта команды формы данными перед отображением формы. Исходя из вашего обновленного вопроса, похоже, что вы просто пытаетесь отобразить форму, заполненную информацией из данной строки базы данных, и обновить эту строку базы данных при отправке формы.

Похоже, у вас уже есть класс модели для вашей записи; Я назову этот класс Record в этом ответе). У вас также есть DAO для класса Record, который я назову RecordDao. Наконец, вам нужен класс UpdateRecordCommand, который будет вашим вспомогательным объектом. UpdateRecordCommand должен быть определен со следующими полями и установщиками / получателями:

public class UpdateRecordCommand {
  // Row ID of the record we want to update
  private int rowId;
  // New name
  private int String name;
  // New scheme
  private int String scheme;
  // New URL
  private int String url;
  // New enabled flag
  private int boolean enabled;

  // Getters and setters left out for brevity
}

Затем определите вашу форму, используя следующий код:

<form:form commandName="update">
  <span>Name:</span>
  <span><form:input path="name" /></span><br/>
  <span>Scheme:</span>
  <span><form:input path="scheme" /></span><br/>
  <span>Url:</span>
  <span><form:input path="url" /></span><br/>
  <span>Enabled:</span>
  <span><form:checkbox path="enabled"/></span><br/>
  <form:hidden path="rowId"/>
  <input type="submit" value="Save Changes" />
</form:form>

Теперь вы определяете свой контроллер формы, который будет заполнять форму в formBackingObject и обрабатывать запрос на обновление в doSubmitAction.

public class UpdateRecordController extends SimpleFormController {

  private RecordDao recordDao;

  // Setter and getter for recordDao left out for brevity

  public UpdateRecordController() {    
      setCommandClass(UpdateRecordCommand.class);
      setCommandName("update");
  }

  @Override
  protected Object formBackingObject(HttpServletRequest request)
      throws Exception {
    // Use one of Spring's utility classes to cleanly fetch the rowId
    int rowId = ServletRequestUtils.getIntParameter(request, "rowId");

    // Load the record based on the rowId paramrter, using your DAO
    Record record = recordDao.load(rowId);

    // Populate the update command with information from the record
    UpdateRecordCommand command = new UpdateRecordCommand();

    command.setRowId(rowId);
    command.setName(record.getName());
    command.setScheme(record.getScheme());
    command.setUrl(record.getUrl());
    command.setEnabled(record.getEnabled());

    // Returning this will pre-populate the form fields
    return command;
  }

  @Override
  protected void doSubmitAction(Object command) throws Exception {
    // Load the record based on the rowId in the update command
    UpdateRecordCommand update = (UpdateRecordCommand) command;
    Record record = recordDao.load(update.getRowId());

    // Update the object we loaded from the data store
    record.setName(update.getName());
    record.setScheme(update.getScheme());
    record.setUrl(update.getUrl());
    record.setEnabled(update.setEnaled());

    // Finally, persist the data using the DAO
    recordDao.save(record);
  }
}
1 голос
/ 31 марта 2009

Что произошло, так это то, что? Name = rowId каким-то образом испортил пост формы. Как только я изменил это на имя, которое не отражает параметр в объекте, все работало нормально. Никаких изменений в DAO или коде контроллера не требуется.

Спасибо всем за ваши ответы. Это помогло мне понять, что происходит.

1 голос
/ 30 марта 2009

Ваша проблема может быть связана с обособленными объектами. Поскольку ваш DAO был изменен вне сеанса Hibernate, вам необходимо повторно присоединить объект к сеансу Hibernate перед сохранением. Вы можете сделать это, явно введя объект в сессию перед сохранением, используя Merge () или update (). Поэкспериментируйте с обоими и прочитайте документацию по этим действиям, так как они имеют разные эффекты в зависимости от структуры ваших объектов данных.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...