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

62
Spring 概概 概概概概概概概概概概概概概概概概概概 for Beginner 2012/2/27 1

Upload: kouhei-toki

Post on 10-Nov-2014

13.354 views

Category:

Technology


0 download

DESCRIPTION

 

TRANSCRIPT

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

for Beginner

2012/2/27

1

前回までのお話• DIによってプログラムが部品化された• AOPによって処理を横断的に追加できるようになった• Spring MVCによってプレゼン層の処理が簡単になった

2

プレゼンテーション

ビジネスロジック

データベースアクセス

RDB

ブラウザ

表示の仕組み 永続化の仕組み業務の仕組み

DI x AOP

Spring MVC

今日お話しする部分• データアクセス周りのコードをシンプルに• トランザクション処理をスマートに

3

プレゼンテーション

ビジネスロジック

データベースアクセス

RDB

ブラウザ

表示の仕組み 永続化の仕組み業務の仕組み

DI x AOP

Spring MVC ココの部分

データアクセス層の一般的な話とSpring

4

デーアクセス層の役割• 業務ロジックからデータアクセスの処理を隠ぺいする– 永続化の仕組みが変わっても業務ロジックへの影響がない

– アプリケーションの肝である業務ロジックのメンテナンス性が上がる

5

プレゼンテーション

ビジネスロジック

データベースアクセス

RDB

ブラウザ

表示の仕組み 永続化の仕組み業務の仕組み

データアクセスの処理が混在した業務ロジック(1/3)• 業務ロジックとデータアクセスの処理が混在するとプログラムが複雑になる• サンプルコード:ある口座から他の口座にお金を振り込む transferメソッド

6

1234567891011121314151617181920212223

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( "select zandaka from account where account_num=?"); ps.setString(1, from.getAccountNumnber()); rs = ps.executeQuery(); if (rs.next() == true) { zandaka = rs.getInt("zandaka"); } else { throw new BussinessException(" データがありません "); } int newZandaka = zandaka - furikomigaku; if (newZandaka < 0) { throw new BussinessException(" 残高が足りません "); } from.setZandaka(newZandaka);

業務ロジック

データアクセスの処理が混在した業務ロジック(2/3)

2425262728293031323334353637383940414243444546

// 振込先の口座の残高を計算 ps.clearParameters(); ps.setString(1, to.getAccountNumnber()); rs = ps.executeQuery(); if (rs.next() == true) { zandaka = rs.getInt("zandaka"); } else { throw new BussinessException(" データがありません "); } newZandaka = zandaka + furikomigaku; to.setZandaka(newZandaka); //  振込元の残高を更新 ps = con.prepareStatement( "update account set zandaka=? where account_num=?"); 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();

業務ロジック

7

4748495051525354555657585960616263646566

} catch (SQLException sqle) { try { con.rollback(); } catch (Exception e) { System.out.println(" システムエラー "+e); } int errorCode = sqle.getErrorCode(); if (errorCode == ERR_DEADLOCK) { throw new BussinessException(" デッドロック発生 ", sqle); } else { throw new SystemException(" システムエラー ", sqle); } } finally { try { con.close(); } catch (Exception e) { System.out.println(" システムエラー "+e); } } }

データアクセスの処理が混在した業務ロジック(3/3)

8

データアクセスの処理を分離させた業務ロジック

• データアクセス専用の部品を作成して業務ロジックからデータアクセスの処理を分離する

1234567891011121314151617

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(" 残高が足りません "); } 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(Data Access Object)と呼ばれる

9

DAOパターンとは?• DB接続の確立や SQLの発行といったデータアクセスの処理を DAO

と呼ばれるオブジェクトに隠蔽するパターン• DBのテーブルごとに作られることが多い

– テーブルの定義情報を元に自動生成する開発プロジェクトもある

• 部品化のため通常はインターフェースを用意する

10

挿入メソッド取得メソッド更新メソッド削除メソッド

XXXDao

データベースアクセス

