Skip to content

Commit d826420

Browse files
committed
Support multi-row insert in Oracle Database
1 parent 4aed99a commit d826420

File tree

13 files changed

+273
-71
lines changed

13 files changed

+273
-71
lines changed

doma-core/src/main/java/org/seasar/doma/jdbc/dialect/Dialect.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@
1616
import org.seasar.doma.jdbc.SqlNode;
1717
import org.seasar.doma.jdbc.criteria.query.CriteriaBuilder;
1818
import org.seasar.doma.jdbc.id.AutoGeneratedKeysType;
19+
import org.seasar.doma.jdbc.query.MultiInsertAssembler;
20+
import org.seasar.doma.jdbc.query.MultiInsertAssemblerContext;
1921
import org.seasar.doma.jdbc.query.UpsertAssembler;
2022
import org.seasar.doma.jdbc.query.UpsertAssemblerContext;
2123
import org.seasar.doma.jdbc.type.JdbcType;
@@ -319,4 +321,14 @@ Sql<?> getIdentityReservationSql(
319321
* @return the UpsertAssembler object for the given context
320322
*/
321323
UpsertAssembler getUpsertAssembler(UpsertAssemblerContext context);
324+
325+
/**
326+
* Returns the MultiInsertAssembler implementation for the given context.
327+
*
328+
* @param <ENTITY> the entity type
329+
* @param context the MultiInsertAssemblerContext object
330+
* @return the MultiInsertAssembler object for the given context
331+
*/
332+
<ENTITY> MultiInsertAssembler getMultiInsertAssembler(
333+
MultiInsertAssemblerContext<ENTITY> context);
322334
}

doma-core/src/main/java/org/seasar/doma/jdbc/dialect/OracleDialect.java

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
import org.seasar.doma.jdbc.JdbcMappingVisitor;
55
import org.seasar.doma.jdbc.SqlLogFormattingVisitor;
66
import org.seasar.doma.jdbc.id.AutoGeneratedKeysType;
7+
import org.seasar.doma.jdbc.query.MultiInsertAssembler;
8+
import org.seasar.doma.jdbc.query.MultiInsertAssemblerContext;
79
import org.seasar.doma.jdbc.query.UpsertAssembler;
810
import org.seasar.doma.jdbc.query.UpsertAssemblerContext;
911

@@ -51,6 +53,11 @@ public boolean supportsAutoGeneratedKeys() {
5153
return true;
5254
}
5355

