SpringMVCでトランザクション管理をおこなう。アノテーションとXMLの設定

SpringFrameWork

SpringMVCでは、アノテーションの設定をおこなうことだけでDBトランザクションの管理をおこなうことができます。


今回は、SpringMVCでトランザクション管理をおこなう方法について説明していきます


SpringMVCでは、トランザクションのスタート位置をアノテーションで指定することにより、トランザクションの開始を定義することができます
トランザクションのスタート位置を定義することにより、どのDB処理を一つのトランザクションとするかをコントロールすることができるということになります。


しかし、こういった制御をおこなうためには、アノテーションの準備とXMLへの設定が必要になります。


トランザクションとは


トランザクションとは何でしょうか?
ここでのトランザクションとは、“データベースのトランザクション”になります。
直訳すると『取引』ですね。


データベース処理では、1回のSQL発行ではデータの整合性が取れない場合が多々あります。
例えば、あるデータをシステムに登録しようとした場合、複数のテーブルにレコードの登録と更新が必要な場合。
テーブルが3つあり、あるデータをシステムに登録しようとした場合、テーブルAにはレコードの登録、テーブルBにはレコードの更新、テーブルCにもレコードの更新が必要だとします。
この場合、テーブルBのレコード更新が失敗したとすると、テーブルAのレコード登録は無かったことにしたいです。
具体的には”コミットしたくない”。


コミットは、全てのDB処理が完了してから(テーブルCへのレコード更新)おこないたいです。


トランザクションの管理

各テーブルへの処理を終わったタイミングでコミットしてしまうと、途中でエラーが発生した場合に、データの整合が取れていない状態でデータができあがってしまいます。
これを避けるために、3つの処理をまとめて考え、3つの処理が全て完了してからコミットします。
途中で、エラー等が発生した場合はロールバックして元に戻します。


この、複数のDB処理を一つにまとめることをトランザクションと呼びます。


どの処理をまとめて一つのトランザクションにするかを考えるのは、安定したシステムを作るのにきちんと考えておきたいポイントです。


common-database-context.xmlの定義


SpringMVCでトランザクション定義をおこなう場合は、「common-database-context.xml」への定義が必要になります。


まずはトランザクション管理するための基本定義です。
トランザクション管理するためのクラス定義と、DBコネクションのデータソース設定です。


「DataSourceTransactionManager」を使用可能な状態にし、「DataSourceTransactionManager」が使用するデータソースを定義します。


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

プロパティ値として、タイムアウト値も定義します。
10秒です。


通常、SpringMVCで開発したアプリケーションは、Tomcat等のWEBアプリケーション上に配置することになります。
WEBアプリケーションが生成したコネクションプール内のDBコネクションを、SpringMVCが取得してアプリケーションが使用するのが通常になります。


次に、トランザクションを有効にするパッケージの定義とアノテーションの定義。
以下のように定義します。


<aop:config proxy-target-class="false">
    <aop:advisor advice-ref="txAdvice" pointcut="execution(public * jp.co.xxx..*Controller.*(..)) && @annotation(jp.co.xxx.annotation.DbTransaction)" />
</aop:config>

「pointcut」に、トランザクション管理対象のパッケージを定義します。
パッケージを「execution」括ります。


かつ、「@annotation」に、トランザクション開始を意味するアノテーション定義を記載します。
「jp.co.xxx.annotation.DbTransaction」が、アノテーションのJavaクラスになります。


トランザクション管理するアノテーション設定


ここまで準備した「common-database-context.xml」への定義とアノテーション定義で、トランザクション管理をおこなうことが可能になりました。
アノテーションを定義したメソッドがトランザクションの開始位置で、そのメソッドが完了したら自動的にコミットされます。
仮に、メソッドの途中で例外が発生してメソッドが中断された場合はロールバックされます。(コミットされません)
定義としては以下になります。


