LoginSignup
8

More than 5 years have passed since last update.

posted at

updated at

Spring 4.3 データアクセス関連の主な変更点

今回は、Spring Framework 4.3の変更点紹介シリーズの第2回で、データアクセス関連の変更点を紹介します。

シリーズ

動作検証環境

  • Spring Framework 4.3.0.RELEASE
  • Spring Boot 1.4.0.BUILD-SNAPSHOT (2016/6/11時点)

Data Access Improvements

今回は、データアクセス関連の主な変更点をみていきますが、公式リファレンスで紹介されている変更はひとつだけでした :sweat_smile: ひとつだけしかないので、無駄に手厚く説明したいと思います :smile:

No データアクセス関連の主な変更点
1 XMLによるBean定義をサポート<jdbc:initialize-database><jdbc:embedded-database>タグにて、SQLファイル単位でSQLの区切り文字(デフォルトは「;」)を指定できるようになります。

SQLファイル単位で区切り文字の指定ができる :thumbsup:

Springは、XMLによるBean定義をサポートするためのXMLネームスペースを数多く提供しています。そのうちのひとつとして、JDBC関連のBean定義をサポートする「jdbc」というネームスペースがあり、以下の2つのタグを提供しています。

  • <jdbc:embedded-database> : 組み込みデータベース向けのDataSourceを生成するためのタグ
  • <jdbc:initialize-database> : データソースを初期化するためのタグ

これらのタグの中には、データソースを初期化するためのSQLを実行するためのタグ(<script>)が指定できます。今回の改善で、SQLファイル内に記述したSQLの区切り文字(終了文字)を<script>タグ単位で指定できるようになります。ちなみに・・・デフォルトの区切り文字は「;」です。

この改善がどんな時に役立つかというと・・・・SQLファンクションやSQLプロシージャーなどを作成するSQLを実行する時です。データベースによって言語仕様がことなりますが、ここではH2 Databaseを例に説明します。

まず、H2 Databaseでユーザ定義のSQLファンクションを作成するSQLを作ります。H2 Databaseは、SQLファクションをJava言語を使って作る事ができます。

src/main/resources/db/function.sql
CREATE ALIAS joinStrings AS $$
import java.util.StringJoiner;
import java.util.stream.Stream;
@CODE
String joinStrings(String... args) {
    StringJoiner joiner = new StringJoiner("-");
    Stream.of(args).forEach(joiner::add);
    return joiner.toString();
}
$$;
src/main/resources/dataSource.xml
<jdbc:embedded-database type="H2" id="dataSource" />
<jdbc:initialize-database data-source="dataSource">
    <jdbc:script location="classpath:/db/function.sql" />
</jdbc:initialize-database>

ポイントは、ファンクションの定義内でセパレータ文字と同じ文字「;」が使われているところです。このSQLを<jdbc:embedded-database><jdbc:initialize-database>で読み込ませると、「CREATE ALIAS joinStrings AS $$ import java.util.StringJoiner」というSQLだと解釈してしまうため、残念ながらSQLエラーになってしまいます。

このエラーを回避するためには、区切り文字を変更する必要があります。ここでは「;/」を区切り文字にします。

src/main/resources/db/function.sql
CREATE ALIAS joinStrings AS $$
import java.util.StringJoiner;
import java.util.stream.Stream;
@CODE
String joinStrings(String... args) {
    StringJoiner joiner = new StringJoiner("-");
    Stream.of(args).forEach(joiner::add);
    return joiner.toString();
}
$$;/
src/main/resources/dataSource.xml
<jdbc:initialize-database data-source="dataSource" separator=";/">
    <jdbc:script location="classpath:/db/function.sql" />
</jdbc:initialize-database>

DIコンテナを起動すると、SQLが実行され正常に終了します。ちなみに・・・ここでは、意図的に<jdbc:embedded-database>タグのseparator属性に区切り文字を指定して、デフォルトの区切り文字を変更しています。

SQLファンクションの次はテーブルを作ります。

src/main/resources/db/schema.sql
CREATE TABLE account (
  id IDENTITY ,
  name NVARCHAR(128),
  tel VARCHAR(32)
);
src/main/resources/dataSource.xml
<jdbc:initialize-database data-source="dataSource" separator=";/">
    <jdbc:script location="classpath:/db/function.sql" />
    <jdbc:script location="classpath:/db/schema.sql" />
