Spring3.1概要 データアクセスとトランザクション処理
Upcoming SlideShare
Loading in...5
×
 

Spring3.1概要 データアクセスとトランザクション処理

on

  • 12,823 views

 

Statistics

Views

Total Views
12,823
Views on SlideShare
11,898
Embed Views
925

Actions

Likes
16
Downloads
121
Comments
1

10 Embeds 925

http://d.hatena.ne.jp 873
http://webcache.googleusercontent.com 28
http://hatenatunnel.appspot.com 11
https://twitter.com 4
http://us-w1.rockmelt.com 2
http://cache.yahoofs.jp 2
https://twimg0-a.akamaihd.net 2
http://cc.bingj.com 1
http://translate.googleusercontent.com 1
http://dhatenane.greatbabyfood.com 1
More...

Accessibility

Categories

Upload Details

Uploaded via as Microsoft PowerPoint

Usage Rights

© All Rights Reserved

Report content

Flagged as inappropriate Flag as inappropriate
Flag as inappropriate

Select your reason for flagging this presentation as inappropriate.

Cancel
  • Full Name Full Name Comment goes here.
    Are you sure you want to
    Your message goes here
    Processing…
Post Comment
Edit your comment
  • というところで、時間も迫ってまいりましたので、 これで、発表を終わります。 ご清聴ありがとうございました。

