2015年1月4日日曜日

imac 2008 に ssd を搭載。

年末に imac に久しぶりにCD-Rを投入したら、認識もしないし、排出もできないという事態に陥った。さらにリブートすると事態は悪化し、CD-R を読みに行こうとしたまま OS が立ち上がらくなった・・・
この際、スーパー・ドライブをSSDに換装するこたおにした。ところが、我が家の imac のスーパー・ドライブは ATAPI というインタフェースらしく、購入した部品は取り付けられず大失敗であった。

購入した部品がもったいないので高速化という名目にかこつけて、ハードディスクの換装に方向転換。

usb 接続で ubuntu をインストールしてハードディスクと換装するとなぜか c キーを押しながらでないとブートできないという新たな問題に直面。 

以前 Fedora20 i686 バージョンは、imacと相性がよかったのを思い出し、これをインストールしてみるとうまくブートできます。そこで、Fedora20 i686 用の boot パーティションを残したまま、ubuntu を再インストールすることにしました。気持ちが悪いのですが、肝心の体感はやはりかなり速くなった感じがします。

ブートメニューからログインまで   パスワード入力から利用できるまで chrome 起動          
ハードディスク 50秒 25秒 11秒
SSD 17秒 10秒 2秒



2015年1月3日土曜日

ubuntu 14.10 で cannon mx880 プリンタを使う

このエントリーをはてなブックマークに追加

最近は、プリンタも「システム設定」より「プリンター」を追加する方法で接続できることが多いようです。印刷トレイを cassete にしないと背面から印刷するのは変わっていないようですが・・・

参照:https://forums.ubuntulinux.jp/viewtopic.php?id=16346

■プリンタ追加(システム設定⇒プリンタから)


■ネットワークプリンター検索(IPアドレス指定)

⇒キュー名が補完される


■ドライバー選択


■背面給紙がデフォルたなので、テスト印刷拒否


■プリンタプロパティのプリンターオプションの給紙方法を cassete に設定


android から ubuntu へ ssh 接続

昨年末に長年お世話になって来た iphone シリーズの継続を諦めて android 系に移行することにしました。

ターミナルソフトを導入
Linux 系ということなのでターミナルを導入して ls すると正しく Linux 系の雰囲気。しかし、ほとんど Linux系コマンドはインストールされておらず・・
BusyBox non root の導入
仕方がないので busybox non root というアプリを見つけてインストールすると、一応一通りのコマンドがそろっていて、vi も使えます。これで、ちょっとしたシェルスクリプトやテキストの編集ができそうですが、キーボードが使う気がしません。
Bluetooth キーボードの導入
折りたたみ式キーボードを導入したら、キー配列に難点はありますが、ソフトウエアキーボードで入力するよりはましです。  
自宅へ ssh 接続
android の中だけではなにもできないので、他のLinuxにssh接続してみました。cui での接続ですから、データ通信量も限られているので問題ないでしょう。busybox 付属の ssh でパスワード認証で接続すると普通に接続できます。では、公開鍵認証にしようと思ったところで、ssh-keygen コマンドが busybox には存在しないことに気づきました。検索してみると、「connect bot」というアプリで接続している方が多いらしい。でも、ターミナルから接続したいところ・・・
busybox の場合、dropbearkey というのがキー生成に対応するそうなので、こいつでキーを生成し、公開鍵をサーバーの authorized_key に登録します。
$ sudo apt-get install android-tools-adb
$ adb shell
$ cd /sdcard
$ mkdir .ssh
$ cd .ssh
$ dropbearkey -t rsa -f /sdcard/.ssh/id_rsa > id_rsa.pub
$ exit
$ cd ~/.ssh
$ adb pull /sdcard/.ssh/id_rsa.pub id_rsa.android
$ sed -n '2 p' id_rsa.android >> authorized_key
$ adb shell
$ ssh -i /sdcard/.ssh/id_rsa uername@ipaddress
と接続できたました、でも、使わないんでしょうね。

2014年9月15日月曜日

最近の ubuntu は重い気が・・・

