「JPAのエラーは全てRuntimeExceptionのサブクラスなので例外をキャッチする必要はない」というノリでJboss7.1.1 でEJBと組み合わせてJPAを使ってみましたが、なかなか思うようにはいきませんでした。
簡単なWebアプリケーションを作成してみると同一のレコードを更新しあう競合の問題に出会います。この回避策として version 番号による方法が知られていて、JPAでは@VERSIONアノテーションで簡単に実装できます。しかし、この場合の例外もRuntimeException です。
一方、EJBはトランザクションの自動管理をしてくれますが、自動でロールバックしてくれるのは「ロールバックされる例外はRuntimeExceptionと java.rmi.RemoteExceptionのサブクラス」なのだそうです。
EJBのロールバックの条件とJPAの例外発生の方針から、例外処理については何もしないのが正解に思えてしまいます。しかし、アプリケーションによって競合の場合はその旨を知らせて再度オペレーションを促したり、WEB画面にスタックトレースが表示されるのを避けたいことも多いはずです。また、ビジネスロジックでエラーが発生した場合もロールバックさせたいはずです。
EJBで通常のException系のエラーでEJBにロールバックさせるには、例外オブジェクトの定義に@ApplicationException(rollback = true)を記入することで実現できるそうです。今回は競合の発生時にこのユーザ定義エラーにして throw することにしました。競合の検出には、更新処理内で flush() することにより、OptimisticLockExceptionを拾う方法が一般的なようです。
また、予期せぬ例外でWEB画面にスタックトレースが表示される問題は、 EJBExceptionを拾ってメッセージに置き換えることにしました。実際のアプリケーションはどのように作られているのでしょう。
jboss7.1.1 で JPA
$ tree . . ├── DATA │ └── create.sh ├── jpatest │ ├── WEB-INF │ │ └── classes │ │ ├── META-INF │ │ │ ├── persistence.xml -> persistence.xml.hibernate │ │ │ ├── persistence.xml.eclipselink │ │ │ ├── persistence.xml.hibernate │ │ │ └── persistence.xml.openjpa │ │ └── jpa │ │ ├── MyException.class │ │ ├── Srvtest01.class │ │ ├── VersionException.class │ │ ├── foods.class │ │ ├── foodsdao.class │ │ └── foodsdaoBean.class │ └── test01.jsp ├── jpatest.war └── src ├── MyException.java ├── Srvtest01.java ├── build.xml ├── foods.java ├── foodsdao.java └── foodsdaoBean.java 7 directories, 19 files
(1) foods.java
package jpa; import java.io.Serializable; import javax.persistence.*; @Entity @Table(name="foods") public class foods implements Serializable { public foods(){}; @Id @Column(name="code") private String code; @Column(name="name") private String name; @Column(name="price") private Integer price; @Version @Column(name="version") private Integer version; public String getCode() { return code; } public void setCode(String code) { this.code = code; } public String getName() { return name; } public void setName(String name) { this.name = name; } public int getPrice() { return price; } public void setPrice(int price) { this.price = price; } public int getVersion() { return version; } public void setVersion(int version) { this.version = version; } }
(2) MyException.java
package jpa; import javax.ejb.ApplicationException; @ApplicationException(rollback = true) public class MyException extends Exception { }
(3) foodsdao.java
package jpa; import java.util.*; public interface foodsdao { public Listallfoods(); public foods sltfoods(String code); public void updfoods(foods foods) throws MyException; }
(4) foodsdaoBean.java
package jpa; import java.util.*; import javax.ejb.*; import javax.persistence.*; @Stateless @Remote public class foodsdaoBean implements foodsdao { @PersistenceContext(unitName="foods") private EntityManager em; public Listallfoods() { Query query = em.createQuery("select p from foods p"); List foods = query.getResultList(); return(foods); } public foods sltfoods(String code) { return(em.find(foods.class,code)); } public void updfoods(foods foods) throws MyException { try { foods foods1 = em.find(foods.class, foods.getCode()); foods foods2 = em.merge(foods); em.flush(); } catch(OptimisticLockException e){ throw new MyException(); } } }
(4) Srvtest01.java
package jpa; import java.util.*; import java.io.*; import javax.servlet.*; import javax.servlet.annotation.*; import javax.servlet.http.*; import javax.ejb.*; import javax.persistence.*; import jpa.foodsdao; @WebServlet(name="Srvtest01", urlPatterns={"/Srvtest01"}) public class Srvtest01 extends HttpServlet { @EJB private foodsdao foodsdao; @Override public void doGet(HttpServletRequest req, HttpServletResponse res) throws ServletException,IOException { res.setContentType("text/html; charset=UTF-8"); PrintWriter out = res.getWriter(); String HTML_TEXT = "</head>" + "<body>" + "<form action='Srvtest01' method='post'>" + "<input type='hidden' name='mode' value='select'/>" + "CODE:" + "<input type='text' name='code'/>" + "<input type='submit' value='検索'/>" + "</form>" + "</body>" + "</html>"; out.println(HTML_TEXT); out.close(); } @Override public void doPost(HttpServletRequest req, HttpServletResponse res) throws ServletException,IOException { req.setCharacterEncoding("UTF-8"); res.setContentType("text/html; charset=UTF-8"); String mode = req.getParameter("mode"); if (mode.equals("select")) { foods foods = null; try { foods = foodsdao.sltfoods(req.getParameter("code")); } catch(EJBException e) { String HTML_TEXT = "</head>" + "<body>" + "検索に失敗しました(EJBException):" + "<a href='Srvtest01'>戻る</a>" + "</body>" + "</html>"; PrintWriter out = res.getWriter(); out.println(HTML_TEXT); out.close(); return; } if (foods != null) { String HTML_TEXT = "</head>" + "<body>" + "<form action='Srvtest01' method='post'>" + "<input type='hidden' name='mode' value='update'/>" + "CODE:%s" + "<input type='hidden' name='code' value='%s'/>" + "NAME:" + "<input type='text' name='name' value='%s'/>" + "PRICE:" + "<input type='text' name='price' value='%d'/>" + "<input type='hidden' name='version' value='%d'/>" + "<p>" + "<a href='Srvtest01'>戻る</a>" + "<input type='submit' value='更新'/>" + "</form>" + "</body>" + "</html>"; PrintWriter out = res.getWriter(); out.println(String.format(HTML_TEXT, foods.getCode(), foods.getCode(), foods.getName(), foods.getPrice(), foods.getVersion())); out.close(); } else { String HTML_TEXT = "</head>" + "<body>" + "検索に失敗しました(対象なし):" + "<a href='Srvtest01'>戻る</a>" + "</body>" + "</html>"; PrintWriter out = res.getWriter(); out.println(HTML_TEXT); out.close(); } } else if (mode.equals("update")) { foods foods = new foods(); foods.setCode(req.getParameter("code")); foods.setName(req.getParameter("name")); foods.setPrice(Integer.parseInt(req.getParameter("price"))); foods.setVersion(Integer.parseInt(req.getParameter("version"))); try { foodsdao.updfoods(foods); String HTML_TEXT = "</head>" + "<body>" + "更新完了しました:" + "<a href='Srvtest01'>戻る</a>" + "</body>" + "</html>"; PrintWriter out = res.getWriter(); out.println(HTML_TEXT); out.close(); } catch(MyException e) { String HTML_TEXT = "</head>" + "<body>" + "更新失敗しました(競合):" + "<a href='Srvtest01'>戻る</a><br />" + "</body>" + "</html>"; PrintWriter out = res.getWriter(); out.println(HTML_TEXT); out.close(); } catch(EJBException e) { String HTML_TEXT = "</head>" + "<body>" + "更新失敗しました(EJBException):" + "<a href='Srvtest01'>戻る</a><br />" + "</body>" + "</html>"; PrintWriter out = res.getWriter(); out.println(HTML_TEXT); out.close(); } } } }
(5) build.xml
<project name="test01" default="default"> <property name="hibernate" location="/home/ユーザ/jboss7/modules" /> <target name="default"> <javac srcdir="." destdir="../jpatest/WEB-INF/classes" > <classpath> <pathelement path="${hibernate}/javax/persistence/api/main/hibernate-jpa-2.0-api-1.0.1.Final.jar" /> <pathelement path="${hibernate}/javax/ejb/api/main/jboss-ejb-api_3.1_spec-1.0.1.Final.jar" /> <pathelement path="${hibernate}/javax/servlet/api/main/jboss-servlet-api_3.0_spec-1.0.0.Final.jar" /> </classpath> </javac> </target> </project>
(6) persistence.xml
<?xml version="1.0" encoding="UTF-8" ?> <persistence xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_2_0.xsd" version="2.0" xmlns="http://java.sun.com/xml/ns/persistence"> <persistence-unit name="foods" transaction-type="JTA"> <provider>org.hibernate.ejb.HibernatePersistence</provider> <jta-data-source>java:jboss/datasources/MySqlDS</jta-data-source> <class>jpa.foods</class> </persistence-unit> </persistence>
(7) create.sh
#! /bin/sh mysql -uユーザ -ppassword sampledb <<EOF drop table if exists foods; create table foods( code varchar(8) primary key, name varchar(100), price integer, version integer ) engine=InnoDB; insert into foods values('000001','りんご',398,0); insert into foods values('000002','みかん',480,0); insert into foods values('000003','柿' ,450,0); select * from foods; EOF
(8) コンパイルと配備
% cd src % ant % cd ../jpatest % jar cvf ../jpatest.war . % rm ~/jboss7/standalone/deployments/jpa* % cp ../jpatest.war ~/jboss7/standalone/deployments % cd ../DATA % ./create.sh(9) 実行確認
0 件のコメント:
コメントを投稿