2012年11月22日木曜日

Struts2 + Spring + mybatis! とりあえず付箋ペタペタ機能を一通り作ってみた

超久しぶりのTagDiaryです。

ちょっと作るたびにblogを更新、だと却ってやる気を失ってしまうので、「ここでまずひと段落」というところまで作りました。
https://github.com/ymotchi/TagDiary

ここまでで、
  • ユーザを作る
  • ログインする
  • 付箋をペタペタ貼る
  • 付箋に文字を書く
  • 付箋を保存する
ができるようになりました。「TagDiary」ってくらいですから、日付ごとに付箋をまとめるような感じにしたかったのですが、まずはその前まで。

もし「利用してみたい」という方がいらっしゃれば、
mavenのプロジェクトを作る → プロジェクト直下にGitHubにあるsrcディレクトリとpom.xmlが来るようにダウンロードする → ビルドする → アプリケーションサーバを起動する
で使えると思われます。

また、こいつはMySQLも使っておりまして、テーブルを2つほど作ってやってください。

user
iduserINT(11) PK, NN
useridVARCHAR(16) NN, UQ
passwordVARCHAR(128) NN

tag
idtagINT(11) PK, NN
iduserVARCHAR(16) NN
tagseqINT(11) NN
xINT(11) NN
yINT(11) NN
widthINT(11) NN
heightINT(11) NN
contentsTEXT

わたくし、今までMySQLをほとんど知りませんで、"contents"列はCLOBにしたいんだけどCLOBは・・・などと思っておりましたら、MySQLではTEXTというのですね。

サーバが起動したら、「http://localhost:ポート/コンテキストルート/create_user_init」にアクセスしてユーザ作成、その後「.../login_init」にアクセスしてログインすると、付箋の編集画面に行きます。

ここで、四角の中をダブルクリックすると、付箋が出現。付箋はサイズ変更やドラッグ&ドロップOK。中身をクリックすれば、文字が書けます。付箋を右クリックすると、メニューが現れ、削除が可能です。下のボタンを押せば保存ができ、次回ログインしたときに、前回書いたものが再現されます。

・・・とつらつら書いてもわかりづらいものです。
よってここに、保存を除いた付箋ペタペタ機能のデモを作りました。この部分はJavascriptでいろいろやっているだけです。


なんかたまに右クリックメニューがあらぬところに現れますが、そこはご愛嬌。

エビデンス!エビデンス!!エビデンス!!! VBAでクリップボード監視 その3

第1回 → エビデンス!エビデンス!!エビデンス!!! VBAでクリップボード監視
第2回 → エビデンス!エビデンス!!エビデンス!!! VBAでクリップボード監視 その2
ユーザーフォームのソースコード → gist
標準モジュールのソースコード → gist

最後に補足。

このマクロを含めたブックは、自動保存をOFFにしたほうが良いです。また、マクロ実行中にブックを保存した場合、保存途中にPrintScreenを押してはいけません。良い子と僕らの約束だぞ。

というのも、クリップボードの変化を検知した後に、画像をシートに貼りつける部分
Public Sub pasteToSheet()
    Dim rowIdx As Integer
     
    With Sheet1
        If .Shapes.Count > 0 Then
            With .Shapes(.Shapes.Count)
                rowIdx = (.Top + .Height) / ROW_HEIGHT + 4
            End With
        Else
            rowIdx = 1
        End If
        .Cells(rowIdx, 1).PasteSpecial
    End With
End Sub
これの、"With .Shapes(.Shapes.Count)"がエラーになるから。
なんでかはよくわからないけど、保存中は、オートシェイプがVBAから正しく認識されないっぽい。
何せ保存中なのだから、そんなことがあってもしょうがない。というわけで、自動保存も非推奨です。

あと、前回こんな風に書きました。
  1.  WM_NCHITTESTであれば…
    これは実ははまるところなのですが、クリップボードの処理に直接関係ないので、次回に飛ばします。
