SqlMapper は、Spring Framework のJDBC機能を使って、「S2JDBC の再実装 + 機能追加」 を目指した SQLマッピング用のライブラリです。

1. セットアップ

1.1. 前提環境・前提条件

本ライブラリの前提条件を以下に示します。

表 1. 依存ライブラリの前提条件
項目 備考

Java

JDK11+

必須

Spring Framework

5.3+

必須

Spring Boot

2.4+

オプション

表 2. サポートしているDB
DB バージョン 備考

H2

1.4.x/2.1.x

HSQLDB

-

Oracle

v12+

Oracle 11以前を使用するときには、OracleLegacyDialect を使用する。

SQLite

PostgreSQL

1.2. Spring Framework の設定

非Spring Boot の環境、または、SqlMapperのstater を使用しない時の環境設定方法を説明します。

1.2.1. Mavenへのライブラリの依存関係の追加

Mavenを使用している場合は、pom.xml に依存関係を追加します。

利用するDBのJDBCドライバの定義も追加します。

pom.xmlへの依存関係の追加
<project>
    <dependencies>
		<!-- SQLMapperの定義 -->
		<dependency>
			<groupId>com.github.mygreen.sqlmapper</groupId>
			<artifactId>sqlmapper-core</artifactId>
			<version>0.3.2</version>
		</dependency>

		<!-- 利用するDBのJDBCドライバを追加します -->
		<dependency>
			<groupId>com.h2database</groupId>
			<artifactId>h2</artifactId>
			<version>2.1.210</version>
		</dependency>

    </dependencies>
</project>

さらに、メタモデルを自動生成する設定を追加します。

pom.xmlへのメタモデルの自動生成設定の追加
<project>
    <build>
        <plugins>
            <!-- メタモデルを生成するプラグインを追加します -->
            <plugin>
				<groupId>com.mysema.maven</groupId>
				<artifactId>apt-maven-plugin</artifactId>
				<version>1.1.3</version>
				<executions>
					<execution>
						<phase>generate-sources</phase>
						<goals>
							<goal>process</goal>
						</goals>
						<configuration>
							<outputDirectory>target/generated-sources/java</outputDirectory>
							<logOnlyOnError>false</logOnlyOnError>
							<processors>
								<processor>com.github.mygreen.sqlmapper.apt.EntityMetamodelProcessor</processor>
							</processors>
							<sourceEncoding>UTF-8</sourceEncoding>
						</configuration>
					</execution>
				</executions>
                <dependencies>
				    <dependency>
						<groupId>com.github.mygreen.sqlmapper</groupId>
						<artifactId>sqlmapper-apt</artifactId>
						<version>0.3.2</version>
					</dependency>
			    </dependencies>
			</plugin>
        </plugins>
    </build>
</project>

1.2.2. Gradleへのライブラリの依存関係の追加

Gradleを使用している場合は、build.gradle に依存関係を追加します。

利用するDBのJDBCドライバの定義も追加します。

さらに、メタモデルを自動生成する設定を追加します。

build.gradleへの依存関係の追加
dependencies {
	implementation 'com.github.mygreen.sqlmapper:sqlmapper-spring-boot-starter:0.3.2'

	// 利用するDBのJDBCドライバを追加
	implementation 'com.h2database:h2:2.1.210'

	// メタモデルを自動生成する設定
	annotationProcessor 'com.github.mygreen.sqlmapper:sqlmapper-apt:0.3.2'

}

1.2.3. JavaConfigの設定

SqlMapper用のJavaConfigのサポートクラス SqlMapperConfigurationSupport を継承して定義します。

データソースとDBの方言種別を環境に合わせ定義します。DBの方言の詳細については JavaDoc を参照してください。

JavaConfigの定義
import javax.sql.DataSource;

import org.springframework.context.annotation.Configuration;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;

import com.github.mygreen.sqlmapper.core.config.SqlMapperConfigurationSupport;
import com.github.mygreen.sqlmapper.core.dialect.Dialect;
import com.github.mygreen.sqlmapper.core.dialect.H2Dialect;


@Configuration
public class SqlMapperConfig extends SqlMapperConfigurationSupport {

    // データソースの定義
    @Override
    public DataSource dataSource() {
        return new EmbeddedDatabaseBuilder()
                .generateUniqueName(true)
                .setType(EmbeddedDatabaseType.H2)
                .setScriptEncoding("UTF-8")
                .build();
    }

    // DBの方言種別の定義
    @Override
    public Dialect dialect() {
        return new H2Dialect();
    }
}
表 3. DBの方言種別
DB種別 クラス 説明

H2 Database Engine

H2Dialect

Java製の組み込みDB。

HSQLDB

HsqlDialect

Java製の組み込みDB。

PostgreSQL

PostgresDialect

OSSのRDMBS。

SQLite

SqliteDialect

組み込みDB。

Oracleデータベース

OracleDialect

商用のRDBS。Oracle12c以上の場合に対応。

OracleLegacyDialect

Oracle11g以前に対応。

プロパティの上書き

テーブルによる識別子の生成などの設定を上書きする場合は、Springのアノテーション @PropertySource を使用し、プロパティを上書きします。

設定可能なプロパティは、設定可能なプロパティ を参照してください。

JavaConfigの定義
import org.springframework.context.annotation.PropertySource;

@Configuration
@PropertySource("classpath:application.properties")
public class SqlMapperConfig extends SqlMapperConfigurationSupport {
    // ・・・省略
}
DBコネクションプールの設定

DBコネクションプールを使用する場合、JavaConfigのDataSourceのインスタンスを変更します。

  • Commons DBCP2 / HikariCP など好きなものを使用してください。

  • 設定値は application.properties などに定義しておき、 Environment で参照します。

    • SqlMapperConfigurationSupportのプロパティ env で定義されてるため、JavaConfig内から参照できます。

DBコネクションプールの設定例
import javax.sql.DataSource;

import org.apache.commons.dbcp2.BasicDataSource;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

import com.github.mygreen.sqlmapper.core.config.SqlMapperConfigurationSupport;


@Configuration
@PropertySource("classpath:application.properties")
public class SqlMapperConfig extends SqlMapperConfigurationSupport {

    // データソースの定義
	@Bean(destroyMethod = "close")
    @Override
    public DataSource dataSource() {
		BasicDataSource dataSource = new BasicDataSource();
		dataSource.setDriverClassName(env.getRequiredProperty("jdbc.driverClassName"));
		dataSource.setUrl(env.getRequiredProperty("jdbc.url"));
		dataSource.setUsername(env.getRequiredProperty("jdbc.username"));
		dataSource.setPassword(env.getRequiredProperty("jdbc.password"));

		return dataSource();
    }

    // ・・・
}
DB接続関連のプロパティ定義例
jdbc.driverClassName=org.postgresql.Driver
jdbc.url=jdbc:postgresql://localhost:5432/sampledb
jdbc.username=sample_user
jdbc.password=sample_password

1.3. Spring Bootの設定

Spring Boot のSqlMapper 専用の starter を使った環境設定方法を説明します。

1.3.1. Mavenへのライブラリの依存関係の追加

Mavenを使用している場合は、pom.xml に依存関係を追加します。

利用するDBのJDBCドライバの定義も追加します。

pom.xmlへの依存関係の追加
<project>
    <dependencies>
		<!-- SQLMapperのSpringBoot用のstarterの追加 -->
        <dependency>
			<groupId>com.github.mygreen.sqlmapper</groupId>
			<artifactId>sqlmapper-spring-boot-starter</artifactId>
			<version>0.3.2</version>
		</dependency>

		<!-- 利用するDBのJDBCドライバを追加します -->
		<dependency>
			<groupId>com.h2database</groupId>
			<artifactId>h2</artifactId>
		</dependency>

    </dependencies>
</project>

さらに、メタモデルを自動生成する設定を追加します。

pom.xmlへのメタモデルの自動生成設定の追加
<project>
    <build>
        <plugins>
            <!-- メタモデルを生成するプラグインを追加します -->
            <plugin>
				<groupId>com.mysema.maven</groupId>
				<artifactId>apt-maven-plugin</artifactId>
				<version>1.1.3</version>
				<executions>
					<execution>
						<phase>generate-sources</phase>
						<goals>
							<goal>process</goal>
						</goals>
						<configuration>
							<outputDirectory>target/generated-sources/java</outputDirectory>
							<logOnlyOnError>false</logOnlyOnError>
							<processors>
								<processor>com.github.mygreen.sqlmapper.apt.EntityMetamodelProcessor</processor>
							</processors>
							<sourceEncoding>UTF-8</sourceEncoding>
						</configuration>
					</execution>
				</executions>
                <dependencies>
				    <dependency>
						<groupId>com.github.mygreen.sqlmapper</groupId>
						<artifactId>sqlmapper-apt</artifactId>
						<version>0.3.2</version>
					</dependency>
			    </dependencies>
			</plugin>
        </plugins>
    </build>
</project>

1.3.2. Gradleへのライブラリの依存関係の追加

Gradleを使用している場合は、build.gradle に依存関係を追加します。

利用するDBのJDBCドライバの定義も追加します。

さらに、メタモデルを自動生成する設定を追加します。

build.gradleへの依存関係の追加
dependencies {
	implementation 'com.github.mygreen.sqlmapper:sqlmapper-spring-boot-starter:0.3.2'

	// 利用するDBのJDBCドライバを追加
	implementation 'com.h2database:h2:2.1.210'

	// メタモデルを自動生成する設定
	annotationProcessor 'com.github.mygreen.sqlmapper:sqlmapper-apt:0.3.2'
}

1.3.3. データソースの設定

データソースの接続定義をアプリケーションプロパティに定義します。

  • データソースの定義は、 Spring Boot標準のプロパティ を使用します。

    • プロパティ spring.datasource.XXX を使用して定義します。

  • 読み込まれたJDBCドライバの定義によって、DBの方言種別設定 が自動的に読み込まれます。

    • 判定は、Spring Bootの DatabaseDriver を使用して判定しているため、DatabaseDriverクラスが対応していないDBの場合は、 Dialect のSpring Beanを独自に定義して登録する必要があります。

データソースの定義
spring:
  datasource:
    driver-class-name: org.h2.Driver
    url: jdbc:h2:mem:testdb;DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=false
    username: sa
    password:
表 4. DBのJDBCドライバー
DB種別 ドライバークラス ライブラリ

H2 Database Engine

org.h2.Driver

com.h2database:h2

HSQLDB

org.hsqldb.jdbc.JDBCDriver

org.hsqldb:hsqldb

PostgreSQL

org.postgresql.Driver

org.postgresql:postgresql

SQLite

org.sqlite.JDBC

org.xerial:sqlite-jdbc

Oracleデータベース

oracle.jdbc.OracleDriver

com.oracle.database.jdbc:ojdbc11

プロパティの上書き

テーブルによる識別子の生成などの設定を上書きする場合は、SpringBootの applicaiton.ymlapplication.properties に定義します。

