5

Spring MVC 2.5を使用しており、GETリクエストからロードするJSTLフォームオブジェクトを取得しようとしています。バッキングオブジェクトとしてHibernatePOJOがあります。

リクエストにクラスID(行の主キー)を持つ別のページに向かう1つのページがあります。リクエストは「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);
    }
}

コメントされたコードからわかるように、リクエストからオブジェクトID(この場合は名前)を手動で設定してみました。ただし、Hibernateは、フォームにデータを永続化しようとすると、オブジェクトが非同期化されることについて不平を言います。

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

このエラーは、セッション全体に何らかの影響を及ぼしているようです。これにより、Webアプリケーション全体で機能しなくなり、上記のStale ObjectStateExceptionが継続的にスローされます。

Spring MVCに精通している人がこれを手伝ったり、回避策を提案したりできるなら、本当にありがたいです。

編集:
セッションファクトリコード。

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

4 に答える 4

6

Spring MVC + hibernate を使用する際の主な欠点の 1 つは、フォームのバッキング オブジェクトとして hibernate ドメイン オブジェクトを使用するのが自然なアプローチであるということです。Spring は、DEFAULT による名前に基づいて、リクエスト内のすべてのものをバインドします。これには、ID や名前 (通常は主キー)、または設定されているその他の休止状態の管理プロパティなどが誤って含まれます。これにより、フォーム インジェクションに対しても脆弱になります。

このシナリオで安全を確保するには、次のようなものを使用する必要があります。

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

許可されたフィールドをフォーム内のフィールドのみに明示的に設定し、主キーを除外しないと、混乱してしまいます!!!

于 2009-08-17T16:39:47.213 に答える
5

当面の質問に答えるために、Hibernate で発生している問題は、次の一連のイベントに関係しています。

  1. 1 つの Hibernate セッションが開かれます (セッション A と呼びましょう)。formBackingObject
  2. セッション A を使用して、Thingie オブジェクトをformBackingObject
  3. 次の結果として Thingie オブジェクトを返します。formBackingObject
  4. Thingie オブジェクトを返すと、セッション A は閉じられますが、Thingie はまだリンクされています
  5. が呼び出されると、バッキング オブジェクトdoSubmitActionの同じインスタンスがコマンドとして渡されます。Thingie
  6. 新しい Hibernate セッション (セッション B と呼びます) が開かれます
  7. セッション B を使用して、Thingie オブジェクト (セッション A で最初に開いたもの) を保存しようとします。

セッション A は閉じられているため、この時点では Hibernate はセッション A について何も認識していないため、エラーが発生します。良いニュースは、そのようにするべきではないということです。正しい方法は、そのエラーを完全に回避します。

このformBackingObjectメソッドは、フォームを表示する前に、フォームのコマンド オブジェクトにデータを入力するために使用されます。更新された質問に基づいて、特定のデータベース行からの情報が入力されたフォームを表示し、フォームが送信されたときにそのデータベース行を更新しようとしているように思えます。

レコード用のモデル クラスが既にあるようです。Recordこの回答ではそれをクラスと呼びます)。Recordクラス用の DAO もあります。これを と呼びますRecordDaoUpdateRecordCommand最後に、バッキング オブジェクトとなるクラスが必要です。は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);
  }
}
于 2009-03-30T15:58:49.793 に答える
1

問題は分離天体に関連している可能性があります。DAOはHibernateセッションの外部で変更されているため、保存する前にオブジェクトをHibernateセッションに再アタッチする必要があります。これを行うには、Merge()またはupdate()を使用して保存する前に、オブジェクトをセッションに明示的に取り込みます。データオブジェクトの構造によって効果が異なるため、両方を試して、これらのアクションのドキュメントを読んでください。

于 2009-03-30T15:55:26.853 に答える