これについてですが、WM_NCHITTESTの時にもCase Elseのようにもともとのウィンドウプロシージャを呼び出すようにすると、マウスカーソルがフォームの中に入った途端なぜだかCPUの使用率が跳ね上がり、フォームが反応しなくなります。無限ループに陥ってるっぽい。
勝手に元ネタにさせていただいたこちらでも、同じかわからないけど、似たような現象を確認されているし・・・。

この"WM_NCHITTEST"というのは、どうやら、マウスが動いたとか、ボタンが押されたとか、とにかくマウス系のイベントが起こると真っ先に送られるものらしく(MSのデベロッパー センター デスクトップ) 試しにこのメッセージを受け取った時にDebug.Printなどしてやると、フォームの上でマウスをぐりんぐりん動かしたときに、まあ出るわ出るわ。

この無限ループっぽい動きの原因はよく分かっていないのですが、このメッセージの時だけ何もしないようにしてやれば、とりあえず大丈夫そうです。
ただしこの場合、クリップボード監視中にフォームをクリックしても、ウィンドウがアクティブになった時の色にならなくなってしまうのですが・・・まぁ動くからよし。
チェックボックスのクリックもできるし。

というわけで、VBAでクリップボード監視はこれでおしまいです。

あと改良するなら、PrintScreenを押したときに、ちゃんと「押されたよ」的なポップアップがうにょんと最前面に出るといいかもしれません。対象のウィンドウを最大化していて、Excelが後ろに隠れていると、本当にPrintScreenが押されたかどうかすぐに確認できないんですよね。

2012年8月19日日曜日

エビデンス!エビデンス!!エビデンス!!! VBAでクリップボード監視 その2

前回 → エビデンス!エビデンス!!エビデンス!!! VBAでクリップボード監視
ユーザーフォームのソースコード → gist
標準モジュールのソースコード → gist

まずはユーザーフォームモジュールのコードから。
Private Sub CheckBox1_Change()
    If CheckBox1.Value = True Then
        Module1.catchClipboard
    Else
        Module1.releaseClipboard
    End If
End Sub

Private Sub UserForm_QueryClose(Cancel As Integer, CloseMode As Integer)
    If CheckBox1.Value = True Then
        Module1.releaseClipboard
        CheckBox1.Value = False
    End If
End Sub
チェックボックスをチェックしたら、クリップボード監視(標準モジュールのcatchClipboardを実行)、 チェックを外したら、クリップボード監視終了(標準モジュールのreleaseClipboardを実行)。
チェックボックス外し忘れでフォームを閉じたときのための、念のためQueryCloseの時にもreleaseClipboardを実行。 以上。

では次に、標準モジュールのコード。
Option Explicit

Private Declare PtrSafe Function FindWindow Lib "user32.dll" Alias "FindWindowA" (ByVal lpClassName As String, ByVal lpWindowName As String) As LongPtr
Private Declare PtrSafe Function SetWindowLong Lib "user32.dll" Alias "SetWindowLongA" (ByVal hWnd As LongPtr, ByVal nIndex As Long, ByVal dwNewLong As Long) As Long
Private Declare PtrSafe Function CallWindowProc Lib "user32.dll" Alias "CallWindowProcA" (ByVal lpPrevWndFunc As Long, ByVal hWnd As LongPtr, ByVal Msg As Long, ByVal wParam As Long, ByVal lParam As LongPtr) As LongPtr
Private Declare PtrSafe Function SendMessage Lib "user32.dll" Alias "SendMessageA" (ByVal hWnd As LongPtr, ByVal Msg As Long, ByVal wParam As LongPtr, ByVal lParam As LongPtr) As LongPtr
Private Declare PtrSafe Function SetClipboardViewer Lib "user32.dll" (ByVal hWndNewViewer As LongPtr) As LongPtr
Private Declare PtrSafe Function ChangeClipboardChain Lib "user32.dll" (ByVal hWndRemove As LongPtr, ByVal hWndNewNext As LongPtr) As Long
Private Declare Function IsClipboardFormatAvailable Lib "user32.dll" (ByVal format As Long) As Long