設定可能なプロパティは、設定可能なプロパティ を参照してください。

DBコネクションプールの設定
DBコネクションプールのデータソースの定義
spring:
  datasource:
    driver-class-name: org.postgresql.Driver
    url: jdbc:postgresql://localhost:5432/sampledb
    username: sample_user
    password: sample_password
	# DBコネクションプールのタイプを指定
	type: com.zaxxer.hikari.HikariDataSource
	# 各コネクションプールの設定値を指定
    hikari:
      maximum-pool-size: 20
      minimum-idle: 10

1.4. 設定可能なプロパティ

表 5. テーブルによるID生成の設定
キー デフォルト値 説明

sqlmapper.table-id-generator.table

ID_SEQUENCE

生成したIDの値を永続化するテーブル名。

sqlmapper.table-id-generator.schema

- (デフォルト値は空)

生成したIDの値を永続化するテーブルが定義されているスキーマ名。

sqlmapper.table-id-generator.catalog

- (デフォルト値は空)

生成したIDの値を永続化するテーブルが定義されているカタログ名。

sqlmapper.table-id-generator.pk-column

SEQUENCE_NAME

生成したIDの名称を保持するカラム名。

sqlmapper.table-id-generator.value-column

SEQUENCE_VALUE

生成したIDの値を保持するカラム名。

sqlmapper.table-id-generator.allocation-size

50

採番を行う際に、予め指定した値分を払い出しておく値です。値を1にすると、毎回レコードを更新することになり、オーバーヘッドが発生します。

sqlmapper.table-id-generator.initial-value

0

生成するIDの値の初期値。

表 6. SQLテンプレートの設定
キー デフォルト値 説明

sqlmapper.sql-template.cache-mode

true

SQLテンプレートのパース結果をキャッシュするかどうか。true のきキャッシュします。

sqlmapper.sql-template.encoding

UTF-8

SQLテンプレートのファイルの文字コード。

2. Criteria API

プログラムでSQLを組み立てる Crieria API の使い方を説明します。

  • エンティティクラスにたいするメタモデルを使用するため、予め セットアップ を参照して設定しておく必要があります。

  • エンティティクラスの定義は、エンティティの定義方法 を参照して作成しておく必要があります。

2.1. Criteria APIでできないこと

Criteria APIでは、ある程度のSQLを組み立てることができますが、全てのSQLをサポートしているわけではありません。

そのため、本ライブラリの非サポートSQLまたは、複雑なSQLを実行する場合は、SQLテンプレート を使用することをお勧めします。

  • SUM などの集計関数はサポートしていません。

  • グループ化(GROUP BY)、HAVING 句はサポートしていません。

2.2. 検索

検索を行うには、selectFrom(…​) メソッドを使用します。

メソッドチェインで検索条件などを指定します。

2.2.1. 複数件検索

複数件を検索する場合は、getResultList() を使います。

  • 検索結果が0件のときは、空のリストを返します。

  • 検索するエンティティは、from() でメタモデルを指定します。

List<Employee> results = sqlMapper.selectFrom(MEmployee.employee)
        .getResultList();

2.2.2. 1件検索

1件検索する場合は、getSingleResult() を使用します。

  • 1件も見つからないときは、Springの例外 EmptyResultDataAccessException がスローされます。

  • 2件以上見つかった場合、Springの例外 IncorrectResultSizeDataAccessException がスローされます。

Employee result = sqlMapper.selectFrom(MEmployee.employee)
        .getSingleResult();

1件も見つからなかったときに例外をスローされないようにするには、getOptionalResult() を使用します。

  • 戻り値を java.util.Optinal で受け取ることができます。

  • ただし、2件以上見つかったときは、Springの例外 IncorrectResultSizeDataAccessException がスローされます。

    • 例外がスローされないように、メソッド limit(1) による件数を指定することをお勧めします。

Optional<Employee> result = sqlMapper.selectFrom(MEmployee.employee)
        .getOptionalResult();

2.2.3. Streamによる検索

検索結果を多くの行を返しメモリの消費量が多くListでまとめて受け取ることが困難な場合は、フェッチによる参照として getResultStream() を使用します。

  • Streamは、必ずクローズするようにしてください。

Streamによるフェッチ検索
Stream<Employee> stream = sqlMapper.selectFrom(MEmployee.employee)
        .getResultStream()

// try-with-resourceでStreamをクローズする。
try (stream) {
    stream.forEarch(entity -> {
        // 任意の処理
    });
}

2.2.4. 検索結果の行数取得

SQLの SELECT COUNT(*) ~ による検索結果の行数を取得するには、getCount() を使用します。

long count = sqlMapper.selectFrom(MEmployee.employee)
        .getCount();

2.2.5. 結合

他のエンティティと結合する場合は、次のメソッドを使用します。

  • innerJoin(…​) : エンティティを内部結合(INNERT JOIN)します。

  • leftJoin(…​) : エンティティを左外部結合(LEFT OUTER JOIN)します。

  • 結合条件は、メタモデルを使用して指定します。

  • 結合したエンティティの保存は、associate(…​) を使用してプログラマティックに行います。

結合したエンティティのクエリ実行
MEmployee e = MEmployee.employee;
MSection s = MSection.section;

List<Employee> results = sqlMapper.selectFrom(e)
        .innerJoin(s, (to) -> to.code.eq(e.sectionCode))
        .associate(e, s, (e1, e2) -> e1.setSection(e2))
        .getResultList();
  • 結合した結果を保持するプロパティ(例. section)は、エンティティ上ではアノテーション @Transient を付与して永続化対象外とします。

結合した結果を保持するエンティティクラスの定義
public class Employee {

    @Id
    private Long id;

    /**
     * 部門情報 - 結合したエンティティを保持する
     */
    @Transient
    private Section section;

    // getter/setterは省略
}

2.2.6. IDとバージョンによる検索条件指定

IDプロパティ(主キー)を指定して検索対象を指定する場合は、id(Object…​) を使用します。

  • 引数はエンティティに定義されたIDプロパティと同じ個数・並び順で指定する必要があります。

Employee results = sqlMapper.selectFrom(MEmployee.employee)
        .id(5)
        .getSingleResult();

IDプロパティと同時にバージョンを指定もできます。 バージョンプロパティを指定する場合は、 version(Object) を使います。

  • IDを指定しないでバージョンだけを指定はできません。もしバージョンだけを指定した場合、例外 IllegalOperateException がスローされます。

Employee result = sqlMapper.selectFrom(MEmployee.employee)
        .id(5)
        .version(2)
        .getSingleResult();

埋め込み型IDを使用する場合は、埋め込み型IDクラスのインスタンスを指定します。

Employee results = sqlMapper.selectFrom(MEmployee.employee)
        .id(new PK(1, 200))
        .getSingleResult();

2.2.7. 複雑な検索条件の指定

より複雑な検索条件を指定する場合は、where(…​) を使用します。

  • メタモデル を使い検索条件をある程度、型安全に組み立てることができます。

  • 使用するエンティティのメタモデルのインスタンスは、seleftFrom(..) / innertJoin(…​) / leftJoin(…​) の何れかで指定したインスタンスである必要があります。

MEmployee e = MEmployee.employee;
MSection s = MSection.section;

List<Employee> results = sqlMapper.selectFrom(e)
        .innerJoin(s, (to) -> to.code.eq(e.sectionCode))
        .where(e.hireDate.before(LocalDate.of(2020, 5, 1)).and(s.name.contains("開発")))
        .getResultList();

2.2.8. 並び順

並び順を指定する場合は、orderBy(…​) を使用します。

  • メタモデル を使いエンティティのプロパティに対する並び順を指定します。

MEmployee e = MEmployee.employee;

List<Employee> results = sqlMapper.selectFrom(e)
        .orderBy(e.name.asc(), e.hireDate.desc())
        .getResultList();

2.2.9. 排他制御

SELECT 時にロックを取得するには、以下のメソッドを使用します。

  • forUpdate()

  • forUpdateNoWait()

  • forUpdateWait(int seconds)

全てのRDBMSでこれらの操作が利用できるわけではありません。 サポートされていないメソッドを呼び出すと IllegalOperateException がスローされます。

List<Employee> results = sqlMapper.selectFrom(MEmployee.employee)
        .forUpdate()
        .getResultList();

2.2.10. 指定したプロパティのみを検索結果に含める

指定したプロパティのみを検索結果に含める場合は、includes(…​) を使用します。

  • ただし、@Id アノテーションが付けられたプロパティは無条件で検索結果に含まれます。

  • 特に、ラージオブジェクトの場合、不要なプロパティを検索結果から除外することで、 データベースから転送されるデータ量やJVMのメモリ使用量を減らすことができます。

MEmployee e = MEmployee.employee;

List<Employee> results = sqlMapper.selectFrom(e)
        .includes(e.id, e.name)
        .getResultList();

次のように結合するエンティティのプロパティを指定もできます。

MEmployee e = MEmployee.employee;
MSection s = MSection.section;

List<Employee> results = sqlMapper.selectFrom(e)
        .innerJoin(s, (to) -> to.code.eq(e.sectionCode))
        .associate(e, s, (e1, e2) -> e1.setSection(e2))
        .includes(e.id, e.name, s.name)
        .getResultList();
includes(…​)excludes(…​) の両方で同じプロパティを指定した場合、includes(…​) が優先されます。

2.2.11. 指定したプロパティを検索結果から除外する

指定したプロパティを検索結果から除外する場合は、excludes(…​) を使用します。

  • ただし、@Id アノテーションが付けられたプロパティは無条件で検索結果に含まれます。

  • 特に、ラージオブジェクトの場合、不要なプロパティを検索結果から除外することで、 データベースから転送されるデータ量やJVMのメモリ使用量を減らすことができます。

MEmployee e = MEmployee.employee;

List<Employee> results = sqlMapper.selectFrom(e)
        .excludes(e.address)
        .getResultList();

次のように結合するエンティティのプロパティを指定できます。

MEmployee e = MEmployee.employee;
MSection s = MSection.section;

List<Employee> results = sqlMapper.selectFrom(e)
        .innerJoin(s, (to) -> to.code.eq(e.sectionCode))
        .associate(e, s, (e1, e2) -> e1.setSection(e2))
        .excludes(e.address, s.tel)
        .getResultList();

2.2.12. ページング

ページングを指定するには、以下のメソッドを使用します。

  • offset(int offset) : 最初に取得する行の位置を指定します。最初の行の位置は0になります。

  • limit(int limit) : 取得する行数を指定します。

ページングを指定するには、必ず 並び順 の指定も必要です。
MEmployee e = MEmployee.employee;

List<Employee> results = sqlMapper.selectFrom(e)
        .orderBy(e.name.asc(), e.hireDate.desc())
        .offset(10)
        .limit(100)
        .getResultList();

2.3. 挿入

2.3.1. 1件挿入

