1

状況:

バーコード付きの PDF を生成する webapp (Java EE-Spring-Hibernate) があります。バーコードには、実行時に生成される ID が含まれています。Hibernate を使用して現在の日付の ID があるかどうかを確認する Java サービスがあり、存在しない場合はエントリが作成されます。存在する場合は、インクリメントおよび更新されます。次のフィールドを含む 1 日あたり 1 つの行があります: id、coverPageId、date

そのため、ユーザーがその日の最初のバーコードを作成すると、現在の日付の行が追加され、coverPageId=1 になります。次に、後続のすべてのバーコードがデータベースから最後の ID を取得し、それをインクリメントして、インクリメントされた値で行を保存します。

問題:

複数のユーザーが同時に PDF を作成している場合、Hibernate は両方のユーザーに対して同じ値を取得し、結果として PDF に同じバーコードが作成されます。

試した解決策:

現在の ID を取得してインクリメントし、行を更新するコードは、すべて同じメソッドで実行されます。これは、Spring Bean 内のメソッドです。Spring Bean はシングルトンなので、メソッドを作成してみましたsynchronized。これは役に立ちませんでした。

方法:

@Transactional(isolation=Isolation.SERIALIZABLE)
public synchronized long getNextId(Date date)
{
    List<CoverpageID> allCoverpageIds = this.getAllCurrentIds(date);

    if(allCoverpageIds.size() == 0)
    {
        loggingService.warn(String.format("Found no existing ID for date '%tD', creating new record", date));
        System.out.println(String.format("Found no existing ID for date '%tD', creating new record", date));
        return this.createNewCoverpageIdRecord(new Date());
    }
    else if(allCoverpageIds.size() == 1)
    {
        CoverpageID coverpageId = allCoverpageIds.get(0);
        loggingService.debug(String.format("Found existing ID for date '%tD': %d, incrementing and persisting", date, coverpageId.getCoverPageId()));
        System.out.println(String.format("Found existing ID for date '%tD': %d, incrementing and persisting", date, coverpageId.getCoverPageId()));
        coverpageId.setCoverPageId(coverpageId.getCoverPageId() + 1);
        dao.save(coverpageId);
        loggingService.debug(String.format("Saved existing ID for date '%tD' with incremented ID: %d", date, coverpageId.getCoverPageId()));
        return coverpageId.getCoverPageId();
    }
    else
    {
        loggingService.warn(String.format("Found multiple records for date '%tD'", date));
        return -1;
    }
}

private List<CoverpageID> getAllCurrentIds(Date date)
{
    String exClause = "where date = :date";
    Map<String, Object> values = new HashMap<String, Object>();
    values.put("date", date);
    return dao.getAllEx(CoverpageID.class, exClause, values);
}

private long createNewCoverpageIdRecord(Date date)
{
    dao.save(new CoverpageID(new Date(), new Long(1)));
    return 1;
}

CoverpageID.class (バーコードのエンティティ):

@NamedQueries({
@NamedQuery(
    name = "getCoverPageIdForDate",
    query = "from CoverpageID c where c.date = :date",
    readOnly = true
)})
@Entity
public class CoverpageID
{
private Long id;
private Date date;
private long coverPageId;

public CoverpageID() {}

public CoverpageID(Date date, Long coverPageId)
{
    this.date = date;
    this.coverPageId = coverPageId;
}

public void setId(Long id)
{
    this.id = id;
}

@Id
@GeneratedValue(strategy=GenerationType.AUTO)
public Long getId()
{
    return id;
}

@Temporal(TemporalType.DATE)
public Date getDate()
{
    return date;
}

public void setDate(Date date)
{
    this.date = date;
}

public long getCoverPageId()
{
    return coverPageId;
}

public void setCoverPageId(long coverPageId)
{
    this.coverPageId = coverPageId;
}
}

この同時実行性の問題が発生するのを防ぐ方法を他に知っている人はいますか?

4

1 に答える 1

1

Hibernate とは何の関係もないと思います。Hibernate を使用していなくても、このような問題に直面することになります。

いくつかの選択肢があります:

関連する部分に Hibernate を使用しないことを検討してください。多くのDBには、アトミックな「存在しない場合は挿入-存在する場合は更新」のようなロジックを実行する機能があります。

また

実際に Hibernate を使用する場合は、次のものが必要です。

  1. レコードの重複を避けるために、DB に一意の制約があることを確認してください
  2. プログラム ロジックで、レコードがないことがわかり、挿入しようとした場合は、重複データ/制約違反の例外をキャッチする準備をしてください。そのような場合、ロジックを失敗として扱わないでください。代わりに、後続の更新を行ってください。

挿入/更新アクションを処理するためにオブジェクトインスタンスを使用して試したソリューションに関しては、あなたのやり方はうまくいきません。

まず、実際にすべての ID をその Bean の状態として保持し、アプリケーションがプロセスのみを持つ場合、それは機能します。あなたのコードは毎回チェックするためにDBに行き、それに応じて挿入/更新を行います。このコードは同期されていますが、機能しません。異なる DB セッションの中で、あるセッションの変更は、トランザクションが実際にコミットされたときにのみ他のセッションに表示されることに注意してください。したがって、このような状況になります

THREAD 1                    THREAD 2
-------------------------------------------------
enter method
check DB, no record found   enter method (wait coz synchronized)
insert record
exit method
                            check DB, no record found (coz prev txn not commited yet)
                            insert record
                            exit method
commit
                            commit

見る?あなたはまだまったく同じ問題に直面しています。

私の方法以外にも解決する方法はありますが、少なくとも、あなたが使用している方法は役に立ちません。

于 2013-03-04T09:56:40.420 に答える