Private Const GWL_WNDPROC As Long = -4

Private Const WM_DRAWCLIPBOARD As Long = &H308
Private Const WM_CHANGECBCHAIN As Long = &H30D
Private Const WM_NCHITTEST As Long = &H84

Private Const CF_BITMAP As Long = 2

Private Const ROW_HEIGHT As Double = 13.5

Private hWndForm As LongPtr
Private wpWindowProcOrg As Long
Private hWndNextViewer As LongPtr
Private firstFired As Boolean

Public Sub catchClipboard()
    hWndForm = FindWindow("ThunderDFrame", UserForm1.Caption)
    wpWindowProcOrg = SetWindowLong(hWndForm, GWL_WNDPROC, AddressOf WindowProc)
    firstFired = False
    hWndNextViewer = SetClipboardViewer(hWndForm)
End Sub

Public Sub releaseClipboard()
    Call ChangeClipboardChain(hWndForm, hWndNextViewer)
    Call SetWindowLong(hWndForm, GWL_WNDPROC, wpWindowProcOrg)
End Sub

Public Function WindowProc(ByVal hWnd As LongPtr, ByVal uMsg As Long, ByVal wParam As LongPtr, ByVal lParam As LongPtr) As LongPtr
    Select Case uMsg
        Case WM_DRAWCLIPBOARD
            If Not firstFired Then
                firstFired = True
            ElseIf IsClipboardFormatAvailable(CF_BITMAP) <> 0 Then
                pasteToSheet
            End If
            If hWndNextViewer <> 0 Then
                Call SendMessage(hWndNextViewer, uMsg, wParam, lParam)
            End If
            WindowProc = 0
        Case WM_CHANGECBCHAIN
            If wParam = hWndNextViewer Then
                hWndNextViewer = lParam
            ElseIf hWndNextViewer <> 0 Then
                Call SendMessage(hWndNextViewer, uMsg, wParam, lParam)
            End If
            WindowProc = 0
        Case WM_NCHITTEST
            WindowProc = 0
        Case Else
            WindowProc = CallWindowProc(wpWindowProcOrg, hWndForm, uMsg, wParam, lParam)
    End Select
End Function

Public Sub pasteToSheet()
    Dim rowIdx As Integer
    
    With Sheet1
        If .Shapes.Count > 0 Then
            With .Shapes(.Shapes.Count)
                rowIdx = (.Top + .Height) / ROW_HEIGHT + 4
            End With
        Else
            rowIdx = 1
        End If
        .Cells(rowIdx, 1).PasteSpecial
    End With
End Sub
フォームから呼ばれる監視スタートのcatchClipboardは…
  1. FindWindowでフォームのハンドルを取得します。
    フォームのクラスは"ThunderDFrame"というらしいです。第2引数でフォームのキャプションを指定します。UserForm.Handleとかしたいのですが、少なくとも2010のVBAでは無理っぽいです。回りくどい。
  2. SetWindowLongでウィンドウプロシージャを自分で定義したWindowProcに入れ替える。
    この時の戻り値は、入れ替える前のウィンドウプロシージャのアドレスなので、記憶しておきます。
  3. firstFiredフラグをFalseにしておきます。
    この次の行でクリップボード監視を始めるのですが、SetClipboardViewerをした瞬間に、なぜか いきなり「クリップボードの中身が入れ替わった」イベント(後述するWindowProcにWM_DRAWCLIPBOARDが渡される)が発火してしま います。なので、それを抑えるために。
  4. SetClipboardViewerで、フォームをクリップボードビューアのチェーンにつなぎます。
    クリップボードのイベントを受け取るクリップボードビューアは、鎖のように連なっています。詳しくは後述。
    ここでの返り値は、自分の一つ後ろのクリップボードビューア(なければ0が返ります)なので、それを記憶しておきます。