最近の ubuntu は、なんとなく重い気がしてVirtualBoxに色々なディストリビューションを入れて操作感を試してみた。結果、メモリ使用量には結構な差があるもののアプリケーションの起動速度にはそれほど大きな差がないようです。(ちなみに、conalinux、debian の libreOffice はバージョンが 3.5で少し古いようです。)

マシンはちょっと前のimacで、以下のCPUでメモリは4Gです。
$ cat /proc/cpuinfo | head -9
processor : 0
vendor_id : GenuineIntel
cpu family : 6
model  : 23
model name : Intel(R) Core(TM)2 Duo CPU     E8235  @ 2.80GHz
stepping : 6
microcode : 0x60c
cpu MHz  : 2800.000
cache size : 6144 KB


起動 ログイン firefox LibreCalc
ConaLinux xfce 52秒 13秒 5秒 8秒
ー 144M 207M 239M
debian7.6 52秒 20秒 5秒 8秒
ー 237M 325M 391M
LinuxMint1.7 xfce 52秒 20秒 9秒 9秒
ー 281M 387M 447M
CentOS7 77秒 28秒 11秒 15秒
ー 550M 687M 723M
Fedora7 63秒 27秒 10秒 10秒
ー 790M 903M 1000M
Ubuntu14.04 50秒 25秒 12秒 10秒
ー 600M 730M 800M


ConaLinux xfce


仮想環境だけでなくて imac に直接インストールもしてみました。なんか個人でここまでやってるのって凄いな〜と思いつつ、軽くていいとも思いましたが、普通にインストールすると efi に対応してなくて、CD-ROMから起動することになりました。efi の知識もなく、訳もなく新しいもの好きなので、32bit の古い構成っぽいので候補から外れました。

debian7.6

なんか debian って通の方が使う印象があって使ってみたかったんですが、これが imac と相性悪くてメチャクチャ苦戦しました。主にビデオボードの radeon 2600 xt にうまく対応できないのか、ログイン画面もロクに表示されず、プロプライエタリなドライバをというのを組み込んでも画面が真っ黒で残念。最初、usbメモリにインストールしてましたが、いつのまにHDから起動できなくなって、ubuntu をクリーンインストールしても復活することがなく、今は fedora をクリーンインストールする以外HDから立ち上がることはなくなりました。そんなわけで、debian も通な方の逸品ということで候補から外れました。

LinuxMint1.7 xfce

先の debian の件でHDブート出来なくなったので、fedora に64G位を割り当てて、そのあとにMint入れて使っています。なにもしなくても、普通に動画関係も見られるし、割とメモリ使用量も少なめで、ファイルマネージャや、ターミナルもスルリと起動できるので気に入っています。デフォルトでパネルに設定されるものが少ないので、画面の明るさ調整とか、ワークスペースの切り替えとかにツールがあるのに気づくのに少し時間がかかりましたが。。

CentOS7

これは使ってみたかったんですが、usdドライブのパーティション構成で失敗してインストールできませんでした。HDからブートできなくなっていたので一度クリーンインストールを試してみればよかったと思いましたが、いずれ又の機会に。

Fedora7

imac では、ちゃんとHDブートするようにインストールしてくれていいんですが、動画関連がデフォルトでは全然使えなくて、色々なドライバを導入しないと使えなさそうな感じです。今回は、Fedora と Mint のデュアルブート構成に落ち着いたので、たまには gnome3 も触るようになるかもしれません。

ubuntu14.04

Linux の初心者には、ubuntu が進められているという理由が何となくわかりました。XP から乗り換えるには重いと思うのですが、とりあえず色々なハードウエアに対応していますし、動画関連なども普通に見られます。3Dデスクトップがそんなに使いやすいわけでもないので、重い印象を与えてしまうのはもったいない気もします。

2014年7月6日日曜日

ubuntuに oracle11g をインストール

