1

update()への呼び出しで、メモリ内の変更されたオブジェクトをデータベースに既に保存されているデータと比較する必要がある Hibernate プロジェクトがあります。たとえば、私のビジネス ロジックでは、レコードが「有効」である (有効日が今日またはそれ以前である) 場合、更新によって有効日を変更することはできません。これを達成するために、次のコードがあります (少し長くて複雑です)。

マネジャー

public class LogicManager {

  @Autowired
  SessionFactory sessionFactory

  private Session getSession() {
    return sessionFactory.getCurrentSession();
  }

  public MemberRecord findRecord(Integer id) {
    // << Code to check authorization >>
    return memberRecordDAO.findById(id);
  }

  public void updateRecord(MemberRecord record) {
    getSession().evict(record);
    MemberRecord oldRecord = memberRecordDAO.findById(record.getId());

    Date oldEffectiveDate = oldRecord.getEffectiveDate();
    if ( isEffective(oldEffectiveDate) && 
         !oldEffectiveDate.equals(record.getEffectiveDate)) {
      throw new IllegalArgumentException("Cannot change date");
    }

    // << Other data checks >>
    memberRecordDAO.update(record);
  }
}

ダオ

public class MemberRecordDAO {
  @Autowired
  private SessionFactory sessionFactory;

  private Session getSession() {
    return sessionFactory.getCurrentSession();
  }

  public MemberRecord findById(Integer id) {
    return (MemberRecord)getSession()
             .getNamedQuery("findMemberById")
             .setInteger("id", id)
             .uniqueResult();
  }
}

クライアントコード

// ...
public void changeEffectiveDate(Integer recordId, Date newDate) {
  LogicManager manager = getBean("logicManager");

  MemberRecord record = manager.findById(recordId);
  record.setEffectiveDate(newDate);
  manager.updateRecord(record);
}

マネージャーに呼び出しを追加する前にevict()、マネージャーが予期しない方法で動作していることに気付きました。レコードを更新するには、まず を呼び出してそのレコードを取得する必要があります。これによりfindById()、レコードがセッション キャッシュに入れられます。そのオブジェクトに変更を加えてから、(おそらく)永続化されたデータを取得するためにupdateRecord()呼び出す呼び出しを行います。この 2 回目の呼び出しは、データベース データを参照するのではなく、キャッシュからオブジェクトをプルするだけであるfindById()ことに気付きました。これにより、とがまったく同じオブジェクトになるため、常に新しく変更された日付と同じになります。findById()oldEffectiveDate recordoldRecord

これに対抗するために、 への呼び出しを追加しましたevict()。これは、オブジェクトがキャッシュから削除され、Hibernate がデータベースにアクセスしてMemberRecord. その変更を行った後、 をMemberRecordDAO呼び出すと例外がスローuniqueResult()されますAssertionFailed: possible nonthreadsafe access to session。デバッガーを実行すると、 と の両方が同じ を使用していることがわかりLogicManagerますMemberRecordDAOSessionこれは正しいと思いました。

だから、私の質問:

  1. 私の考え方/アルゴリズムは正しいですか? 正しいevict()ことはありますか?より良い方法はありますか?私はセッション、キャッシング、またはevict(). スレッドの問題に対処する前に、このロジックが正しいことを確認したいと思います。
  2. SessionDAO からのアクセスがスレッドセーフでないのはなぜですか?
4

2 に答える 2

2

evict()アプローチは機能しますが、「物事を行うための好ましい休止状態の方法」は、次のようにSession.merge()を使用することだと思います。

public MemberRecord updateRecord(MemberRecord newRecord) {

    MemberRecord oldRecord = memberRecordDAO.findById(record.getId());

    Date oldEffectiveDate = oldRecord.getEffectiveDate();
    if ( isEffective(oldEffectiveDate) && 
     !oldEffectiveDate.equals(newRecord.getEffectiveDate)) {
      throw new IllegalArgumentException("Cannot change date");
    } else {
       MemberRecord merged = (MemberRecord) session.merge(newRecord);
       return merged;
    }
}

Session.merge()は、oldRecordのすべてのフィールドをnewRecordの値で更新することに注意してください。

于 2011-11-09T20:33:52.673 に答える