エンティティを挿入する場合は、insert(…​)execute() を組み合わせて使用します。

  • insert(…​) の引数はエンティティのインスタンスを指定します。

  • execute() の戻り値は、更新した行数です。

  • 挿入するときに識別子を自動設定できます。

  • 一意制約違反によりエンティティの挿入ができない場合は、例外 org.springframework.dao.DuplicateKeyException がスローされます。

int count = sqlMapper.insert(employee)
        .execute();

2.3.2. バッチ挿入

複数のエンティティをバッチ挿入する場合は、insertBatch(…​)execute() を組み合わせて使用します。

  • insertBatch(…​) の引数はエンティティのインスタンスのリストあるいは配列(可変長引数)を指定します。

  • execute() の戻り値は、更新した行数の配列です。

  • 挿入するときに識別子を自動設定できます。

  • 一意制約違反によりエンティティの挿入ができない場合は、例外 org.springframework.dao.DuplicateKeyException がスローされます。

int[] countArray = sqlMapper.insertBatch(employees)
        .execute();
処理時のバッチサイズは引数で指定したエンティティのサイズと同一になります。 バッチサイズを変更したい場合は、エンティティを分割して実行してください。

2.3.3. 指定したプロパティのみを挿入対象にする

  • 指定したプロパティのみを挿入対象にする場合は、 includes() を使用します。

  • 次のプロパティは自動的に挿入対象となり、includes() で指定する必要はありません。

    • @Id を付与したID(主キー)。

    • @Version を付与したバージョンキー(排他キー)。

MEmployee e = MEmployee.employee;

int count = sqlMapper.insert(employee)
        .includes(e.id, e.name)
        .execute();
includes(…​)excludes(…​) の両方で同じプロパティを指定した場合、includes(…​) が優先されます。

2.3.4. 指定したプロパティを挿入対象から除外する

  • 指定したプロパティを挿入対象から除外する場合は、 excludes() を使用します。

  • 次のプロパティは自動的に挿入対象となり、除外対象に指定できません。

    • @Id を付与したID(主キー)。

    • @Version を付与したバージョンキー(排他キー)。

MEmployee e = MEmployee.employee;

int count = sqlMapper.insert(employee)
        .excludes(e.version)
        .execute();

2.4. 更新

2.4.1. 1件更新

エンティティを更新する場合は、update(…​)execute() を組み合わせます。

  • update(…​) の引数はエンティティのインスタンスを指定します。

  • execute() の戻り値は、更新した行数です。

    • 更新対象のプロパティ(カラム)がない場合は、0 を返します。

  • 更新するときに、バージョンによる楽観的排他チェックをができます。

    • 楽観敵排他エラーが発生したときは例外 org.springframework.dao.OptimisticLockingFailureException がスローされます。

    • 詳しくは、 バージョン定義 を参照してください。

  • 識別子定義のなエンティティは、 update(…​) で更新できません。

int count = sqlMapper.update(employee)
        .execute();

2.4.2. バッチ更新

複数のエンティティをバッチ更新する場合は、updateBatch(…​)execute() を組み合わせます。

  • updateBatch(…​) の引数はエンティティのインスタンスのリストあるいは配列(可変長引数)を指定します。

  • execute() の戻り値は、更新した行数の配列です。

  • 更新するときに、バージョンによる楽観的排他チェックをができます。

    • 楽観的排他エラーが発生したときは、例外 org.springframework.dao.OptimisticLockingFailureException がスローされます。

    • 詳しくは、 バージョン定義 を参照してください。

  • 識別子定義のなエンティティは、 updateBatch(…​) で更新できません。

int[] countArray = sqlMapper.updateBatch(employees)
        .execute();
処理時のバッチサイズは引数で指定したエンティティのサイズと同一になります。 バッチサイズを変更したい場合は、エンティティを分割して実行してください。

2.4.3. バージョンプロパティを通常の更新対象にする

バージョンプロパティを通常の更新対象に含め、バージョンチェックの対象外にする場合は、 includesVersion() を使います。

int count = sqlMapper.update(employee)
        .includesVersion()
        .execute();

2.4.4. nullの項目を更新しない

更新の対象からnullの項目を除外する場合は、 excludesNull() を使用します。

バッチ系の更新は、すべてのエンティティに同じSQLを適用しなければならないので、 null を除外してバッチ更新することはできません。 なぜなら、すべてのエンティティの null の項目が同じだとは限らないからです。
int count = sqlMapper.update(employee)
        .excludesNull()
        .execute();

2.4.5. 指定したプロパティのみを更新対象にする

指定したプロパティのみを更新対象にする場合は、includes() を使用します。

MEmployee e = MEmployee.employee;

int count = sqlMapper.update(employee)
        .includes(e.id, e.name)
        .execute();
includes(…​)excludes(…​) の両方で同じプロパティを指定した場合、includes(…​) が優先されます。

2.4.6. 指定したプロパティを更新対象から除外する

指定したプロパティを更新対象から除外する場合は、 excludes() を使用します。

MEmployee e = MEmployee.employee;

int count = sqlMapper.update(employee)
        .excludes(e.version)
        .execute();

2.4.7. 変更のあったプロパティのみを更新対象にする

変更のあったプロパティのみを更新対象にする場合は、changedFrom() を使います。

バッチ系の更新は、すべてのエンティティに同じSQLを適用しなければならないので、 変更のあったプロパティのみをバッチ更新することはできません。 なぜなら、変更のあったプロパティがすべてのエンティティで同じだとは限らないからです。 最初の引数は、変更前の状態を持ったエンティティもしくはマップです。
Employee before = ...;

int count = sqlMapper.update(employee)
        .changedFrom(before)
        .execute();

2.4.8. 更新行数をチェックしない

バージョンによる楽観的排他チェックを行う場合、 更新できた行数が0だと org.springframework.dao.OptimisticLockingFailureException が発生します。

更新行数を正しく返さないJDBCドライバを使用する場合は、suppresOptimisticLockException() を呼び出すことで、更新できた行数のチェックを行わなくなります。

Employee before = ...;

int count = sqlMapper.update(employee)
        .suppresOptimisticLockException()
        .execute();

2.5. 削除

2.5.1. 1件削除

エンティティを削除する場合は、delete(…​)execute() を組み合わせて使用します。

  • insert(…​) の引数はエンティティのインスタンスを指定します。

  • execute() の戻り値は、削除した行数です。

  • 削除するときに、バージョンによる楽観的排他チェックができます。

    • 楽観敵排他エラーが発生したときは、例外 org.springframework.dao.OptimisticLockingFailureException がスローされます。

    • 詳しくは、 バージョン定義 を参照してください。

  • 識別子定義のなエンティティは delete(…​) で削除できません。

    • 代わりに、deleteFrom(…​) を使用してください。

int count = sqlMapper.delete(employee)
        .execute();

2.5.2. バッチ削除

複数のエンティティをバッチ削除する場合は、deleteBatch(…​)execute() を組み合わせて使用します。

  • execute() の戻り値は、削除した行数の配列です。

  • 削除するときに、バージョンによる楽観的排他チェックができます。

    • 楽観的排他エラーが発生したときは例外 org.springframework.dao.OptimisticLockingFailureException がスローされます。

    • 詳しくは、 バージョン定義 を参照してください。

  • 識別子定義のなエンティティは updateBatch(…​) で削除できません。

int[] countArray = sqlMapper.deleteBatch(employees)
        .execute();
処理時のバッチサイズは引数で指定したエンティティのサイズと同一になります。 バッチサイズを変更したい場合は、エンティティを分割して実行してください。

2.5.3. 削除条件の指定

任意の条件で削除する場合、deleteFrom(…​)execute() を使用します。

  • 条件を指定する場合は、where(…​) を使用します。

    • 条件を指定しなければ、全レコードが削除されるので注意してください。

MEmployee e = MEmployee.employee;

int count = sqlMapper.deleteFrom(e)
        .where(e.hireDate.before(LocalDate.of(2020, 5, 1)))
        .execute();

2.5.4. バージョンをチェックしないで削除する

バージョンをチェックしないで削除する場合は、ignoreVersion() を使用します。

削除条件を指定する deleteFrom(…​) を使用するときは、ignoreVersion() を指定することはできません。
int count = sqlMapper.delete(employee)
        .ignoreVersion()
        .execute();

2.5.5. 削除行数をチェックしない

バージョンによる楽観的排他チェックを行う場合、 削除できた行数が0だと org.springframework.dao.OptimisticLockingFailureException がスローされます。

削除行数を正しく返さないJDBCドライバを使用する場合は、suppresOptimisticLockException() を呼び出すことで、更新できた行数のチェックを行わなくなります。

削除条件を指定する deleteFrom(…​) を使用するときは、suppresOptimisticLockException() を使用することはできません。
Employee before = ...;

int count = sqlMapper.delete(employee)
        .suppresOptimisticLockException()
        .execute();

3. SQLによるクエリ実行

SQLによるクエリの実行方法を説明します。

  • SQLパーサには、SQLテンプレートの1つのライブラリである splate を使用しています。

    • 単純なSQLとしても使用できますが、SQLテンプレート独自の文法も使用できます。

    • 文法などの詳細な情報は、splateのドキュメントを参照してください。

3.1. 検索

3.1.1. 複数件検索

SQLを使って、複数件検索をする場合は、selectBySql(..)getResultList() を組み合わせます。

SqlTemplateContext templateContext = new MapSqlTemplateContext(
        Map.of("salary", new BigDecimal(2000)));

List<Employee> results = sqlMapper.selectBySql(
            Employee.class,
            "select * from employee where salary >= /*salary*/",
            templateContext)
        .getResultList();

3.1.2. 1件検索

SQLを使って1件検索をする場合は、 selectBySql(…​)getSingleResult() を組み合わせます。

  • 1件も見つからないときは、Springの例外 EmptyResultDataAccessException がスローされます。

  • 2件以上見つかった場合、Springの例外 IncorrectResultSizeDataAccessException がスローされます。

SqlTemplateContext templateContext = new MapSqlTemplateContext(
        Map.of("id", 10));

Employee result = sqlMapper.sqlMapper.selectBySql(
            Employee.class,
            "select * from employee where id = /*id*/",
            templateContext)
        .getSingleResult();

1件も見つからなかったときに例外をスローされないようにするには、getOptionalResult() を使用します。

  • 戻り値を java.util.Optinal で受け取ることができます。

  • ただし、2件以上見つかったときは、Springの例外 IncorrectResultSizeDataAccessException がスローされます。

    • 例外がスローされないように、メソッド limit(1) による件数を指定することをお勧めします。

SqlTemplateContext templateContext = new MapSqlTemplateContext(
        Map.of("id", 10));

Optional<Employee> result = sqlMapper.selectBySql(
            Employee.class,
            "select * from employee where id = /*id*/",
            templateContext)
        .getOptionalResult();

3.1.3. Streamによる検索