ubuntu16.10 に oracle11g(R2) をインストールする際、以前にもまして以下のようなエラーが発生する。
/usr/bin/ld: /u01/app/oracle/product/11.2.0/dbhome_1/lib//libpls11.a(plzsql.o): relocation R_X86_64_32 against
情報:  `_2__STRING.0.0' can not be used when making a shared object。 -fPIC を付けて再コンパイルしてください。
/usr/bin/ld: 最終リンクに失敗しました: 出力に対応するセクションがありません

このエラーには、該当箇所に -no-pie オプションを付与することで解決した。
※このエラーについては、「Ubuntu 16 で Stack + GHC がライブラリのリンクに失敗する場合の対処法」 を参照させて頂いた。

省力化のため、この問題も含めインストール中に出るエラーの対策をまとめた patch と oracle11g(R2) をインストールする前準備のシェルを作成した。 なお、この手順は VirtualBox に ubuntu16.04 および ubuntu16.10 をインストールし、2017/04/06日時点までの update を当てて作成した。






1.事前準備として以下の構成のディレクトリを作成
 /mnt/vbox/share/oracle11g 
 |-- database : linux.x64_11gR2_database_[12]of2.zip を展開
 `-- inst11g : inst11g.tar.gz を展開

2.oracle11g が要求する各種設定を適用
$ sudo -s
# cd /mnt/vbox/share/oracle11g/inst11g
# ./inst11g.sh

※inst11g.sh の内容は、 「oracle11g が要求する各種設定を一括設定」 を参照

3.oracle11g のインストール
# cd /mnt/vbox/share/oracle11g/database/install
# cp /usr/bin/unzip .
# passwd oracle
Changing password for user oracle.
New UNIX password: <oracle ユーザーパスワード>
Retype new UNIX password: <oracle ユーザーパスワード>
passwd: all authentication tokens updated successfully.  
# xhost +
# su - oracle
$ cd /mnt/vbox/share/oracle11g/database
$ ./runInstaller -jreloc /usr/lib/jvm/default-java
4.エラーが発生したら patch を適用
$ cd /u01/app/oracle/product/11.2.0/dbhome_1
$ patch -p1 -d . < /mnt/vbox/share/oracle11g/inst11g/ora11g-ubuntu.patch
<再施行>
$ exit
# /u01/app/oraInventory/orainstRoot.sh
# /u01/app/oracle/product/11.2.0/dbhome_1/root.sh
# su - oracle



5.リスナとデータベース作成の前準備
 $HOME/.profile に以下の行を追記
export ORACLE_BASE=/u01/app/oracle
export ORACLE_HOME=$ORACLE_BASE/product/11.2.0/dbhome_1
export ORACLE_SID=orcl
export PATH=$ORACLE_HOME/bin:$PATH
export NLS_LANG=JAPANESE_JAPAN.UTF8

6.リスナの作成
$ vi `which netca`
===========================================================
diff bin.org/netca bin/netca
99c99,100
< JRE_DIR=/u01/app/oracle/product/11.2.0/dbhome_1/jdk/jre
---
> #JRE_DIR=/u01/app/oracle/product/11.2.0/dbhome_1/jdk/jre
> JRE_DIR=/usr/lib/jvm/default-java
===========================================================
$ netca
 ※リスナー作成時のメニュー選択は1521ポートを使用するのであれば全てリターンで作成可

7.データベースの作成
$ vi `which dbca`
===========================================================
diff bin.org/dbca bin/dbca
69c69,70
< JRE_DIR=/u01/app/oracle/product/11.2.0/dbhome_1/jdk/jre
---
> #JRE_DIR=/u01/app/oracle/product/11.2.0/dbhome_1/jdk/jre
> JRE_DIR=/usr/lib/jvm/default-java
===========================================================
$ dbca



================【以下は2014年9月の記載内容】=================== h

まずは、oracle11g をこちらから以下のファイルをダウンロード。
  • linux.x64_11gR2_database_1of2.zip
  • linux.x64_11gR2_database_2of2.zip

最初にホスト名を設定。
127.0.0.1       localhost
127.0.1.1       linux
192.168.11.200  linux.oracle.com linux

次にライブラリ環境などを用意。
$ cat test11g-01.sh
#! /bin/sh
#
# ubuntu14.04(workstation)〜ubuntu15.10(workstation)
# sudo apt-get install gcc make binutils gawk x11-utils rpm build-essential libaio1 libaio-dev libmotif4 libtool expat alien ksh pdksh unixODBC unixODBC-dev sysstat elfutils libelf-dev 
# ubuntu16.04(workstation)
sudo apt-get install gawk rpm libaio1 libaio-dev libtool expat alien ksh unixodbc unixodbc-dev sysstat elfutils libelf-dev libstdc++5 lsb-cxx libstdc++5