ビジネスロジック 使う

AccountDaoBankDaoUserDaoなど

CRUD が一通り揃っている

インターフェースを用意

データアクセス技術のいろいろ• Javaのデータアクセス技術は複数存在する• 開発プロジェクトごとに適切なものを選択する

11

データベース

JDBC

Hibernate

iBATIS(mybatis)

JPA

JDO

自社フレームワーク

挿入メソッド取得メソッド更新メソッド削除メソッド

XXXDao

・・

S2Dao

データアクセスの Springの機能• データアクセス技術をより使い易くする機能を提供

12

データベース

Spring JDBC

挿入メソッド取得メソッド更新メソッド削除メソッド

XXXDao

JDBC

Hibernate 連携

Hibernate

JPA 連携

JPA

iBATIS 連携

iBATIS(2.x)

JDO 連携

JDO

Springのデータアクセスの機能を使う利点•コードがシンプルになる•汎用的で体系的なデータアクセス例外に変換してくれる

• Springのトランザクション機能が利用可能になる

13

Spring JDBC

14

JDBCの問題 (1/3)• JDBCを直接使ってデータアクセスを行う場合・・・

+ getZandaka(accountNumber:String):int+ updateZandaka(account:Account):void

AccountDao

JDBC データベース

15

JDBCの問題 (2/3)•・・・いろいろと問題がある1234567891011121314151617181920212223242526

public int getZandaka(String accountNumber) { Connection con = null; PreparedStatement ps = null; ResultSet rs = null; try { con = dataSource.getConnection(); int zandaka = -1; ps = con.prepareStatement("select zandaka from account where account_num=?"); ps.setString(1, accountNumber); rs = ps.executeQuery(); if (rs.next() == true) { zandaka = rs.getInt("zandaka"); } else { throw new BussinessException(" データがありません "); } return zandaka; } catch (SQLException sqle) { throw new SystemException(" システムエラー ", sqle); } finally { try { con.close(); } catch (Exception e) { System.out.println(" システムエラー "+e); } } }

・コード量が多い  Connection 、 PreparedStatement ・・ リソースの取得・解放が面倒 解放漏れの危険も

getZandaka メソッド

16

JDBCの問題 (3/3)1234567891011121314151617181920212223242526272829303132

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("update account set zandaka=? where account_num=?"); 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(" システムエラー " + e); } int errorCode = sqle.getErrorCode(); if (errorCode == ERR_DEADLOCK) { throw new BussinessException(" デッドロック発生 ", sqle); } else { throw new SystemException(" システムエラー ", sqle); } } finally { try { con.close(); } catch (Exception e) { System.out.println(" システムエラー " + e); } } }

・例外 SQLException の解釈が必要  原因を特定するためのエラーコードが   DB 製品毎に異なる

updateZandaka メソッド

17

Spring JDBCによる解決• Spring JDBCとは?

– JDBCをラップした APIを提供し JDBCを直接使用した場合に発生する冗長な処理の記述を隠蔽してくれる機能

• 利点– コードがシンプルになる– SQLExceptionの解釈が不要

Spring JDBC挿入メソッド取得メソッド更新メソッド削除メソッド

XXXDao

JDBCデータベース

18

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(奨励しないという意味 ) になった

19

Templateクラスの使い方•以下について説明します

– Templateクラスの Beanの準備–クエリ

•エンティティクラスへ変換しない場合•エンティティクラスへ変換する場合

–インサート・デリート・アップデート–バッチアップデート・プロシージャコール

20

Templateクラスの Beanの準備・・・ <bean class="org.springframework.jdbc.core.JdbcTemplate"> <property name="dataSource" ref="dataSource" /> </bean> <bean class="org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate"> <property name="dataSource" ref="dataSource" /> </bean> <bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource"> <property name="driverClass" value="oracle.jdbc.driver.OracleDriver"/> <property name="jdbcUrl" value="jdbc:oracle:thin:@localhost:1521:sample"/> <property name="user" value="spring3"/> <property name="password" value="spring3"/> </bean>・・・ ・・・

