ロック制御
今回はロックについてです。
【前提条件】
[環境]
- JDK 1.7.0_07
- Glassfish 3.1.2.2
- PostgreSQL 9.1(JDBC:postgresql-9.1-901.jdbc4.jar)
【サンプルコード】
[テーブル]
今回は「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>