ロック制御

今回はロックについてです。

【前提条件】

[環境]
[その他]
  • JPAの設定が完了している*1

【概要】

ロック制御はJPAAPIにより制御を行うことができます。

悲観的ロック、楽観的ロックの両方に対応していますが、
サンプルでは楽観的ロックを使用します。

【サンプルコード】

[テーブル]

今回は「goods」テーブルを新しく作成します。

CREATE TABLE goods
(
    goods_code character varying(5) NOT NULL,
    name character varying(30),
    lastupdate timestamp with time zone DEFAULT now(),
    CONSTRAINT sales_priymary_key PRIMARY KEY (goods_code )
)

楽観ロック用の更新日時を持たせます。

[Entity]

エンティティのクラスです。

package jp.glory.persistence.entity;

import java.sql.Timestamp;

import javax.persistence.Cacheable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.Version;

@Entity
@NamedQueries(@NamedQuery(name = "findAllGoods", query = "SELECT g FROM Goods g"))
@Cacheable(value = false)
public class Goods {

    @Id
    @Column(name = "goods_code")
    private String goodsCode = null;

    private String name = null;

    @Version
    private Timestamp lastupdate = null;

    // アクセサメソッドは省略
}

楽観的ロックで使用する項目には
javax.persistence.Versionアノテーションをつけます。

Versionアノテーションには属性を持たないので、
アノテーションをつけるだけです。

Versionアノテーションが使用できる型は
int、Integer、short、Short、long、Long、Timestamp
のいずれかです。

[EJB]

登録を行うEJBのクラスです。
ロックはレコード取得時に行う方法と
ロック用のメソッドを実行する方法があります。

package jp.glory.application;

import java.util.List;

import javax.ejb.Stateless;
import javax.persistence.EntityManager;
import javax.persistence.LockModeType;
import javax.persistence.PersistenceContext;
import javax.persistence.TypedQuery;

import jp.glory.persistence.entity.Goods;

@Stateless
public class GoodsApplication {

    @PersistenceContext(unitName = "blog")
    private EntityManager manager = null;

    public void update(final String goodsCode, final String name) {

        final Goods goods = getGoods(goodsCode);

        goods.setName(name);

        manager.persist(goods);
    }

    private Goods getGoods(final String goodsCode) {

        final Goods result = manager.find(Goods.class, goodsCode, LockModeType.OPTIMISTIC_FORCE_INCREMENT);

        if (result == null) {

            final Goods newGoods = new Goods();
            newGoods.setGoodsCode(goodsCode);

            return newGoods;
        }

        return result;
    }
}

レコードの取得時に行う場合、
EntityManager#findメソッド実行時にパラメータで指定します。

一つ目のパラメータにはエンティティのクラス、
二つ目のパラメータにはキー値、
三つ目にロックのモード
を指定します。

    final Goods result = manager.find(Goods.class, goodsCode);

    manager.lock(result, LockModeType.OPTIMISTIC_FORCE_INCREMENT);

ロック用のメソッドはEntityManager#lockメソッドを使用します。
lockメソッドにはロックをかけるエンティティとロックのモードを指定します。

findメソッド/lockメソッドでロックモードを指定します
ロックモードは8種類定義されています。

■NONE
 ロックを行いません

■OPTIMISTIC / READ
 楽観的ロックを行います。
 更新後、Versionアノテーションの値は更新しません。
 READはOPTIMISITCの別名(シノニム)です。

■OPTIMISTIC_FORCE_INCREMENT / WRITE
 楽観的ロックを行います。
 更新後、Versionアノテーションの値を更新します。
 WRITEはOPTIMISTICの別名(シノニム)です。

■PESSIMISTIC_FORCE_INCREMENT
 悲観的ロック(書き込みロック)を行います。
 更新後、Versionアノテーションの値を更新します。

■PESSIMISTIC_WRITE
 悲観的ロック(書き込みロック)を行います。
 更新後、Versionアノテーションの値は更新しません。

■PESSIMISTIC_READ
 悲観的ロック(読み取りロック)を行います。

[ManagedBean]

登録用のManagedBeanです。

package jp.glory.ui;

import javax.ejb.EJB;
import javax.ejb.EJBException;
import javax.faces.application.FacesMessage;
import javax.faces.bean.ManagedBean;
import javax.faces.bean.RequestScoped;
import javax.faces.context.FacesContext;
import javax.persistence.OptimisticLockException;
import javax.transaction.RollbackException;

import jp.glory.application.GoodsApplication;

@ManagedBean(name = "goodsEntryPage")
@RequestScoped
public class GoodsEntryPage {

    @EJB
    private GoodsApplication application = null;

    private String goodsCode = null;

    private String name = null;

    public void update() {

        try {
            application.update(goodsCode, name);

            goodsCode = null;
            name = null;
        } catch (final EJBException ejbException) {

            final FacesContext context = FacesContext.getCurrentInstance();

            final RollbackException rollBackException = (RollbackException) ejbException.getCausedByException();
            final Throwable cause = rollBackException.getCause();
            if (cause instanceof OptimisticLockException) {

                context.addMessage("", new FacesMessage("ロックエラー"));
            } else {

                context.addMessage("", new FacesMessage("その他エラー"));
            }
        }
    }

    // アクセサメソッドは省略
}
[ページ]

登録用の画面です。

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
    "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 
<html xmlns="http://www.w3.org/1999/xhtml"
    xmlns:f="http://java.sun.com/jsf/core"
    xmlns:ui="http://java.sun.com/jsf/facelets"
    xmlns:h="http://java.sun.com/jsf/html">
    <f:view>
        <h:head>
            <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />
            <title>登録サンプル</title>
        </h:head>
        <h:body>
            <h:form id="entryForm">
                <div>
                    商品コード:<h:inputText id="userId" value="#{goodsEntryPage.goodsCode}" />
                </div>
                <div>
                    商品名:<h:inputText id="goodsCode" value="#{goodsEntryPage.name}" />
                </div>
                <div>
                    <h:commandButton action="#{goodsEntryPage.update()}" value="登録" />
                </div>
                <h:messages />
            </h:form>
        </h:body>
    </f:view>
</html>