検索結果を多くの行を返しメモリの消費量が多くListでまとめて受け取ることが困難な場合は、フェッチによる参照として getResultStream() を使用します。

  • Streamは、必ずクローズするようにしてください。

Streamによるフェッチ検索
SqlTemplateContext templateContext = new MapSqlTemplateContext(
        Map.of("salary", new BigDecimal(2000)));

Stream<Employee> stream = sqlMapper.selectBySql(
            Employee.class,
            "select * from employee where salary >= /*salary*/",
            templateContext)
        .getResultStream();

// try-with-resourceでStreamをクローズする。
try (stream) {
    stream.forEarch(entity -> {
        // 任意の処理
    });
}

3.1.4. 検索結果の行数取得

SELECT COUNT(*) ~ による検索結果の行数を取得するには、getCountBySql(…​) を使用します。

SQLファイルで定義したSQLを、select count(*) from (<SQLテンプレートの内容>) に変換して実行されるため、count 句をSQLテンプレートに記載する必要はありません。
SqlTemplateContext templateContext = new MapSqlTemplateContext(
        Map.of("salary", new BigDecimal(2000)));

long count = sqlMapper.selectBySql(
            Employee.class,
            "select * from employee where salary >= /*salary*/",
            templateContext)
            getCount();

3.1.5. ページング

ページングを指定するには、以下のメソッドを使用します。

  • offset(int offset) : 最初に取得する行の位置を指定します。最初の行の位置は0になります。

  • limit(int limit) : 取得する行数を指定します。

  • SQLファイルで定義したSQLを、<SQLテンプレートの内容> offset <開始位置> limit <取得行数> に変換して実行されるため、offset/limit 句をSQLテンプレートに記載する必要はありません。

  • 並び順が一定になるように、SQLテンプレート内で並び順を指定しておく必要があります。

SqlTemplateContext templateContext = new MapSqlTemplateContext(
        Map.of("salary", new BigDecimal(2000)));

List<Employee> results = sqlMapper.selectBySql(
            Employee.class,
            "select * from employee where salary >= /*salary*/",
            templateContext)
        .offset(10)
        .limit(100)
        .getResultList();

3.2. 挿入・更新・削除

SQLを使ってエンティティを更新する場合は、 updateBySql(…​)execute() を組み合わせます。

  • 挿入、削除も updateBySql(…​) を使います。

  • execute() の戻り値は、更新した行数です。

SqlTemplateContext templateContext = new MapSqlTemplateContext(
        Map.of("salary", new BigDecimal(2000), "id", 5));

int count = sqlMapper.updateBySql(
            Employee.class,
            "update employee set salary = /*salary*/1000 */ where id = /*id*/1",
            templateContext)
        .execute();

4. SQLファイルによるクエリ実行

SQLファイルによるクエリの実行方法を説明します。

単純なSQLだとソースコードに直接記述したほうが、面倒くさくなくて楽(わざわざファイルを作る必要がない)ですが、複雑なSQLはファイルに記述した方がメンテナンスしやすくなります。

  • SQLファイルは、SQLテンプレートと呼ばれ、処理するライブラリとして、 splate を使用しています。

    • 文法などの詳細情報は、splateのドキュメントを参照してください。

4.1. SQLファイルのパス

SQLテンプレートのファイルパスは、Spring Framework の ResourceLoader を使用しているため接頭語を付けることで様々なリソースにアクセスできます。

  • classpath:~ - クラスパス上から読み込みます。例)classpath:/sql/hoge.sql

  • file:~ - システムファイルから読み込みます。例)file:c:/sql/hoge.sql

  • http:~ - ネットワーク上のURLから読み込みます。例)http://hoge.com/sql/hoge.sql

  • なし - Spring Framework の ApplicationContext のインスタンスに依存します 。例) /sql/hoge.sql

SQLテンプレートの記述
select * from employee
where
salary >= /*salaryMin*/1000
and salary <= /*salaryMax*/2000
ファイルに定義したSQLテンプレートの指定
SelectParam beanParam = new SelectParam();
beanParam.salaryMin = new BigDecimal(1200);
beanParam.salaryMax = new BigDecimal(1800);

SqlTemplateContext templateContext = new BeanPropertySqlTemplateContext(beanParam);

List<Employee> results = sqlMapper.selectBySqlFile(
            Employee.class,
            "sqltemplate/employee/selectAll.sql",
            templateContext)
        .getResultList();

4.1.1. SQLファイルの文字コードとキャッシュ設定

SQLファイルの設定を変更したい場合は、SQLテンプレートの設定可能なプロパティ を参照して設定変更をしてください。

  • SQLファイルのデフォルトの文字コードは、UTF-8 です。

  • SQLファイルはデフォルトでパースした結果をキャッシュしておき次回実行時の性能向上します。

4.2. 検索

4.2.1. 複数件検索

SQLファイルを使って、複数件検索をする場合は、selectBySqlFile(..)getResultList() を組み合わせます。

SqlTemplateContext templateContext = new MapSqlTemplateContext(
        Map.of("salary", new BigDecimal(2000)));

List<Employee> results = sqlMapper.selectBySqlFile(
            Employee.class,
            "examples/sql/employee/selectAll.sql",
            templateContext)
        .getResultList();

4.2.2. 1件検索

SQLファイルを使って1件検索をする場合は、 selectBySqlFile(…​)getSingleResult() を組み合わせます。

  • 1件も見つからないときは、Springの例外 EmptyResultDataAccessException がスローされます。

  • 2件以上見つかった場合、Springの例外 IncorrectResultSizeDataAccessException がスローされます。

SqlTemplateContext templateContext = new MapSqlTemplateContext(
        Map.of("id", 10));

Employee result = sqlMapper.selectBySqlFile(
            Employee.class,
            "examples/sql/employee/selectSingle.sql",
            templateContext)
        .getSingleResult();

1件も見つからなかったときに例外をスローされないようにするには、getOptionalResult() を使用します。

  • 戻り値を java.util.Optinal で受け取ることができます。

  • ただし、2件以上見つかったときは、Springの例外 IncorrectResultSizeDataAccessException がスローされます。

    • 例外がスローされないように、メソッド limit(1) による件数を指定することをお勧めします。

SqlTemplateContext templateContext = new MapSqlTemplateContext(
        Map.of("id", 10));

Optional<Employee> result =sqlMapper.sqlMapper.selectBySqlFile(
            Employee.class,
            "examples/sql/employee/selectSingle.sql",
            templateContext)
        .getOptionalResult();

4.2.3. Streamによる検索

検索結果を多くの行を返しメモリの消費量が多くListでまとめて受け取ることが困難な場合は、フェッチによる参照として getResultStream() を使用します。

  • Streamは、必ずクローズするようにしてください。

Streamによるフェッチ検索
SqlTemplateContext templateContext = new MapSqlTemplateContext(
        Map.of("salary", new BigDecimal(2000)));

Stream<Employee> stream = sqlMapper.selectBySqlFile(
            Employee.class,
            "examples/sql/employee/selectAll.sql",
            templateContext)
        .getResultStream();

// try-with-resourceでStreamをクローズする。
try (stream) {
    stream.forEarch(entity -> {
        // 任意の処理
    });
}

4.2.4. 検索結果の行数取得

SELECT COUNT(*) ~ による検索結果の行数を取得するには、getCountBySqlFile(…​) を使用します。

SQLファイルで定義したSQLを、select count(*) from (<SQLテンプレートの内容>) に変換して実行されるため、count 句をSQLテンプレートに記載する必要はありません。
SqlTemplateContext templateContext = new MapSqlTemplateContext(
        Map.of("salary", new BigDecimal(2000)));

long count = sqlMapper.selectBySqlFile(
            Employee.class,
            "examples/sql/employee/selectAll.sql",
            templateContext)
            getCount();

4.2.5. ページング

ページングを指定するには、以下のメソッドを使用します。

  • offset(int offset) : 最初に取得する行の位置を指定します。最初の行の位置は0になります。

  • limit(int limit) : 取得する行数を指定します。

  • SQLファイルで定義したSQLを、<SQLテンプレートの内容> offset <開始位置> limit <取得行数> に変換して実行されるため、offset/limit 句をSQLテンプレートに記載する必要はありません。

  • 並び順が一定になるように、SQLテンプレート内で並び順を指定しておく必要があります。

SqlTemplateContext templateContext = new MapSqlTemplateContext(
        Map.of("salary", new BigDecimal(2000)));

List<Employee> results = sqlMapper.selectBySqlFile(
            Employee.class,
            "examples/sql/employee/selectAll.sql",
            templateContext)
        .offset(10)
        .limit(100)
        .getResultList();

4.3. 挿入・更新・削除

SQLファイルを使ってエンティティを更新する場合は、 updateBySqlFile(…​)execute() を組み合わせます。

  • 挿入、削除も updateBySqlFile(…​) を使います。

  • execute() の戻り値は、更新した行数です。

SqlTemplateContext templateContext = new MapSqlTemplateContext(
        Map.of("salary", new BigDecimal(2000), "id", 5));

int count = sqlMapper.updateBySqlFile(
            Employee.class,
            "examples/sql/employee/update.sql",
            templateContext)
        .execute();

5. エンティティの定義方法

アノテーションを使って定義します。

5.1. アノテーション一覧

表 7. アノテーション一覧
アノテーション 概要 詳細リンク

@Entity

クラスがエンティティであることを定義します。

詳細

@Table

テーブル情報を補完し定義します。

詳細

@MappedSuperclass

エンティティの親クラスであることを定義します。

詳細

@Column

カラム情報を定義します。

詳細

@Id

プロパティがID(識別子/主キー)であることを定義します。

詳細

@EmbeddedId

プロパティが埋め込みID(複合主キー)であることを定義します。

詳細

@Embeddable

埋め込みクラスであることを定義します。

詳細

@Version

楽観的排他キーとしてのバージョンキーであることを定義します。

詳細

@Transient

プロパティが永続化対象外であることを定義します。

詳細

@Lob

プロパティがラージオブジェクトであることを定義します。

詳細

@Temporal

時制のタイプを変換規則を定義します。時制のタイプが不明なときに使用します。

詳細

@Enumerated

列挙型のタイプの変換規則を定義します。

詳細

@Converter

独自のタイプの変換方法を定義します。

詳細

@GeneratedValue

ID(主キー)の値の採番方法を定義します。

詳細

@SequenceGenerator

ID(主キー)の値の採番方法がシーケンスを用いるときに、詳細を定義します。

詳細

@TableGenerator

ID(主キー)の値の採番方法がテーブルを用いるときに、詳細を定義します。

詳細

@CreatedAt

エンティティを新規に保存するときに、監査情報としての日時を自動的に設定する対象であることを定義します。

詳細

@UpdatedAt

既存のエンティティを更新するときに、監査情報としての日時を自動的に設定する対象であることを定義します。

詳細

@CreatedBy