sudo ln -sf /bin/bash /bin/sh
sudo ln -s /usr/bin/awk /bin/awk
sudo ln -s /usr/bin/rpm /bin/rpm
sudo ln -s /usr/bin/basename /bin/basename
sudo ln -s /usr/lib/x86_64-linux-gnu/libstdc++.so.5 /usr/lib/libstc++.so.5
sudo ln -s /usr/lib/x86_64-linux-gnu /usr/lib64
sudo ln -s /lib/x86_64-linux-gnu/libgcc_s.so.1 /lib64
#
sudo mkdir /etc/rc.d
sudo ln -s /etc/rc0.d /etc/rc.d/rc0.d
sudo ln -s /etc/rc1.d /etc/rc.d/rc1.d
sudo ln -s /etc/rc2.d /etc/rc.d/rc2.d
sudo ln -s /etc/rc3.d /etc/rc.d/rc3.d
sudo ln -s /etc/rc4.d /etc/rc.d/rc4.d
sudo ln -s /etc/rc5.d /etc/rc.d/rc5.d
sudo ln -s /etc/rc6.d /etc/rc.d/rc6.d
sudo ln -s /etc/rcS.d /etc/rc.d/rcS.d

$ ./test11g-01.sh

次にstatic-glibc ライブラリをこのあたりよりダウンロードして追加インストール。
$ sudo alien glibc-static-*.rpm
$ sudo dpkg -i glibc-static_*.deb

次にオラクルユーザを作成。
$ cat test11g-02.sh
#! /bin/sh
#
sudo groupadd oinstall
sudo groupadd dba
sudo adduser oracle
sudo usermod -g oinstall -G dba oracle
sudo mkdir -p /u01/app
sudo chown -R oracle:oinstall /u01/app
sudo chmod -R 775 /u01/app

$./test11g-02.sh

次にOSパラメータなどを設定。
$ cat test11g-03.sh
#! /bin/sh
#
sudo sh -c 'echo "Red Hat Linux release 5"               > /etc/redhat-release'

#
sudo sh -c 'echo "#"                              >> /etc/security/limits.conf'
sudo sh -c 'echo "oracle soft nproc 2047"         >> /etc/security/limits.conf'
sudo sh -c 'echo "oracle hard nproc 16384"        >> /etc/security/limits.conf'
sudo sh -c 'echo "oracle soft nofile 1024"        >> /etc/security/limits.conf'
sudo sh -c 'echo "oracle hard nofile 65536"       >> /etc/security/limits.conf'
sudo sh -c 'echo "oracle soft stack 10240"        >> /etc/security/limits.conf'

#
sudo sh -c 'echo "#"                                       >> /etc/sysctl.conf'
sudo sh -c 'echo "fs.aio-max-nr=1048576"                   >> /etc/sysctl.conf'
sudo sh -c 'echo "fs.file-max=6815744"                     >> /etc/sysctl.conf'
sudo sh -c 'echo "kernel.shmall=2097152"                   >> /etc/sysctl.conf'
sudo sh -c 'echo "kernel.shmmni=4096"                      >> /etc/sysctl.conf'
sudo sh -c 'echo "kernel.sem=250 32000 100 128"            >> /etc/sysctl.conf'
sudo sh -c 'echo "net.ipv4.ip_local_port_range=9000 65500" >> /etc/sysctl.conf'
sudo sh -c 'echo "net.core.rmem_default=262144"            >> /etc/sysctl.conf'
sudo sh -c 'echo "net.core.rmem_max=4194304"               >> /etc/sysctl.conf'
sudo sh -c 'echo "net.core.wmem_default=262144"            >> /etc/sysctl.conf'
sudo sh -c 'echo "net.core.wmem_max=1048586"               >> /etc/sysctl.conf'
sudo sh -c 'echo "kernel.shmmax=1073741824"                >> /etc/sysctl.conf'
sudo sysctl -p

$./test11g-03.sh