一方、フォームから呼ばれる監視ストップのreleaseClipboardは…
  1. ChangeClipboardChainでフォームをクリップボードビューアのチェーンから切り離します。
    第1引数は切り離すウィンドウのハンドル、第2引数は切り離すウィンドウの一つ後ろのクリップボードビューアのハンドルです。
  2. SetWindowLongでフォームのウィンドウプロシージャを元に戻します。
    ちなみに、SetWindowLongを使っていますが、VBAでなくVBならば、SetWindowLongPtr(SetWindowLongPrtA)という関数が使えます。
    これは、第3引数と返り値がLongPtrになり、多い日も安心な感じがします。
    64bitを徹底的に気にするなら、本来はこちらを使うべきなのですが、user32.dllではなく、user32.libの関数のようで、だめでした。
    前回書いた通り、別にすべてのLongPtrをLongにしても大丈夫だと思います。きっと。
さて、catchClipboardにて、フォームのウィンドウプロシージャを"WindowProc"に入れ替えました。こういうのを、「サブクラス化」というらしいのですが、ではWindowProcを見てみます。
まず受け取ったメッセージuMsgを見てみます。
  1. WM_DRAWCLIPBOARDであれば…
    クリップボードの中身が変わったイベントです。このイベントはSetClipboardViewerを使った瞬間にもなぜか起きるので、そこはfirstFiredで調節しています。
    次 に、IsClipboardFormatAvailableで、中身がビットマップかどうかを判定しています。(Alt+)PrintScreenを使う と、ビットマップになります。テキストとかをコピペした時は無視させるためです。でビットマップならば、pasteToSheetでシートに貼りつけま す。
    最後に、次のクリップボードビューアが0でない(存在する)ならば、SendMessageでフォームが受け取ったメッセージを渡してやります。
    これは「クリップボードビューアは鎖のように連なっている」ためです。これをしないと、後続のクリップボードビューアが処理できません。見方を変えれば、後続に処理させたくない場合には、SendMessageを使わなければよいわけです。
    ここでは、シートに絵を貼りつけた後は別にクリップボードを独占する必要はないので、後続のビューアにメッセージを送ってやっています。
  2. WM_CHANGECBCHAINであれば…
    クリップボードビューアチェーンの誰かが離脱したイベントです。
    この時、OSで勝手にチェーンをつなぎかえてはくれません。自分でやります。
    wParamが離脱するビューア、lParamがその次のビューアのハンドルです。つまり、ChangeClipboardChainの引数そのものだったりします。
    なので、wParamが自分の一つ後ろならば、hWndNextViewerをlParamにしてやります。また、そうでないならば、自分は何もせずにSendMessageで後ろのビューアにメッセージを渡してやります。
  3.  WM_NCHITTESTであれば…
    これは実ははまるところなのですが、クリップボードの処理に直接関係ないので、次回に飛ばします。
  4.  それ以外であれば…
    フォームにやってくるメッセージは、クリップボード関連だけではありません。なんせ普通のフォームなので、マウスやキーなどのイベントのメッセージがやってきます(上のWM_NCHITTESTはマウス関連のイベントです)。
    そうした場合には、フォーム本来の処理をさせるべきです。
    そこで、CallWindowProcで、もともとフォームが持っていたウィンドウプロシージャを呼び出してやります。
最後に、pasteToSheetですが、これはまあコードを見ればOKかと思います。 Sheet1に次々とクリップボードの中身を貼りつけてやっています。

2012年6月17日日曜日

エビデンス!エビデンス!!エビデンス!!! VBAでクリップボード監視

ひとつとってはSEのため
ふたつとっては客のため
みっつとっては・・・誰のため?

さぁテストだ、となると、
ブラウザで画面表示 → PrintScreen → Excelに貼り付け
を延々と繰り返して、テストのエビデンスをとるわけですよ。

まあ非生産的なこと、誰の役に立つのでしょう、しかも表計算のExcelがお絵かき帳だなんて、と思っても口には出さないわけですが、
仕事で作んなくちゃいけない、と決まっているので作るわけです。