@DbTransaction
    public void xxx() {

通常は上記でトランザクション管理ができるのですが、個別にトランザクションの開始やコミット・ロールバックをおこないたい場合があります。
独自のトランザクション管理についても可能です。
以下のように、自分でイベントをプログラミングすればよいです。


// トランザクション管理を読み込み
@Autowired(required = false)
private PlatformTransactionManager transactionManager;

public void xxx() {

    // 処理開始
    TransactionStatus status = startTransaction();

        ・
        ・
        ・

    if (isDone) {
        // コミット
        transactionManager.commit(status);
    } else {
        // ロールバック
        transactionManager.rollback(status);
    }
}

まとめ


いかがでしたでしょうか?


SpringMVCでは、アノテーションの準備をするだけでトランザクション管理をおこなうことが可能になります。


システムの方針によるかと思うのですが、トランザクションの単位をどれにするかもいろいろです。
1つのコントローラメソッドについて1トランザクションにするか、サービスのメソッドについて1トランザクションにするか、
といった悩みどころもあります。


システムでルールを統一した上で、SpringMVCで開発を進めていくのがいいかと思います。


それではまた!



SpringMVCでアノテーションを使ったり、自分で作ってみたりする

SpringFrameWork

皆さん、普段は普通にアノテーションを使ってコーディングしているかと思います。
筆者は、アノテーションについて、Java開発初期時は特に重要に考えていませんでした。
ただの、JavaDocに記述するためのキーワードぐらい?程度です。


でも、アノテーションにロジックを組み込めることを知ってから、自作アノテーションは重宝しています。


今回は、アノテーションを自分で作成する方法を紹介していきます。


大規模なシステムとなり開発メンバが増えれば増えるほど、処理の統一の意味であらかじめ準備されているアノテーションではなく、システム固有のアノテーションを作りたくなってきます。
自分でアノテーションを作って、使用する処理のヘッダに組み込んでいく、といった形です。


今回は、自前でアノテーションを作る方法を紹介していきます。


アノテーションとは


アノテーションには、大きく分けて以下の3つがあります。


  • 名前だけでデータのないマーカー 例:@Override、@Deprecated
  • データをひとつ持つ単一アノテーション 例:@SuppressWarnings
  • 複数のデータをもつフルアノテーション

アノテーションは、メソッドのコメントに「@(アットマーク)」を付けることで有効になります。
当然ながら、ここに書いたアノテーションはJavaDocに出力されます。


日本語としては『注釈』です。
『注釈』の説明としては以下になります。


  • 語句や文章の意味をわかりやすく解説すること。また、それをした文。 「古典を-する」 「 -を加える」
  • 補足的な説明。

そもそもアノテーションを記載する意味ですが、要は、”複数人でプログラムを開発する時に、コーディングのやり方を統一するため”と言ってよいかと思います。
複数人で開発を同時進行する場合、コーディング規約を定めていたとしても、どうしても人によってそれぞれのプログラムが出来上がってしまうことがあります。
その、人による差をゼロにすることはできないですが、なるべく差を小さくしていくための仕組みの一つがアノテーションです。


例えば、「@Override」。
「@Override」を付ける意味は、”このメソッドは親メソッドのオーバーライドメソッドだよ。”という『注釈』です。
このアノテーションを付けた場合、親メソッドに同メソッドが存在しない場合はエラーになります。


開発チーム内で、こういったルールを設けていれば、勝手にオーバーライドメソッドを作る確率が減ります。
※あくまで、”確率が減る”だけですが。


代表的なアノテーション


SpringMVCであらかじめ準備されているアノテーションについて紹介しておきます。
代表的なやつ、です。


@Autowired

自動注入(Dependency Injection)する際に使用する

@Autowired

自動注入(Dependency Injection)する際に使用する

@Component

自動注入(Dependency Injection)して使いたいクラスに付与

@Service

サービスクラスに付与する

@Transactional

トランザクション境界を定義する際に付与

@RestController

APIのコントローラに付与

@Controller

ページのコントローラに付与

@Validated

バリデーションを自動的に実施

@Min

最少値チェック

@Max

最大値チェック

@Size

桁数チェック

@Pattern

正規表現チェック

 


アノテーションを自分で作る


それではアノテーションを自分で作ってみましょう。
アノテーションを作るには、以下の3つを準備すればよいです。


アノテーションの定義


「@interface」をつけて、アノテーション定義をおこないます。
ここでは、「anotationAccess」というアノテーションを定義しています。


public class anotationControl {
    @Target({ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    @Documented
    public @interface anotationAccess {
    }
}

アノテーションのロジック部


アノテーションが実行された際のロジックを記述するクラスを準備します。
ここでは、「anotationLogic」というメソッドを準備しています。


public class anotationLogic {

    private anotationLogic() {
    }

    public void anotationAccessLogic() {

        ※アノテーションの中身のロジック。ここに書く。

    }
}

定義とロジックの紐付け


アノテーションの定義とロジックの紐つけをおこないます。
定義は「servlet-context.xml」におこないます


ここまで作成した定義とロジックであれば、以下のような紐つけになります。


<aop:config>
  <aop:aspect id="ana" ref="anotationAccess">
    <aop:pointcut id="anaPointcut" expression="@annotation(jp.co.xxx.anotationLogic)"/>
      <aop:before pointcut-ref="racPointcut" method="anotationAccessLogic" />
    </aop:aspect>
</aop:config>

「jp.co.xxx.anotationLogic」は、システム独自のパッケージになります。
こうすることで、システム内で自作のアノテーションが使用できるようになります。


アノテーションの使い方は、あらかじめ準備されているアノテーションを使う場合とまったく同じです。


@anotationAccess
public xxx method() {

まとめ


いかがでしたでしょうか?
アノテーションを使ったり、自分で作る際の参考にしてもらえればと思います。


それではまた!



SpringSecurityを使ったセッション制御。同時ログインを許可しない

SpringSecurity

今回は、SpringSecurityを使っての同時ログイン制御について説明します。


SpringSecurityを使った場合、セッションの作成や制御はSpringSecurityが自動でやってくれますが、「applicationSecurity.xml」で然るべき定義をおこなえば、同時ログイン制御も簡単におこなうことができます。



同時ログイン制御は、システムによって仕様が異なるかと思います。
同じユーザIDで複数ユーザでのログインを許可しているシステムもあれば、同じユーザIDで複数ユーザでのログインを許可しないシステムもあります。


こういった制御は、自前で制御を作るのではなく、「applicationSecurity.xml」の定義で制御可能です。


SpringSecurityの基本的な使い方


SpringSecurityの基本的な使い方については、以下の記事を参照してください。



SpringSecurityを使えば、ログイン機能があるシステムで必要な以下の制御が可能になります。


  • ログイン認証
  • セッション管理
  • 不正アクセスに対する制御
  • ワンタイムトークン

同時ログイン制御で必要な「applicationSecurity.xml」の定義


基本的な「applicationSecurity.xml」の定義は以下になります。
同時ログインを許可していないシステムでの定義例になります。


<bean id="sessionStrategy" class="org.springframework.security.web.authentication.session.CompositeSessionAuthenticationStrategy" >
  <constructor-arg index="0">
    <list>
      <bean class="jp.co.xxx.MyConcurrentSessionControlAuthenticationStrategy">
        <constructor-arg index="0" ref="sessionRegistry" />
        <property name="maximumSessions" value="1" />
        <property name="exceptionIfMaximumExceeded" value="false"/>
      </bean>
    </list>
  </constructor-arg>
</bean>

同時ログインを許可しないために必要なプロパティ設定について解説していきます。


最大セッション数 – maximumSessions


1ユーザが許可する最大セッション数になります。
ここを1にすると、1ユーザが許可する最大セッション数は1になるので、同時ログインは許可しないということになります。


例えば、この値に「100」を定義すると、同じユーザIDでログインを許可する最大セッション数は100になります。
つまり、同時ログインを100セッションまで許可する、ということになります。


セッション数が最大を超えた場合に例外を発生させる – exceptionIfMaximumExceeded


「maximumSessions」の数を超えた場合、例外を発生させるかどうか?の定義になります。
trueだと例外を発生させ、falseだと例外を発生させない。


上記の「applicationSecurity.xml」の定義では、例外は発生させたくないのでfalseにしています。


Javaで制御をおこなう – ConcurrentSessionControlAuthenticationStrategy


XMLに定義されていますが、「MyConcurrentSessionControlAuthenticationStrategy」で個別制御をおこなうことができます。
「MyConcurrentSessionControlAuthenticationStrategy」は「org.springframework.security.web.authentication.session.ConcurrentSessionControlAuthenticationStrategy」
を継承したクラスになります。


具体的には、以下のクラスを準備して個別制御をおこないます。


import java.util.List;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.session.SessionInformation;
import org.springframework.security.core.session.SessionRegistry;
import org.springframework.security.web.authentication.session.ConcurrentSessionControlAuthenticationStrategy;

public class MyConcurrentSessionControlAuthenticationStrategy extends
        ConcurrentSessionControlAuthenticationStrategy {

    private SessionRegistry mySessionRegistry;

    public MyConcurrentSessionControlAuthenticationStrategy(
            SessionRegistry sessionRegistry) {
        super(sessionRegistry);
        this.mySessionRegistry = sessionRegistry;
    }

    @Override
    public void onAuthentication(Authentication authentication,
            HttpServletRequest request, HttpServletResponse response) {

        //-- 個別処理をこの中に書く --//

    }
}

「onAuthentication」は、認証が成功した場合に実行されるメソッドになります。
個別処理が必要な場面とは以下になります。
全て、同時ログインを許可しない「applicationSecurity.xml」の定義をおこなった場合の制御です。


  • ある条件に一致するユーザは、同時ログイン制御を許可させる。
  • 『後勝ち』の制御をおこなう。

『後勝ち』の制御はスペシャルですね。
既にログインしているユーザのセッションを削除する処理が必要になります。


以下のような実装が必要になります。


// 同一ユーザでのセッションリストを取得
List<SessionInformation> sessions = this.mySessionRegistry
    .getAllSessions(authentication.getPrincipal(), false);

 // 許可するセッションの数を取得
int allowedNum = getMaximumSessionsForThisUser(authentication);

// セッションリストのうち、ログインしたユーザだけを有効にし、
// 他セッションを無効にする
allowableSessionsExceeded(sessions, allowedNum, this.mySessionRegistry);

まとめ


いかがでしたでしょうか?


SpringSecurityで同時ログイン制御が簡単におこなうことができることがわかって頂けたかと思います。


「applicationSecurity.xml」の定義だけではなく、SpringSecurityは必ずスペシャルな処理を個別に実装することが可能です。
「applicationSecurity.xml」の定義だけでやり切る(やり切れる)部分と、スペシャルに実装する部分を見極めて実装することが大事かと思います。


それではまた!



JavaでSocket通信をおこなう。サーバとクライアントのサンプルコード。

javaソケット

Javaでソケット通信を、自前で開発しなければいけない事はあるかと思います。
ソケットを待ち受ける側が常駐プロセスとして確立させ、ソケットの送信を常に待ち受けている状態。
ソケットを待ち受ける側がサーバとなり、ソケットを送る側がクライアント側になりますね。


今回は、ソケット通信のサーバ側とクライアント側のサンプルソースコードを紹介いたします。
ソケットの送受信のみのサンプルコードになりますので、システム固有の処理については別途組み込んで使ってください。


環境情報


今回のサンプルコードのビルド環境、実行環境は以下になります。


  • Java12.0.1

Javaの標準ライブラリしか使っていないです。
Apache等の拡張ライブラリ(JAR)は必要なし、になります。


サンプルプログラムの機能


サーバのクラス名は「SocketReceive」
クライアントのクラス名は「SocketSend」


クライアントは、クライアント上に存在するファイルである「client_send.txt」をストリーム上に読み込みます。
クライアントの「SocketSend」は、サーバの「SocketReceive」に接続して「client_send.txt」の内容を送信します。
「SocketReceive」は受信した内容を「server_recv.txt」にファイル出力します。


「SocketReceive」は、ファイル出力が終わった後にまったく逆のことをおこないます。
「SocketReceive」は、「server_send.txt」をストリーム上に読み込み「SocketSend」に送信します。
「SocketSend」は受信した内容を「cient_rect.txt」に出力します。


送受信プログラムの概要

ソケット通信プログラム

クライアントプログラム – ソケット送信


クライアントプログラム(送信側)のサンプルは以下になります。
上の説明での「SocketSend」になります。


import java.sql.SQLException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.io.IOException;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.OutputStream;

public class SocketSend {

    private SocketSend() {
    }

    public static void main(String[] args) {

        Socket socket = null;
        FileOutputStream fos = null;
        FileInputStream fis = null;
        try {
            System.out.println("start.");

            // 出力ソケットを作成
            socket = new Socket("localhost", 8100);

            // 入出力ストリームを準備
            fis = new FileInputStream("client_send.txt");
            fos = new FileOutputStream("client_recv.txt");

            // 入力ストリームの内容を全て送信
            int ch;
            OutputStream output = socket.getOutputStream();
            while ((ch = fis.read()) != -1) {
                output.write(ch);
            }
            output.write(0);

            // サーバからのレスポンスを受信し、ファイルに出力
            InputStream input = socket.getInputStream();
            while ((ch = input.read()) != -1) {
                fos.write(ch);
            }
        } catch (SocketException e) {
            System.out.println("occured SocketException.");
        } catch (Exception e) {
            System.out.println("occured Exception.");
        } finally {
            try {
                socket.close();
                fos.close();
                fis.close();
            } catch (IOException e) {
                System.out.println("occured IOException.");
            }
            System.out.println("end.");
        }
    }
}

サーバプログラム – ソケット受信


次はサーバプログラム(受信側)のサンプルです。
上の説明での「SocketReceive」になります。


import java.sql.SQLException;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.io.IOException;
import java.io.File;
import java.io.FileOutputStream;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.OutputStream;

public class SocketReceive {

    private SocketReceive() {
    }

    public static void main(String[] args) {

        Socket socket = null;
        FileOutputStream fos = null;
        FileInputStream fis = null;
        try {
            System.out.println("start.");

            // 入出力ストリームを準備する
            fos = new FileOutputStream("server_recv.txt");
            fis = new FileInputStream("server_send.txt");

            // 入力ソケットを作成して、送信を待ち受ける
            ServerSocket server = new ServerSocket(8100);
            socket = server.accept();

            // ソケットを受信する
            int ch;
            InputStream input = socket.getInputStream();
            while ((ch = input.read()) != 0) {
                fos.write(ch);
            }

            // 受信した内容をファイル出力
            OutputStream output = socket.getOutputStream();
            while ((ch = fis.read()) != -1) {
                output.write(ch);
            }
        } catch (SocketException e) {
            System.out.println("occured SocketException.");
        } catch (IOException e) {
            System.out.println("occured IOException.");
        } catch (Exception e) {
            System.out.println("occured Exception.");
        } finally {
            try {
                socket.close();
                fos.close();
                fis.close();
            } catch (IOException e) {
                System.out.println("occured IOException.");
            }
            System.out.println("end.");
        }
    }
}

まとめ


いかがでしたでしょうか?
ソケット通信の仕組みを開発する必要が出てきた場合は参考にしてください。


ちょっとできなかったのは、「SocketReceive」の常駐プロセス化。
現在のサンプルプログラムは1回受信したら終わってしまうので。


ここら辺はアレンジして使ってください。


それではまた!