エンティティを新規に保存するときに、監査情報としての作成者を自動的に設定する対象であることを定義します。

詳細

@UpdatedBy

既存のエンティティを更新するときに、監査情報としての更新者を自動的に設定する対象であることを定義します。

詳細

5.2. エンティティとテーブルのマッピング方法

エンティティはPOJOとして定義します。

  • エンティティであるクラスには、必ず アノテーション @Entity を付与します。

  • 引数ありのコンストラクタを定義した場合、デフォルトコンストラクタを必ず定義します。

  • フィールドの修飾子がpublic以外の場合は、getter/setterメソッドを必ず定義します。

基本的なエンティティの定義
@Entity
public class User {

    @Id
    private long id;

    private String name;

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

}

手動でエンティティクラスを作成する際には、 Lombok を使用して記述減らすことをお勧めします。

Lombokを使用したエンティティの定義
@Data
@Entity
public class User {

    @Id
    private long id;

    private String name;
}

5.2.1. エンティティとテーブル名の規約

テーブル名とのマッピングは、エンティティのクラス名を元に決まります。

  • Javaのクラスであるエンティティは、キャメルケースで定義します。

    • 例:UserAttribute

  • DBのテーブルは、スネークケースで定義します。

    • 例:USER_ATTRIBUTE ※大文字・小文字の区別はなし。

エンティティのクラス名とテーブル名が一致しない場合は、アノテーション @Table を使用します。

  • アノテーション @Table では、その他に、スキーマ名やカタログ名を定義できます。

@Entity
@Table(name = "USER_ATTR")
public class UserAttribute {

    private long userId;

    private String address;

    // getter/setterメソッドは省略

}

独自の命名規則を使用するときは、NamingRule の実装クラスをSpring Beanとしてコンテナに定義します。 デフォルトでは、DefaultNamingRule が使用されています。

5.3. フィールドとカラムのマッピング方法

エンティティクラスのフィールドは、デフォルト永続化対象です。

  • フィールドの修飾子が、非public(private/protected/なし(デフォルト))の場合は、publicなアクセッサメソッド(setter/getter)を定義します。

  • フィールドの修飾子がpublicの場合は、アクセッサメソッドは不要です。

  • 主キーとなるカラムには、アノテーション @Id を付与します。

アクセッサメソッド必要なフィールド
@Entity
public class User {

    @Id
    private long id;

    private String name;

    protected LocalDate birthday;

    String address;

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public LocalDate getBirthday() {
        return birthday;
    }

    public void setBirthday(LocalDate birthday) {
        this.birthday = birthday;
    }

    public String getAddress() {
        return address;
    }

    public void setAddress(String address) {
        this.address = address;
    }
}
アクセッサメソッド不要なフィールド
@Entity
public class User {

    public String name;

    public LocalDate birthday;

    public address;
}

5.3.1. サポートするクラスタイプ

フィールドの型は次のいずれかである必要があります。

DBの型は、JDBCドライバがサポートしていれば、対応しているJava型にマッピングが可能です。

表 8. 標準でサポートするタイプ
Java型 一般的なDB型 備考

String

CHAR / VARCHAR / CLOB

boolean / Boolean

BOOLEAN/数値型

DBの数値型をマッピングする場合は、

short / Short

TINYINT / SMALLINT

int / Integer

INTEGER

float / Float

FLOAT/REAL

double / Double

DOUBLE

java.math.BigDecimal

DECIMAL / NUMERIC

java.sql.Date / java.time.LocalDate / java.util.Date

DATE

java.util.Dateの場合は、@Temporal でタイプを指定する必要がある。

java.sql.Time / java.time.LocalTime / java.util.Date

TIME

java.util.Dateの場合は、@Temporal でタイプを指定する必要がある。

java.sql.Timestamp / java.time.LocalDateTime / java.util.Date

TIMESTAMP

java.util.Dateの場合は、@Temporal でタイプを指定する必要がある。

byte[]

BLOB

java.sql.Blob

BLOB

java.sql.Clob

CLOB

java.util.UUID

VARCHAR

UUID型をサポートしているDBも多くありますが、JDBCドライバーがそれぞれ対応方法が異なるため、SqlMapperではDB側は文字列型に変換して永続化します。もし、DB固有のUUIDを使用したい場合は、@Convert で独自の型変換処理を実装して対応する必要があります。

列挙型

CHAR / VARCHAR / TINTYINT / SMALLINT / INTEGER

列挙型をマッピングする際には、@Enumerated でタイプを指定する必要がある。

5.3.2. フィールドとカラム名の規約

カラムとのマッピングは、フィールド名を元に決まります。

  • Javaのフィールドは、キャメルケースで定義します。

    • 例:firstName

  • DBのカラムは、スネークケースで定義します。

    • 例:FIRST_NMAE ※大文字・小文字の区別はなし。

フィールド名とカラム名が一致しない場合は、アノテーション @Column を使用します。

  • アノテーション @Column では、その他に、スキーマ名やカタログ名を定義できます。

@Entity
public class UserAttribute {

    @Column(name="sub_id")
    private long userId;

    private String address;

    // getter/setterメソッドは省略

}

独自の命名規則を使用するときは、NamingRule の実装クラスをSpring Beanとしてコンテナに定義します。 デフォルトでは、DefaultNamingRule が使用されています。

5.4. 識別子(ID)の自動採番

主キーなどの識別子(ID)の値をを自動生成したい場合は、フィールドにアノテーション @GeneratedValue を付与します。

  • 属性 strategy にて生成方法を指定します。

  • 属性 generator にて、独自の生成方法を実装を指定することもできます。

  • @GenratedValue は、識別子を定義するアノテーション @Id とともに付与する必要があります。

@Entity
public class User {

    @Id
    @GeneratedValue(strategry=GenerationType.IDENTITY)
    private long id;

    private String name;

    // setter/getterメソッドは省略
}
表 9. 識別子の生成方法
指定方法 概要

@GeneratedValue(strategry=GenerationType.AUTO)

データベースにより生成方法が自動手的に選択されます。

  • 選択される方法は、DBダイアクレクト Dialect#getDefaultGenerationType() により決定されます。

  • strategyを指定しなければ、AUTO と解釈されます。

@GeneratedValue(strategry=GenerationType.IDENTITY)

IDENTITY列を使用して採番を行います。

  • テーブルのカラムの定義も合わせてIDENTITYにする必要があります。

  • IDENTITY列をサポートしているDBMSのみ利用できます。

@GeneratedValue(strategy=GenerationType.SEQUENCE)

  • アノテーション @SequenceGeneratorにて、詳細に定義することができます。

  • シーケンスをサポートしているDBMSのみ利用できます。

@GeneratedValue(strategy=GenerationType.TABLE)

  • アノテーション @TableGenerator により詳細に定義できます。

  • 設定可能なプロパティ にて全体の設定を変更することもできます。

@GeneratedValue(strategy=GenerationType.UUID)

java.util.UUID を使用しランダムなセキュアな値を生成します。

@GeneratedValue(generator=<独自実装>)

独自の採番方式として、Springコンテナに登録されているBean名を指定します。

  • 指定するSpring Beanは、インタフェース com.github.mygreen.sqlmapper.core.id.IdGenerator を実装する必要があります。

5.4.1. シーケンスによる識別子(ID)の自動採番

  • シーケンスをサポートしているDBMSで利用できます。

  • 利用する際には、事前に採番用のシーケンスを定義しておく必要があります。

    • シーケンス名は、アノテーション @SequenceGenerator でカスタマイズできます。

    • アノテーション @SequenceGenerator でシーケンス名を指定しない場合は、「<テーブル名>_<カラム名>」のシーケンス名を使用します。

  • 文字列にマッピングする際、@SequenceGenerator(format="xxx") でフォーマットを指定できます。

    • フォーマットは、java.text.DecimalFormat で解釈可能な書式を指定します。

採番用シーケンスのDDL
-- シーケンス名(<テーブル名>_<カラム名>)
CREATE SEQUENCE USER_ID start with 1;
標準的な使い方
@Entity
public class User {

    @Id
    @GeneratedValue(strategry=GenerationType.SEQUENCE)
    private long id;

    private String name;
    // setter/getterメソッドは省略
}
文字列型にフォーマット指定で採番する場合
@Entity
public class User {

    @Id
    @SequenceGenerator(format="0000000000")
    @GeneratedValue(strategry=GenerationType.SEQUENCE)
    private String id;

    private String name;
    // setter/getterメソッドは省略
}

5.4.2. テーブルによる識別子(ID)の自動採番

IDENTITYやシーケンスなどの機能を使わないで、テーブルを使って採番を行うため、全てのDMBSにて利用できます。

  • 利用する際には、事前に採番用のテーブルを定義しておく必要があります。

    • テーブル名、カラム名などは、アノテーション @TableGenerator または、プロパティ によってカスタマイズできます。

  • 文字列にマッピングする際、@TableGenerator(format="xxx") でフォーマットを指定できます。

    • フォーマットは、java.text.DecimalFormat で解釈可能な書式を指定します。

採番用テーブルのDDL
CREATE TABLE IF NOT EXISTS ID_SEQUENCE (
    -- キー名(<テーブル名>_<カラム名>)
	SEQUENCE_NAME varchar(255) primary key,
    -- 採番した値
	SEQUENCE_VALUE bigint NOT NULL
);
標準的な使い方
@Entity
public class User {

    @Id
    @GeneratedValue(strategry=GenerationType.TABLE)
    private long id;

    private String name;
    // setter/getterメソッドは省略
}
文字列型にフォーマット指定で採番する場合
@Entity
public class User {

    @Id
    @TableGenerator(format="0000000000")
    @GeneratedValue(strategry=GenerationType.TABLE)
    private String id;

    private String name;
    // setter/getterメソッドは省略
}
テーブルによる識別子の自動生成の設定
  • カスタマイズする場合は、アノテーション @TableGenerator または、プロパティファイルにて定義します。

  • プロパティファイで定義する場合は、アプリ全体に反映されます。

カスタマイズする場合
@Entity
public class User {

    @Id
    @GeneratedValue(strategry=GenerationType.TABLE)
    @TableGenerator(table="USER_GEN", pkColumn="GEN_NAME", valueColumn="GEN_VALUE")
    private long id;

    private String name;
    // setter/getterメソッドは省略
}
表 10. テーブルによる識別子生成のカスタマイズ可能な項目
アノテーションの属性 プロパティのキー 初期値 説明

table

sqlmapper.table-id-generator.table

ID_SEQUENCE

生成したIDの値を永続化するテーブル名。

schema

sqlmapper.table-id-generator.schema

- (デフォルト値は空)

生成したIDの値を永続化するテーブルが定義されているスキーマ名。

catalog

sqlmapper.table-id-generator.catalog

- (デフォルト値は空)

生成したIDの値を永続化するテーブルが定義されているカタログ名。

pkColumn