@Repositorypublic class AccountDaoSpringJdbc implements AccountDao { @Autowired private JdbcTemplate jdbcTemplate; @Autowired private NamedParameterJdbcTemplate npJdbcTemplate;・・・

21

クエリ ~エンティティクラスへ変換しない場合 (1/2)• 取得結果が数値型

– queryForInt– queryForLongint count = jdbcTemplate.queryForInt("select count(*) from pet");

int count = jdbcTemplate.queryForInt("select count(*) from pet where owner_name=?“ , ownerName);

・・・ public int getZandaka(String accountNumber) { int zandaka = jdbcTemplate.queryForInt( "select zandaka from account where account_num=?", accountNumber); return zandaka; }・・・

【 AccountDao の getZandaka メソッドの場合】

22

クエリ ~エンティティクラスへ変換しない場合 (2/2)• 取得結果が文字列型や日付型

– queryForObject

• 取得結果 (1レコード ) を Mapで取得– queryForMap

• 取得結果 ( 複数レコード ) を Mapのリストで取得– queryForList

String petName = jdbcTemplate.queryForObject("select pet_name from pet where pet_id=?", String.class, id);

Date birthDate = jdbcTemplate.queryForObject("select birth_date from pet where pet_id=?", Date.class, id);

Map<String, Object> pet = jdbcTemplate.queryForMap("select * from pet where pet_id=?", id);

String petName = (String)pet.get("pet_name");

List<Map<String, Object>> petList = jdbcTemplate.queryForList("select * from pet where owner_name=?", ownerName);

23

クエリ ~エンティティクラスへ変換する場合 (1/2)• 1 レコードの場合

– queryForObjectPet pet = jdbcTemplate.queryForObject(

"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("pet_id"));p.setPetName(rs.getString("pet_name"));p.setOwnerName(rs.getString("owner_name"));return p;

}}, id);

24

※ 匿名クラス

※匿名クラス:「その場限り」のサブクラスもしくは実装クラス。名前が無い

≪補足≫匿名クラスを使わない書き方

class MyRowMapper implements RowMapper<Pet> { public Pet mapRow(ResultSet rs, int rowNum) throws SQLException { Pet p = new Pet(); p.setPetId(rs.getInt("pet_id")); p.setPetName(rs.getString("pet_name")); p.setOwnerName(rs.getString("owner_name")); return p; } } Pet pet = jdbcTemplate.queryForObject( "select * from pet where pet_id=?" ,new MyRowMapper() ,id);

25

RowMapperとシーケンス図

26

クエリ ~エンティティクラスへ変換する場合 (2/2)•複数レコードの場合

– query

List<Pet> petList = jdbcTemplate.query("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("pet_id"));p.setPetName(rs.getString("pet_name"));p.setOwnerName(rs.getString("owner_name"));return p;

}}, ownerName);

27

インサート・アップデート・デリート• update

– インサート

– アップデート

– デリート

jdbcTemplate.update("insert into pet (pet_id, pet_name, owner_name) values (?, ?, ?)", pet.getPetId(), pet.getPetName(), pet.getOwnerName());

jdbcTemplate.update("update pet set pet_name=?, owner_name=? where pet_id=?", pet.getPetName(), pet.getOwnerName(), pet.getPetId());