にしても、もう少しましにならないか・・・。

たとえば、PrintScreenを押すと、勝手にExcelに貼りつけてくれる、とか。
と思って、「VBA クリップボード 監視」で検索してみると・・・
やっぱり先人はいるのですね、たとえばここ
(興味を持たれた方は、(1)~(4)まであるので、全部読むことをお勧めします)

あとはこの仕組みに、ワークシートへの図のペーストを入れればオシマイ!なのですが、
ついでにちょっと気になるところがあったので、そこをちょっといじってみたりしました。

こんな感じです。適当なExcelブックを作成して、標準モジュールのModule1に書きます。

これは、64bit版のWindows 7でExcel 2010を使ったのでこういう書き方になったのですが、そうでなければ、たとえばExcel 2003とかなら、

LongPtr → Long
上のほうでDeclare FunctionしているところのPtrSafeを削除

とすればOKです。というか、別に2010でも同じようにやっても多分動くと思います。LongPtr使ってみたかっただけ。
そして、ユーザフォームを作成します。フォームにはチェックボックスをひとつだけ。


こんな感じです。

コードはこうします。ユーザフォームの名前は"UserForm1"、チェックボックスの名前は"CheckBox1"にします。また、ユーザフォームはモードレス、つまり、ShowModalをFalseにしておきます。

さて、説明は後回しにして、先に使い方を言いますと、

1. ユーザフォームを実行するなりして、 ユーザフォームを表示します。
2. フォームの中のチェックボックスをクリックします。
3. PrintScreenやAlt+PrintScreenするなどして、とにかくクリップボードに図を入れます。
4. すると、そのブックの1シート目に、クリップボードに入れた図がどんどん貼りついていくではないですか!

さて、次回は、このコードの解説と、自分的に気になったポイントを書こうと思います。

2012年3月7日水曜日

Struts2 + Spring + mybatis! 準備ができたところでGitHubに上げてみた

ここまでで、アプリを作成する準備段階は整ったと思うので、せっかくだからと、GitHubというものにあげてみることにした。

mavenで作ったこのプロジェクト全体をどうすればよろしくアップできるのか分からなかったので、とりあえずsrc下のみをあげることにした。
URLはこちら。
https://github.com/ymotchi/TagDiary

ただ、準備段階だからと言って、まったく動かないソースもアレだったので、トランザクション管理がちゃん動くかのテストも兼ねて、超簡単な画面をつけてみた。

まず、それ用のテーブル「testtable」を用意しておく。

testtable
idtesttableint(11) PK
testcolumnvarchar(32)

そしてTomcatなどのサーバでアプリを起動させ、「http://localhost:ポート/コンテキストルート/index」にアクセス。

で、「」の欄に

1) 適当な文字列を入れた場合
2) 「throwable」と入れた場合
3) 「exception」と入れた場合

で、挙動が変わってくる。

1)の場合は、『「(入力した文字列)」が入力されました』と表示され、testtableに入力した文字列が書き込まれる。
2)の場合は、エラーが画面に表示され、testtableに「throwable」が書き込まれる。
3)の場合は、エラーが画面に表示され、testtableに何も書き込まれない。

これは、applicationContext.xmlのトランザクションの設定のところで、
「rollback-for="java.lang.Exception"」という記述をしたため。
2)の場合は、サービスクラスのメソッドでThrowableが投げられるが、これはExcpetionの親クラスなので、ロールバックはしない。
一方3)の場合は、Exceptionが投げられるため、ロールバックされる。

この設定の効果が分かりやすく表れた。

細かいところは、実際にソースを見ていただくのが良いと思う。


忘れてた・・・。
このソースは、Java SE 7(以上)じゃないと動かない。
新機能を使ってみたくて、文字列のswitch文を使ってみたから。

2012年2月27日月曜日

Struts2 + Spring + mybatis! jspの最初の部分を1つのファイルにまとめる