56+
@Override
57+
public boolean supportsMultiRowInsertStatement() {
58+
return true;
59+
}
60+
5461
@Override
5562
public AutoGeneratedKeysType getAutoGeneratedKeysType() {
5663
return AutoGeneratedKeysType.FIRST_COLUMN;
@@ -83,4 +90,10 @@ public static class OracleScriptBlockContext extends Oracle11ScriptBlockContext
8390
public UpsertAssembler getUpsertAssembler(UpsertAssemblerContext context) {
8491
return new OracleUpsertAssembler(context);
8592
}
93+
94+
@Override
95+
public <ENTITY> MultiInsertAssembler getMultiInsertAssembler(
96+
MultiInsertAssemblerContext<ENTITY> context) {
97+
return new OracleMultiInsertAssembler<>(context);
98+
}
8699
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
package org.seasar.doma.jdbc.dialect;
2+
3+
import java.util.List;
4+
import org.seasar.doma.internal.jdbc.sql.PreparedSqlBuilder;
5+
import org.seasar.doma.jdbc.Naming;
6+
import org.seasar.doma.jdbc.entity.EntityPropertyType;
7+
import org.seasar.doma.jdbc.entity.EntityType;
8+
import org.seasar.doma.jdbc.entity.Property;
9+
import org.seasar.doma.jdbc.query.MultiInsertAssembler;
10+
import org.seasar.doma.jdbc.query.MultiInsertAssemblerContext;
11+
12+
public class OracleMultiInsertAssembler<ENTITY> implements MultiInsertAssembler {
13+
14+
public final PreparedSqlBuilder buf;
15+
public final EntityType<?> entityType;
16+
public final Naming naming;
17+
public final Dialect dialect;
18+
public final List<EntityPropertyType<ENTITY, ?>> targetPropertyTypes;
19+
public final List<ENTITY> entities;
20+
21+
public OracleMultiInsertAssembler(MultiInsertAssemblerContext<ENTITY> context) {
22+
this.buf = context.buf;
23+
this.entityType = context.entityType;
24+
this.naming = context.naming;
25+
this.dialect = context.dialect;
26+
this.targetPropertyTypes = context.targetPropertyTypes;
27+
this.entities = context.entities;
28+
}
29+
30+
@Override
31+
public void assemble() {
32+
buf.appendSql("insert all ");
33+
for (ENTITY entity : entities) {
34+
buf.appendSql("into ");
35+
buf.appendSql(entityType.getQualifiedTableName(naming::apply, dialect::applyQuote));
36+
buf.appendSql(" (");
37+
if (!targetPropertyTypes.isEmpty()) {
38+
for (EntityPropertyType<?, ?> propertyType : targetPropertyTypes) {
39+
buf.appendSql(propertyType.getColumnName(naming::apply, dialect::applyQuote));
40+
buf.appendSql(", ");
41+
}
42+
buf.cutBackSql(2);
43+
}
44+
buf.appendSql(") values (");
45+
for (EntityPropertyType<ENTITY, ?> propertyType : targetPropertyTypes) {
46+
Property<ENTITY, ?> property = propertyType.createProperty();
47+
property.load(entity);
48+
buf.appendParameter(property.asInParameter());
49+
buf.appendSql(", ");
50+
}
51+
buf.cutBackSql(2);
52+
buf.appendSql(") ");
53+
}
54+
buf.appendSql("select 1 from dual");
55+
}
56+
}

doma-core/src/main/java/org/seasar/doma/jdbc/dialect/OracleUpsertAssembler.java

Lines changed: 16 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ public class OracleUpsertAssembler implements UpsertAssembler {
2424

2525
private final List<? extends EntityPropertyType<?, ?>> insertPropertyTypes;
2626

27-
private final List<InsertRow> insertValues;
27+
private final List<InsertRow> insertRows;
2828
private final List<QueryOperandPair> setValues;
2929
private final QueryOperand.Visitor queryOperandVisitor = new QueryOperandVisitor();
3030

@@ -34,12 +34,9 @@ public OracleUpsertAssembler(UpsertAssemblerContext context) {
3434
this.duplicateKeyType = context.duplicateKeyType;
3535
this.keys = context.keys;
3636
this.insertPropertyTypes = context.insertPropertyTypes;
37-
this.insertValues = context.insertRows;
37+
this.insertRows = context.insertRows;
3838
this.setValues = context.setValues;
3939
this.upsertAssemblerSupport = new UpsertAssemblerSupport(context.naming, context.dialect);
40-
if (context.insertRows.size() > 1) {
41-
throw new UnsupportedOperationException();
42-
}
4340
}
4441

4542
@Override
@@ -84,21 +81,21 @@ public void assemble() {
8481
}
8582

8683
private void excludeQuery() {
87-
// TODO
88-
InsertRow row =
89-
insertValues.stream().findFirst().orElseThrow(UnsupportedOperationException::new);
90-
List<Pair<? extends EntityPropertyType<?, ?>, QueryOperand>> pairs =
91-
Zip.stream(insertPropertyTypes, row).collect(Collectors.toList());
92-
93-
buf.appendSql("select ");
94-
for (Pair<? extends EntityPropertyType<?, ?>, QueryOperand> pair : pairs) {
95-
pair.snd.accept(queryOperandVisitor);
96-
buf.appendSql(" as ");
97-
column(pair.fst);
98-
buf.appendSql(", ");
84+
for (InsertRow row : insertRows) {
85+
List<Pair<? extends EntityPropertyType<?, ?>, QueryOperand>> pairs =
86+
Zip.stream(insertPropertyTypes, row).collect(Collectors.toList());
87+
buf.appendSql("select ");
88+
for (Pair<? extends EntityPropertyType<?, ?>, QueryOperand> pair : pairs) {
89+
pair.snd.accept(queryOperandVisitor);
90+
buf.appendSql(" as ");
91+
column(pair.fst);
92+
buf.appendSql(", ");
93+
}
94+
buf.cutBackSql(2);
95+
buf.appendSql(" from dual");
96+
buf.appendSql(" union all ");
9997
}
100-
buf.cutBackSql(2);
101-
buf.appendSql(" from dual");
98+
buf.cutBackSql(11);
10299
}
103100

104101
private void tableNameAndAlias(EntityType<?> entityType) {

doma-core/src/main/java/org/seasar/doma/jdbc/dialect/StandardDialect.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,8 @@
4545
import org.seasar.doma.jdbc.criteria.query.AliasManager;
4646
import org.seasar.doma.jdbc.criteria.query.CriteriaBuilder;
4747
import org.seasar.doma.jdbc.id.AutoGeneratedKeysType;
48+
import org.seasar.doma.jdbc.query.MultiInsertAssembler;
49+
import org.seasar.doma.jdbc.query.MultiInsertAssemblerContext;
4850
import org.seasar.doma.jdbc.query.UpsertAssembler;
4951
import org.seasar.doma.jdbc.query.UpsertAssemblerContext;
5052
import org.seasar.doma.jdbc.type.EnumType;
@@ -1153,4 +1155,10 @@ private void appendSql() {
11531155
public UpsertAssembler getUpsertAssembler(UpsertAssemblerContext context) {
11541156
throw new JdbcUnsupportedOperationException(getClass().getName(), "getUpsertBuilder");
11551157
}
1158+
1159+
@Override
1160+
public <ENTITY> MultiInsertAssembler getMultiInsertAssembler(
1161+
MultiInsertAssemblerContext<ENTITY> context) {
1162+
return new StandardMultiInsertAssembler<>(context);
1163+
}
11561164
}
Lines changed: 60 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,60 @@
1+
package org.seasar.doma.jdbc.dialect;
2+
3+
import java.util.List;
4+
import org.seasar.doma.internal.jdbc.sql.PreparedSqlBuilder;
5+
import org.seasar.doma.jdbc.Naming;
6+
import org.seasar.doma.jdbc.entity.EntityPropertyType;
7+
import org.seasar.doma.jdbc.entity.EntityType;
8+
import org.seasar.doma.jdbc.entity.Property;
9+
import org.seasar.doma.jdbc.query.MultiInsertAssembler;
10+
import org.seasar.doma.jdbc.query.MultiInsertAssemblerContext;
11+
12+
public class StandardMultiInsertAssembler<ENTITY> implements MultiInsertAssembler {
13+
14+
public final PreparedSqlBuilder buf;
15+
public final EntityType<?> entityType;
16+
public final Naming naming;
17+
public final Dialect dialect;
18+
public final List<EntityPropertyType<ENTITY, ?>> targetPropertyTypes;
19+
public final List<ENTITY> entities;
20+
21+
public StandardMultiInsertAssembler(MultiInsertAssemblerContext<ENTITY> context) {
22+
this.buf = context.buf;
23+
this.entityType = context.entityType;
24+
this.naming = context.naming;
25+
this.dialect = context.dialect;
26+
this.targetPropertyTypes = context.targetPropertyTypes;
27+
this.entities = context.entities;
28+
}
29+
30+
@Override
31+
public void assemble() {
32+
buf.appendSql("insert into ");
33+
buf.appendSql(entityType.getQualifiedTableName(naming::apply, dialect::applyQuote));
34+
buf.appendSql(" (");
35+
if (!targetPropertyTypes.isEmpty()) {
36+
for (EntityPropertyType<?, ?> propertyType : targetPropertyTypes) {
37+
buf.appendSql(propertyType.getColumnName(naming::apply, dialect::applyQuote));
38+
buf.appendSql(", ");
39+
}
40+
buf.cutBackSql(2);
41+
}
42+
buf.appendSql(") values ");
43+
if (!entities.isEmpty()) {
44+
for (ENTITY entity : entities) {
45+
buf.appendSql("(");
46+
if (!targetPropertyTypes.isEmpty()) {
47+
for (EntityPropertyType<ENTITY, ?> propertyType : targetPropertyTypes) {
48+
Property<ENTITY, ?> property = propertyType.createProperty();
49+
property.load(entity);
50+
buf.appendParameter(property.asInParameter());
51+
buf.appendSql(", ");
52+
}
53+
buf.cutBackSql(2);
54+
}
55+
buf.appendSql("), ");
56+
}
57+
buf.cutBackSql(2);
58+
}
59+
}
60+
}

doma-core/src/main/java/org/seasar/doma/jdbc/query/AutoMultiInsertQuery.java

Lines changed: 5 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -157,33 +157,11 @@ protected void prepareSql() {
157157
}
158158

159159
private void assembleInsertSql(PreparedSqlBuilder builder, Naming naming, Dialect dialect) {
160-
builder.appendSql("insert into ");
161-
builder.appendSql(entityType.getQualifiedTableName(naming::apply, dialect::applyQuote));
162-
builder.appendSql(" (");
163-
if (!targetPropertyTypes.isEmpty()) {
164-
for (EntityPropertyType<ENTITY, ?> propertyType : targetPropertyTypes) {
165-
builder.appendSql(propertyType.getColumnName(naming::apply, dialect::applyQuote));
166-
builder.appendSql(", ");
167-
}
168-
builder.cutBackSql(2);
169-
}
170-
builder.appendSql(") values ");
171-
if (!entities.isEmpty()) {
172-
for (ENTITY entity : entities) {
173-
builder.appendSql("(");
174-
if (!targetPropertyTypes.isEmpty()) {
175-
for (EntityPropertyType<ENTITY, ?> propertyType : targetPropertyTypes) {
176-
Property<ENTITY, ?> property = propertyType.createProperty();
177-
property.load(entity);
178-
builder.appendParameter(property.asInParameter());
179-
builder.appendSql(", ");
180-
}
181-
builder.cutBackSql(2);
182-
}
183-
builder.appendSql("), ");
184-
}
185-
builder.cutBackSql(2);
186-
}
160+
MultiInsertAssemblerContext<ENTITY> context =
161+
MultiInsertAssemblerContextBuilder.buildFromEntityList(
162+
builder, entityType, naming, dialect, targetPropertyTypes, entities);
163+
MultiInsertAssembler assembler = dialect.getMultiInsertAssembler(context);
164+
assembler.assemble();
187165
}
188166

189167
private void assembleUpsertSql(PreparedSqlBuilder builder, Naming naming, Dialect dialect) {
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package org.seasar.doma.jdbc.query;
2+
3+
/** Assemble the multi-row insert query interface. Implement this interface for each dialect. */
4+
public interface MultiInsertAssembler {
5+
void assemble();
6+
}
Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package org.seasar.doma.jdbc.query;
2+
3+
import java.util.List;
4+
import java.util.Objects;
5+
import org.seasar.doma.DomaIllegalArgumentException;
6+
import org.seasar.doma.internal.jdbc.sql.PreparedSqlBuilder;
7+
import org.seasar.doma.jdbc.Naming;
8+
import org.seasar.doma.jdbc.dialect.Dialect;
9+
import org.seasar.doma.jdbc.entity.EntityPropertyType;
10+
import org.seasar.doma.jdbc.entity.EntityType;
11+
12+
/**
13+
* An assembler for multi insert statements.
14+
*
15+
* @param <ENTITY> the entity type
16+
*/
17+
public class MultiInsertAssemblerContext<ENTITY> {
18+
public final PreparedSqlBuilder buf;
19+
public final EntityType<ENTITY> entityType;
20+
public final Naming naming;
21+
public final Dialect dialect;
22+
public final List<EntityPropertyType<ENTITY, ?>> targetPropertyTypes;
23+
public final List<ENTITY> entities;
24+
25+
/**
26+
* Creates an instance.
27+
*
28+
* @param buf the SQL buffer
29+
* @param entityType the entity type
30+
* @param naming the naming convention
31+
* @param dialect the SQL dialect
32+
* @param targetPropertyTypes the property types that are targets for the insert
33+
* @param entities the entities
34+
*/
35+
MultiInsertAssemblerContext(
36+
PreparedSqlBuilder buf,
37+
EntityType<ENTITY> entityType,
38+
Naming naming,
39+
Dialect dialect,
40+
List<EntityPropertyType<ENTITY, ?>> targetPropertyTypes,
41+
List<ENTITY> entities) {
42+
Objects.requireNonNull(buf);
43+
Objects.requireNonNull(entityType);
44+
Objects.requireNonNull(naming);
45+
Objects.requireNonNull(dialect);
46+
Objects.requireNonNull(targetPropertyTypes);
47+
Objects.requireNonNull(entities);
48+
if (entities.isEmpty()) {
49+
throw new DomaIllegalArgumentException(
50+
"entities", "The entities must not be empty when performing an insert.");
51+
}
52+
this.buf = buf;
53+
this.entityType = entityType;
54+
this.naming = naming;
55+
this.dialect = dialect;
56+
this.targetPropertyTypes = targetPropertyTypes;
57+
this.entities = entities;
58+
}
59+
}
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
package org.seasar.doma.jdbc.query;
2+
3+
import java.util.List;
4+
import org.seasar.doma.internal.jdbc.sql.PreparedSqlBuilder;
5+
import org.seasar.doma.jdbc.Naming;
6+
import org.seasar.doma.jdbc.dialect.Dialect;
7+
import org.seasar.doma.jdbc.entity.EntityPropertyType;
8+
import org.seasar.doma.jdbc.entity.EntityType;
9+
10+
public class MultiInsertAssemblerContextBuilder {
11+
12+
public static <ENTITY> MultiInsertAssemblerContext<ENTITY> buildFromEntityList(
13+
PreparedSqlBuilder buf,
14+
EntityType<ENTITY> entityType,
15+
Naming naming,
16+
Dialect dialect,
17+
List<EntityPropertyType<ENTITY, ?>> targetPropertyTypes,
18+
List<ENTITY> entities) {
19+
20+
return buildInternal(buf, entityType, naming, dialect, targetPropertyTypes, entities);
21+
}
22+
23+
private static <ENTITY> MultiInsertAssemblerContext<ENTITY> buildInternal(
24+
PreparedSqlBuilder buf,
25+
EntityType<ENTITY> entityType,
26+
Naming naming,
27+
Dialect dialect,
28+
List<EntityPropertyType<ENTITY, ?>> targetPropertyTypes,
29+
List<ENTITY> entities) {
30+
31+
return new MultiInsertAssemblerContext<ENTITY>(
32+
buf, entityType, naming, dialect, targetPropertyTypes, entities);
33+
}
34+
}

0 commit comments

Comments
 (0)