DomaのCriteria APIやDaoでUPSERT
前提
以下バージョンから、DomaのCriteria API,DaoでUPSERTができるようになりました。
version | リリース日 | |
---|---|---|
org.seasar.doma:doma-kotlin | 2.57.0以降 | 2024-03-20 |
org.seasar.doma:doma-core | 2.57.0以降 | 2024-03-20 |
MySQLは5.7が前提ですが、MySQL8でもUPSERTができるようにする方法も記載しています。
UPSERTのSQL
基本、以下Komapperの記事と同じですが、MySQLだけDomaで対応しているversionが5なので、MySQLだけ異なります。
MySQL(MySQL5)の場合のSQL
insert into DEPARTMENT (DEPARTMENT_ID, DEPARTMENT_NO, DEPARTMENT_NAME, LOCATION, VERSION)
values (1, 60, 'DEVELOPMENT', 'KYOTO', 1)
on duplicate key update DEPARTMENT_NO = values(DEPARTMENT_NO),
DEPARTMENT_NAME = values(DEPARTMENT_NAME),
LOCATION = values(LOCATION),
VERSION = values(VERSION)
使い方
Komapperと似たようなインターフェイスとなります。
insert時に重複キーがある場合、updateするのかignoreなのかを指定するというものです。
Daoとentityqlでの動作は、 domaのentityの定義で、@Id
を付与したものを重複キーとして扱います。
entityqlでは以下のように記載します。※Kotlinで書いています
entityql.insert(d, list).onDuplicateKeyUpdate().execute()
entityql.insert(d, department).onDuplicateKeyIgnore().execute()
@Daoでは以下のように記載します。※Kotlinで書いています
@Dao
interface DepartmentDao {
@Insert(duplicateKeyType = DuplicateKeyType.UPDATE)
fun insertOnDuplicateKeyUpdate(entity: Department): Int
@Insert(duplicateKeyType = DuplicateKeyType.IGNORE)
fun insertOnDuplicateKeyIgnore(entity: Department): Int
@BatchInsert(duplicateKeyType = DuplicateKeyType.UPDATE)
fun insertOnDuplicateKeyUpdate(entities: List<Department>): Array<Int>
@BatchInsert(duplicateKeyType = DuplicateKeyType.IGNORE)
fun insertOnDuplicateKeyIgnore(entities: List<Department>): Array<Int>
}
nativeSqlでは、重複のキーをkeysメソッドで、重複した場合の更新値をsetメソッドで任意に指定できます。
また、nativeSqlであっても、keysメソッドやsetメソッドでの指定を省略できます。省略した場合、@Idを付与したプロパティが重複キーとして扱われ、@Idを付与していないプロパティが更新値として扱われます。
set句で使えるexcludedメソッドは、insert句のvaluesで指定した値を指定できます。
nativeSql
.insert(d)
.values {
it.value(d.departmentId, 1)
it.value(d.departmentNo, 60)
it.value(d.departmentName, "DEVELOPMENT")
it.value(d.location, "KYOTO")
it.value(d.version, 2)
}
.onDuplicateKeyUpdate()
.keys(d.departmentId)
.set {
it.value(d.departmentNo, 60)
it.value(d.departmentName, "DEVELOPMENT")
it.value(d.location, it.excluded(e.location))
it.value(d.version, 3)
}
.execute()
nativeSql
.insert(d)
.values {
it.value(d.departmentId, 1)
it.value(d.departmentNo, 60)
it.value(d.departmentName, "DEVELOPMENT")
it.value(d.location, "KYOTO")
it.value(d.version, 2)
}
.onDuplicateKeyIgnore()
.keys(d.departmentId)
.execute()
MySQL8でのUPSERTをしたい場合
org.seasar.doma.jdbc.dialect.MysqlDialect#getUpsertAssemblerをoverrideして、以下実装を返すようにすれば、MySQL8でもUPSERTができるようになります。
Mysql8でのUpsertAssembler
import java.util.List;
import org.seasar.doma.internal.jdbc.sql.PreparedSqlBuilder;
import org.seasar.doma.jdbc.InParameter;
import org.seasar.doma.jdbc.criteria.tuple.Tuple2;
import org.seasar.doma.jdbc.entity.EntityPropertyType;
import org.seasar.doma.jdbc.entity.EntityType;
import org.seasar.doma.jdbc.query.DuplicateKeyType;
import org.seasar.doma.jdbc.query.UpsertAssembler;
import org.seasar.doma.jdbc.query.UpsertAssemblerContext;
import org.seasar.doma.jdbc.query.UpsertAssemblerSupport;
import org.seasar.doma.jdbc.query.UpsertSetValue;
public class Mysql8UpsertAssembler implements UpsertAssembler {
private final PreparedSqlBuilder buf;
private final EntityType<?> entityType;
private final DuplicateKeyType duplicateKeyType;
private final UpsertAssemblerSupport upsertAssemblerSupport;
private final List<Tuple2<EntityPropertyType<?, ?>, InParameter<?>>> insertValues;
private final List<Tuple2<EntityPropertyType<?, ?>, UpsertSetValue>> setValues;
private final UpsertSetValue.Visitor upsertSetValueVisitor = new UpsertSetValueVisitor();
public MysqlUpsertAssembler(UpsertAssemblerContext context) {
this.buf = context.buf;
this.entityType = context.entityType;
this.duplicateKeyType = context.duplicateKeyType;
this.insertValues = context.insertValues;
this.setValues = context.setValues;
this.upsertAssemblerSupport = new UpsertAssemblerSupport(context.naming, context.dialect);
}
@Override
public void assemble() {
buf.appendSql("insert");
if (duplicateKeyType == DuplicateKeyType.IGNORE) {
buf.appendSql(" ignore");
}
buf.appendSql(" into ");
tableNameOnly(entityType);
buf.appendSql(" (");
for (Tuple2<EntityPropertyType<?, ?>, InParameter<?>> insertValue : insertValues) {
column(insertValue.component1());
buf.appendSql(", ");
}
buf.cutBackSql(2);
buf.appendSql(") values (");
for (Tuple2<EntityPropertyType<?, ?>, InParameter<?>> insertValue : insertValues) {
buf.appendParameter(insertValue.component2());
buf.appendSql(", ");
}
buf.cutBackSql(2);
buf.appendSql(") as ");
excludeAlias();
if (duplicateKeyType == DuplicateKeyType.UPDATE) {
buf.appendSql(" on duplicate key update ");
for (Tuple2<EntityPropertyType<?, ?>, UpsertSetValue> setValue : setValues) {
column(setValue.component1());
buf.appendSql(" = ");
setValue.component2().accept(upsertSetValueVisitor);
buf.appendSql(", ");
}
buf.cutBackSql(2);
}
}
private void tableNameOnly(EntityType<?> entityType) {
String sql =
this.upsertAssemblerSupport.targetTable(
entityType, UpsertAssemblerSupport.TableNameType.NAME);
buf.appendSql(sql);
}
private void excludeAlias() {
String sql = this.upsertAssemblerSupport.excludeAlias();
buf.appendSql(sql);
}
private void column(EntityPropertyType<?, ?> propertyType) {
String sql = this.upsertAssemblerSupport.prop(propertyType);
buf.appendSql(sql);
}
class UpsertSetValueVisitor implements UpsertSetValue.Visitor {
@Override
public void visit(UpsertSetValue.Param param) {
buf.appendParameter(param.inParameter);
}
@Override
public void visit(UpsertSetValue.Prop prop) {
String sql =
upsertAssemblerSupport.excludeProp(
prop.propertyType, UpsertAssemblerSupport.ColumnNameType.NAME_ALIAS);
buf.appendSql(sql);
}
}
}
おわりに
各データベース毎の違いについては、Komapperを参考にさせてもらったので、苦労することなく実装できました…。感謝。
Discussion