web.xmlの中で、こんな風に書いた。
  <jsp-config>
    <jsp-property-group>
      <url-pattern>*.jsp</url-pattern>
      <el-ignored>false</el-ignored>
      <page-encoding>UTF-8</page-encoding>
      <scripting-invalid>false</scripting-invalid>
      <include-prelude>/WEB-INF/include/jsp_header.jsp</include-prelude>
    </jsp-property-group>
  </jsp-config>

ここで、"include-prelude"とは、すべてのJSPファイルの先頭に挿入したいコードを記述したファイルを指定する。pageやtaglib宣言なんかにうってつけ。
そこで、 このファイルをこのようにした。

jsp_header.jsp
<%@ page contentType="text/html; UTF-8" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="f" uri="http://java.sun.com/jsp/jstl/functions" %>
<%@ taglib prefix="s" uri="/struts-tags" %>
 
コンテンツタイプをtext/html、出力文字コードをUTF-8にする宣言と、あとはよく使うJSTLとStruts2のタグのtaglib宣言。これで、各JSPファイルにいちいち書いて回る必要がなくなる。

ちなみに、すべてのJSPの末尾に特定のコードを挿入したい場合は、同じようなノリで"include-coda"を使う。
"prelude"に"coda"。このネーミングセンスはよいなぁと思う。

2012年1月6日金曜日

Struts2 + Spring + mybatis! applicationContext.xmlに必要なあれこれ

という訳で、次はapplicationContext.xml。これは、web.xmlで指定したとおり、WEB-INFの下に置く。
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
 xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
 xmlns:jee="http://www.springframework.org/schema/jee"
 xmlns:tx="http://www.springframework.org/schema/tx"
 xmlns:aop="http://www.springframework.org/schema/aop"
 xmlns:context="http://www.springframework.org/schema/context"
 xsi:schemaLocation="http://www.springframework.org/schema/beans
      http://www.springframework.org/schema/beans/spring-beans-3.0.xsd
      http://www.springframework.org/schema/jee
      http://www.springframework.org/schema/jee/spring-jee-3.0.xsd
      http://www.springframework.org/schema/tx
      http://www.springframework.org/schema/tx/spring-tx-3.0.xsd
      http://www.springframework.org/schema/aop
      http://www.springframework.org/schema/aop/spring-aop-3.0.xsd
      http://www.springframework.org/schema/context
      http://www.springframework.org/schema/context/spring-context-3.0.xsd"
 default-autowire="byName">

 <context:annotation-config/>
 <context:component-scan base-package="com.motchi.tagdiary"/>

 <bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
  <property name="driverClassName">
   <value>com.mysql.jdbc.Driver</value>
  </property>
  <property name="url">
   <value>jdbc:mysql://localhost:3306/mytest?useUnicode=true&amp;characterEncoding=utf8</value>
  </property>
  <property name="username">
   <value>mytest</value>
  </property>
  <property name="password">
   <value>mytestpass</value>
  </property>
 </bean>

 <bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
  <property name="dataSource" ref="dataSource"/>
 </bean>

 <bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
  <property name="dataSource" ref="dataSource"/>
 </bean>

 <aop:config proxy-target-class="true">
  <aop:advisor advice-ref="txAdvice" pointcut="@within(org.springframework.stereotype.Service)"/>
 </aop:config>
 <tx:advice id="txAdvice">
  <tx:attributes>
   <tx:method name="insert*" propagation="REQUIRED" read-only="false" rollback-for="java.lang.Exception"/>
   <tx:method name="update*" propagation="REQUIRED" read-only="false" rollback-for="java.lang.Exception"/>
   <tx:method name="delete*" propagation="REQUIRED" read-only="false" rollback-for="java.lang.Exception"/>
   <tx:method name="*" propagation="REQUIRED" read-only="true"/>
  </tx:attributes>
 </tx:advice>

 <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
  <property name="sqlSessionFactory" ref="sqlSessionFactory"/>
  <property name="basePackage" value="com.motchi.tagdiary"/>
  <property name="annotationClass" value="com.motchi.tagdiary.common.annotation.SqlMapper"/>
 </bean>