次に java をインストール。oracle 付属の java でインストーラを起動すると文字化けが発生。
(英語モード(LANG=C)でインストーラ起動する場合には不要。)
$ sudo apt-get install default-jre

次に oracle ユーザで oracle パッケージのを展開。
$ mv linux.x64_11gR2_database_1of2.zip /tmp
$ mv linux.x64_11gR2_database_2of2.zip /tmp
$ xhost +
$ su - oracle
$ unzip /tmp/linux.x64_11gR2_database_1of2.zip
$ unzip /tmp/linux.x64_11gR2_database_2of2.zip
$ cd database

次に oracle インストーラを起動します。
(ubuntu15.04 では、database/install/unzip でエラーとなったため、/usr/bin/unzip を上書き)
$ ./runInstaller -jreLoc /usr/lib/jvm/default-java
# 英語モード表示の場合
$ export LANG=c
$ ./runInstaller

「セキュリティ・アップデートをMy Oracle Support経由で受け取ります」のチェックを外して次へをクリック。


警告は「はい」で無視。


「データベース・ソフトウェアのみインストール」を選択して次へをクリック。


単一インスタンス・データベースのインストール」を選択して次へをクリック。


言語に「日本語」と「英語」が選択されていることを確認して次へをクリック。


「Enterprise Edition」を選択して次へをクリック。


パスを確認して次へをクリック。


oraInventoryグループ名に「oinstall」が選択されていることを確認して次へをクリック。


データべース管理者(OSDBA)グループに「dba」 データベース・オペレータ(OSOPER)グループに「oinstall」 が選択されていることを確認して次へをクリック。


前提条件チェックでアラートは無視して次に進む。



終了をクリックしてインストール開始。


バイナリリンク中に以下のポップアップがでたら以下の作業を行って再試行。


sudo  vi /u01/app/oracle/product/11.2.0/dbhome_1/ctx/lib/ins_ctx.mk
  => -static $(LINK_CTXHX) $(CTXHXOBJ) $(INSO_LINK) /usr/lib64/libc.a

sudo vi /u01/app/oracle/product/11.2.0/dbhome_1/sysman/lib/ins_emagent.mk
  =>190: $(MK_EMAGENT_NMECTL) -lnnz11

sudo vi /u01/app/oracle/product/11.2.0/dbhome_1/bin/genorasdksh
  =>278: OLIBS= ... -lagtsh -lorasdkbase

sudo vi /u01/app/oracle/product/11.2.0/dbhome_1/srvm/lib/env_srvm.mk
  =>90: PRODUCT=srvm

sudo vi /u01/app/oracle/product/11.2.0/dbhome_1/srvm/lib/ins_srvm.mk
  => GETCRSHOME_LINKLINE= ... -Wl,--start-group $(OCRLIBS_DEFAULT) -Wl,--end-group $(OCRLIBS_DEFAULT) ...

sudo vi /u01/app/oracle/product/11.2.0/dbhome_1/network/lib/env_network.mk
  => 2150: TNSLSNR_LINKLINE=... -Wl,--no-as-needed $(LLIBONS) ...

sudo vi /u01/app/oracle/product/11.2.0/dbhome_1/rdbms/lib/env_rdbms.mk
  =>2113: ORACLE_KERNEL_LIBS=...$(SPOBJS) -Wl,--no-as-needed $(LLIBSERVER)...



端末をもう一つ立ち上げてrootユーザで構成スクリプトを実行して終了。
次に .profile に以下の変数を追加し、source .profile
export ORACLE_BASE=/u01/app/oracle
export ORACLE_HOME=$ORACLE_BASE/product/11.2.0/dbhome_1
export ORACLE_SID=orcl
export PATH=$ORACLE_HOME/bin:$PATH
export NLS_LANG=JAPANESE_JAPAN.UTF8


次に netca インストーラを起動。
$ netca
# 日本語で起動したい場合は、99行目のJREを変更。
JRE_DIR=/usr/lib/jvm/default-java/jre
#JRE_DIR=/u01/app/oracle/product/11.2.0/dbhome_1/jdk/jre


「リスナー構成」を選択して次へをクリック。


「追加」を選択して次へをクリック。

