Skip to content

Commit 7551cbd

Browse files
mipo256schauder
authored andcommitted
Support inserts for id only entities.
This broke in the past for some databases that do not support empty value lists. Closes #777 Original pull request #1047
1 parent a2a5953 commit 7551cbd

23 files changed

+263
-62
lines changed

Diff for: spring-data-jdbc/src/main/java/org/springframework/data/jdbc/core/convert/SqlGenerator.java

+5-2
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,7 @@
5050
* @author Tyler Van Gorder
5151
* @author Milan Milanov
5252
* @author Myeonghyeon Lee
53+
* @author Mikhail Polivakha
5354
*/
5455
class SqlGenerator {
5556

@@ -92,12 +93,14 @@ class SqlGenerator {
9293
SqlGenerator(RelationalMappingContext mappingContext, JdbcConverter converter, RelationalPersistentEntity<?> entity,
9394
Dialect dialect) {
9495

96+
final RenderContextFactory renderContextFactory = new RenderContextFactory(dialect);
97+
9598
this.mappingContext = mappingContext;
9699
this.entity = entity;
97100
this.sqlContext = new SqlContext(entity);
98-
this.sqlRenderer = SqlRenderer.create(new RenderContextFactory(dialect).createRenderContext());
101+
this.sqlRenderer = SqlRenderer.create(renderContextFactory.createRenderContext());
99102
this.columns = new Columns(entity, mappingContext, converter);
100-
this.renderContext = new RenderContextFactory(dialect).createRenderContext();
103+
this.renderContext = renderContextFactory.createRenderContext();
101104
}
102105

103106
/**

Diff for: spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/JdbcAggregateTemplateIntegrationTests.java

+12
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,7 @@
8080
* @author Tyler Van Gorder
8181
* @author Clemens Hahn
8282
* @author Milan Milanov
83+
* @author Mikhail Polivakha
8384
*/
8485
@ContextConfiguration
8586
@Transactional
@@ -887,6 +888,12 @@ public void saveAndLoadDateTimeWithMicrosecondPrecision() {
887888
assertThat(loaded.testTime).isEqualTo(entity.testTime);
888889
}
889890

891+
@Test // DATAJDBC-557
892+
public void insertWithIdOnly() {
893+
WithIdOnly entity = new WithIdOnly();
894+
assertThat(template.save(entity).id).isNotNull();
895+
}
896+
890897
private <T extends Number> void saveAndUpdateAggregateWithVersion(VersionedAggregate aggregate,
891898
Function<Number, T> toConcreteNumber) {
892899
saveAndUpdateAggregateWithVersion(aggregate, toConcreteNumber, 0);
@@ -1254,6 +1261,11 @@ static class WithLocalDateTime {
12541261
LocalDateTime testTime;
12551262
}
12561263

1264+
@Table
1265+
class WithIdOnly {
1266+
@Id Long id;
1267+
}
1268+
12571269
@Configuration
12581270
@Import(TestConfiguration.class)
12591271
static class Config {

Diff for: spring-data-jdbc/src/test/java/org/springframework/data/jdbc/core/convert/SqlGeneratorUnitTests.java

+16-4
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,8 @@
3939
import org.springframework.data.mapping.PersistentPropertyPath;
4040
import org.springframework.data.relational.core.dialect.AnsiDialect;
4141
import org.springframework.data.relational.core.dialect.Dialect;
42+
import org.springframework.data.relational.core.dialect.PostgresDialect;
43+
import org.springframework.data.relational.core.dialect.SqlServerDialect;
4244
import org.springframework.data.relational.core.mapping.Column;
4345
import org.springframework.data.relational.core.mapping.NamingStrategy;
4446
import org.springframework.data.relational.core.mapping.PersistentPropertyPathExtension;
@@ -61,6 +63,7 @@
6163
* @author Tom Hombergs
6264
* @author Milan Milanov
6365
* @author Myeonghyeon Lee
66+
* @author Mikhail Polivakha
6467
*/
6568
class SqlGeneratorUnitTests {
6669

@@ -391,13 +394,22 @@ void updateWithVersion() {
391394
}
392395

393396
@Test // DATAJDBC-264
394-
void getInsertForEmptyColumnList() {
397+
void getInsertForEmptyColumnListPostgres() {
395398

396-
SqlGenerator sqlGenerator = createSqlGenerator(IdOnlyEntity.class);
399+
SqlGenerator sqlGenerator = createSqlGenerator(IdOnlyEntity.class, PostgresDialect.INSTANCE);
397400

398-
String insert = sqlGenerator.getInsert(emptySet());
401+
String insertSqlStatement = sqlGenerator.getInsert(emptySet());
402+
403+
assertThat(insertSqlStatement).endsWith(" VALUES (DEFAULT) ");
404+
}
405+
406+
@Test //DATAJDBC-557
407+
void gerInsertForEmptyColumnListMsSqlServer() {
408+
SqlGenerator sqlGenerator = createSqlGenerator(IdOnlyEntity.class, SqlServerDialect.INSTANCE);
409+
410+
String insertSqlStatement = sqlGenerator.getInsert(emptySet());
399411

400-
assertThat(insert).endsWith("()");
412+
assertThat(insertSqlStatement).endsWith(" DEFAULT VALUES ");
401413
}
402414

403415
@Test // DATAJDBC-334

Diff for: spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-db2.sql

+7
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ DROP TABLE WITH_READ_ONLY;
3737
DROP TABLE VERSIONED_AGGREGATE;
3838
DROP TABLE WITH_LOCAL_DATE_TIME;
3939

40+
DROP TABLE WITH_ID_ONLY;
41+
4042
CREATE TABLE LEGO_SET
4143
(
4244
"id1" BIGINT GENERATED BY DEFAULT AS IDENTITY (START WITH 1) PRIMARY KEY,
@@ -350,4 +352,9 @@ CREATE TABLE WITH_LOCAL_DATE_TIME
350352
(
351353
ID BIGINT NOT NULL PRIMARY KEY,
352354
TEST_TIME TIMESTAMP(9)
355+
);
356+
357+
CREATE TABLE WITH_ID_ONLY
358+
(
359+
ID BIGINT GENERATED BY DEFAULT AS IDENTITY (START WITH 1) PRIMARY KEY
353360
);

Diff for: spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-h2.sql

+5
Original file line numberDiff line numberDiff line change
@@ -321,4 +321,9 @@ CREATE TABLE WITH_LOCAL_DATE_TIME
321321
(
322322
ID BIGINT PRIMARY KEY,
323323
TEST_TIME TIMESTAMP(9) WITHOUT TIME ZONE
324+
);
325+
326+
CREATE TABLE WITH_ID_ONLY
327+
(
328+
ID SERIAL PRIMARY KEY
324329
);

Diff for: spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-hsql.sql

+6-1
Original file line numberDiff line numberDiff line change
@@ -323,4 +323,9 @@ CREATE TABLE WITH_LOCAL_DATE_TIME
323323
(
324324
ID BIGINT PRIMARY KEY,
325325
TEST_TIME TIMESTAMP(9)
326-
);
326+
);
327+
328+
CREATE TABLE WITH_ID_ONLY
329+
(
330+
ID BIGINT GENERATED BY DEFAULT AS IDENTITY (START WITH 1) PRIMARY KEY
331+
)

Diff for: spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-mariadb.sql

+5
Original file line numberDiff line numberDiff line change
@@ -296,4 +296,9 @@ CREATE TABLE WITH_LOCAL_DATE_TIME
296296
(
297297
ID BIGINT PRIMARY KEY,
298298
TEST_TIME TIMESTAMP(6)
299+
);
300+
301+
CREATE TABLE WITH_ID_ONLY
302+
(
303+
ID BIGINT AUTO_INCREMENT PRIMARY KEY
299304
);

Diff for: spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-mssql.sql

+7
Original file line numberDiff line numberDiff line change
@@ -324,4 +324,11 @@ CREATE TABLE WITH_LOCAL_DATE_TIME
324324
(
325325
ID BIGINT PRIMARY KEY,
326326
TEST_TIME datetime2(7)
327+
);
328+
329+
DROP TABLE IF EXISTS WITH_ID_ONLY;
330+
331+
CREATE TABLE WITH_ID_ONLY
332+
(
333+
ID BIGINT IDENTITY PRIMARY KEY
327334
);

Diff for: spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-mysql.sql

+5
Original file line numberDiff line numberDiff line change
@@ -301,4 +301,9 @@ CREATE TABLE WITH_LOCAL_DATE_TIME
301301
(
302302
ID BIGINT PRIMARY KEY,
303303
TEST_TIME TIMESTAMP(6)
304+
);
305+
306+
CREATE TABLE WITH_ID_ONLY
307+
(
308+
ID BIGINT AUTO_INCREMENT PRIMARY KEY
304309
);

Diff for: spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-oracle.sql

+6
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ DROP TABLE NO_ID_MAP_CHAIN4 CASCADE CONSTRAINTS PURGE;
2828
DROP TABLE VERSIONED_AGGREGATE CASCADE CONSTRAINTS PURGE;
2929
DROP TABLE WITH_READ_ONLY CASCADE CONSTRAINTS PURGE;
3030
DROP TABLE WITH_LOCAL_DATE_TIME CASCADE CONSTRAINTS PURGE;
31+
DROP TABLE WITH_ID_ONLY CASCADE CONSTRAINTS PURGE;
3132

3233
CREATE TABLE LEGO_SET
3334
(
@@ -332,4 +333,9 @@ CREATE TABLE WITH_LOCAL_DATE_TIME
332333
(
333334
ID NUMBER PRIMARY KEY,
334335
TEST_TIME TIMESTAMP(9)
336+
);
337+
338+
CREATE TABLE WITH_ID_ONLY
339+
(
340+
ID NUMBER GENERATED by default on null as IDENTITY PRIMARY KEY
335341
);

Diff for: spring-data-jdbc/src/test/resources/org.springframework.data.jdbc.core/JdbcAggregateTemplateIntegrationTests-postgres.sql

+6
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ DROP TABLE CHAIN2;
1212
DROP TABLE CHAIN1;
1313
DROP TABLE CHAIN0;
1414
DROP TABLE WITH_READ_ONLY;
15+
DROP TABLE WITH_ID_ONLY;
1516

1617
CREATE TABLE LEGO_SET
1718
(
@@ -335,4 +336,9 @@ CREATE TABLE WITH_LOCAL_DATE_TIME
335336
(
336337
ID BIGINT PRIMARY KEY,
337338
TEST_TIME TIMESTAMP(9) WITHOUT TIME ZONE
339+
);
340+
341+
CREATE TABLE WITH_ID_ONLY
342+
(
343+
ID SERIAL PRIMARY KEY
338344
);

Diff for: spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/Dialect.java

+10
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232
* @author Jens Schauder
3333
* @author Myeonghyeon Lee
3434
* @author Christoph Strobl
35+
* @author Mikhail Polivakha
3536
* @since 1.1
3637
*/
3738
public interface Dialect {
@@ -109,4 +110,13 @@ default Collection<Object> getConverters() {
109110
default Set<Class<?>> simpleTypes() {
110111
return Collections.emptySet();
111112
}
113+
114+
/**
115+
* @return an appropriate {@link InsertWithDefaultValues } for that specific dialect.
116+
* for most of the Dialects the default implementation will be valid, but, for
117+
* example, in case of {@link SqlServerDialect} it is not
118+
*/
119+
default InsertWithDefaultValues getSqlInsertWithDefaultValues() {
120+
return new InsertWithDefaultValues() {};
121+
}
112122
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
package org.springframework.data.relational.core.dialect;
2+
3+
import org.springframework.data.relational.core.mapping.InsertDefaultValues;
4+
5+
/**
6+
* This interface aggregates information about an Insert with default values statement.
7+
* @author Mikhail Polivakha
8+
*/
9+
public interface InsertWithDefaultValues {
10+
11+
/**
12+
* @return the part of the sql statement, that follows after <b>INSERT INTO table</b>
13+
*/
14+
default String getDefaultInsertPart() {
15+
return InsertDefaultValues.DEFAULT.getDefaultInsertPart();
16+
}
17+
}

Diff for: spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/RenderContextFactory.java

+22-9
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@
1616
package org.springframework.data.relational.core.dialect;
1717

1818
import org.springframework.data.relational.core.sql.IdentifierProcessing;
19+
import org.springframework.data.relational.core.sql.render.InsertRenderContext;
1920
import org.springframework.data.relational.core.sql.render.NamingStrategies;
2021
import org.springframework.data.relational.core.sql.render.RenderContext;
2122
import org.springframework.data.relational.core.sql.render.RenderNamingStrategy;
@@ -26,6 +27,7 @@
2627
* Factory for {@link RenderContext} based on {@link Dialect}.
2728
*
2829
* @author Mark Paluch
30+
* @author Mikhail Polivakha
2931
* @since 1.1
3032
*/
3133
public class RenderContextFactory {
@@ -65,9 +67,9 @@ public void setNamingStrategy(RenderNamingStrategy namingStrategy) {
6567
*/
6668
public RenderContext createRenderContext() {
6769

68-
SelectRenderContext select = dialect.getSelectContext();
70+
SelectRenderContext selectRenderContext = dialect.getSelectContext();
6971

70-
return new DialectRenderContext(namingStrategy, dialect.getIdentifierProcessing(), select);
72+
return new DialectRenderContext(namingStrategy, dialect, selectRenderContext);
7173
}
7274

7375
/**
@@ -76,17 +78,18 @@ public RenderContext createRenderContext() {
7678
static class DialectRenderContext implements RenderContext {
7779

7880
private final RenderNamingStrategy renderNamingStrategy;
79-
private final IdentifierProcessing identifierProcessing;
8081
private final SelectRenderContext selectRenderContext;
82+
private final Dialect renderingDialect;
8183

82-
DialectRenderContext(RenderNamingStrategy renderNamingStrategy, IdentifierProcessing identifierProcessing, SelectRenderContext selectRenderContext) {
84+
DialectRenderContext(RenderNamingStrategy renderNamingStrategy, Dialect renderingDialect, SelectRenderContext selectRenderContext) {
8385

8486
Assert.notNull(renderNamingStrategy, "RenderNamingStrategy must not be null");
85-
Assert.notNull(identifierProcessing, "IdentifierProcessing must not be null");
87+
Assert.notNull(renderingDialect, "renderingDialect must not be null");
88+
Assert.notNull(renderingDialect.getIdentifierProcessing(), "IdentifierProcessing of renderingDialect must not be null");
8689
Assert.notNull(selectRenderContext, "SelectRenderContext must not be null");
8790

8891
this.renderNamingStrategy = renderNamingStrategy;
89-
this.identifierProcessing = identifierProcessing;
92+
this.renderingDialect = renderingDialect;
9093
this.selectRenderContext = selectRenderContext;
9194
}
9295

@@ -105,16 +108,26 @@ public RenderNamingStrategy getNamingStrategy() {
105108
*/
106109
@Override
107110
public IdentifierProcessing getIdentifierProcessing() {
108-
return identifierProcessing;
111+
return renderingDialect.getIdentifierProcessing();
109112
}
110113

111114
/*
112115
* (non-Javadoc)
113116
* @see org.springframework.data.relational.core.sql.render.RenderContext#getSelect()
114117
*/
115118
@Override
116-
public SelectRenderContext getSelect() {
119+
public SelectRenderContext getSelectRenderContext() {
117120
return selectRenderContext;
118121
}
122+
123+
@Override
124+
public InsertRenderContext getInsertRenderContext() {
125+
return new InsertRenderContext() {
126+
@Override
127+
public String getInsertDefaultValuesPartSQL() {
128+
return renderingDialect.getSqlInsertWithDefaultValues().getDefaultInsertPart();
129+
}
130+
};
131+
}
119132
}
120-
}
133+
}

Diff for: spring-data-relational/src/main/java/org/springframework/data/relational/core/dialect/SqlServerDialect.java

+12
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
*/
1616
package org.springframework.data.relational.core.dialect;
1717

18+
import org.springframework.data.relational.core.mapping.InsertDefaultValues;
1819
import org.springframework.data.relational.core.sql.IdentifierProcessing;
1920
import org.springframework.data.relational.core.sql.LockOptions;
2021
import org.springframework.data.relational.core.sql.render.SelectRenderContext;
@@ -26,6 +27,7 @@
2627
* @author Mark Paluch
2728
* @author Myeonghyeon Lee
2829
* @author Jens Schauder
30+
* @author Mikhail Polivakha
2931
* @since 1.1
3032
*/
3133
public class SqlServerDialect extends AbstractDialect {
@@ -150,4 +152,14 @@ public SelectRenderContext getSelectContext() {
150152
public IdentifierProcessing getIdentifierProcessing() {
151153
return IdentifierProcessing.NONE;
152154
}
155+
156+
@Override
157+
public InsertWithDefaultValues getSqlInsertWithDefaultValues() {
158+
return new InsertWithDefaultValues() {
159+
@Override
160+
public String getDefaultInsertPart() {
161+
return InsertDefaultValues.MS_SQL_SERVER.getDefaultInsertPart();
162+
}
163+
};
164+
}
153165
}

0 commit comments

Comments
 (0)