</beans> 
まず、"@Resource"でいろいろインジェクトできるようにするため、context:annotation-configを使用。
そして、そのインジェクトするBeanも、やはりXMLではなくアノテーションを書くことでSpringに見つけてもらいたいので、context:component-scanも使った。

データベースの設定は、「bean id="dataSource"」のところにユーザ名やパスワードまでベタ書き。今回はDBにMySQLを使うので、ドライバはcom.mysql.jdbc.Driver。普通DBの設定は、外部のXMLファイルやpropertiesファイルに出すところだが、そんなのは後回し。まずはひと通り動くところまで作ってしまおう。

続く「bean id="sqlSessionFactory"」が、mybatisとSpringの架け橋。ここで先ほど定義したデータソースを使う。

そして、ここからが凝りがいのある(かもしれない)ところ。トランザクション境界の設定だ。
今回のアプリのDBアクセスは、こんな風にしたい。

・アクションは、直接DAOを触らず、必ずサービスを介する。
・サービスのメソッドをトランザクション境界とする。
んで、更新系のメソッドは、チェック例外だろうが非チェック例外だろうが、Exceptionの子クラスの例外が発生したらロールバックしたい。
メソッドが更新系かどうかは、メソッド名でわかるようにする。
それ以外のメソッドはSelectするだけ。
・DAOは、サービスから呼ばれて、ただただSQLを実行する。

まず、「bean id="transactionManager"」のところで、データソースを指定し、DataSourceTransactionManagerを作る。これは普通だと思われる。ちなみに、idを「transactionManager」にしておくと、Springが「あーはいはい、例のアレね」と認識してくれて、その下のトランザクションの設定で名前を出す必要がないらしい。

そして次に、aop:config。proxy-target-class="true"にしているのは、サービスはインタフェースではなくクラスにするから。
ああ分かるさ。オブジェクト指向的にはインタフェースの方が望ましいさ。
でもね、多態性を使うわけじゃなし、

HogeHogeServiceってインタフェースにHogeHogeServiceImplってクラスが、
または、
IHogeHogeServiceってインタフェースにHogeHogeServiceってクラスが、

どーせ一対一で対応すんでしょ!

いいじゃんクラス1個で。

で、クラスは「@Service」アノテーションでSpringに認識してもらうので、pointcutは「@within(org.springframework.stereotype.Service)」で決まり。これは、「@Serviceがついたクラスの中のメソッドが対象」という意味らしい。サービスは全部トランザクショナルになってもらいます。

更新系のメソッドは、安直だけど、プレフィックスにinsert, update, deleteを持つものとし、これらの中でExceptionが起こればロールバックをするようにする。これが「tx:advice id="txAdvice"」の中身。

最後の「bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"」で、DAOを自動的に探してもらう。
このMapperScannerConfigurerの使い方、ちょっとぐぐってみたところ、sqlSessionFactoryとbasePackageを指定する例しか出てこなかった。
まあ、普通はそれで事足りるよね。
DAOをどこかのパッケージに集めて、そのパッケージをbasePackageで指定すればいいんだから。
でもね。
パッケージが画面単位や機能単位に分かれていて、それぞれにDAOがあったりすることもあるじゃないですか。
っていう理由を置いといても、サービスを探す方法がアノテーションなんだから、DAOもアノテーションで探してみたい!という好奇心からAPIを調べると、・・・あるじゃないですか、
setAnnotationClass(Class<? extends Annotation> annotationClass)
なんてメソッドが。
そこで、DAOを識別するための俺々アノテーションSqlMapperを作成(中身は空っぽでOK)し、こいつをannotationClassに指定。basePackageはどどーんとルートパッケージにして完成。

SqlMapper.java
package com.motchi.tagdiary.common.annotation;

public @interface SqlMapper {

}
これで準備完了。あとはクラスやらJSPやらを書くだけだ。