リスナー名が「LISTENER」となっていることを確認して次へをクリック。


「TCP」が選択されていることを確認して次へをクリック。


「標準ポート番号の1521を使用」を選択して次へをクリック。


「いいえ」を選択して次へをクリック。


次へをクリック。


「完了」をクリック。


次に dbca を起動。
クローニング中に「out of memory」のエラーで異常終了する場合、script の生成、実行でデータベースで回避。
$ dbca
# 日本語で起動したい場合は、69行目のJREを変更。
JRE_DIR=/usr/lib/jvm/default-java/jre
# JRE_DIR=/u01/app/oracle/product/11.2.0/dbhome_1/jdk/jre


次へをクリック。

「データベースの作成」を選択して次へをクリック。

「汎用またはトランザクション処理」を選択して次へをクリック。


データベース識別情報を入力して次へをクリック。


「Enterprise Managerの構成」にチェックが付いているのを確認して次へをクリック。


「すべてのアカウントに同じ管理パスワードを使用」を選択してパスワードを入力。


警告に対して「はい」で受け入れ。


今回はファイルシステムで作成するため、デフォルトのまま次へをクリック。


必要であれば「アーカイブの有効化」にチェック後、データベースを「アーカイブ・モード」に設定。


「サンプル・スキーマ」にチェックを入れて次へをクリック。


「キャラクタ・セット」タブをクリック。


「Unicode(AL32UTF8)を使用」を選択して次へをクリック。


次へをクリック。


「データベースの作成」にチェックが付いてることを確認して完了をクリック。
(「out of memory」で異常終了する場合は、「データベースの作成」のチェックを外し、「データベース作成スクリプトの生成」をチェックして実行する。生成された /u01/app/oracle/admin/orcl/scripts/ora.sh を実行するとデータベースの生成が可能であった。)


データベースの構成サマリーを確認してOKをクリック。


この画面が出たらデータベースの作成終了。
最後に、以下のファイルをリネーム。
sudo mv /etc/redhat-release /etc/redhat-release.bk


2014年1月12日日曜日

Jboss7.1.1 とJPA(eclipselink)

Jboss7.1.1 で eclipselink を使ってみた。

(1) eclipselink をダウンロード
% wget http://ftp.yz.yamagata-u.ac.jp/pub/eclipse//rt/eclipselink/releases/2.4.2/eclipselink-2.4.2.v20130514-5956486.zip
% unzip eclipselink-2.4.2.v20130514-5956486.zip

(2) modules の配置
・解凍したファイルより、 eclipselink.jar を以下の配置にコピーする。
% cd ~/jboss7/modules/org
% tree eclipse
eclipse
└── persistence
    └── main
        ├── eclipselink.jar
        ├── eclipselink.jar.index
        └── module.xml

2 directories, 3 files

(3) module.xml
<module xmlns="urn:jboss:module:1.1" name="org.eclipse.persistence">
  <resources>
    <resource-root path="eclipselink.jar"/>
    <!-- Insert resources here -->
  </resources>
  <dependencies>
    <module name="javax.api"/>
    <module name="javax.persistence.api"/>
    <module name="javax.transaction.api"/>
    <module name="javax.validation.api"/>
    <module name="javax.xml.bind.api"/>
    <module name="org.antlr"/>
    <module name="org.apache.commons.collections"/>
    <module name="org.dom4j"/>
    <module name="org.javassist"/>
    <module name="org.jboss.logging"/>
  </dependencies>
</module>
(4) persistence.xml
・最初、JBoss 用の property を記載しなくて、トランザクションが掛からなくてハマりました。
<?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.eclipse.persistence.jpa.PersistenceProvider</provider>
        <jta-data-source>java:jboss/datasources/MySqlDS</jta-data-source>
        <class>jpa.foods</class>
        <properties>
            <property name="eclipselink.target-server" value="JBoss" />
<!--          
            <property name="eclipselink.logging.level" value="FINEST" />
-->
        </properties>
    </persistence-unit>

</persistence>

jboss7.1.1 と JPA(jhibernate)

jboss7.1.1 上で JPA(hibernate)を使ってみた。

「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 List allfoods();
  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 List allfoods() {
    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) 実行確認