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

端数処理


「丸め」と「四捨五入」はよく混同される。
というか混同していた。


厳密には、「丸め」は丸め単位(10進であるとは限らないわけだ)の中間点の場合は偶数にすること*1

四捨五入
1.5=>2
2.5=>3

丸め
1.5=>2
2.5=>2


ROUNDは直訳すれば「丸め」なのだが、関数になるとなぜか「四捨五入」になる。


OracleJavaのROUND関数は実際は「四捨五入」だ。

SQL> select round(1.5) from dual;

ROUND(1.5)
----------
         2

SQL> select round(2.5) from dual;

ROUND(2.5)
----------
         3


javajava.lang.Mathを使う)

  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


ざっと調べてみると、AccessVBでは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()もあるが、コンストラクタでも丸め処理ができるらしい

*3:http://ichy.seesaa.net/article/2842259.html

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>


このJSPIEから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を借用して戻る画面を定義してみることに。


struts-confix.xml

<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におけるクロスサイトスクリプティング脆弱性


げげげげげ


Strutsにおけるクロスサイトスクリプティング脆弱性*1
Security Advisory: Struts Error Message Cross Site Scripting*2

【調査方法】(保証なし)
1.存在しないパスを叩く
2.
 a.404がでる=>とりあえずセーフ
 b.400がでる=>アウト


てことかな?


Tomcat5.Xではとりあえず大丈夫なようだけど、古いアプリ(Tomcat4.X)を調査してみたら400のでるやつがあるなぁ。

メールアドレスに使用してもよい文字


メールアドレスで使用できる文字はRFCなんちゃら*1に書かれているらしいのだが、さっぱり分からないわけで。


んで、結論としては

英数字と!#$%&'*+-/=?^_`{|}~.

ということらしい。


らしいというのは、このネタをどこから持ってきたのか分からないからだな。


あれ?大文字のアルファベットは・・・昔niftyのアドレスが大文字だったかな。


struts用の正規表現パターン(動作保証なし)

<constant>
	<constant-name>mailchars</constant-name>
	<constant-value>^[A-Za-z0-9!#$%&amp;&apos;\*\+\-/=\?\^_`\{\|\}~\.]*$</constant-value>
</constant>


はてな
メールアドレスで使用できない文字の種類がわかるサイトを教えてください。*2
vodafone ってメールアドレスに ? の文字を使えるんですか?*3