sqlmapper.table-id-generator.pk-column

SEQUENCE_NAME

生成したIDの名称を保持するカラム名。

valueColumn

sqlmapper.table-id-generator.value-column

SEQUENCE_VALUE

生成したIDの値を保持するカラム名。

allocationSize

sqlmapper.table-id-generator.allocation-size

50

採番を行う際に、予め指定した値分を払い出しておく値です。値を1にすると、毎回レコードを更新することになり、オーバーヘッドが発生します。

initialValue

sqlmapper.table-id-generator.initial-value

0

生成するIDの値の初期値。

5.4.3. 独自実装による識別子(ID)の自動採番

独自の採番処理の実装を指定する方法を説明します。

  • @GeneratedValue の属性 generator として、Springのコンテナに登録されているBean名を指定します。

  • Spring Beanは、インタフェース com.github.mygreen.sqlmapper.core.id.IdGenerator を実装する必要があります。

独自の識別子の生成処理の指定
@Entity
public class User {

    @Id
    @GeneratedValue(generator="myIdGenerator")
    private long id;

    private String name;

    // setter/getterメソッドは省略
}
独自の識別子の生成処理の実装
@Component
public class MyIdGenerator implements IdGenerator {

    /**
     * サポートしているクラスタイプ
     */
    private static final List<Class<?>> SUPPORTED_TYPE_LIST = List.of(Long.class, String.class);

    /**
     * 生成するIDのクラスタイプ
     */
    private final Class<?> requiredType;

    @Override
    public boolean isSupportedType(Class<?> type) {
        return SUPPORTED_TYPE_LIST.contains(type);
    }

    @Override
    public Class<?>[] getSupportedTypes() {
        return SUPPORTED_TYPE_LIST.toArray(new Class[SUPPORTED_TYPE_LIST.size()]);
    }

    @Override
    public Object generateValue(IdGenerationContext context) {

        //TODO: 識別子の実装
        return ...;
    }
}

5.5. 複合識別子(複合主キー)の定義

主キーであることを指定には、@Id を使用しますが、複合主キーの場合は複数個指定します。

複合主キーの定義
@Entity
public class Sample {

    @Id
    private String id2;

    @Id
    private Integer id2;

    private String value;

    // getter/setterは省略

}

また、埋め込み型として別クラスに抽出して定義することでもできます。

  • プロパティには、アノテーション @EmbeddedId を付与します。

  • 複合キーを定義しているクラスには、アノテーション @Embeddable を付与します。

    • フィールドには、@Id を付与する必要はりません。

    • エンティティクラスと同様、引数ありのコンストラクタを定義した場合、デフォルトコンストラクタを必ず定義します。

    • @Column にて、カラム名のとのマッピングを定義もできます。

    • 埋め込みクラスは、staticな内部クラスとしても定義できます。

埋め込み型の複合主キーの定義
@Entity
public class Sample {

    @EmbeddedId
    private SamplePK pk;

    private String value;

    // getter/setterは省略

    /**
     * 埋め込み主キーのクラス
     */
    @Embeddable
    public static class SamplePK {

        private String id2;

        private Integer id2;

        // getter/setterは省略
    }

}

5.6. 継承したエンティティを定義する

登録日時や更新日など、各テーブルに共通的なカラムを持つ場合は、継承専用のクラスに集約できます。

  • 継承元の親クラスに、アノテーション @MappedSuperclass を付与すると、フィールド情報が継承先の子クラスに引き継ぐことができます。

    • @MappedSuperclass を付与したクラスは、エンティティクラスとしては使用できません。

継承モデル
/**
 * 継承元の親クラス
 */
@MappedSuperclass
public abstract class AbstractEntity {

    @CreatedAt
    protected Timestamp createdAt;

    @ModdifiedAt
    protected Timestamp updatedAt;

    @Version
    protected long version;

    // getter/setterは省略
}

/**
 * 継承先の子クラス
 */
@Entity
public class User extends AbstractEntity {

    @Id
    private Long id;

    private String name;

    // getter/setterは省略
}

5.7. 楽観的排他チェック用のバージョン

エンティティ更新時にバージョンキー(排他キー)を利用して楽観的排他ロックを行う場合、アノテーション @Version を付与します。

  • 楽観的排他チェックとは、排他処理のために、DBないしテーブルにロックをかけるのではなく、更新時にレコードが既に他のトランザクションによって更新されていないことを検出する手法です。

  • レコードを更新するときに、更新対象のレコードのバージョンキーと更新元のデータの値を比較し同じであれば「+1」し、異なれば既に他のトランザクションによって更新されていると判断し例外をスローします。

  • 楽観的排他チェックの制約に違反した場合、org.springframework.dao.OptimisticLockingFailureException がスローされます。

  • バージョンキーとして定義可能なフィールドの型は、int/Integer または、long/Long です。

  • 挿入時の初期値は、0 です。

    • エンティティのバージョンキーに null 以外の値が設定されていれば、設定されている値で挿入されます。

public class User {

    @Id
    private String id;

    private String name;

    @Version
    private long version;

    // getter/setterは省略
}

5.8. 列挙型を扱う

フィールドの型に列挙型を指定したとき、DBカラムの型としては、Enum#name() で取得した値で永続化されます。

アノテーション @Enumerated にて、列挙型の変換規則を指定することができます。

  • アノテーションの属性 value の値として、EnumType を指定することで、どの値を採用するか定義できます。

    • 属性 value はデフォルト属性なので、省略して指定することができます。

public class User {

    @Id
    private long id;

    private String name;

    @Enumerated(EnumType.ORDINAL)
    private UserType type;
}

// 列挙型の定義
public enum UserType {
    CUSTOMER, ADMIN, DEVELOPER;
}

5.8.1. 列挙型の任意のメソッドの値を使用する場合

TODO

5.9. 時制を扱う

フィールドのタイプが、java.sql.Date / java.sql.Time などの場合、マッピングするSQLの型がそれぞれ DATETIME と明確なため自動的にマッピングできます。

しかし、java.util.Date の場合は、日付/時間/日時なのか不明なため、アノテーション @Temporal を使用し、マッピング規則を指定する必要があります。

@Entity
public class Sample {

    private java.sql.Date datetime1;

    // 時制のタイプが不明なので、アノテーションを付与する必要がある。
    @Temporal(TemporalType.TIMESTAMP)
    private java.util.Date datetime2;

    //setter/getterは省略
}

また、本ライブラリでは、Java8で追加された、Date and Time APIの LocalDate / LocalTIme / LocalDateTime も扱うことができます。

表 11. 時制型の定義方法
DB型 Java型

TIMESTAMP

  • java.sql.Timestmap

  • java.time.LocalDateTime

  • @Temporal(TemporalType.TIMESTAMP) java.util.Date

DATE

  • java.sql.Date

  • java.time.LocalDate

  • @Temporal(TemporalType.DATE) java.util.Date

TIME

  • java.sql.Time

  • java.time.LocalTime

  • @Temporal(TemporalType.TIME) java.util.Date

5.10. ラージオブジェクトを扱う

ラージオブジェクトをマッピングする場合は、アノテーション @Lob を付与します。

  • BLOB(Binay Large Object)とCLOB(Character Large Object)の区別なく同じアノテーションを付与します。

  • DBMSによって、BLOBとCLOBのデータ型は異なるため、詳しくは利用するDBMSのドキュメント及び、JDBCドライバを参照してください。

ラージオブジェクトのマッピング
@Entity
public class Sample {

    @Id
    private String id;

    /**
     * BLOBのマッピング
     */
    @Lob
    private byte[] imageData;

    /**
     * CLOBのマッピング
     */
    @Lob
    private String textData;

    // getter/setterは省略

}
表 12. ラージオブジェクトの対応
DB型 Java型

BLOB

byte[] / java.sql.Blob

CLOB

String / java.sql.Clob

5.11. 永続化対象外を指定する

SqlMapperでは、フィールドを定義すると、標準で永続化対象となります。 そのため、該当するカラムがテーブルに存在しないとSQL実行エラーとなる場合があります。

永続化対象から外したい場合は、そのフィールドにアノテーション @Transient を付与します。

  • アノテーション @Transient を付与したフィールドはメタモデルの生成からも除外されます。

  • Javaの修飾子 transient を指定しても、SqlMapperの永続化対象外と同じ効果を得られます。

    • 修飾子 transient は、Webセッションにおいても永続化、直列化(serialize)から対象外となるため、SqlMapperとしては使用しないで、アノテーション @Transient の方を利用することをお勧めします。

    • 修飾子 transient の使い道は、インタフェース java.io.Serializable を実装していないクラスで、値を格納するようなEntityやDTOではないSpringBeanのような機能を持ったインスタンスを永続化対象から外すために使用します。

@Entity
public class User {

    @Id
    private String id;

    private String name;

    @Transient
    private UserAttribute attribute;

    // getter/setterは省略
}

5.12. 監査情報を記録する

5.12.1. 作成/更新日時の自動設定

レコードの挿入時、更新時に自動的にタイムスタンプを設定したい時があります。

その場合、フィールドにアノテーション @CreatedAt / @UpdatedAt を付与します。

  • アノテーションを付与するには時制型である必要があります。

  • @CreatedAt は、レコードの作成、すなわち、SQLの INSERT 文を実行するときにシステム日時を設定します。

  • @UpdatedAt は、レコードの作成時と更新時、すなわち、SQLの INSERT / UPDATE 文を実行するときにシステム日時を設定します。

この機能は、SQL実行前にエンティティのフィールドの値を更新して実行します。 そのため、何かしらのエラーによりDBのロールバックがされたときは、更新されたエンティティのフィールドの値は戻らないので注意してください。
@Entity
public class User {

    @Id
    private long id;

    private String name;

    @CreatedAt
    private Timestamp createdAt;

    @UpdatedAt
    private Timestamp updatedAt;

    // getter/setterは省略
}

5.12.2. 作成/更新ユーザの永続化方法

レコードの挿入時、更新時に自動的に操作したユーザ情報を設定したい時があります。

その場合、フィールドにアノテーション @CreatedBy / @UpdatedBy を付与します。

  • 本機能を利用するには、インタフェース AuditorProvider を実装したクラスをSpringBeanに登録する必要があります。

    • Spring Securityを利用している場合は、SecurityContextHolder からユーザ情報を取得します。

  • @CreatedBy は、レコードの作成、すなわち、SQLの INSERT 文を実行するときにユーザ情報を設定します。

  • @UpdatedBy は、レコードの作成時と更新時、すなわち、SQLの INSERT / UPDATE 文を実行するときにユーザ情報を設定します。

この機能は、SQL実行前にエンティティのフィールドの値を更新して実行します。 そのため、何かしらのエラーによりDBのロールバックがされたときは、更新されたエンティティのフィールドの値は戻らないので注意してください。
エンティティの定義
@Entity
public class User {

    @Id
    private long id;

    private String name;

