SqlMapper は、Spring Framework のJDBC機能を使って、「S2JDBC の再実装 + 機能追加」 を目指した SQLマッピング用のライブラリです。
1. セットアップ
1.1. 前提環境・前提条件
本ライブラリの前提条件を以下に示します。
項目 | 値 | 備考 |
---|---|---|
Java |
JDK11+ |
必須 |
Spring Framework |
5.3+ |
必須 |
Spring Boot |
2.4+ |
オプション |
DB | バージョン | 備考 |
---|---|---|
H2 |
1.4.x/2.1.x |
|
HSQLDB |
- |
|
Oracle |
v12+ |
Oracle 11以前を使用するときには、 |
SQLite |
||
PostgreSQL |
1.2. Spring Framework の設定
非Spring Boot の環境、または、SqlMapperのstater を使用しない時の環境設定方法を説明します。
1.2.1. Mavenへのライブラリの依存関係の追加
Mavenを使用している場合は、pom.xml
に依存関係を追加します。
利用するDBのJDBCドライバの定義も追加します。
<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>
さらに、メタモデルを自動生成する設定を追加します。
<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ドライバの定義も追加します。
さらに、メタモデルを自動生成する設定を追加します。
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 を参照してください。
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();
}
}
DB種別 | クラス | 説明 |
---|---|---|
|
Java製の組み込みDB。 |
|
|
Java製の組み込みDB。 |
|
|
OSSのRDMBS。 |
|
|
組み込みDB。 |
|
|
商用のRDBS。Oracle12c以上の場合に対応。 |
|
|
Oracle11g以前に対応。 |
プロパティの上書き
テーブルによる識別子の生成などの設定を上書きする場合は、Springのアノテーション @PropertySource
を使用し、プロパティを上書きします。
設定可能なプロパティは、設定可能なプロパティ を参照してください。
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内から参照できます。
-
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();
}
// ・・・
}
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ドライバの定義も追加します。
<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>
さらに、メタモデルを自動生成する設定を追加します。
<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ドライバの定義も追加します。
さらに、メタモデルを自動生成する設定を追加します。
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:
DB種別 | ドライバークラス | ライブラリ |
---|---|---|
|
||
|
||
|
||
|
||
|
プロパティの上書き
テーブルによる識別子の生成などの設定を上書きする場合は、SpringBootの applicaiton.yml
や application.properties
に定義します。
設定可能なプロパティは、設定可能なプロパティ を参照してください。
DBコネクションプールの設定
-
DBコネクションプールを使用する場合、 Spring Boot標準のプロパティ を使用します。
-
プロパティ
spring.datasource.XXX
を使用して定義します。
-
-
Commons DBCP2 / HikariCP など好きなものを使用してください。
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. 設定可能なプロパティ
キー | デフォルト値 | 説明 |
---|---|---|
|
|
生成したIDの値を永続化するテーブル名。 |
|
- (デフォルト値は空) |
生成したIDの値を永続化するテーブルが定義されているスキーマ名。 |
|
- (デフォルト値は空) |
生成したIDの値を永続化するテーブルが定義されているカタログ名。 |
|
|
生成したIDの名称を保持するカラム名。 |
|
|
生成したIDの値を保持するカラム名。 |
|
|
採番を行う際に、予め指定した値分を払い出しておく値です。値を1にすると、毎回レコードを更新することになり、オーバーヘッドが発生します。 |
|
|
生成するIDの値の初期値。 |
キー | デフォルト値 | 説明 |
---|---|---|
|
|
SQLテンプレートのパース結果をキャッシュするかどうか。 |
|
|
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<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は、必ずクローズするようにしてください。
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)
: 取得する行数を指定します。
|
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
select * from employee
where
salary >= /*salaryMin*/1000
and salary <= /*salaryMax*/2000
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は、必ずクローズするようにしてください。
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)
: 取得する行数を指定します。
|
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. アノテーション一覧
アノテーション | 概要 | 詳細リンク |
---|---|---|
|
クラスがエンティティであることを定義します。 |
|
|
テーブル情報を補完し定義します。 |
|
|
エンティティの親クラスであることを定義します。 |
|
|
カラム情報を定義します。 |
|
|
プロパティがID(識別子/主キー)であることを定義します。 |
|
|
プロパティが埋め込みID(複合主キー)であることを定義します。 |
|
|
埋め込みクラスであることを定義します。 |
|
|
楽観的排他キーとしてのバージョンキーであることを定義します。 |
|
|
プロパティが永続化対象外であることを定義します。 |
|
|
プロパティがラージオブジェクトであることを定義します。 |
|
|
時制のタイプを変換規則を定義します。時制のタイプが不明なときに使用します。 |
|
|
列挙型のタイプの変換規則を定義します。 |
|
|
独自のタイプの変換方法を定義します。 |
|
|
ID(主キー)の値の採番方法を定義します。 |
|
|
ID(主キー)の値の採番方法がシーケンスを用いるときに、詳細を定義します。 |
|
|
ID(主キー)の値の採番方法がテーブルを用いるときに、詳細を定義します。 |
|
|
エンティティを新規に保存するときに、監査情報としての日時を自動的に設定する対象であることを定義します。 |
|
|
既存のエンティティを更新するときに、監査情報としての日時を自動的に設定する対象であることを定義します。 |
|
|
エンティティを新規に保存するときに、監査情報としての作成者を自動的に設定する対象であることを定義します。 |
|
|
既存のエンティティを更新するときに、監査情報としての更新者を自動的に設定する対象であることを定義します。 |
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 を使用して記述減らすことをお勧めします。 |
@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メソッドは省略
}
独自の命名規則を使用するときは、 |
5.3. フィールドとカラムのマッピング方法
エンティティクラスのフィールドは、デフォルト永続化対象です。
-
フィールドの修飾子が、非public(private/protected/なし(デフォルト))の場合は、publicなアクセッサメソッド(setter/getter)を定義します。
-
フィールドの修飾子がpublicの場合は、アクセッサメソッドは不要です。
-
主キーとなるカラムには、アノテーション
@Id
を付与します。-
主キーの値を自動生成したい場合は、識別子(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型にマッピングが可能です。
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の場合は、 |
java.sql.Time / java.time.LocalTime / java.util.Date |
TIME |
java.util.Dateの場合は、 |
java.sql.Timestamp / java.time.LocalDateTime / java.util.Date |
TIMESTAMP |
java.util.Dateの場合は、 |
byte[] |
BLOB |
|
java.sql.Blob |
BLOB |
|
java.sql.Clob |
CLOB |
|
java.util.UUID |
VARCHAR |
UUID型をサポートしているDBも多くありますが、JDBCドライバーがそれぞれ対応方法が異なるため、SqlMapperではDB側は文字列型に変換して永続化します。もし、DB固有のUUIDを使用したい場合は、 |
列挙型 |
CHAR / VARCHAR / TINTYINT / SMALLINT / INTEGER |
列挙型をマッピングする際には、 |
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メソッドは省略
}
独自の命名規則を使用するときは、 |
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メソッドは省略
}
指定方法 | 概要 |
---|---|
|
データベースにより生成方法が自動手的に選択されます。
|
|
IDENTITY列を使用して採番を行います。
|
|
シーケンスを使用して採番を行います。
|
|
テーブルを使用して採番を行います。
|
|
|
|
独自の採番方式として、Springコンテナに登録されているBean名を指定します。
|
5.4.1. シーケンスによる識別子(ID)の自動採番
-
シーケンスをサポートしているDBMSで利用できます。
-
利用する際には、事前に採番用のシーケンスを定義しておく必要があります。
-
シーケンス名は、アノテーション
@SequenceGenerator
でカスタマイズできます。 -
アノテーション
@SequenceGenerator
でシーケンス名を指定しない場合は、「<テーブル名>_<カラム名>
」のシーケンス名を使用します。
-
-
文字列にマッピングする際、
@SequenceGenerator(format="xxx")
でフォーマットを指定できます。-
フォーマットは、
java.text.DecimalFormat
で解釈可能な書式を指定します。
-
-- シーケンス名(<テーブル名>_<カラム名>)
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
で解釈可能な書式を指定します。
-
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
または、プロパティファイルにて定義します。 -
プロパティファイで定義する場合は、アプリ全体に反映されます。
-
JavaConfigによる設定している場合は、
@PropertySource
にて定義したファイルを読み込んでください。 -
SpringBootを使用している場合は、
application.properties
または、application.yml
` に定義をしてください。
-
@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メソッドは省略
}
アノテーションの属性 | プロパティのキー | 初期値 | 説明 |
---|---|---|---|
|
|
|
生成したIDの値を永続化するテーブル名。 |
|
|
- (デフォルト値は空) |
生成したIDの値を永続化するテーブルが定義されているスキーマ名。 |
|
|
- (デフォルト値は空) |
生成したIDの値を永続化するテーブルが定義されているカタログ名。 |
|
|
|
生成したIDの名称を保持するカラム名。 |
|
|
|
生成したIDの値を保持するカラム名。 |
|
|
|
採番を行う際に、予め指定した値分を払い出しておく値です。値を1にすると、毎回レコードを更新することになり、オーバーヘッドが発生します。 |
|
|
|
生成する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の型がそれぞれ DATE
、TIME
と明確なため自動的にマッピングできます。
しかし、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
も扱うことができます。
DB型 | Java型 |
---|---|
|
|
|
|
|
|
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は省略
}
DB型 | Java型 |
---|---|
|
|
|
|
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の演算子に対応するメソッドが実装されており、迷うことなくクエリを組み立てることができます。
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
を使用した宣言的トランザクションの簡単な使用方法を説明します。
-
Spring Bootを使用していない場合は、設定が必要になります。
-
詳細は、 Spring Frameworkのドキュメント を参照してください。
7.1.1. Spring Frameworkの設定
-
機能を有効にするには、SpringFrameworkのアノテーション
@EnableTransactionManagement
を JavaConfigクラスに付与します。 -
アノテーション
@EnableTransactionManagement
を使用するには、ライブラリ spring-tx が必要ですが、SqlMapperの依存関係として追加されているため、Maven/Gradleなどを使用している場合は自動的に読み込まれているはずです。
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 を参照してください。
-
イベントクラス | 説明 |
---|---|
|
エンティティを参照後のイベントです。 |
|
エンティティに対する挿入前のイベントです。 |
|
エンティティに対する挿入後のイベントです。 |
|
エンティティに対する更新前のイベントです。 |
|
エンティティに対する更新後のイベントです。 |
|
エンティティに対する削除前のイベントです。 |
|
エンティティに対する削除後のイベントです。 |
|
エンティティに対するバッチ挿入前のイベントです。 |
|
エンティティに対するバッチ挿入後のイベントです。 |
|
エンティティに対するバッチ更新前のイベントです。 |
|
エンティティに対するバッチ更新後のイベントです。 |
|
エンティティに対するバッチ削除前のイベントです。 |
|
エンティティに対するバッチ削除後のイベントです。 |
イベントを受信するには、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.x
⇒2.1.x
に更新。
-
9.2. ver.0.3.1 - 2022-01-13
9.3. ver.0.3 - 2021-12-31
9.3.1. 機能追加・変更
-
#3 - 監査情報を記録するためのアノテーションの名称を変更しました。
-
@ModfiedAt
⇒UpdatedAt
-
@ModfiedBy
⇒UpdatedBy
-
-
#4 - 識別子の独自実装を指定できる機能を追加。
-
#5 - 以下のメソッド名を変更しました。
-
Dialect#isSupportedGenerationType
⇒Dialect#supportsGenerationType
-
Dialect#isSupportedSelectForUpdate
⇒Dialect#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
用のメソッドcurrentDateTime
をcurrentTimestamp
に変更しました。 -
#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
-
初期リリース