Spring3.1概要 データアクセスとトランザクション処理 Spring3.1概要 データアクセスとトランザクション処理 Presentation Transcript

  • Spring 概要 データアクセスとトランザクション処理 for Beginner 2012/2/27
  • 前回までのお話
    • DI によってプログラムが部品化された
    • AOP によって処理を横断的に追加できるようになった
    • Spring MVC によってプレゼン層の処理が簡単になった
    プレゼン テーション ビジネス ロジック データベース アクセス RDB ブラウザ 表示の仕組み 永続化の仕組み 業務の仕組み DI x AOP Spring MVC
  • 今日お話しする部分
    • データアクセス周りのコードをシンプルに
    • トランザクション処理をスマートに
    プレゼン テーション ビジネス ロジック データベース アクセス RDB ブラウザ 表示の仕組み 永続化の仕組み 業務の仕組み DI x AOP Spring MVC ココの部分
  • データアクセス層の一般的な話と Spring
  • デーアクセス層の役割
    • 業務ロジックからデータアクセスの処理を隠ぺいする
      • 永続化の仕組みが変わっても業務ロジックへの影響がない
      • アプリケーションの肝である業務ロジックのメンテナンス性が上がる
    プレゼン テーション ビジネス ロジック データベース アクセス RDB ブラウザ 表示の仕組み 永続化の仕組み 業務の仕組み
  • データアクセスの処理が混在した業務ロジック (1/3)
    • 業務ロジックとデータアクセスの処理が混在するとプログラムが複雑になる
    • サンプルコード:ある口座から他の口座にお金を振り込む transfer メソッド
    業務ロジック 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 public void transfer(Account from, Account to, int furikomigaku) { Connection con = null; PreparedStatement ps = null; ResultSet rs = null; try { con = dataSource.getConnection(); con.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE); // 振込元の残高が足りてるか確認 int zandaka = -1; ps = con.prepareStatement( &quot;select zandaka from account where account_num=?&quot;); ps.setString(1, from.getAccountNumnber()); rs = ps.executeQuery(); if (rs.next() == true) { zandaka = rs.getInt(&quot;zandaka&quot;); } else { throw new BussinessException(&quot; データがありません &quot;); } int newZandaka = zandaka - furikomigaku; if (newZandaka < 0) { throw new BussinessException(&quot; 残高が足りません &quot;); } from.setZandaka(newZandaka);
  • データアクセスの処理が混在した業務ロジック (2/3) 業務ロジック 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 // 振込先の口座の残高を計算 ps.clearParameters(); ps.setString(1, to.getAccountNumnber()); rs = ps.executeQuery(); if (rs.next() == true) { zandaka = rs.getInt(&quot;zandaka&quot;); } else { throw new BussinessException(&quot; データがありません &quot;); } newZandaka = zandaka + furikomigaku; to.setZandaka(newZandaka); //  振込元の残高を更新 ps = con.prepareStatement( &quot;update account set zandaka=? where account_num=?&quot;); ps.setInt(1, from.getZandaka()); ps.setString(2, from.getAccountNumnber()); ps.execute(); // 振込先の残高を更新 ps.clearParameters(); ps.setInt(1, to.getZandaka()); ps.setString(2, to.getAccountNumnber()); ps.execute(); con.commit();
  • データアクセスの処理が混在した業務ロジック (3/3) 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 } catch (SQLException sqle) { try { con.rollback(); } catch (Exception e) { System.out.println(&quot; システムエラー &quot;+e); } int errorCode = sqle.getErrorCode(); if (errorCode == ERR_DEADLOCK) { throw new BussinessException(&quot; デッドロック発生 &quot;, sqle); } else { throw new SystemException(&quot; システムエラー &quot;, sqle); } } finally { try { con.close(); } catch (Exception e) { System.out.println(&quot; システムエラー &quot;+e); } } }
  • データアクセスの処理を分離させた業務ロジック
    • データアクセス専用の部品を作成して業務ロジックからデータアクセスの処理を分離する
    ・データアクセス専用の部品 ・業務ロジックを含まない ・ DAO (Data Access Object) と呼ばれる 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 public void transfer(Account from, Account to, int furikomigaku) { // 振込元の残高を確認 int zandaka = accountDao.getZandaka(from.getAccountNumnber()); int newZandaka = zandaka - furikomigaku; if (newZandaka < 0) { throw new BussinessException(&quot; 残高が足りません &quot;); } from.setZandaka(newZandaka); // 振込先の口座の残高を計算 zandaka = accountDao.getZandaka(to.getAccountNumnber()); newZandaka = zandaka + furikomigaku; to.setZandaka(newZandaka); // 振込元の残高を更新 accountDao.updateZandaka(from); // 振込先の残高を更新 accountDao.updateZandaka(to); } AccountDao + getZandaka(accountNumber:String):int + updateZandaka(account:Account):void
  • DAO パターンとは?
    • DB 接続の確立や SQL の発行といったデータアクセスの処理を DAO と呼ばれるオブジェクトに隠蔽するパターン
    • DB のテーブルごとに作られることが多い
      • テーブルの定義情報を元に自動生成する開発プロジェクトもある
    • 部品化のため通常はインターフェースを用意する
    データ ベース アクセス ビジネス ロジック 使う AccountDao BankDao UserDao など CRUD が一通り 揃っている インターフェースを 用意 挿入メソッド 取得メソッド 更新メソッド 削除メソッド XXXDao
  • データアクセス技術のいろいろ
    • Java のデータアクセス技術は複数存在する
    • 開発プロジェクトごとに適切なものを選択する
    データ ベース JDBC Hibernate iBATIS(mybatis) JPA JDO 自社フレームワーク ・ ・ S2Dao 挿入メソッド 取得メソッド 更新メソッド 削除メソッド XXXDao
  • データアクセスの Spring の機能
    • データアクセス技術をより使い易くする機能を提供
    データ ベース Spring JDBC JDBC Hibernate 連携 Hibernate JPA 連携 JPA iBATIS 連携 iBATIS(2.x) JDO 連携 JDO 挿入メソッド 取得メソッド 更新メソッド 削除メソッド XXXDao
  • Spring のデータアクセスの機能を使う利点
    • コードがシンプルになる
    • 汎用的で体系的なデータアクセス例外に変換してくれる
    • Spring のトランザクション機能が利用可能になる
  • Spring JDBC
  • JDBC の問題 (1/3)
    • JDBC を直接使ってデータアクセスを行う場合・・・
    JDBC データ ベース + getZandaka(accountNumber:String):int + updateZandaka(account:Account):void AccountDao
  • JDBC の問題 (2/3)
    • ・・・いろいろと問題がある
    ・コード量が多い   Connection 、 PreparedStatement ・・  リソースの取得・解放が面倒 解放漏れの危険も getZandaka メソッド 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 public int getZandaka(String accountNumber) { Connection con = null; PreparedStatement ps = null; ResultSet rs = null; try { con = dataSource.getConnection(); int zandaka = -1; ps = con.prepareStatement(&quot;select zandaka from account where account_num=?&quot;); ps.setString(1, accountNumber); rs = ps.executeQuery(); if (rs.next() == true) { zandaka = rs.getInt(&quot;zandaka&quot;); } else { throw new BussinessException(&quot; データがありません &quot;); } return zandaka; } catch (SQLException sqle) { throw new SystemException(&quot; システムエラー &quot;, sqle); } finally { try { con.close(); } catch (Exception e) { System.out.println(&quot; システムエラー &quot;+e); } } }
  • JDBC の問題 (3/3) ・例外 SQLException の解釈が必要  原因を特定するためのエラーコードが    DB 製品毎に異なる updateZandaka メソッド 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 public void updateZandaka(Account account) { Connection con = null; PreparedStatement ps = null; ResultSet rs = null; try { con = dataSource.getConnection(); con.setTransactionIsolation(Connection.TRANSACTION_SERIALIZABLE); ps = con.prepareStatement(&quot;update account set zandaka=? where account_num=?&quot;); ps.setInt(1, account.getZandaka()); ps.setString(2, account.getAccountNumnber()); ps.execute(); con.commit(); } catch (SQLException sqle) { try { con.rollback(); } catch (Exception e) { System.out.println(&quot; システムエラー &quot; + e); } int errorCode = sqle.getErrorCode(); if (errorCode == ERR_DEADLOCK) { throw new BussinessException(&quot; デッドロック発生 &quot;, sqle); } else { throw new SystemException(&quot; システムエラー &quot;, sqle); } } finally { try { con.close(); } catch (Exception e) { System.out.println(&quot; システムエラー &quot; + e); } } }
  • Spring JDBC による解決
    • Spring JDBC とは?
      • JDBC をラップした API を提供し JDBC を直接使用した場合に発生する冗長な処理の記述を隠蔽してくれる 機能
    • 利点
      • コードがシンプルになる
      • SQLException の解釈が不要
    Spring JDBC JDBC データ ベース 挿入メソッド 取得メソッド 更新メソッド 削除メソッド XXXDao
  • Template クラスを使ってコードをシンプルに
    • 本質的ではないコードを共通化した クラス ( 便宜上 Template< ひな形 > クラス と呼ぶ ) を提供
    • 3つの Template クラス
      • JdbcTemplate
        • 3つの Template クラスの中で一番メソッドの種類が多彩であり、直接利用可能な JDBC の API の範囲も広い
        • Spring のバージョン 1.0 の頃から提供されている
      • NamedParameterJdbcTemplate
        • Spring のバージョン 2.0 から提供された
        • SQL のパラメータに任意の名前をつけることができる
      • SimpleJdbcTemplate
        • JdbcTemplate と NamedParameterTemplate で頻繁に使用されるメソッドを寄せ集めたクラス
        • Spring のバージョン 2.0 から提供されたが、バージョン 3.1 から deprecated( 奨励しないという意味 ) になった
  • Template クラスの使い方
    • 以下について説明します
      • Template クラスの Bean の準備
      • クエリ
        • エンティティクラスへ変換しない場合
        • エンティティクラスへ変換する場合
      • インサート・デリート・アップデート
      • バッチアップデート・プロシージャコール
  • Template クラスの Bean の準備 ・・・ <bean class=&quot;org.springframework.jdbc.core.JdbcTemplate&quot;> <property name=&quot;dataSource&quot; ref=&quot;dataSource&quot; /> </bean> <bean class=&quot;org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate&quot;> <property name=&quot;dataSource&quot; ref=&quot;dataSource&quot; /> </bean> <bean id=&quot;dataSource&quot; class=&quot;com.mchange.v2.c3p0.ComboPooledDataSource&quot;> <property name=&quot;driverClass&quot; value=&quot;oracle.jdbc.driver.OracleDriver&quot;/> <property name=&quot;jdbcUrl&quot; value=&quot;jdbc:oracle:thin:@localhost:1521:sample&quot;/> <property name=&quot;user&quot; value=&quot;spring3&quot;/> <property name=&quot;password&quot; value=&quot;spring3&quot;/> </bean> ・・・ ・・・ @Repository public class AccountDaoSpringJdbc implements AccountDao { @Autowired private JdbcTemplate jdbcTemplate; @Autowired private NamedParameterJdbcTemplate npJdbcTemplate; ・・・
  • クエリ ~エンティティクラスへ変換しない場合 (1/2)
    • 取得結果が数値型
      • queryForInt
      • queryForLong
    int count = jdbcTemplate.queryForInt( &quot;select count(*) from pet&quot; ); int count = jdbcTemplate.queryForInt( &quot;select count(*) from pet where owner_name=?“ , ownerName); ・・・ public int getZandaka(String accountNumber) { int zandaka = jdbcTemplate.queryForInt( &quot;select zandaka from account where account_num=?&quot; , accountNumber); return zandaka; } ・・・ 【 AccountDao の getZandaka メソッドの場合】
  • クエリ ~エンティティクラスへ変換しない場合 (2/2)
    • 取得結果が文字列型や日付型
      • queryForObject
    • 取得結果 (1 レコード ) を Map で取得
      • queryForMap
    • 取得結果 ( 複数レコード ) を Map のリストで取得
      • queryForList
    String petName = jdbcTemplate.queryForObject( &quot;select pet_name from pet where pet_id=?&quot; , String.class, id); Date birthDate = jdbcTemplate.queryForObject( &quot;select birth_date from pet where pet_id=?&quot; , Date.class, id); Map<String, Object> pet = jdbcTemplate.queryForMap( &quot;select * from pet where pet_id=?&quot; , id); String petName = (String)pet.get(&quot;pet_name&quot;); List<Map<String, Object>> petList = jdbcTemplate.queryForList( &quot;select * from pet where owner_name=?&quot; , ownerName);
  • クエリ ~エンティティクラスへ変換する場合 (1/2 )
    • 1 レコードの場合
      • queryForObject
    • Pet pet = jdbcTemplate.queryForObject(
    • &quot;select * from pet where pet_id=?“
    • , new RowMapper<Pet>() {
        • public Pet mapRow(ResultSet rs, int rowNum) throws SQLException {
        • Pet p = new Pet();
        • p.setPetId(rs.getString(&quot;pet_id&quot;));
        • p.setPetName(rs.getString(&quot;pet_name&quot;));
        • p.setOwnerName(rs.getString(&quot;owner_name&quot;));
        • return p;
    • }}
    • , id);
    ※ 匿名クラス ※ 匿名クラス:「その場限り」のサブクラスもしくは実装クラス。名前が無い
  • ≪ 補足≫匿名クラスを使わない書き方 class MyRowMapper implements RowMapper<Pet> { public Pet mapRow(ResultSet rs, int rowNum) throws SQLException { Pet p = new Pet(); p.setPetId(rs.getInt(&quot;pet_id&quot;)); p.setPetName(rs.getString(&quot;pet_name&quot;)); p.setOwnerName(rs.getString(&quot;owner_name&quot;)); return p; } } Pet pet = jdbcTemplate.queryForObject( &quot;select * from pet where pet_id=?&quot; ,new MyRowMapper() ,id);
  • RowMapper とシーケンス図
  • クエリ ~エンティティクラスへ変換する場合 (2/2)
    • 複数レコードの場合
      • query
    • List<Pet> petList = jdbcTemplate.query(
    • &quot;select * from pet where owner_name=?“
    • , new RowMapper<Pet>() {
        • public Pet mapRow(ResultSet rs, int rowNum) throws SQLException {
        • Pet p = new Pet();
        • p.setPetId(rs.getString(&quot;pet_id&quot;));
        • p.setPetName(rs.getString(&quot;pet_name&quot;));
        • p.setOwnerName(rs.getString(&quot;owner_name&quot;));
        • return p;
        • }}
        • , ownerName);
  • インサート・アップデート・デリート
    • update
      • インサート
      • アップデート
      • デリート
    jdbcTemplate.update( &quot;insert into pet (pet_id, pet_name, owner_name) values (?, ?, ?)&quot; , pet.getPetId(), pet.getPetName(), pet.getOwnerName()); jdbcTemplate.update( &quot;update pet set pet_name=?, owner_name=? where pet_id=?&quot; , pet.getPetName(), pet.getOwnerName(), pet.getPetId()); jdbcTemplate.update( &quot;delete from pet where pet_id=?“ , pet.getPetId());
  • NamedParameterJdbcTemplate を使った場合
    • SQL のパラメータに任意の名前を設定 し 名前と値を明示的に紐づける ことができる
      • パラメータが多い場合に適している
    npJdbcTemplate.update( &quot;insert into pet (pet_id, pet_name, owner_name)&quot; + &quot; values (:pet_id, :pet_name, :owner_name)&quot; ,new MapSqlParameterSource() .addValue(&quot;pet_id&quot;, pet.getPetId()) .addValue(&quot;pet_name&quot;, pet.getPetName()) .addValue(&quot;owner_name&quot;, pet.getOwnerName()) ); ※ メソッドチェーン:複数のメソッド呼び出しを1センテンスで記述するテクニック ※ メソッドチェーン
  • ≪ 補足≫メソッドチェーンを使わないやり方 MapSqlParameterSource map = new MapSqlParameterSource(); map.addValue(&quot;pet_id&quot;, pet.getPetId()); map.addValue(&quot;pet_name&quot;, pet.getPetName()); map.addValue(&quot;owner_name&quot;, pet.getOwnerName()); npJdbcTemplate.update( &quot;insert into pet (pet_id, pet_name, owner_name)&quot; + &quot; values (:pet_id, :pet_name, :owner_name)&quot; ,map );
  • バッチアップデート
    • batchUpdate
    List<Object[]> args = new ArrayList<Object[]>(); for (Pet pet : petList) { args.add(new Object[]{pet.getOwnerName(), pet.getPetId()}); } int[] num = jdbcTemplate.batchUpdate( &quot;update pet set owner_name=? where pet_id=?“ , args);
  • プロシージャコール
    • SimpleJdbcCall が便利
      • プロシージャ名: calc_pet_price
      • IN パラメータ: pet_id
      • OUT パラメータ: price
    SimpleJdbcCall call = new SimpleJdbcCall(jdbcTemplate.getDataSource()); call.withProcedureName( &quot;calc_pet_price&quot; ); MapSqlParameterSource args = new MapSqlParameterSource() .addValue(&quot;pet_id&quot;, petId); Map<String, Object> out = call.execute(args); int price = (Integer)out.get(&quot;price&quot;);
  • 汎用データアクセス例外で SQLException の解釈を不要に
    • SQLException の問題
      • データアクセスのエラーの原因はざまざまだが JDBC の例外の型は基本1つ (SQLException) しかない
      • 検査例外なので catch 句または throw を記述しなければならない
      • エラーコードで原因を特定できるが DB 製品ごとに定義が異なる
    ・・・ } catch (SQLException sqle) { int errorCode = sqle.getErrorCode(); if (errorCode == ERR_DATAINTEGRITY) { throw new DataIntegrityException(&quot; 整合性違反エラー発生 &quot;, sqle); } else if (errorCode == ERR_PERMISSION) { throw new DataPermissionException(“ 権限が不正 &quot;, sqle); } else if (errorCode == ・・・ ・・・
  • Spring の汎用データアクセス例外
    • データアクセス時のエラーの種類に合わせて体系立てられた例外クラス群
    • Template クラスの API の処理の内部で変換してくれる
    • データベース製品間の差異を吸収してくれる
    • 実行時例外なので必要に応じて catch すればよい
    【例外クラスの例 ( 全部で 20 個以上存在する ) 】 CannotAcquireLockException ロックの取得に失敗 ConcurrencyFailureException 同時実行時のエラー DataIntegrityViolationException 整合性違反エラー DeadlockLoserDataAccessException デッドロックが発生 EmptyResultDataAccessException 取得しようとしたデータが存在しない IncorrectResultSizeDataAccessException 取得したレコードの数が不正 OptimisticLockingFailureException 楽観的ロックに失敗 PermissionDeniedDataAccessException 権限エラー
  • 汎用データアクセス例外のハンドリングの方針
    • DAO の中では catch しない
    • サービスやコントローラにおいては、対処可能な例外のみ catch して対処する
    • 対処できない例外は共通の仕組みを用意して一元的に処理する ( エラーページを表示するなど )
    プレゼン テーション ビジネス ロジック データベース アクセス RDB ブラウザ サービス DAO コントローラ 共 通 対応可能な例外のみ catch 対応可能な例外のみ catch 対処できない例外は一元的に処理
  • データソース
    • データソースとは?
      • データベース接続オブジェクト (Connection) のファクトリ
        • 接続情報 ( ユーザ・パスワードなど ) を保持
      • JDBC の API としてインターフェースが提供されている (DataSource)
    • DataSource にはさまざまな実装がある
      • Spring が提供する もの
      • サードパーティが提供する もの
      • AP サーバーが 提供するもの
  • データソースの使い方
    • 接続情報などの設定はプログラム上でも可能だが通常は Bean 定義ファイルで行う
    • 設定したデータソースは、 Bean 定義ファイルやアノテーションで他の Bean に DI して使う
    ・・・ <bean id=&quot;dataSource&quot; > ・・・ </bean> ・・・ ・・・ <bean id=“foo&quot; > <property name=&quot;dataSource&quot; ref=&quot;dataSource&quot; /> </bean> ・・・ ・・・ @Autowired private DataSource dataSource; ・・・
  • Spring が提供する DataSource
    • SingleConnectionDataSource
      • 1つのデータベース接続オブジェクトを、クローズせずに使いまわす
    • DriverManagerDataSource
      • 取得の要求のたびに、データベース接続オブジェクトを作成して返す
    注意) テスト用途で用意されたものなので本番で使用するものではない <bean id=&quot;dataSource&quot; class=&quot;org.springframework.jdbc.datasource.DriverManagerDataSource&quot;> <property name=&quot;driverClassName&quot; value=&quot;org.postgresql.Driver&quot; /> <property name=&quot;url&quot; value=&quot;jdbc:postgresql://localhost/samaple&quot; /> <property name=&quot;username&quot; value=&quot;spring3&quot; /> <property name=&quot;password&quot; value=&quot;spring3&quot; /> </bean> 【 DriverManagerDataSource の 設定の例 】
  • サードパーティ が提供する DataSource
    • 以下の2つのプロダクトが代表的
      • どちらも オープンソースで無償
    • DBCP(Apache )
    • c3p0(Machinery For Change, Inc)
    <bean id=&quot;dataSource&quot; class=&quot;com.mchange.v2.c3p0.ComboPooledDataSource&quot; destroy-method=&quot;close&quot;> <property name=&quot;driverClass&quot; value=&quot;org.postgresql.Driver&quot; /> <property name=&quot;jdbcUrl&quot; value=&quot;jdbc:postgresql://localhost/sample&quot; /> <property name=&quot;user&quot; value=&quot;spring3&quot; /> <property name=&quot;password&quot; value=&quot;spring3&quot; /> <property name=&quot;maxPoolSize&quot; value=&quot;20&quot;/> </bean> 【 c3p0 の 設定の例 】
  • AP サーバ が提供する DataSource
    • JNDI 経由で取得する
      • 昔ながらのやり方
      • jee スキーマを使用したやり方
      • @Resource を使用したやり方
    <bean id=&quot;dataSource&quot; class=&quot;org.springframework.jndi.JndiObjectFactoryBean&quot;> <property name=&quot;jndiName&quot; value=&quot;jdbc/MyDataSource&quot;/> </bean> <jee:jndi-lookup id=&quot;dataSource&quot; jndi-name=&quot;jdbc/MyDataSource&quot; /> <bean class=&quot;org.springframework.context.annotation.CommonAnnotationBeanPostProcessor&quot;> <property name=&quot;alwaysUseJndiLookup&quot; value=&quot;true&quot;/> </bean> @Resource(name=“jdbc/MyDataSource”) private DataSource dataSource; Common Anotations(JSR250) 用の 設定が必要
  • トランザクション機能
  • トランザクションとは?
    • 関連する複数の処理を一つの処理単位としてまとめたもの
      • 長いもの
        • Web サイトで商品を注文してから商品が家に届くまで
      • 短いもの
        • 注文のリクエストを受けて、発注テーブルと顧客テーブル,在庫テーブルを更新する
    • 守るべき「 ACID 属性」
    ACID 意味 解説 Atomic トランザクションの原子性 トランザクション内の全ての処理は、全ておこなわれたか、もしくは何もおこなわれなかったかのどちらかだけであること。 Consistent データの一貫性 データに一貫性があること。一貫性を守ってない例:親テーブルがないのに子テーブルがある。 Isolated トランザクションの独立性 平行して走るトランザクションが互いに独立していること。 Durable データの永続性 データが永続化されていること。永続化されているデータが読み出せること。
  • Web アプリのトランザクション処理
    • 複数のリクエストに跨るもの
      • 例:カタログ画面からショッピングカートを使って商品を注文する
      • アプリケーショントランザクション、ロングトランザクションなどと呼ばれる
    • 1つのリクエスト内で行うもの
      • 例:注文確定のリクエストを受けて、発注テーブルと顧客テーブル,在庫テーブルを更新する
      • データソースが1つの場合
        • ローカルトランザクション
      • データソースが複数の場合
        • グローバルトランザクション
  • トランザクションの境界
    • 業務ロジックを持ったビジネス層でトランザクション処理を行うため、通常はプレゼンテーション層とビジネス層の間に引く
    • トランザクションの境界とメソッド
      • サービスのメソッドが呼び出されたときにトランザクションを開始
      • 処理の途中でエラーが発生したらロールバックしてメソッドを抜ける
      • メソッドの処理が完了したらコミット
    プレゼン テーション ビジネス ロジック データベース アクセス RDB ブラウザ サービス DAO コントローラ トランザクションの開始 トランザクションの終了
  • 実装する場所の問題
    • トランザクション処理を業務ロジックで記述しようとすると・・・
    • commit や rollback メソッドは JDBC の Connection クラスが持っているため ...
      • ビジネスロジックが JDBC の API に依存してしまう
      • Connection のオブジェクトを DAO のメソッド間で共有させる必要がある
        • DAO のメソッドの引数に入れるなど
    :ビジネスロジック : AccountDao transfer updateZandaka( 振込元 ) updateZandaka( 振込先 )
  • 問題
    • 次のような状況のときに、何という技術で対応しますか?
      • ビジネス層のメソッドが呼ばれたときと終了したときにトランザクション処理 ( 開始・コミット・ロールバック ) を実施したい
      • ビジネス層のメソッドの中は業務ロジックだけに特化したい ( トランザクション処理を記述したくない )
  • AOP を使用したトランザクション処理
    • トランザクション処理を Proxy に任せる
    クライアント トランザクション 処理 サービス DAO RDB トランザクションの開始 トランザクションのコミット サービスの Proxy
  • トランザクションマネージャ
    • Advice の処理を自作する必要はない
    • Spring が提供するトランザクションマネージャを利用すればよい
    • トランザクションマネージャの特徴
      • コミット・ロールバックだけでなく、トランザクションの定義情報 ( ロールバックする時の条件や独立性レベルなど ) を細かく設定できる
      • データアクセス技術 (JDBC 、 Hibernate 、 iBATIS など ) を隠ぺい
        • データアクセス技術ごとにトランザクションマネージャの実装が用意されている
  • トランザクション定義情報
    • 伝搬属性
    • 独立性レベル
    • タイムアウト秒
    • 読取専用有無
    • ロールバック対象例外
    • コミット対象例外
  • 伝搬属性
    • トランザクションの 伝搬の仕方を設定
    サービス2 サービス1 DAO 1 DAO 2 コントローラ2 コントローラ1 ① ② 伝搬 属性 サービス1に対して設定を行ったとき ① の場合 ② の場合 PROPAGATION_REQUIRED トランザクションを開始 サービス2のトランザクションを利用 PROPAGATION_REQUIRES_NEW トランザクションを開始 新しいトランザクションを開始 PROPAGATION_SUPPORTS トランザクションを行わない サービス2のトランザクションを利用 PROPAGATION_MANDATORY 例外を投げる サービス2のトランザクションを利用 PROPAGATION_NESTED トランザクションを開始 部分的なトランザクションを開始 (save ポイント ) PROPAGATION_NEVER トランザクションを行わない 例外を投げる PROPAGATION_NOT_SUPPORTED トランザクションを行わない トランザクションを行わない
  • 独立性レベル
    • トランザクション処理が並行して実行される際に 独立性を どこまで保つかを設定
    トランザクション1 トランザクション2 独立性レベル 解説 ISOLATION_READ_COMMITTED 他のトランザクションが変更したがまだコミットしていないデータは読み出せない。 ISOLATION_READ_UNCOMMITTED 他のトランザクションが変更したがまだコミットしていないデータを読み出せる。 ISORATION_REPEATABLE_READ トランザクション内で複数回データを読み込んだ場合、他のトランザクションが途中でデータを変更しても同じ値が読み込まれる ISORATION_SERIALIZABLE トランザクションを完全に独立させる ISOLATION_DEFAULT データベースが提供するデフォルトの独立性レベルを利用する。
  • データが矛盾した状態と独立性レベル
    • 矛盾した状態
    • 独立性レベルごとの対応
    ○ :許す  × :許さない Dirty Read 他のトランザクションが変更したがまだコミットしていないデータを読み出してしまう Unrepeatable Read トランザクションの中で同じデータを複数回読み出す際、他のトランザクションが当該データを変更すると、以前に読み出したときと違ったデータを読み出してしまう。 Phantom Read トランザクションの中で同じデータを複数回読み出す際、他のトランザクションが新しくレコードを追加すると、以前に読み出したときに無かったレコードを読み出してしまう。 独立性レベル Dirty Read Unrepeatable Read Phantom Read ISOLATION_READ_UNCOMMITTED ○ ○ ○ ISOLATION_READ_COMMITTED × ○ ○ ISORATION_REPEATABLE_READ × × ○ ISORATION_SERIALIZABLE × × ×
  • その他のトランザクション定義情報
    • タイムアウト秒
      • トランザクションがキャンセルされるタイムアウトの秒を設定する
    • 読取専用有無
      • トランザクション内の処理が読み取り専用かどうかを設定する (DB や ORM フレームワーク側で最適化が行われる )
    • ロールバック対象例外
      • どの例外が投げられた時にロールバックするかを設定することができる
      • デフォルトでは、実行時例外 (RuntimeException およびそのサブクラスの例外 ) が投げられた場合にロールバックが行われる
        • デフォルトでは検査例外が投げられてもロールバックされない
    • コミット対象例外
      • どの例外が投げられた時にコミットするかを設定することができる
      • デフォルトでは、検査例外が投げられた際はコミットが行われる
  • トランザクションマネージャの実装 ≪ Interface≫ PlatformTransactionManager DataSourceTransactionManager HibernateTransactionManager JtaTransactionManager JpaTransactionManager JdoTransactionManager データアクセス技術に合わせて 適切なトランザクションマネージャを 選択する
  • トランザクションマネージャの登録
    • Bean 定義ファイルに記述
    ・・・ <bean id=&quot;transactionManager&quot; class=&quot;org.springframework.jdbc.datasource.DataSourceTransactionManager&quot;> <property name=&quot;dataSource&quot; ref=&quot;dataSource&quot; /> </bean> ・・・ DataSourceTransactionManager の例
  • 2種類の使い方
    • 宣言的トランザクション
      • トランザクション処理の対象とするメソッドを Bean 定義ファイルもしくはアノテーションで指定
        • 合わせてトランザクション定義情報も設定
      • トランザクションの開始、コミット、ロールバックが定義情報に従って自動的に行われる
    • 明示的トランザクション
      • トランザクションマネージャの API( 開始、コミット、ロールバックなど ) をプログラムから直接呼び出す
      • トランザクション定義情報もプログラムで設定する
  • 宣言的トランザクションの設定 (Bean 定義ファイル )
    • トランザクション処理を行うクラスの指定
    • メソッドごとにトランザクション定義情報を設定
    <aop:config> <aop:advisor advice-ref=&quot;transactionAdvice&quot; pointcut=&quot;execution(* *..*Service.*(..))&quot; /> </aop:config> <tx:advice id=&quot;transactionAdvice&quot; transaction-manager=&quot;transactionManager&quot;> <tx:attributes> <tx:method name=&quot;get*&quot; read-only=&quot;true&quot; /> <tx:method name=&quot;update*&quot; propagation=&quot;REQUIRED&quot; isolation=&quot;READ_COMMITTED&quot; timeout=&quot;10&quot; read-only=&quot;false&quot; rollback-for=“SystemException&quot; /> </tx:attributes> </tx:advice>
  • 宣言的トランザクションの設定 ( アノテーション )
    • クラスおよびメソッドに対して設定する
      • クラスに設定した場合は、クラスが持つすべてのメソッド (public メソッドのみ ) に適用される
    @Transactional( propagation=Propagation.REQUIRED, isolation=Isolation.READ_COMMITTED, timeout=10, readOnly=false, rollbackForClassName=&quot;BusinessException&quot; ) public void updatePet(Pet pet) throws BusinessException { ・・・省略 }
  • 明示的トランザクションの使い方
    • トランザクションマネージャの Bean を DI してプログラムから API を呼び出す
    @Autowired private PlatformTransactionManager txManager; public void transfer(Account from, Account to, int furikomigaku) { DefaultTransactionDefinition def = new DefaultTransactionDefinition(); def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED); def.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED); def.setTimeout(10); def.setReadOnly(false); TransactionStatus status = txManager.getTransaction(def); try { // 業務ロジック・・・ } catch (RuntimeException e) { txManager.rollback(status); throw e; } txManager.commit(status); }
  • 明示的トランザクションの利用シーンの例
    • 同一オブジェクトの処理の一部でトランザクション処理を行いたい場合
      • Proxy を介す宣言的トランザクションでは対応できないため
    :クライアント : Proxy :サービス updatePet updatePet トランザクション開始 トランザクション終了 「○」 Proxy を介すためトランザクション処理が行われる 「 × 」 Proxy を介さないためトランザクション処理が行われない updateInternal
  • ご清聴ありがとうございました
  • ライセンスについて
    • JSUG マスコットアイコン(本スライド左下)が残されている場合に限り、本作品(またそれを元にした派生作品)の複製・頒布・表示・上演を認めます。
    • 非商用目的に限り、本作品(またそれを元にした派生作品)の複製・頒布・表示・上演を認めます。
    • 本作品のライセンスを遵守する限り、派生作品を頒布することを許可します。