    @CreatedAt
    private String createdBy;

    @UpdateddAt
    private String updatedBy;

    // getter/setterは省略
}
ユーザ情報の取得の実装例
import java.util.Optional;

import com.github.mygreen.sqlmapper.core.audit.AuditorProvider;

import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;

@Component
public class MyAuditorProvider<String> implements AuditorProvider {

    /**
     * 現在の監査人を取得します。
     *
     * @return 現在の監査人。
     */
    Optional<String> getCurrentAuditor() {
        Authentication authentication =
                SecurityContextHolder.getContext().getAuthentication();
        if (authentication == null || !authentication.isAuthenticated()) {
            // 未ログインのときのユーザ名
            return "unknown";
        }

        LoginUser user = (LoginUser) authentication.getPrincipal();
        return Optional.ofNullable(user.getName());
    }
}

5.13. 独自の型をマッピングする

本ライブラリで未サポートのJava型にマッピングする場合、アノテーション @Convert を付与します。

  • 属性 type にて、変換処理を行う ValueType の実装クラスを指定します。

  • 属性 name を指定した場合、ValueType の実装をSpringのコンテナに登録されているSpringBeanを取得します。

エンティティの定義
@Entity
public class User {

    @Id
    private String id;

    private String name;

    @Convert(type=UrlType.class)
    private URL url;

    // getter/setterは省略
}
変換処理の実装
import java.net.URL;
import java.sql.ResultSet;
import java.sql.SQLException;

import com.github.mygreen.sqlmapper.core.type.ValueType;

public class UrlType implements ValueType<URL> {

    /**
     * カラムの値を返します。(DB -> Javaへの変換処理)
     */
    @Override
    public URL getValue(ResultSet rs, int columnIndex) throws SQLException {

        String value = rs.getString(columnIndex);
        if(value == null) {
            return null;
        }

        return new URL(value);
    }

    /**
     * SQLのパラメータ変数として値を取得します。(Java -> DBへの変換処理)
     * <p>JDBCが対応していないタイプの場合は、対応している値に変換します。</p>
     * <p>{@link SqlParameterValue} として返すことで、特殊な値を対応することができます。</p>
     */
    @Override
    public Object getSqlParameterValue(URL value) {
        return value != null ? value.toString() : null;
    }

}

6. メタモデル

6.1. メタモデルとは

エンティティのメタモデルとは、Criteria APIを使ってクエリを組み立てる際の補助的なクラスです。

次のようなエンティティクラスが定義されているとします。

エンティティのコード
@Entity
public class Employee {

    @Id
    public Integer id;

    public String name;

    public Integer age;

    @Version
    public Integer version;
}

上記のエンティティクラスに対するメタモデルクラスは、MEmployee のコードになります。

エンティティクラスと同じ名前のプロパティ id / name / age / vesion を持ちます。

メタモデルのコード
public class MEmployee extends EntityPathBase<Employee> {

    public static final MEmployee employee = new MEmployee("employee");

    public MEmployee(Class<? extends Employee> type, String name) {
        super(type, name);
    }

    public MEmployee(String name) {
        super(Employee.class, name);
    }

    public final StringPath id = createString("id");

    public final StringPath name = createString("name");

    public final NumberPath<Integer> age = createNumber("age", Integer.class);

    public final NumberPath<Long> version = createNumber("version", Long.class);
}

Criteria APIを使用して検索するとき、メタモデルクラスは検索条件を組み立てるときに使用できます。

プロパティのクラスタイプによって、予めSQLの演算子に対応するメソッドが実装されており、迷うことなくクエリを組み立てることができます。

Criteria APIの検索条件の組み立て
MEmployee e = MEmployee.employee;

List<Employee> results = sqlMapper.selectFrom(e)
        .where(e.name.lower().starts("yamada").and(e.age.beteween(20, 30)))
        .getResultList();

組み立てられるSQLは次のようになります。

select T1_.ID, T1_.NAME, T1_.AGE, T1_.VERSION  from EMPLOYEE T1_ where lower(T1_.NAME) like ? and T1.AGE BETWEEN ? and ?
  • メタモデルを使用すると、演算子に対するメソッドの引数として、ジェネリクスを使用しているため、ある程度 型安全(タイプセーフ) に利用できます。

    • 例えば、String型のプロパティ name に対するメソッド starts(…​) は、文字列型を渡す必要がありますが、数値型を渡すとコンパイルエラーとなります。

  • また、メタモデルの他の利点としてテーブルのカラムを変更する際、エンティティのプロパティの名称も変更しますが、Criteria APIで使用している個所がコンパイルエラーとなり、 影響個所 がすぐにわかります。さらに、プロパティ名の綴り間違など防ぐことができます。

本ライブラリのCriteria APIの型安全は、プロパティのクラスタイプしか保障しません。 例えば、同じ文字列型であるが電話番号と氏名は別な意味であり、ドメインという意味的な範囲は保障できません。

6.2. 自動生成のカスタマイズ

エンティティのメタモデルに対する自動生成の設定の基本は、セットアップ を参照してください。

  • メタモデルの出力ディレクトリは、タグ <outputDirectory> で指定します。

  • メタモデルのソースの文字コードは、タグ <sourceEncoding> で指定します。

    • メタモデルのクラス名の接頭語/接尾語をカスタマイズを行う場合は、タグ <option> の中で指定します。

    • <sqlmapper.prefix> : メタでモルのクラス名の接頭語。

      • デフォルトは、M です。

    • <sqlmapper.suffix> : メタでモルのクラス名の接尾語。

      • デフォルトは、空文字です。

<plugin>
	<groupId>com.mysema.maven</groupId>
	<artifactId>apt-maven-plugin</artifactId>
	<version>1.1.3</version>
	<executions>
		<execution>
			<phase>generate-sources</phase>
			<goals>
				<goal>process</goal>
			</goals>
			<configuration>
                <!-- ▼▼▼メタモデルの出力ディレクトリ▼▼▼ -->
				<outputDirectory>target/generated-sources/java</outputDirectory>
				<logOnlyOnError>false</logOnlyOnError>
				<processors>
					<processor>com.github.mygreen.sqlmapper.apt.EntityMetamodelProcessor</processor>
				</processors>
                <!-- ▼▼▼メタモデルのソースの文字コード▼▼▼ -->
				<sourceEncoding>UTF-8</sourceEncoding>
				<options>
                    <!-- ▼▼▼メタモデルの接頭語▼▼▼ -->
					<sqlmapper.prefix>M</sqlmapper.prefix>
                    <!-- ▼▼▼メタモデルの接尾語▼▼▼ -->
					<sqlmapper.suffix></sqlmapper.suffix>
				</options>
			</configuration>
		</execution>
	</executions>
    <dependencies>
	    <dependency>
			<groupId>com.github.mygreen.sqlmapper</groupId>
			<artifactId>sqlmapper-apt</artifactId>
		</dependency>
    </dependencies>
</plugin>

7. トランザクション

7.1. 宣言的トランザクション管理

Spring Frameworkのアノテーション @Transactional を使用した宣言的トランザクションの簡単な使用方法を説明します。

7.1.1. Spring Frameworkの設定

  • 機能を有効にするには、SpringFrameworkのアノテーション @EnableTransactionManagementJavaConfigクラスに付与します。

  • アノテーション @EnableTransactionManagement を使用するには、ライブラリ spring-tx が必要ですが、SqlMapperの依存関係として追加されているため、Maven/Gradleなどを使用している場合は自動的に読み込まれているはずです。

宣言的トランザクションを有効にするJavaConfigの定義
import javax.sql.DataSource;

import org.springframework.context.annotation.Configuration;
import org.springframework.transaction.annotation.EnableTransactionManagement;

import com.github.mygreen.sqlmapper.core.config.SqlMapperConfigurationSupport;


@EnableTransactionManagement
@Configuration
public class SqlMapperConfig extends SqlMapperConfigurationSupport {

    // 省略
}

7.1.2. Spring Bootの設定

Spring BootのSqlMapper専用のstarter を使っている場合は、特に設定は必要ありません。

