6.1. データベースアクセス(共通編)¶
6.1.1. Overview¶
本節では、RDBMSで管理されているデータにアクセスする方法について、説明する。
MyBatis3に依存する部分については、データベースアクセス(MyBatis3編)を参照されたい。
6.1.1.1. JDBC DataSourceについて¶
6.1.1.1.1. アプリケーションサーバ提供のJDBCデータソース¶
¶ 項番 アプリケーションサーバ 参照ページ
Apache Tomcat 9.0 Apache Tomcat 9.0 User Guide(JNDI Datasource HOW-TO)(Apache Commons DBCP 2)を参照されたい。
Apache Tomcat 8.5 Apache Tomcat 8.5 User Guide(JNDI Datasource HOW-TO)(Apache Commons DBCP 2)を参照されたい。
Apache Tomcat 7 Apache Tomcat 7 User Guide(JNDI Datasource HOW-TO)(Apache Commons DBCP)を参照されたい。
Oracle WebLogic Server 12c Oracle WebLogic Server Product Documentationを参照されたい。
IBM WebSphere Application Server Version 9.0 WebSphere Application Server Online information centerを参照されたい。
JBoss Enterprise Application Platform 7.2 JBoss Enterprise Application Platform 7.2 Product Documentationを参照されたい。
JBoss Enterprise Application Platform 6.4 JBoss Enterprise Application Platform 6.4 Product Documentationを参照されたい。
6.1.1.1.2. OSS/Third-Partyライブラリ提供のJDBCデータソース¶
¶ 項番 ライブラリ名 説明
Apache Commons DBCP Apache Commons DBCPを参照されたい。
6.1.1.1.3. Spring Framework提供のJDBCデータソース¶
6.1.1.2. トランザクションの管理方法について¶
6.1.1.3. トランザクション境界/属性の宣言について¶
@Transactional
アノテーションを指定することで実現する。6.1.1.4. データの排他制御について¶
6.1.1.5. 例外ハンドリングについて¶
java.sql.SQLException
)や、O/R Mapper固有の例外を、Spring Frameworkから提供しているデータアクセス例外(org.springframework.dao.DataAccessException
のサブクラス)に変換する機能がある。DataAccessException
をcatchするのではなく、エラー内容を通知するサブクラスの例外をcatchすること。
¶ 項番 クラス名 説明
org.springframework.dao.DuplicateKeyException 一意制約違反が発生した場合に発生する例外。
org.springframework.dao.OptimisticLockingFailureException 楽観ロックに成功しなかった場合に発生する例外。他の処理によって同一データが更新されていた場合に発生する。本例外は、O/R MapperとしてJPAを使用する場合に発生する例外である。MyBatisには楽観ロックを行う機能がないため、O/R Mapper本体から本例外が発生することはない。
org.springframework.dao.PessimisticLockingFailureException 悲観ロックに成功しなかった場合に発生する例外。他の処理で同一データがロックされており、ロック解放待ちのタイムアウト時間を超えてもロックが解放されない場合に発生する。Note
O/R MapperにMyBatisを使用して楽観ロックを実現する場合は、ServiceやRepositoryの処理として楽観ロック処理を実装する必要がある。
本ガイドラインでは、楽観ロックに失敗したことを、Controllerに通知する方法として、
OptimisticLockingFailureException
およびその子クラスの例外を発生させることを推奨する。理由は、アプリケーション層の実装(Controllerの実装)を、使用するO/R Mapperに依存させないためである。
下記は、一意制約違反を、ビジネス例外として扱う実装例である。
try { accountRepository.saveAndFlash(account); } catch(DuplicateKeyException e) { // (1) throw new BusinessException(ResultMessages.error().add("e.xx.xx.0002"), e); // (2) }
項番 説明 (1) 一意制約違反が発生した場合に発生する例外(DuplicateKeyException)をcatchする。 (2) データが重複している旨を伝えるビジネス例外を発生させている。例外をcatchした場合は、必ず原因例外(“e
” ) をビジネス例外に指定すること。
6.1.1.6. 複数データソースについて¶
¶ 項番 ケース 例 特徴
データ(テーブル)の分類毎にデータベースやスキーマがわかれている場合。 顧客情報を保持するテーブル群と請求情報を保持するテーブル群が別々のデータベースやスキーマに格納されている場合など。 処理で扱うデータは決まっているので、静的に使用するデータソースを決定することができる。
利用者(ログインユーザ)によって使用するデータベースやスキーマが分かれている場合。 利用者の分類毎にデータベースやスキーマがわかれている場合など(マルチテナント等)。 利用者によって使用するデータソースが異なるため、動的に使用するデータソースを決定する必要がある。
6.1.2. How to use¶
6.1.2.1. データソースの設定¶
6.1.2.1.1. アプリケーションサーバで定義したDataSourceを使用する場合の設定¶
xxx-context.xml
(Tomcatの設定ファイル)<!-- (1) --> <Resource type="javax.sql.DataSource" name="jdbc/SampleDataSource" driverClassName="org.postgresql.Driver" url="jdbc:postgresql://localhost:5432/terasoluna" username="postgres" password="postgres" defaultAutoCommit="false" /> <!-- (2) -->
xxx-env.xml
<jee:jndi-lookup id="dataSource" jndi-name="jdbc/SampleDataSource" /> <!-- (3) -->
項番 属性名 説明 (1)- データソースを定義する。 type リソースの種類を指定する。 javax.sql.DataSource
を指定する。name リソース名を指定する。ここで指定した名前がJNDI名となる。 driverClassName JDBCドライバクラスを指定する。例では、PostgreSQLから提供されているJDBCドライバクラスを指定する。 url 接続URLを指定する。 【環境に合わせて変更が必要】 username 接続ユーザ名を指定する。【環境に合わせて変更が必要】 password 接続ユーザのパスワードを指定する。【環境に合わせて変更が必要】 defaultAutoCommit 自動コミットフラグのデフォルト値を指定する。falseを指定する。トランザクション管理下であれば強制的にfalseになる。 (2)- Tomcat8.5の場合、factory属性を省略するとtomcat-jdbc-poolが使用される。設定項目の詳細については、Attributes of The Tomcat JDBC Connection Poolを参照されたい。 (3)- データソースのJNDI名を指定する。Tomcatの場合は、データソース定義時のリソース名「(1)-name」に指定した値を指定する。
6.1.2.1.2. Bean定義したDataSourceを使用する場合の設定¶
xxx-env.xml
<bean id="dataSource" class="org.apache.commons.dbcp2.BasicDataSource" destroy-method="close"> <!-- (1) (8) --> <property name="driverClassName" value="org.postgresql.Driver" /> <!-- (2) --> <property name="url" value="jdbc:postgresql://localhost:5432/terasoluna" /> <!-- (3) --> <property name="username" value="postgres" /> <!-- (4) --> <property name="password" value="postgres" /> <!-- (5) --> <property name="defaultAutoCommit" value="false"/> <!-- (6) --> <!-- (7) --> </bean>
項番 説明 (1)データソースの実装クラスを指定する。例では、Apache Commons DBCPから提供されているデータソースクラス( org.apache.commons.dbcp2.BasicDataSource
)を指定する。 (2)JDBCドライバクラスを指定する。例では、PostgreSQLから提供されているJDBCドライバクラスを指定する。 (3)接続URLを指定する。 【環境に合わせて変更が必要】 (4)接続ユーザ名を指定する。【環境に合わせて変更が必要】 (5)接続ユーザのパスワードを指定する。【環境に合わせて変更が必要】 (6)自動コミットフラグのデフォルト値を指定する。falseを指定する。トランザクション管理下であれば、強制的にfalseになる。 (7) BasicDataSourceには上記以外に、JDBC共通の設定値の指定、JDBCドライバー固有のプロパティ値の指定、コネクションプーリング機能の設定値の指定を行うことができる。設定項目の詳細については、DBCP Configurationを参照されたい。 (8) 設定例では値を直接指定しているが、環境によって設定値がかわる項目については、Placeholder(${…})を使用して、実際の設定値はプロパティファイルに指定すること。Placeholderについては、Spring Reference DocumentのPropertyPlaceholderConfigurer
を参照されたい。
6.1.2.2. トランザクション管理を有効化するための設定¶
トランザクション管理を有効化するための基本的な設定は、ドメイン層の実装のトランザクション管理を使うための設定についてを参照されたい。
PlatformTransactionManagerについては、使用するO/R Mapperによって使うクラスがかわるので、詳細設定は、
を参照されたい。
6.1.2.3. JDBCのDebug用ログの設定¶
Warning
log4jdbc-remixが提供しているLog4jdbcProxyDataSourceを使用していると、ログレベルを”debug”以外に設定しても、オーバーヘッドが少なからず発生する。 そのため、本設定はデバッグ用として使用し、性能試験及び商用環境にリリースする場合はLog4jdbcProxyDataSourceを経由せずにデータベースへ接続することを推奨する。
6.1.2.3.1. log4jdbc提供のデータソースの設定¶
xxx-env.xml
<jee:jndi-lookup id="realDataSource" jndi-name="jdbc/SampleDataSource" /> <!-- (1) --> <bean id="dataSource" class="net.sf.log4jdbc.Log4jdbcProxyDataSource"> <!-- (2) --> <constructor-arg ref="realDataSource" /> <!-- (3) --> </bean>
項番 説明 (1)データソースの実体を定義する。例では、アプリケーションサーバからJNDI経由で取得したデータソースを使用している。 (2)log4jdbcより提供されている net.sf.log4jdbc.Log4jdbcProxyDataSource
を指定する。 (3)データソースの実体となるbeanを、コンストラクタに指定する。 Warning
性能試験及び商用環境にリリースする場合、データソースとしてLog4jdbcProxyDataSourceは使用しないこと。
具体的には、(2)と(3)の設定を外し、
realDataSource
のbean名をdataSource
に変更する。
6.1.2.3.2. log4jdbc用ロガーの設定¶
logback.xml
<!-- (1) --> <logger name="jdbc.sqltiming"> <level value="debug" /> </logger> <!-- (2) --> <logger name="jdbc.sqlonly"> <level value="warn" /> </logger> <!-- (3) --> <logger name="jdbc.audit"> <level value="warn" /> </logger> <!-- (4) --> <logger name="jdbc.connection"> <level value="warn" /> </logger> <!-- (5) --> <logger name="jdbc.resultset"> <level value="warn" /> </logger> <!-- (6) --> <logger name="jdbc.resultsettable"> <level value="debug" /> </logger>
項番 説明 (1) バインド変数に値が設定された状態のSQL文と、SQLの実行時間を出力するためのロガー。値がバインドされた形式のSQLが出力されるので、DBアクセスツールに貼りつけて実行する事ができる。 (2) バインド変数に値が設定された状態のSQL文を、出力するためのロガー。(1)との違いは、実行時間が出力されない。 (3) ResultSetインタフェースを除く、JDBCインタフェースのメソッド呼び出し(引数と、返り値)を出力するためのロガー。JDBC関連で問題が発生した時の解析に有効なログであるが、出力されるログの量が多い。 (4) Connectionの接続/切断イベントと使用中の接続数を出力するためのロガー。接続リークが発生時の解析に有効なログであるが、接続リークの問題がなければ、出力する必要はない。 (5) ResultSetインタフェースに対するメソッド呼び出し(引数と、返り値)を出力するためのロガー。取得結果が、想定と異なった時の解析に有効なログであるが、出力されるログの量が多い。 (6) ResultSetの中身を確認しやすい形式にフォーマットして出力するためのロガー。取得結果が、想定と異なった時の解析に有効なログであるが、出力されるログの量が多い。Warning
ロガーによっては大量にログが出力されるので、必要なロガーのみ定義、または出力対象にすること。
上記サンプルでは、開発中の非常に有効なログを出力するロガーについて、ログレベルを
debug
に設定している。 その他のロガーについては、必要に応じてdebug
に設定する必要がある。性能試験及び商用環境にリリースする場合、正常終了時にlog4jdbc用のロガーによってログが出力されないようにすること。
具体的には、ログレベルを
warn
に設定する。
6.1.2.3.3. log4jdbcのオプションの設定¶
クラスパス直下に、log4jdbc.properties
というプロパティファイルを配置することで、log4jdbcのデフォルトの動作をカスタマイズすることができる。
log4jdbc.properties
# (1) log4jdbc.dump.sql.maxlinelength=0 # (2)
項番 説明 (1)SQL分の折り返し文字数を指定する。0を指定すると、折り返しはされない。 (2)オプションの詳細については、log4jdbc project page -Options-を参照されたい。
6.1.3. How to extend¶
6.1.3.1. 動的にデータソースを切り替えるための設定¶
org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource
を継承したクラスを作成し、どのような条件でデータソースを切り替えるかを実装する必要がある。determineCurrentLookupKey
メソッドの戻り値となるキーとデータソースをマッピングさせることによって、これを実現する。キーの選択には通常、認証ユーザー情報、時間、ロケール等のコンテキスト情報を使用する。6.1.3.1.1. AbstractRoutingDataSourceの実装¶
AbstractRoutingDataSource
を拡張して作成したDataSource
を、通常のデータソースと同じように使用することでデータソースの動的な切り替えが実現できる。AbstractRoutingDataSource
を継承したクラスの実装例`
package com.examples.infra.datasource; import javax.inject.Inject; import org.joda.time.DateTime; import org.springframework.jdbc.datasource.lookup.AbstractRoutingDataSource; import org.terasoluna.gfw.common.date.jodatime.JodaTimeDateFactory; public class RoutingDataSource extends AbstractRoutingDataSource { // (1) @Inject JodaTimeDateFactory dateFactory; // (2) @Override protected Object determineCurrentLookupKey() { // (3) DateTime dateTime = dateFactory.newDateTime(); int hour = dateTime.getHourOfDay(); if (7 <= hour && hour <= 23) { // (4) return "OPEN"; // (5) } else { return "CLOSE"; } } }
項番 説明 (1)AbstractRoutingDataSource
を継承する。 (2)時刻を取得するため、 JodaTimeDateFactory
を使用する。詳細は、システム時刻を参照のこと。 (3)determineCurrentLookupKey
メソッドを実装する。このメソッドの返り値と後述するbean定義ファイル内のtargetDataSources
に定義したkey
をマッピングすることにより使用するデータソースが決定される。 (4)メソッド内で、コンテキスト情報(ここでは時間)を参照し、キーの切り替えを行う。ここは業務用件に合わせて実装する必要がある。このサンプルは、時刻が「7:00から23:59まで」と「0:00から6:59まで」で違うキーを返すように実装されている。 (5)後述するbean定義ファイル内の targetDataSources
とマッピングさせるkey
を返す。
6.1.3.1.2. データソースの定義¶
作成したAbstractRoutingDataSource
拡張クラスをbean定義ファイルに定義する。
xxx-env.xml
<bean id="dataSource" class="com.examples.infra.datasource.RoutingDataSource"> <!-- (1) --> <property name="targetDataSources"> <!-- (2) --> <map> <entry key="OPEN" value-ref="dataSourceOpen" /> <entry key="CLOSE" value-ref="dataSourceClose" /> </map> </property> <property name="defaultTargetDataSource" ref="dataSourceDefault" /> <!-- (3) --> </bean>
項番 説明 (1)先ほど作成した AbstractRoutingDataSource
を継承したクラスを定義する。 (2)使用するデータソースを定義する。 key
はdetermineCurrentLookupKey
メソッドで返却しうる値を定義する。value-ref
にはkey
ごとに使用するデータソースを指定する。データソースの設定をもとに切り替えるデータソースの個数分、定義を行う必要がある。 (3)determineCurrentLookupKey
メソッドで指定したkey
がtargetDataSources
に存在しない場合は、このデータソースが使用される。実装例の場合、デフォルトが使用されることはないが、今回は説明のため、defaultTargetDataSource
を定義している。
6.1.4. how to solve the problem¶
6.1.4.1. N+1問題の対策方法¶
N+1問題とは、データベースから取得するレコード数に比例して実行されるSQLの数が増えることにより、データベースへの負荷およびレスポンスタイムの劣化を引き起こす問題のことである。
以下に、具体的をあげる。
項番 説明 (1) 検索条件に一致するレコードを、メインとなるテーブルから検索する。上記例では、 MainTableテーブルのcol1カラムが、'Foo'
のレコードを取得しており、20件のレコードが取得されている。 (2) (1)で検索した各レコードに対して、関連レコードを関連テーブルから取得する。上記例では、SubTableテーブルのidカラムが、(1)で取得したレコードのidカラムと同じレコードを取得している。このSQLは、(1)で取得されたレコード件数分、実行される。上記例では、合計で21回のSQLが発行されることになる。仮に関連テーブルが3テーブルあると、合計で61回のSQLが発行されることになるため、対策が必要となる。
N+1問題の解決方法の代表例を、以下に示す。
6.1.4.1.1. JOIN(Join Fetch)を使用して解決する¶
項番 説明 (1) 検索条件に一致するレコードを検索する際に、関連テーブルをJOINすることで、メインとなるテーブルと関連テーブルから、レコードを一括で取得する。上記例では、 MainTableテーブルのcol1カラムが'Foo'
のレコードと、検索条件に一致したレコードのidが一致するSubTableのレコードを一括で取得している。カラム名が重複する場合は、別名を付与してどちらのテーブルのカラムなのか識別する必要がある。JOIN(Join Fetch)を使用すると、1回のSQLの発行で必要なデータを全て取得することができる。Warning
関連テーブルとの関連が、1:Nの場合は、JOIN(Join Fetch)による解決も可能だが、以下の点に注意すること。
- 1:Nの関連をもつレコードをJOINする場合、関連テーブルのレコード数に比例して、無駄なデータを取得することになる。 詳細については、一括取得時の注意事項を参照されたい。
6.1.4.1.2. 関連レコードを一括で取得する事で解決する¶
項番 説明 (1) 検索条件に一致するレコードを、メインとなるテーブルから検索する。上記例では、 MainTableテーブルのcol1カラムが、'Foo'
のレコードを取得しており、20件のレコードが取得されている。 (2) (1)で検索した各レコードに対して、関連レコードを関連テーブルから取得する。1レコード毎に取得するのではなく、(1)で取得した各レコードの外部キーに一致するレコードを、一括で取得する。上記例では、SubTableテーブルのidカラムが、(1)で取得したレコードのidカラムと同じレコードを、IN句を使用して一括取得している。 (3) (2)で取得したSubTableのレコードを、(1)で取得したレコードに振り分けマージする。上記例では、合計で2回のSQLの発行で、必要なデータを取得することができる。仮に、関連テーブルが、3テーブルあっても、合計で4回のSQLの発行で済むことになる。Note
この方法は、SQLの発行を最小限におさえつつ、必要なデータのみ取得することができるという特徴をもつ。 関連テーブルのレコードをプログラミングによって振り分ける必要があるが、関連テーブルの数が多い場合や、1:NのNのレコード数が多い場合は、この方法で解決する方がよいケースがある。
6.1.5. Appendix¶
6.1.5.1. LIKE検索時のエスケープについて¶
LIKE検索を行う場合は、検索条件として使用する値を、LIKE検索用にエスケープする必要がある。
共通ライブラリでは、LIKE検索用のエスケープ処理を行うためのコンポーネントとして、以下のクラスを提供している。
項番 | クラス | 説明 |
---|---|---|
org.terasoluna.gfw.common.query.
QueryEscapeUtils
|
SQL及びJPQLのエスケープ処理を行うメソッドを提供するユーティリティクラス。 本クラスでは、
を提供している。 |
|
org.terasoluna.gfw.common.query.
LikeConditionEscape
|
LIKE検索用のエスケープ処理を行うクラス。 |
Note
LikeConditionEscape
クラスは、「LIKE検索用のワイルドカード文字の扱いに関するバグ」を修正するために、
terasoluna-gfw-common 1.0.2.RELEASEから追加したクラスである。
LikeConditionEscape
クラスは、データベース及びデータベースのバージョンの違いによるワイルドカード文字の違いを吸収する役割を持つ。
6.1.5.1.1. 共通ライブラリのエスケープ仕様について¶
共通ライブラリから提供しているエスケープ処理の仕様は、以下の通りである。
- エスケープ文字は「 “
~
” 」。 - エスケープ対象文字は、デフォルトでは「 “
%
” , “_
”」の2文字。
Note
エスケープ対象文字は、terasoluna-gfw-common 1.0.1.RELEASEまでは「 “%
” , “_
” , “%
” , “_
”」の4文字であったが、
「LIKE検索用のワイルドカード文字の扱いに関するバグ」を修正するために、
terasoluna-gfw-common 1.0.2.RELEASEより「 “%
” , “_
”」の2文字に変更している。
なお、エスケープ対象文字として全角文字「”%
” , “_
”」を含めてエスケープする方法も提供している。
具体的なエスケープ例を以下に示す。
[デフォルト仕様のエスケープ例]
エスケープ対象文字としてデフォルト値を使用する場合のエスケープ例を以下に示す。
項番 対象文字列 エスケープ後文字列 エスケープ有無 解説
“ a
”“ a
”無 エスケープ対象文字が含まれていないため、エスケープされない。
a~
a~~
有 エスケープ文字が含まれているため、エスケープされる。
a%
a~%
有 エスケープ対象文字が含まれているため、エスケープされる。
a_
a~_
有 No.3と同様。
_a%
~_a~%
有 エスケープ対象文字が含まれているため、エスケープされる。エスケープ対象文字が複数存在する場合はすべてエスケープされる。
a%
a%
無 No.1と同様。
terasoluna-gfw-common 1.0.2.RELEASEより、デフォルト仕様では「”
%
”」はエスケープ対象外の文字として扱う。
a_
a_
無 No.1と同様。
terasoluna-gfw-common 1.0.2.RELEASEより、デフォルト仕様では「”
_
”」はエスケープ対象外の文字として扱う。
" "
" "
無 No.1と同様。
""
""
無 No.1と同様。
null
null
無 No.1と同様。
[全角文字を含める場合のエスケープ例]
エスケープ対象文字として全角文字を含める場合のエスケープ例を以下に示す。 項番6と7以外は、デフォルト仕様のエスケープ例を参照されたい。
項番 対象文字列 エスケープ後文字列 エスケープ有無 解説
a%
a~%
有 エスケープ対象文字が含まれているため、エスケープされる。
a_
a~_
有 No.6と同様。
6.1.5.1.2. 共通ライブラリから提供しているエスケープ用のメソッドについて¶
共通ライブラリから提供しているQueryEscapeUtils
クラスとLikeConditionEscape
クラスのLIKE検索用のエスケープメソッドの一覧を、以下に示す。
項番 メソッド名 説明
toLikeCondition(String) 引数で渡された文字列をLIKE検索用にエスケープする。SQLやJPQL側で一致方法(前方一致、後方一致、部分一致)を指定する場合は、本メソッドを使用してエスケープのみ行う。
toStartingWithCondition(String) 引数で渡された文字列をLIKE検索用にエスケープした上で、エスケープ後の文字列の最後尾に “%
” を付与する。前方一致検索用の値に変換する場合に使用するメソッドである。
toEndingWithCondition(String) 引数で渡された文字列をLIKE検索用にエスケープした上で、エスケープ後の文字列の先頭に “%
” を付与する。後方一致検索用の値に変換する場合に使用するメソッドである。
toContainingCondition(String) 引数で渡された文字列をLIKE検索用にエスケープした上で、エスケープ後の文字列の先頭と最後尾に “%
” を付与する。部分一致検索用の値に変換する場合に使用するメソッドである。Note
No.2, 3, 4 については、SQLやJPQL側で一致方法(前方一致、後方一致、部分一致)を指定するのではなく、プログラム側で指定する時に使用するメソッドである。
6.1.5.1.3. 共通ライブラリの使用方法¶
LIKE検索時のエスケープ処理の実装例については、使用するO/R Mapper向けのドキュメントを参照されたい。
- MyBatis3を使用する場合は、データベースアクセス(MyBatis3編)のLIKE検索時のエスケープを参照されたい。
Note
エスケープ処理を行うために使用するAPIは、使用するデータベースがサポートしているワイルドカード文字によって使い分ける必要がある。
[ワイルドカードとして「 “%” , “_”」(半角文字)のみをサポートしているデータベースの場合]
String escapedWord = QueryEscapeUtils.toLikeCondition(word);
項番 説明 (1)QueryEscapeUtils
クラスのメソッドを直接使用して、エスケープ処理を行う。
[ワイルドカードとして「”%” , “_”」(全角文字)もサポートしているデータベースの場合]
String escapedWord = QueryEscapeUtils.withFullWidth() // (2) .toLikeCondition(word); // (3)
項番 説明 (2)QueryEscapeUtils
メソッドのwithFullWidth()
メソッドを呼び出して、LikeConditionEscape
クラスのインスタンスを取得する。 (3)(2)で取得した LikeConditionEscape
クラスのインスタンスのメソッドを使用して、エスケープ処理を行う。
6.1.5.2. Sequencerについて¶
Note
共通ライブラリとしてSequencerを用意した理由
Sequencerを用意した理由は、JPAの機能として提供されているID採番機能において、シーケンス値を文字列としてフォーマットする仕組みがないためである。 実際のアプリケーション開発では、フォーマットされた文字列をプライマリキーに設定するケースもあるため、共通ライブラリとしてSequencerを提供している。
Sequencerを用意した主な目的は、JPAでサポートされていない機能の補完であるが、JPAと関係ない処理で、シーケンス値が必要な場合に、使用することもできる。
6.1.5.2.1. 共通ライブラリから提供しているクラスについて¶
項番 クラス名 説明
org.terasoluna.gfw.common.sequencer.Sequencer 次のシーケンス値を取得するメソッド(getNext)とシーケンスの現在値を返却するメソッド(getCurrent)を定義しているインタフェース。
org.terasoluna.gfw.common.sequencer.JdbcSequencerSequencer
インタフェースのJDBC用の実装クラス。データベースにSQLを発行してシーケンス値を取得するためのクラスである。データベースのシーケンスオブジェクトから値を取得することを想定したクラスではあるが、データベースに登録されているファンクションを呼び出すことで、シーケンスオブジェクト以外から値を取得することもできる。
6.1.5.2.2. 共通ライブラリの利用方法¶
Sequencerをbean定義する。
xxx-infra.xml
<!-- (1) --> <bean id="articleIdSequencer" class="org.terasoluna.gfw.common.sequencer.JdbcSequencer"> <!-- (2) --> <property name="dataSource" ref="dataSource" /> <!-- (3) --> <property name="sequenceClass" value="java.lang.String" /> <!-- (4) --> <property name="nextValueQuery" value="SELECT TO_CHAR(NEXTVAL('seq_article'),'AFM0000000000')" /> <!-- (5) --> <property name="currentValueQuery" value="SELECT TO_CHAR(CURRVAL('seq_article'),'AFM0000000000')" /> </bean>
項番 説明 (1)org.terasoluna.gfw.common.sequencer.Sequencer
インタフェースを実装したクラスを、bean定義する。上記例では、SQLを発行してシーケンス値を取得するためのクラス(JdbcSequencer
)を指定している。 (2) シーケンス値を取得するSQLを、実行するデータソースを指定する。 (3) 取得するシーケンス値の型を指定する。上記例では、SQLで文字列へ変換しているので、java.lang.String
型を指定している。 (4) 次のシーケンス値を取得するためのSQLを指定する。上記例では、データベース(PostgreSQL)のシーケンスオブジェクトから取得したシーケンス値を、文字列としてフォーマットしている。データベースのから取得したシーケンス値が、”1
” の場合、A0000000001
がSequencer#getNext()
メソッドの返り値として返却される。 (5) 現在のシーケンス値を取得するためのSQLを指定する。データベースのから取得したシーケンス値が、”2
” の場合、A0000000002
がSequencer#getCurrent()
メソッドの返り値として返却される。
bean定義したSequencerからシーケンス値を取得する。
- Service
// omitted // (1) @Inject @Named("articleIdSequencer") // (2) Sequencer<String> articleIdSequencer; // omitted @Transactional public Article createArticle(Article inputArticle) { String articleId = articleIdSequencer.getNext(); // (3) inputArticle.setArticleId(articleId); Article savedArticle = articleRepository.save(inputArticle); return savedArticle; }
項番 説明 (1) bean定義したSequencer
オブジェクトをInjectする。上記例では、シーケンス値は、フォーマットされた文字列として取得するため、Sequencer
のジェネリクス型には、java.lang.String
型を指定している。 (2) Injectするbeanのbean名を@javax.inject.Named
アノテーションのvalue属性に指定する。上記例では、xxx-infra.xml
に定義したbean名(articleIdSequencer
)を指定している。 (3)Sequencer#getNext()
メソッドを呼び出し、次のシーケンス値を取得する。上記例では、取得したシーケンス値を、EntityのIDとして使用している。現在のシーケンス値を取得する場合は、Sequencer#getCurrent()
メソッドを呼び出す。Tip
bean定義する
Sequencer
が一つの場合は、@Named
アノテーションが省略できる。複数指定する場合は、@Named
アノテーションを使用して、bean名の指定が必要となる。
6.1.5.3. Spring Frameworkから提供されているデータアクセス例外へ変換するクラス¶
Spring Frameworkのデータアクセス例外へ変換する役割を持つクラスを、以下に示す。
¶ 項番 クラス名 説明
org.springframework.jdbc.support.SQLErrorCodeSQLExceptionTranslatorMyBatisや、 JdbcTemplate
を使った場合、本クラスによって、JDBC例外が、Spring Frameworkのデータアクセス例外に変換される。変換ルールは、XMLファイルに記載されており、デフォルトで使用されるXMLファイルは、spring-jdbc.jar
内のorg/springframework/jdbc/support/sql-error-codes.xml
となる。 クラスパス直下に、XMLファイル(sql-error-codes.xml
)を配置することで、デフォルトの動作を変更することもできる。
org.springframework.orm.jpa.vendor.HibernateJpaDialectJPA(Hibernateの実装)を使った場合、本クラスによって、O/R Mapper例外(Hibernateの例外)がSpring Frameworkのデータアクセス例外に変換される。
org.springframework.orm.jpa.EntityManagerFactoryUtilsHibernateJpaDialect
で変換できない例外が発生した場合は、本クラスによって、JPA例外がSpring Frameworkのデータアクセス例外に変換される。
org.hibernate.dialect.DialectのサブクラスJPA(Hibernateの実装)を使った場合、本クラスによって、JDBC例外とO/R Mapper例外に変換される。
6.1.5.4. Spring Frameworkから提供されているJDBCデータソースクラス¶
¶ 項番 クラス名 説明
org.springframework.jdbc.datasource.DriverManagerDataSourceアプリケーションからコネクションの取得依頼があったタイミングで、 java.sql.DriverManager#getConnection
を呼び出し、新しいコネクションを生成するデータソースクラス。 コネクションのプーリングが必要な場合は、アプリケーションサーバのデータソース、または、OSS/Third-Partyライブラリから提供されているデータソースを使用すること。
org.springframework.jdbc.datasource.SingleConnectionDataSourceDriverManagerDataSource
の子クラスで、一つのコネクションを使いまわす実装になっており、シングルスレッドで動くユニットテスト向けのデータソースクラスである。 ユニットテストでも、マルチスレッドでデータソースにアクセスする場合は、本クラスを使用すると、期待した動作にならないことがあるので、注意が必要である。
org.springframework.jdbc.datasource.SimpleDriverDataSourceアプリケーションからコネクションの取得依頼があったタイミングで、 java.sql.Driver#getConnection
を呼び出し、新しいコネクションを生成するデータソースクラス。 コネクションのプーリングが必要な場合は、アプリケーションサーバのデータソース、または、OSS/Third-Partyライブラリから提供されているデータソースを使用すること。
¶ 項番 クラス名 説明
org.springframework.jdbc.datasource.TransactionAwareDataSourceProxyトランザクション管理されていないデータソースを、Spring Frameworkのトランザクション管理対象にするためのアダプタークラス。
org.springframework.jdbc.datasource.lookup.IsolationLevelDataSourceRoute実行中のトランザクションの独立性レベルによって、使用するデータソースを切り替えるためのアダプタークラス。