</jdbc:initialize-database>

この状態でDIコンテナを起動すると、schema.sqlで指定したSQLが「CREATE TABLE ACCOUNT (」というSQLとして解釈され、SQLエラーになります・・・ :confounded:
Spring 4.2までは、この事象を回避するために、すべてのSQLファイルの区切り文字を統一しておく必要がありました。

前置きが長くなってしまいましたが・・・Spring 4.3からは、

src/main/resources/dataSource.xml
<jdbc:initialize-database data-source="dataSource">
    <jdbc:script location="classpath:/db/function.sql" separator=";/" /> <!-- SQL単位に指定できる -->
    <jdbc:script location="classpath:/db/schema.sql" /> <!-- 省略するとデフォルト値「;」が使われる -->
</jdbc:initialize-database>

と書けばOKです :thumbsup:
<script>タグ(SQLファイル)単位で区切り文字を指定できるため、必要なところだけデフォルト値を上書きできます。

Spring Bootでは?

Spring Bootでは、通常XMLによるBean定義は行いません。加えてSpring Bootには、自動でSQLを実行する仕組みが組み込まれており、デフォルトだとクラスパス直下にあるschema.sql(+ schema-${spring.datasource.platform:all}.sql)とdata.sql(+ data-${spring.datasource.platform:all}.sql)を読み込んで実行してくれます。が、しかし・・・残念ながらSQLファイル毎に区切り文字を切り替える仕組みは提供されていません。

では、どうするのか・・・

Java Configを使う

Java Configでorg.springframework.jdbc.datasource.init.DataSourceInitializerのBean定義を行うことで、XMLファイルと同等の定義ができます。

@Configuration
public class JdbcConfig {
    @Bean
    public DataSourceInitializer dataSourceInitializer(DataSource dataSource) {
        DataSourceInitializer initializer = new DataSourceInitializer();
        initializer.setDataSource(dataSource);

        ResourceDatabasePopulator functionPopulator = new ResourceDatabasePopulator(
                new ClassPathResource("function.sql"));
        functionPopulator.setSeparator(";/");

        ResourceDatabasePopulator generalPopulator = new ResourceDatabasePopulator(
                new ClassPathResource("schema.sql"),
                new ClassPathResource("data.sql"));

        initializer.setDatabasePopulator(new CompositeDatabasePopulator(functionPopulator, generalPopulator));

        return initializer;
    }
}

Note: CompositeDatabasePopulatorのコンストラクタについて

Spring 4.3から、DatabasePopulatorを引数にとるCompositeDatabasePopulatorのコンストラクタが追加され若干使いやすくなっています!!

そして、Spring Bootの機能が提供している初期化の仕組みが動かないようにします。この設定を忘れると、初期化処理が重複するため、良からぬ動作になる可能性が高いです。

src/main/resources/application.properties
spring.datasource.initialize=false

正直なところ・・・XMLファイルの方がシンプルですね・・・ :sweat_smile: 加えて、Spring Bootが提供している機能を忠実に再現しようと思うと、もう少し複雑になります。(ここではシンプルにするためにSpring Bootの挙動の再現はしていません)

XMLファイルを使う?

Spring Frameworkの仕組み上は、XMLファイルを併用することはできます。が、しかし・・・Java ConfigとXML Configが混在するのは気持ちがわるい。。。とはいえ、シンプルな状態に保つことも非常に重要な要素の一つなので・・・(特に推奨するわけではありませんが、)XMLファイルを併用する方法も紹介しておきます。

@Configuration
@ImportResource("classpath:dataSource.xml") // XMLファイルをインポートする
public class JdbcConfig {
    // ...
}
src/main/resources/dataSource.xml
<jdbc:initialize-database data-source="dataSource">
    <jdbc:script location="classpath:function.sql" separator=";/" />
    <jdbc:script location="classpath:schema.sql" />
    <jdbc:script location="classpath:data.sql" />
</jdbc:initialize-database>

Java ConfigとXML Configが混在するのは気持ちわると言いましたが・・・XMLの方が勝っている(シンプルに表現できる)ところもあるので、柔軟によいところを採用していくことも大事な気もしています・・・ :wink:

まとめ

Spring 4.3でのデータアクセス関連の変更点はあまりない?ようですね。次回は、「キャッシュ関連の主な変更点」を紹介する予定です。

参考サイト

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
What you can do with signing up
8