7.1.3. 使い方

  • トランザクション境界のメソッドにアノテーション @Transactional を付与します。

  • 参照しか行わない場合は属性 readOnly=true を設定しておくことで、もし、間違ってDBを更新する処理を実行してしまったときに例外がスローされデータを守ることができます。

    • ただし、JDBCドライバー、TransactionManagerの種類によっては例外がスローされないこともあります。

    • 参照専用の場合は、必ずしも必須ではありませんが、間違って更新することを防ぐためにも付与することをお勧めします。

  • アノテーション @Transactional が適用されるデフォルトの条件としては、publicメソッドのみです。

    • protected/private/パッケージprivateメソッドに @Transactional` を付けてもエラーは発生しません。

宣言的トランザクションの使用例
import java.util.List;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.annotation.Transactional;

import com.github.mygreen.sqlmapper.core.SqlMapper;

@Service
public class EmployeeService {

    @Autowired
    private SqlMapper sqlMapper;

    // 読み取り専用のトランザクション
    @Transactional(readOnly=true)
    public List<Employee> findEmployee(String name) {
        MEmployee e = MEmployee.employee;
        List<Employee> results = sqlMapper.selectFrom(e)
                .where(e.name.eq(name))
                .orderBy(e.name.asc(), e.hireDate.desc())
                .getResultList();
    }

    // 更新可能なトランザクション
    @Transactional
    public void saveOrUpdate(Employee employee) {

        MEmployee e = MEmployee.employee;
        boolean exists  = sqlMapper.selectFrom(e)
                .id(employee.getId())
                .forUpdate()
                .getResultList().size() > 0;   // forUpdateのときはcountなどの集約関数は使用できない
        if (exists) {
            sqlMapper.update(employee)
                .execute();
        } else {
            sqlMapper.insert(employee)
                .execute();
        }


    }
}

7.2. プログラムによるトランザクション管理

Spring Frameworkのクラス TransactionTemplate を使用した宣言的トランザクションの簡単な使用方法を説明します。

詳細は、 Spring Frameworkのドキュメント を参照してください。

  • PlatformTransactionManager 元に TransactionTemplate のインスタンスを作成します。

    • PlatformTransactionManager は、SqlMapperのJavaConfigである SqlMapperConfigurationSupport などで予めSpringBeanとして登録されています。

  • よく使用するトランザクション伝搬方式の REQUIRED_NEW が使いやすいです。

  • TransactionTemplate#execute(TransactionCallback) で戻り値がある場合のトランザクションを適用します。

    • TransactionCallback は関数型インタフェースのため、Lambda式で記述できます。

  • TransactionTemplate#executeWithoutResult(Consumer) で戻り値がない場合のトランザクションを適用します。

プログラムによるトランザクションの使用例
import java.util.List;

import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.support.TransactionTemplate;

import com.github.mygreen.sqlmapper.core.SqlMapper;

@Service
public class EmployeeService {

    @Autowired
    private TransactionManager transactionManager;

    @Autowired
    private SqlMapper sqlMapper;

    /**
     * REQUIRED_NEWのトランザクションテンプレートを作成します。
     */
    protected TransactionTemplate txNew() {
        TransactionTemplate template = new TransactionTemplate(transactionManager);
        template.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW);
        return template;
    }

    // 戻り値ありのトランザクションを適用する
    public Employee save(Employee employee) {

        Employee result = new Employee();
        BeanUtils.copyProperties(employee, result)
        return txNew().execute(status -> {
            sqlMapper.insert(result)
                    .execute();
            return result;
        });

    }

    // 戻り値なしのトランザクションを適用する
    public void saveOrUpdate(Employee employee) {

        txNew().executeWithoutResult(status -> {
            MEmployee e = MEmployee.employee;
            boolean exists  = sqlMapper.selectFrom(e)
                    .id(employee.getId())
                    .forUpdate()
                    .getCount() > 0;
            if (exists) {
                sqlMapper.update(employee)
                    .execute();
            } else {
                sqlMapper.insert(employee)
                    .execute();
            }
        });

    }
}

8. イベントリスナー

テーブルの参照/挿入/更新/削除時のイベントを Spring Frameworkの ApplicationEvent として受信できます。

  • テーブルに対するエンティティの各種操作の処理の前後のイベントが用意されています。

    • 詳細は、 JavaDoc を参照してください。

表 13. イベント一覧
イベントクラス 説明

PostSelectEvent

エンティティを参照後のイベントです。

PreInsertEvent

エンティティに対する挿入前のイベントです。

PostInsertEvent

エンティティに対する挿入後のイベントです。

PreUpdateEvent

エンティティに対する更新前のイベントです。

PostUpdatetEvent

エンティティに対する更新後のイベントです。

PreDeleteEvent

エンティティに対する削除前のイベントです。

PostDeleteEvent

エンティティに対する削除後のイベントです。

PreBatchInsertEvent

エンティティに対するバッチ挿入前のイベントです。

PostBatchInsertEvent

エンティティに対するバッチ挿入後のイベントです。

PreBatchUpdateEvent

エンティティに対するバッチ更新前のイベントです。

PostBatchUpdateEvent

エンティティに対するバッチ更新後のイベントです。

PreBatchDeleteEvent

エンティティに対するバッチ削除前のイベントです。

PostBatchDeleteEvent

エンティティに対するバッチ削除後のイベントです。

イベントを受信するには、Spring Frameworkのアノテーション @EventListener を使用します。

  • 各処理後のイベントを受信するときには、DBトランザクションと同期する @TransactionalEventListener を使用します。

イベントの受信例
import org.springframework.context.event.EventListener;
import org.springframework.transaction.event.TransactionalEventListener;

import com.github.mygreen.sqlmapper.core.event.PreInsertEvent;
import com.github.mygreen.sqlmapper.core.event.PostUpdateEvent;
import com.github.mygreen.sqlmapper.core.meta.EntityMeta;


@Service
public class SampleEventService {

   /**
     * 挿入前のエンティティ情報の受信処理
     * @param event イベント情報。
     */
    @EventListener
    public void onPreInsert(PreInsertEvent event) {

        // メタ情報
        EntityMeta meta = event.getEntityMeta();

        // 挿入対象のエンティティのインスタンス
        Object entity = getEntity();

        // ・・・

    }

    /**
     * 挿入後のエンティティ情報の受信処理
     * @param event イベント情報。
     */
    @TransactionalEventListener
    public void onPostInsert(PostInsertEvent event) {

        // メタ情報
        EntityMeta meta = event.getEntityMeta();

        // 挿入対象のエンティティのインスタンス
        Object entity = getEntity();

        // ・・・

    }

}

9. リリースノート

9.1. ver.0.3.2 - 2022-01-30

  • #53 - テーブルによるID採番の不具合の修正/シーケンス名の決定方法の修正。

    • テーブルによるID採番時に、シーケンス名が空になる事象を修正。

    • シーケンス名の決定方法を、NamingRuleで決定するよう変更。

  • #54 - プロパティファイルによるテーブルを使った採番の初期値が反映されない事象の修正。

  • #55 - テーブルによる自動採番のインクリメントの条件を修正。

    • テーブルによる自動採番において、allocationSize=1 or 2 のとき、DBの更新値が間違っている事象を修正。

  • #56 - 非OracleモードのH2DBのシーケンスインクリメンターの修正。

    • H2DBを 1.4.x2.1.x に更新。

9.2. ver.0.3.1 - 2022-01-13

  • #50 - 依存ライブラリであるSQLテンプレートのライブラリ splate をv0.3に更新しました。

  • #51 - pom.xml 定義間違いにより依存関係が解決できない事象を修正しました。

9.3. ver.0.3 - 2021-12-31

9.3.1. 機能追加・変更

  • #3 - 監査情報を記録するためのアノテーションの名称を変更しました。

    • @ModfiedAtUpdatedAt

    • @ModfiedByUpdatedBy

  • #4 - 識別子の独自実装を指定できる機能を追加。

  • #5 - 以下のメソッド名を変更しました。

    • Dialect#isSupportedGenerationTypeDialect#supportsGenerationType

    • Dialect#isSupportedSelectForUpdateDialect#supportsSelectForUpdate

  • #6 - 識別子の生成戦略を @GeneratedValue(strategy=…​) で指定したとき、DBの方言ごとにサポートしているかチェックを追加しました。

  • #8 - OracleのときのエンティティのプロパティがBoolean型のときをサポートしました。

  • #10 - ストアドプロシージャ/ファンクションの機能を追加しました。

  • #11 - JdbcTemplateの設定をクエリ単位に設定可能に各主クエリにメソッドを追加しました。

  • #12 - @Column(insertable) の属性を削除しました。@Table(readOnly) の属性を追加しまいた。

  • #13 - メタモデルの式において、LIKE演算子にエスケープ文字を指定できるようにしました。

  • #14 - メタモデルの式において、文字列結合用の関数を追加しました。

  • #15 - メタモデルの式を内部処理として、関数ごとに処理を分割しました。

    • Dialectごとに、カスタマイズ可能になりました。

  • #16 - メタモデルの式において、任意のSQL関数を指摘できる機能を追加しました。

  • #17 - メタモデルの式のデバッグ表示として、SQL関数に対応しました。

  • #20 - メタモデルの式のmod演算において、評価時には関数で実行するよう変更しました。

  • #22 - メタモデルの式を評価する際に、親と子ノードが同じ演算子グループのとき括弧で囲むよう変更。

  • #28 - 各クエリのメソッドで、includes / excludes の両方を指定したとき、includes を優先するよう修正しました。

  • #30 - レコード挿入時のバージョンキーの初期値を 1 から 0 に変更しました。

  • #33 - UUID 型を指定したとき、DB側では文字列として扱うよう変更。

    • DB側を UUID 型で使用したい場合は、 @Converter で独自の変換処理を作成して対応してください。

  • #34 - 自動採番時のフォーマット指定を @GeneratedValue(forrmat="xxx") による指定方法を止め、@SequenceGenerator(format="xxx") / @TableGenerator(format="xxx") で指定するよう変更。

  • #37 - テーブルによる自動採番処理において、キャッシュのリフレッシュ機能を追加しました。

  • #38 - テーブルを参照する処理用のメソッド getOptionalResult() において、レコードが複数件見つかったとき例外 IncorrectResultSizeDataAccessException をスローするよう変更しました。

    • さらに、getSingleResult で0件のとき例外を、IncorrectResultSizeDataAccessException のサブクラス EmptyResultDataAccessException に変更。

  • #39 - SQLテンプレートで getCountBySql 実行時に、select count(*) from (<SQLファイルの内容>) のように、自動的にcount句を付与するよう修正、

    • さらに、 selectBySql 事項時に、offset/limit句を指定できる機能を追加しました。

  • #41 - java.sql.Blob / java.sql.Clob 型をサポートしました。

    • APTによるメタモデル生成処理において、Lob型のアノテーションが指定されている場合は、GeneralPath とするよう修正。

    • LobByteArrayType / LobStringType のinsert/update時に値がnullのときの判定処理を追加。

9.3.2. 不具合修正

  • #7 - フィールドに修飾子 transient が付与されていても永続化対象外とならない事象を修正しました。

  • #8 - OracleのときのLIMIT句の文法間違いを修正。

  • #18 - LIMIT句のoffsetが -1 のときlimit句が設定されない事象を修正しました。

  • #19 - メタモデルのIN句に渡した値を評価する際に、展開されない事象を修正しました。

  • #21 - メタモデルの商演算子の評価時において、左辺が評価されない事象を修正しました。

  • #23 - メタモデルのIN句を評価する際に、カンマが抜けていた事象を修正。

  • #24 - メタモデルのBETWEEN句を定数以外の式などを指定したとき、正しく評価されない事象を修正。

  • #25 - メタモデルの式において、LocalDateTime 用のメソッド currentTime の戻り値が java.sql.Time 用のインスタンスになっている事象を修正しました。

  • #26 - メタモデルの式において、LocalDateTime 用のメソッド currentDateTimecurrentTimestamp に変更しました。

  • #27 - selectクエリのOFFSETの値として 0 を指定したとき、無視される事象を修正しました。

  • #29 - メタモデルの式において、定数を指定したとき、ValueType による変換がされない事象を修正しました。

  • #31 - レコード挿入時に、バージョン用のプロパティが includes / excludes の処理対象外とならない事象を修正。

  • #32 - テーブルによる主キーの採番処理において、正しくインクリメントされない事象を修正しました。

  • #35 - バッチ挿入/更新時にNPEが発生する事象を修正しました。

  • #36 - バッチ削除時のクエリ組み立て時に、1件の削除処理が呼ばれてしまう事象を修正しました。

  • #42 - @CreateAt / @UpdateAt にて、プロパティが java.sql.XXX のとき、ClassCastException が発生する事象を修正しました。

    • バッチ更新の時、@UpdateAt / @UpdateBy で指定したプロパティのカラムが更新されない事象を修正しました。

  • #43 - 埋め込み型のプロパティを使用したときの次の不具合を修正しました。

    • テーブルの値をマッピングする際に、例外が発生する事象を修正しました。

    • SELECT句のメソッド id(…​) で埋め込み型プロパティの値を指定したとき、個数チェックのエラーが発生する事象を修正しました。

    • ORDER BY句で埋め込み型のプロパティを指定したとき、SQLを評価する際に、テーブルのエイリアスが null となる事象を修正しました。

  • #44 - @CreateAt / @UpdateAt にて、プロパティが java.util.Date のとき、ClassCastException が発生する事象を修正しました。

  • #45 - 埋め込み型プロパティに @GeneratedValue を付与した際に不正なエンティティ定義と判定され例外が発生する事象を修正しました。

9.4. ver.0.2 - 2021-07-10

  • 初期リリース