SEQの手動カウントアップ
DBAを持っている人がいないのに、SEQの値がずれているのをなおさねばという時(誰かが手動でデータを作った?w)。
仕方がないので無理矢理カウントアップする力技。
SQL> SELECT SEQ_HOGE.CURRVAL FROM DUAL; CURRVAL ---------- 65637 SQL> SELECT MAX(HOGE_NO) FROM FOO; MAX(HOGE_NO) ------------- 167693 SQL> DECLARE 2 P_CNT NUMBER; --SEQの実際の値 3 M_CNT NUMBER; --現在のレコードの値 4 RET NUMBER; --TMP 5 CNT NUMBER; --カウンタ 6 BEGIN 7 SELECT SEQ_HOGE.CURRVAL INTO P_CNT FROM DUAL; 8 SELECT MAX(HOGE_NO) INTO M_CNT FROM FOO; 9 FOR CNT IN P_CNT..M_CNT LOOP 10 SELECT SEQ_HOGE.NEXTVAL INTO RET FROM DUAL; 11 END LOOP; 12 END; 13 / PL/SQLプロシージャが正常に完了しました SQL> SELECT SEQ_HOGE.CURRVAL FROM DUAL; CURRVAL ---------- 167694
うーん、カコワルイ。
UUID
java2 SE5.0からはjava.util.UUID*1が追加された。
理論上世界で唯一のものとなるらしいので*2、永続化するときのIDとするには適切なのかもしれない。
現状うちのアプリでは主キーを持たないテーブルではSEQをまわしてIDとして、レコードはそのうち廃棄することにしている。
ただ、SEQが1週したらどうなるの?という問題はあって(1週するには10数年かかる見込みではあるが)、実装者としてはいささか落ち着かないものであることも確かだ。
そこでUUIDを使用すれば、そのような心配がなくなるわけだ。
このUUIDはhibernateのgeneratorクラスとしても指定できる*3。
org.hibernate.id.UUIDHexGeneratorクラスがその生成ロジックだ。
SEQを自作するよりかは使いやすいかもしれないけど、気になるのはパフォーマンスかなぁ。
あとはトラブった時に、レコードがどの順番で作成されたのかがわかるかどうかというところか。
参考
唯一のキー(ID)を作成する方法について*4
Hibernateで理解するO/Rマッピング(5)*5
*1:http://java.sun.com/j2se/1.5.0/ja/docs/ja/api/java/util/UUID.html
*2:http://www.atmarkit.co.jp/icd/root/52/94084052.html
*3:http://www.hibernate.org/hib_docs/v3/reference/en/html/mapping.html#mapping-declaration-id
*4:http://www.atmarkit.co.jp/bbs/phpBB/viewtopic.php?topic=27238&forum=12
*5:http://www.atmarkit.co.jp/fjava/rensai3/ormap05/ormap05_1.html
端数処理
「丸め」と「四捨五入」はよく混同される。
というか混同していた。
厳密には、「丸め」は丸め単位(10進であるとは限らないわけだ)の中間点の場合は偶数にすること*1。
四捨五入
1.5=>2
2.5=>3
丸め
1.5=>2
2.5=>2
ROUNDは直訳すれば「丸め」なのだが、関数になるとなぜか「四捨五入」になる。
OracleとJavaのROUND関数は実際は「四捨五入」だ。
SQL> select round(1.5) from dual; ROUND(1.5) ---------- 2 SQL> select round(2.5) from dual; ROUND(2.5) ---------- 3
System.out.println("Math.round(1.5)="+Math.round(1.5)); System.out.println("Math.round(2.5)="+Math.round(2.5));
実行結果
Math.round(1.5)=2 Math.round(2.5)=3
Javaで「丸め」が必要な場合はjava.lang.Math#rint()やjava.math.BigDecimalを使用する*2
MathContext mc = new MathContext(1, RoundingMode.valueOf("HALF_EVEN")); System.out.println("BigDecimal(1.5)="+new BigDecimal(1.5, mc)); System.out.println("BigDecimal(2.5)="+new BigDecimal(2.5, mc)); System.out.println("Math.rint(1.5)="+Math.rint(1.5)); System.out.println("Math.rint(2.5)="+Math.rint(2.5));
実行結果
BigDecimal(1.5)=2 BigDecimal(2.5)=2 Math.rint(1.5)=2.0 Math.rint(2.5)=2.0
ざっと調べてみると、AccessやVBではROUND()は「丸め」をしてくれるらしい。
でもExcelでは「四捨五入」みたいだなぁ。
ROUND関数が「丸め」の場合に「四捨五入」にするのはこんな感じらしい*3。
Round() がこういう挙動をする言語で四捨五入を実現する方法も常套手段があって、
1)四捨五入したい位が1の位になるよう、(10のn乗)をかける。
2)四捨五入したい値が正の数だったら 0.5 を足す。負の数だったら 0.5 を引く。
3)小数点以下を切り捨てる(Int関数やFloor関数など)
4)1)で使った数字の逆数をかける。(桁数を元に戻す)
という処理です。(キャストや変数の型による精度も要注意)
例えば、java.lang.Math#round(double a)の説明にはこう記述してある。
引数にもっとも近い long を返します。結果は 1/2 を加えて floor メソッドで取り int にキャストして整数に丸められます。すなわち、結果は次の式の値になります。
(long)Math.floor(a + 0.5d)
1円の誤差でも100万回繰り返せば100万円だし、お金の絡むところでは注意しておかないとな。
*1:http://ja.wikipedia.org/wiki/Round%E9%96%A2%E6%95%B0
*2:java.math.BigDecimal#round()もあるが、コンストラクタでも丸め処理ができるらしい
LookupDispatchAction + エンター
izuさんのところのむかーしの記事のタイトルをぱくってみました。
LookupDispatchActionはクリックされたボタンにより処理を振り分けるもの。
例えばこんな風にしておけば、
<html:form action="/hoge"> <html:submit property="action"><bean:message key="button.moge"/></html:submit> <html:submit property="action"><bean:message key="button.hage"/></html:submit> </html:form>
クリックされたボタンにより処理の振分が行われる。
ところが、次のようにフォーム内に入力フィールドなどのオブジェクトが一つだけある場合に困ったことになる。
例えばこんなソース。
<html:form action="/hoge"> <html:input property="hogehoge"/> <html:submit property="action"><bean:message key="button.moge"/></html:submit> <html:submit property="action"><bean:message key="button.hage"/></html:submit> </html:form>
このJSPをIEからPOSTするのだが、入力フィールドにカーソルを当てて、enterキーを叩くと画面が白くなる。
理由はStrutsのソースをデバッグしてみると分かるのだが、LookupDispatchActionのgetMethodName()内で「どのボタンがクリックされたのか」のパラメータ(keyName)が取得できていないため、nullが返却されるのだ。
protected String getMethodName( ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response, String parameter) throws Exception { // Identify the method name to be dispatched to. // dispatchMethod() will call unspecified() if name is null String keyName = request.getParameter(parameter); if (keyName == null || keyName.length() == 0) { return null; } String methodName = getLookupMapName(request, keyName, mapping); return methodName; }
当然Forward先"null"は存在しないので画面が白くなるというわけだ。
※複数のフィールドがある場合や、FireFoxではこの現象はでていない。その画面の最も最初に出てくるボタンのパラメータが送られてくる。
※requestをデバッグしてみると、「request-coyoteRequest-parameters-paramHashStringArray-table」の中に正常な場合はディスパッチのキーが含まれているのが分かる。
さてどうするか。
izuさんはデフォルトのForwardを定義しているが、今回は大先輩に逆らってみよう。
ディスパッチを使用する場合、僕はvalidateを飛ばした先で行っている。
そこで、validateで使用するinputを借用して戻る画面を定義してみることに。
<action path="/hoge" parameter="action" type="hoge.MyDispatchAction" scope="request" input="/hogeInput" validate="false"> <forward name="moge" path="/moge.do"/> <forward name="hage" path="/hage.do"/> </action>
izuさんの案に手を入れたもの
hoge.MyDispatchActionはLookupDispatchActionを継承したこのクラスをさらに継承する。
public abstract class LookupDispatchCustomAction extends LookupDispatchAction { public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response ) throws Exception { if ( request.getParameter( mapping.getParameter() ) == null ) { String input = mapping.getInput(); if(input != null){ //inputが定義されている ActionForward af = new ActionForward(); af.setPath(input); af.setRedirect(false); return af; }else{ // 不明 throw new UnknownDispatchException(); } } else { //LookupDispatchActionに処理をまかせる return super.execute( mapping, form, request, response ); } }
abstractで定義してあるのは、スーパークラスのLookupDispatchActionがgetKeyMethodMap()を必要とするため。
こうしておけば、パラメータが送られてこないときでもinputが定義されていればその画面に飛ぶし、なければきちんとエラーとして扱うことができる。
もう一つ考えたのは、Actionクラス全般を処理後にインターセプトして、ActionForward先がnullの場合はエラーにするもの。
AOPを使えばできるだろうけど、後から分かり難くなりそうなので今回はちょっとパス。
参考
izuさんの日記*1
@ITに同じような質問があったけれども、解決策は提示されず*2
あとは海外の掲示板で2〜3件あったぐらいかな?
今回Strutsのソースを直接調べたのが勉強になったなぁ・・・
やっぱり気になったので後日修正
"default"というforwordを定義しておいて、存在しないならエラーにすることに
private static final String DEFAULT_FORWORD_NAME = "default"; public ActionForward execute(ActionMapping mapping, ActionForm form, HttpServletRequest request, HttpServletResponse response ) throws Exception { if ( request.getParameter( mapping.getParameter() ) == null ) { ActionForward def = mapping.findForward(DEFAULT_FORWORD_NAME); if(def != null){ //defaultが定義されている場合はそこに戻るようにする return def; }else{ // それ以外はエラー画面に遷移する throw new UnknownDispatchException(); } } else { //LookupDispatchActionに処理をまかせる return super.execute( mapping, form, request, response ); } }
Strutsにおけるクロスサイトスクリプティング脆弱性
メールアドレスに使用してもよい文字
メールアドレスで使用できる文字はRFCなんちゃら*1に書かれているらしいのだが、さっぱり分からないわけで。
んで、結論としては
英数字と!#$%&'*+-/=?^_`{|}~.
ということらしい。
らしいというのは、このネタをどこから持ってきたのか分からないからだな。
あれ?大文字のアルファベットは・・・昔niftyのアドレスが大文字だったかな。
<constant> <constant-name>mailchars</constant-name> <constant-value>^[A-Za-z0-9!#$%&'\*\+\-/=\?\^_`\{\|\}~\.]*$</constant-value> </constant>
?はてな
メールアドレスで使用できない文字の種類がわかるサイトを教えてください。*2
vodafone ってメールアドレスに ? の文字を使えるんですか?*3