jdbcTemplate.update("delete from pet where pet_id=?“, pet.getPetId());

28

NamedParameterJdbcTemplateを使った場合• SQLのパラメータに任意の名前を設定し名前と値を明示的に紐づけることができる– パラメータが多い場合に適している

npJdbcTemplate.update("insert into pet (pet_id, pet_name, owner_name)" +

" values (:pet_id, :pet_name, :owner_name)",new MapSqlParameterSource().addValue("pet_id", pet.getPetId()).addValue("pet_name", pet.getPetName()).addValue("owner_name", pet.getOwnerName())

);

29

※メソッドチェーン:複数のメソッド呼び出しを1センテンスで記述するテクニック

※ メソッドチェーン

≪補足≫メソッドチェーンを使わないやり方

30

MapSqlParameterSource map = new MapSqlParameterSource(); map.addValue("pet_id", pet.getPetId()); map.addValue("pet_name", pet.getPetName()); map.addValue("owner_name", pet.getOwnerName()); npJdbcTemplate.update(

"insert into pet (pet_id, pet_name, owner_name)" +" values (:pet_id, :pet_name, :owner_name)"

,map );

バッチアップデート• batchUpdate

List<Object[]> args = new ArrayList<Object[]>();for (Pet pet : petList) {

args.add(new Object[]{pet.getOwnerName(), pet.getPetId()});}int[] num = jdbcTemplate.batchUpdate(

"update pet set owner_name=? where pet_id=?“, args);

31

プロシージャコール• SimpleJdbcCallが便利

–プロシージャ名: calc_pet_price– INパラメータ: pet_id– OUTパラメータ: price

SimpleJdbcCall call = new SimpleJdbcCall(jdbcTemplate.getDataSource());call.withProcedureName("calc_pet_price");MapSqlParameterSource args = new MapSqlParameterSource()

.addValue("pet_id", petId);Map<String, Object> out = call.execute(args);int price = (Integer)out.get("price");

32

汎用データアクセス例外で SQLExceptionの解釈を不要に• SQLExceptionの問題

– データアクセスのエラーの原因はざまざまだが JDBCの例外の型は基本1つ (SQLException)しかない

– 検査例外なので catch句または throwを記述しなければならない

– エラーコードで原因を特定できるが DB製品ごとに定義が異なる・・・ } catch (SQLException sqle) { int errorCode = sqle.getErrorCode(); if (errorCode == ERR_DATAINTEGRITY) { throw new DataIntegrityException(" 整合性違反エラー発生 ", sqle); } else if (errorCode == ERR_PERMISSION) { throw new DataPermissionException(“ 権限が不正 ", sqle); } else if (errorCode == ・・・・・・

33

Springの汎用データアクセス例外• データアクセス時のエラーの種類に合わせて体系立てら

れた例外クラス群• Templateクラスの APIの処理の内部で変換してくれ

る• データベース製品間の差異を吸収してくれる• 実行時例外なので必要に応じて catchすればよい

CannotAcquireLockException ロックの取得に失敗ConcurrencyFailureException 同時実行時のエラーDataIntegrityViolationException 整合性違反エラーDeadlockLoserDataAccessException デッドロックが発生EmptyResultDataAccessException 取得しようとしたデータが存在しないIncorrectResultSizeDataAccessException 取得したレコードの数が不正OptimisticLockingFailureException 楽観的ロックに失敗PermissionDeniedDataAccessException 権限エラー

【例外クラスの例 ( 全部で 20 個以上存在する ) 】

34

汎用データアクセス例外のハンドリングの方針

• DAOの中では catchしない• サービスやコントローラにおいては、対処可能な例外の

み catchして対処する• 対処できない例外は共通の仕組みを用意して一元的に処

理する ( エラーページを表示するなど )

プレゼンテーション

ビジネスロジック

データベースアクセス

RDB

ブラウザ

サービス DAOコントローラ

35

共通

対応可能な例外のみ catch対応可能な例外のみ catch

対処できない例外は一元的に処理

データソース•データソースとは?

–データベース接続オブジェクト(Connection)のファクトリ•接続情報 ( ユーザ・パスワードなど ) を保持

– JDBCの APIとしてインターフェースが提供されている (DataSource)

• DataSourceにはさまざまな実装がある– Springが提供するもの–サードパーティが提供するもの– APサーバーが提供するもの

36

データソースの使い方•接続情報などの設定はプログラム上でも可能だが通常は Bean定義ファイルで行う

•設定したデータソースは、 Bean定義ファイルやアノテーションで他の Beanに DIして使う ・・・

<bean id="dataSource" >        ・・・ </bean>・・・

・・・ <bean id=“foo" > <property name="dataSource" ref="dataSource" /> </bean>・・・

・・・ @Autowired private DataSource dataSource;・・・

37

Springが提供する DataSource• SingleConnectionDataSource

– 1つのデータベース接続オブジェクトを、クローズせずに使いまわす

• DriverManagerDataSource– 取得の要求のたびに、データベース接続オブジェクトを作成して返す

注意) テスト用途で用意されたものなので本番で使用するものではない

<bean id="dataSource"class="org.springframework.jdbc.datasource.DriverManagerDataSource"><property name="driverClassName" value="org.postgresql.Driver" /><property name="url" value="jdbc:postgresql://localhost/samaple" /><property name="username" value="spring3" /><property name="password" value="spring3" />

</bean>

【 DriverManagerDataSource の設定の例】

38

サードパーティが提供する DataSource

•以下の2つのプロダクトが代表的–どちらもオープンソースで無償

• DBCP(Apache)• c3p0(Machinery For Change, Inc)

<bean id="dataSource"class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close"><property name="driverClass" value="org.postgresql.Driver" /><property name="jdbcUrl" value="jdbc:postgresql://localhost/sample" /><property name="user" value="spring3" /><property name="password" value="spring3" /><property name="maxPoolSize" value="20"/>

</bean>

【 c3p0 の設定の例】

39

APサーバが提供する DataSource• JNDI経由で取得する

–昔ながらのやり方

– jeeスキーマを使用したやり方

– @Resourceを使用したやり方

<bean id="dataSource" class="org.springframework.jndi.JndiObjectFactoryBean"><property name="jndiName" value="jdbc/MyDataSource"/>

</bean>

<jee:jndi-lookup id="dataSource" jndi-name="jdbc/MyDataSource" />

<bean class="org.springframework.context.annotation.CommonAnnotationBeanPostProcessor"> <property name="alwaysUseJndiLookup" value="true"/> </bean>

@Resource(name=“jdbc/MyDataSource”) private DataSource dataSource;

Common Anotations(JSR250) 用の設定が必要

40

トランザクション機能

41

トランザクションとは?• 関連する複数の処理を一つの処理単位としてまとめたもの– 長いもの

• Webサイトで商品を注文してから商品が家に届くまで

– 短いもの•注文のリクエストを受けて、発注テーブルと顧客テーブル,在庫テーブルを更新する

• 守るべき「 ACID属性」ACID 意味 解説

Atomic トランザクションの原子性

トランザクション内の全ての処理は、全ておこなわれたか、もしくは何もおこなわれなかったかのどちらかだけであること。

Consistent データの一貫性

データに一貫性があること。一貫性を守ってない例:親テーブルがないのに子テーブルがある。

Isolated トランザクションの独立性

平行して走るトランザクションが互いに独立していること。

Durable データの永続性

データが永続化されていること。永続化されているデータが読み出せること。 42

Webアプリのトランザクション処理• 複数のリクエストに跨るもの

– 例:カタログ画面からショッピングカートを使って商品を注文する

– アプリケーショントランザクション、ロングトランザクションなどと呼ばれる

• 1つのリクエスト内で行うもの– 例:注文確定のリクエストを受けて、発注テーブルと顧客テーブル,在庫テーブルを更新する

– データソースが1つの場合• ローカルトランザクション

– データソースが複数の場合• グローバルトランザクション

43

トランザクションの境界• 業務ロジックを持ったビジネス層でトランザクション処理を行うた

め、通常はプレゼンテーション層とビジネス層の間に引く

• トランザクションの境界とメソッド– サービスのメソッドが呼び出されたときにトランザクションを開始– 処理の途中でエラーが発生したらロールバックしてメソッドを抜ける– メソッドの処理が完了したらコミット

プレゼンテーション

ビジネスロジック

データベースアクセス

RDB

ブラウザ

サービス DAOコントローラ

トランザクションの開始トランザクションの終了

44

実装する場所の問題• トランザクション処理を業務ロジックで記述しようとすると・・・

• commitや rollbackメソッドは JDBCの Connectionクラスが持っているため ...– ビジネスロジックが JDBCの APIに依存してしまう– Connectionのオブジェクトを DAOのメソッド間で共有させる必要がある

• DAOのメソッドの引数に入れるなど

:ビジネスロジック : AccountDao

transferupdateZandaka( 振込元 )

updateZandaka( 振込先 )

45

問題•次のような状況のときに、何という技術で対応しますか?–ビジネス層のメソッドが呼ばれたときと終了したときにトランザクション処理 ( 開始・コミット・ロールバック ) を実施したい

–ビジネス層のメソッドの中は業務ロジックだけに特化したい ( トランザクション処理を記述したくない )

46

AOPを使用したトランザクション処理

•トランザクション処理を Proxyに任せる

クライアント トランザクション処理 サービス DAO RDB

トランザクションの開始

トランザクションのコミット

サービスの Proxy

47

トランザクションマネージャ• Adviceの処理を自作する必要はない• Springが提供するトランザクションマネージャを利用すればよい

•トランザクションマネージャの特徴–コミット・ロールバックだけでなく、トランザクションの定義情報( ロールバックする時の条件や独立性レベルなど ) を細かく設定できる

–データアクセス技術(JDBC、 Hibernate、 iBATISなど ) を隠ぺい•データアクセス技術ごとにトランザクションマネージャの実装が用意されている

48

トランザクション定義情報•伝搬属性•独立性レベル•タイムアウト秒•読取専用有無•ロールバック対象例外•コミット対象例外

49

伝搬属性•トランザクションの伝搬の仕方を設定

サービス2

サービス1 DAO 1

伝搬属性 サービス1に対して設定を行ったとき① の場合 ② の場合

PROPAGATION_REQUIRED トランザクションを開始 サービス2のトランザクションを利用PROPAGATION_REQUIRES_NEW トランザクションを開始 新しいトランザクションを開始PROPAGATION_SUPPORTS トランザクションを行わ

ないサービス2のトランザクションを利用

PROPAGATION_MANDATORY 例外を投げる サービス2のトランザクションを利用PROPAGATION_NESTED トランザクションを開始 部分的なトランザクションを開始 (save ポ

イント )PROPAGATION_NEVER トランザクションを行わ

ない例外を投げる

PROPAGATION_NOT_SUPPORTED トランザクションを行わない

トランザクションを行わない 50

DAO 2コントローラ2

コントローラ1①

独立性レベル•トランザクション処理が並行して実行される際に独立性をどこまで保つかを設定

独立性レベル 解説ISOLATION_READ_COMMITTED 他のトランザクションが変更したがまだコミットしていないデー

タは読み出せない。ISOLATION_READ_UNCOMMITTED 他のトランザクションが変更したがまだコミットしていないデー

タを読み出せる。ISORATION_REPEATABLE_READ トランザクション内で複数回データを読み込んだ場合、他のトラ

ンザクションが途中でデータを変更しても同じ値が読み込まれるISORATION_SERIALIZABLE トランザクションを完全に独立させるISOLATION_DEFAULT データベースが提供するデフォルトの独立性レベルを利用する。

51

トランザクション1

トランザクション2

データが矛盾した状態と独立性レベル

•矛盾した状態

•独立性レベルごとの対応

Dirty Read他のトランザクションが変更したがまだコミットしていないデータを読み出してしまう

Unrepeatable Readトランザクションの中で同じデータを複数回読み出す際、他のトランザクションが当該データを変更すると、以前に読み出したときと違ったデータを読み出してしまう。

Phantom Readトランザクションの中で同じデータを複数回読み出す際、他のトランザクションが新しくレコードを追加すると、以前に読み出したときに無かったレコードを読み出してしまう。

独立性レベル Dirty ReadUnrepeatable

ReadPhantom

ReadISOLATION_READ_UNCOMMITTED ○ ○ ○ISOLATION_READ_COMMITTED × ○ ○ISORATION_REPEATABLE_READ × × ○ISORATION_SERIALIZABLE × × ×

○ :許す  × :許さない

52

その他のトランザクション定義情報• タイムアウト秒

– トランザクションがキャンセルされるタイムアウトの秒を設定する• 読取専用有無

– トランザクション内の処理が読み取り専用かどうかを設定する (DBやORMフレームワーク側で最適化が行われる )

• ロールバック対象例外– どの例外が投げられた時にロールバックするかを設定することができ

る– デフォルトでは、実行時例外 (RuntimeExceptionおよびそのサブク

ラスの例外 ) が投げられた場合にロールバックが行われる• デフォルトでは検査例外が投げられてもロールバックされない

• コミット対象例外– どの例外が投げられた時にコミットするかを設定することができる– デフォルトでは、検査例外が投げられた際はコミットが行われる

53

トランザクションマネージャの実装

≪Interface≫PlatformTransactionManager

DataSourceTransactionManager

HibernateTransactionManager

JtaTransactionManager

JpaTransactionManager

JdoTransactionManager

データアクセス技術に合わせて適切なトランザクションマネージャを選択する

54

トランザクションマネージャの登録

• Bean定義ファイルに記述

・・・<bean id="transactionManager"

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

</bean>・・・

DataSourceTransactionManager の例

55

2種類の使い方•宣言的トランザクション

–トランザクション処理の対象とするメソッドをBean定義ファイルもしくはアノテーションで指定•合わせてトランザクション定義情報も設定

–トランザクションの開始、コミット、ロールバックが定義情報に従って自動的に行われる

•明示的トランザクション–トランザクションマネージャの API(開始、コミット、ロールバックなど ) をプログラムから直接呼び出す

–トランザクション定義情報もプログラムで設定する

56

宣言的トランザクションの設定(Bean定義ファイル )• トランザクション処理を行うクラスの指定

• メソッドごとにトランザクション定義情報を設定

<aop:config><aop:advisor advice-ref="transactionAdvice"

pointcut="execution(* *..*Service.*(..))" /></aop:config>

<tx:advice id="transactionAdvice" transaction-manager="transactionManager"><tx:attributes>

<tx:method name="get*" read-only="true" /><tx:method name="update*"

propagation="REQUIRED" isolation="READ_COMMITTED"timeout="10" read-only="false"rollback-for=“SystemException" />

</tx:attributes></tx:advice>

57

宣言的トランザクションの設定( アノテーション )•クラスおよびメソッドに対して設定する

–クラスに設定した場合は、クラスが持つすべてのメソッド (publicメソッドのみ ) に適用される@Transactional(

propagation=Propagation.REQUIRED, isolation=Isolation.READ_COMMITTED, timeout=10, readOnly=false, rollbackForClassName="BusinessException" )

public void updatePet(Pet pet) throws BusinessException {・・・省略}

58

明示的トランザクションの使い方•トランザクションマネージャの 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); }

59

明示的トランザクションの利用シーンの例

• 同一オブジェクトの処理の一部でトランザクション処理を行いたい場合– Proxyを介す宣言的トランザクションでは対応できないため

:クライアント : Proxy

:サービス

updatePet

updatePetトランザクション開始

トランザクション終了

「○」Proxy を介すためトランザクション処理が行われる

「 × 」Proxy を介さないためトランザク

ション処理が行われない

updateInternal

60

61

ご清聴ありがとうございました

62

ライセンスについて• JSUGマスコットアイコン(本スライド左下)が残されている場合に限り、本作品(またそれを

元にした派生作品)の複製・頒布・表示・上演を認めます。

• 非商用目的に限り、本作品(またそれを元にした派生作品)の複製・頒布・表示・上演を認めます。

• 本作品のライセンスを遵守する限り、派生作品を頒布することを許可します。