diff --git a/README.adoc b/README.adoc
index bb40623f3..19ad15a6b 100644
--- a/README.adoc
+++ b/README.adoc
@@ -22,7 +22,7 @@ This means that it does rather little out of the box. But it offers plenty of pl
=== Fast running tests
-Fast running tests can executed with a simple
+Fast running tests can executed with a simple
[source]
----
@@ -44,8 +44,9 @@ This will also execute the unit tests.
Currently the following _databasetypes_ are available:
-* hsql (default, does not need to be running)
+* hsql (default, does not require a running database)
* mysql
+* postgres
=== Run tests with all databases
@@ -65,4 +66,4 @@ Here are some ways for you to get involved in the community:
* Github is for social coding: if you want to write code, we encourage contributions through pull requests from http://help.github.com/forking/[forks of this repository]. If you want to contribute code this way, please reference a JIRA ticket as well covering the specific issue you are addressing.
* Watch for upcoming articles on Spring by http://spring.io/blog[subscribing] to spring.io.
-Before we accept a non-trivial patch or pull request we will need you to https://cla.pivotal.io/sign/spring[sign the Contributor License Agreement]. Signing the contributor’s agreement does not grant anyone commit rights to the main repository, but it does mean that we can accept your contributions, and you will get an author credit if we do. If you forget to do so, you'll be reminded when you submit a pull request. Active contributors might be asked to join the core team, and given the ability to merge pull requests.
\ No newline at end of file
+Before we accept a non-trivial patch or pull request we will need you to https://cla.pivotal.io/sign/spring[sign the Contributor License Agreement]. Signing the contributor’s agreement does not grant anyone commit rights to the main repository, but it does mean that we can accept your contributions, and you will get an author credit if we do. If you forget to do so, you'll be reminded when you submit a pull request. Active contributors might be asked to join the core team, and given the ability to merge pull requests.
diff --git a/pom.xml b/pom.xml
index 4ffa5540e..165139428 100644
--- a/pom.xml
+++ b/pom.xml
@@ -1,243 +1,263 @@
+ xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
- 4.0.0
+ 4.0.0
org.springframework.data
spring-data-jdbc
1.0.0.BUILD-SNAPSHOT
- Spring Data JDBC
- Spring Data module for JDBC repositories.
- http://projects.spring.io/spring-data-jdbc
-
-
- org.springframework.data.build
- spring-data-parent
- 2.0.0.BUILD-SNAPSHOT
-
-
-
-
- DATAJDBC
-
- 2.0.0.BUILD-SNAPSHOT
-
- reuseReports
- 1.8.0.10
-
-
-
-
-
- release
-
-
-
- org.jfrog.buildinfo
- artifactory-maven-plugin
- false
-
-
-
-
-
-
- all-dbs
-
-
-
- org.apache.maven.plugins
- maven-surefire-plugin
-
-
- mysql-test
- test
-
- test
-
-
-
- **/*IntegrationTests.java
-
-
- mysql
-
-
-
-
- postgres-test
- test
-
- test
-
-
-
- **/*IntegrationTests.java
-
-
- postgres
-
-
-
-
-
-
-
-
-
-
-
-
-
- ${project.groupId}
- spring-data-commons
- ${springdata.commons}
-
-
-
- org.springframework
- spring-tx
-
-
-
- org.springframework
- spring-context
-
-
-
- org.springframework
- spring-beans
-
-
-
- org.springframework
- spring-jdbc
-
-
-
- org.springframework
- spring-core
-
-
- commons-logging
- commons-logging
-
-
-
-
-
- org.hsqldb
- hsqldb
- 2.2.8
- test
-
-
-
- org.assertj
- assertj-core
- 3.6.2
- test
-
-
-
- mysql
- mysql-connector-java
- 5.1.41
- test
-
-
-
- org.postgresql
- postgresql
- 42.0.0
- test
-
-
-
- de.schauderhaft.degraph
- degraph-check
- 0.1.4
- test
-
-
-
-
-
-
-
-
- org.apache.maven.plugins
- maven-surefire-plugin
- 2.12
-
-
-
-
-
-
-
- org.jacoco
- jacoco-maven-plugin
- ${jacoco}
-
- ${jacoco.destfile}
-
-
-
- jacoco-initialize
-
- prepare-agent
-
-
-
-
-
-
- org.apache.maven.plugins
- maven-surefire-plugin
-
-
- default-test
-
-
- **/*Tests.java
-
-
-
-
-
-
- org.apache.maven.plugins
- maven-assembly-plugin
-
-
- org.codehaus.mojo
- wagon-maven-plugin
-
-
- org.asciidoctor
- asciidoctor-maven-plugin
-
-
-
-
-
-
- spring-libs-snapshot
- https://repo.spring.io/libs-snapshot
-
-
-
-
-
- spring-plugins-snapshot
- https://repo.spring.io/plugins-snapshot
-
-
+ Spring Data JDBC
+ Spring Data module for JDBC repositories.
+ http://projects.spring.io/spring-data-jdbc
+
+
+ org.springframework.data.build
+ spring-data-parent
+ 2.0.0.BUILD-SNAPSHOT
+
+
+
+
+ DATAJDBC
+
+ 2.0.0.BUILD-SNAPSHOT
+
+ reuseReports
+ 1.8.0.10
+
+
+
+
+
+ release
+
+
+
+ org.jfrog.buildinfo
+ artifactory-maven-plugin
+ false
+
+
+
+
+
+
+ all-dbs
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+
+
+ mysql-test
+ test
+
+ test
+
+
+
+ **/*IntegrationTests.java
+
+
+ **/*HsqlIntegrationTests.java
+
+
+ mysql
+
+
+
+
+ postgres-test
+ test
+
+ test
+
+
+
+ **/*IntegrationTests.java
+
+
+ **/*HsqlIntegrationTests.java
+
+
+ postgres
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ${project.groupId}
+ spring-data-commons
+ ${springdata.commons}
+
+
+
+ org.springframework
+ spring-tx
+
+
+
+ org.springframework
+ spring-context
+
+
+
+ org.springframework
+ spring-beans
+
+
+
+ org.springframework
+ spring-jdbc
+
+
+
+ org.springframework
+ spring-core
+
+
+ commons-logging
+ commons-logging
+
+
+
+
+
+ org.mybatis
+ mybatis-spring
+ 1.3.1
+ true
+
+
+
+ org.mybatis
+ mybatis
+ 3.4.4
+ true
+
+
+
+ org.hsqldb
+ hsqldb
+ 2.2.8
+ test
+
+
+
+ org.assertj
+ assertj-core
+ 3.6.2
+ test
+
+
+
+ mysql
+ mysql-connector-java
+ 5.1.41
+ test
+
+
+
+ org.postgresql
+ postgresql
+ 42.0.0
+ test
+
+
+
+ de.schauderhaft.degraph
+ degraph-check
+ 0.1.4
+ test
+
+
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+ 2.12
+
+
+
+
+
+
+
+ org.jacoco
+ jacoco-maven-plugin
+ ${jacoco}
+
+ ${jacoco.destfile}
+
+
+
+ jacoco-initialize
+
+ prepare-agent
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-surefire-plugin
+
+
+ default-test
+
+
+ **/*Tests.java
+
+
+
+
+
+
+ org.apache.maven.plugins
+ maven-assembly-plugin
+
+
+ org.codehaus.mojo
+ wagon-maven-plugin
+
+
+ org.asciidoctor
+ asciidoctor-maven-plugin
+
+
+
+
+
+
+ spring-libs-snapshot
+ https://repo.spring.io/libs-snapshot
+
+
+
+
+
+ spring-plugins-snapshot
+ https://repo.spring.io/plugins-snapshot
+
+
\ No newline at end of file
diff --git a/src/main/java/org/springframework/data/jdbc/core/CascadingDataAccessStrategy.java b/src/main/java/org/springframework/data/jdbc/core/CascadingDataAccessStrategy.java
new file mode 100644
index 000000000..5ffc4493b
--- /dev/null
+++ b/src/main/java/org/springframework/data/jdbc/core/CascadingDataAccessStrategy.java
@@ -0,0 +1,113 @@
+/*
+ * Copyright 2017 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.data.jdbc.core;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Map;
+import java.util.function.Consumer;
+import java.util.function.Function;
+
+import org.springframework.data.jdbc.mapping.model.JdbcPersistentProperty;
+import org.springframework.data.mapping.PropertyPath;
+
+/**
+ * Delegates each methods to the {@link DataAccessStrategy}s passed to the constructor in turn until the first that does
+ * not throw an exception.
+ *
+ * @author Jens Schauder
+ */
+public class CascadingDataAccessStrategy implements DataAccessStrategy {
+
+ private final List strategies;
+
+ public CascadingDataAccessStrategy(List strategies) {
+ this.strategies = new ArrayList<>(strategies);
+ }
+
+ @Override
+ public void insert(T instance, Class domainType, Map additionalParameters) {
+ collectVoid(das -> das.insert(instance, domainType, additionalParameters));
+ }
+
+ @Override
+ public void update(S instance, Class domainType) {
+ collectVoid(das -> das.update(instance, domainType));
+ }
+
+ @Override
+ public void delete(Object id, Class> domainType) {
+ collectVoid(das -> das.delete(id, domainType));
+ }
+
+ @Override
+ public void delete(Object rootId, PropertyPath propertyPath) {
+ collectVoid(das -> das.delete(rootId, propertyPath));
+ }
+
+ @Override
+ public void deleteAll(Class domainType) {
+ collectVoid(das -> das.deleteAll(domainType));
+ }
+
+ @Override
+ public void deleteAll(PropertyPath propertyPath) {
+ collectVoid(das -> das.deleteAll(propertyPath));
+ }
+
+ @Override
+ public long count(Class> domainType) {
+ return collect(das -> das.count(domainType));
+ }
+
+ @Override
+ public T findById(Object id, Class domainType) {
+ return collect(das -> das.findById(id, domainType));
+ }
+
+ @Override
+ public Iterable findAll(Class domainType) {
+ return collect(das -> das.findAll(domainType));
+ }
+
+ @Override
+ public Iterable findAllById(Iterable> ids, Class domainType) {
+ return collect(das -> das.findAllById(ids, domainType));
+ }
+
+ @Override
+ public Iterable findAllByProperty(Object rootId, JdbcPersistentProperty property) {
+ return collect(das -> das.findAllByProperty(rootId, property));
+ }
+
+ @Override
+ public boolean existsById(Object id, Class domainType) {
+ return collect(das -> das.existsById(id, domainType));
+ }
+
+ private T collect(Function function) {
+ return strategies.stream().collect(new FunctionCollector<>(function));
+ }
+
+ private void collectVoid(Consumer consumer) {
+
+ collect(das -> {
+ consumer.accept(das);
+ return null;
+ });
+ }
+
+}
diff --git a/src/main/java/org/springframework/data/jdbc/core/DataAccessStrategy.java b/src/main/java/org/springframework/data/jdbc/core/DataAccessStrategy.java
new file mode 100644
index 000000000..92037e9b8
--- /dev/null
+++ b/src/main/java/org/springframework/data/jdbc/core/DataAccessStrategy.java
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2017 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.data.jdbc.core;
+
+import java.util.Map;
+
+import org.springframework.data.jdbc.mapping.model.JdbcPersistentProperty;
+import org.springframework.data.mapping.PropertyPath;
+
+/**
+ * Abstraction for accesses to the database that should be implementable with a single SQL statement and relates to a single entity as opposed to {@link JdbcEntityOperations} which provides interactions related to complete aggregates.
+ *
+ * @author Jens Schauder
+ */
+public interface DataAccessStrategy {
+
+ void insert(T instance, Class domainType, Map additionalParameters);
+
+ void update(S instance, Class domainType);
+
+ void delete(Object id, Class> domainType);
+
+ /** Deletes all entities reachable via {@literal propertyPath} from the instance identified by {@literal rootId}.
+ *
+ * @param rootId Id of the root object on which the {@literal propertyPath} is based.
+ * @param propertyPath Leading from the root object to the entities to be deleted.
+ */
+ void delete(Object rootId, PropertyPath propertyPath);
+
+ void deleteAll(Class domainType);
+
+ /** Deletes all entities reachable via {@literal propertyPath} from any instance.
+ *
+ * @param propertyPath Leading from the root object to the entities to be deleted.
+ */
+ void deleteAll(PropertyPath propertyPath);
+
+ long count(Class> domainType);
+
+ T findById(Object id, Class domainType);
+
+ Iterable findAll(Class domainType);
+
+ Iterable findAllById(Iterable> ids, Class domainType);
+
+ /**
+ * Finds all entities reachable via {@literal property} from the instance identified by {@literal rootId}.
+ *
+ * @param rootId Id of the root object on which the {@literal propertyPath} is based.
+ * @param property Leading from the root object to the entities to be found.
+ */
+ Iterable findAllByProperty(Object rootId, JdbcPersistentProperty property);
+
+ boolean existsById(Object id, Class domainType);
+
+}
diff --git a/src/main/java/org/springframework/data/jdbc/core/DefaultDataAccessStrategy.java b/src/main/java/org/springframework/data/jdbc/core/DefaultDataAccessStrategy.java
new file mode 100644
index 000000000..296642135
--- /dev/null
+++ b/src/main/java/org/springframework/data/jdbc/core/DefaultDataAccessStrategy.java
@@ -0,0 +1,320 @@
+/*
+ * Copyright 2017 the original author or authors.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.springframework.data.jdbc.core;
+
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+import java.util.stream.Collectors;
+import java.util.stream.StreamSupport;
+
+import org.springframework.core.convert.ConversionService;
+import org.springframework.core.convert.support.DefaultConversionService;
+import org.springframework.core.convert.support.GenericConversionService;
+import org.springframework.dao.EmptyResultDataAccessException;
+import org.springframework.dao.InvalidDataAccessApiUsageException;
+import org.springframework.dao.NonTransientDataAccessException;
+import org.springframework.data.convert.Jsr310Converters;
+import org.springframework.data.jdbc.mapping.model.BasicJdbcPersistentEntityInformation;
+import org.springframework.data.jdbc.mapping.model.JdbcMappingContext;
+import org.springframework.data.jdbc.mapping.model.JdbcPersistentEntity;
+import org.springframework.data.jdbc.mapping.model.JdbcPersistentEntityInformation;
+import org.springframework.data.jdbc.mapping.model.JdbcPersistentProperty;
+import org.springframework.data.jdbc.support.JdbcUtil;
+import org.springframework.data.mapping.PropertyHandler;
+import org.springframework.data.mapping.PropertyPath;
+import org.springframework.data.repository.core.EntityInformation;
+import org.springframework.jdbc.core.namedparam.MapSqlParameterSource;
+import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
+import org.springframework.jdbc.support.GeneratedKeyHolder;
+import org.springframework.jdbc.support.KeyHolder;
+import org.springframework.util.Assert;
+
+/**
+ * Generates and executes actual SQL statements.
+ *
+ * @author Jens Schauder
+ */
+public class DefaultDataAccessStrategy implements DataAccessStrategy {
+
+ private static final String ENTITY_NEW_AFTER_INSERT = "Entity [%s] still 'new' after insert. Please set either"
+ + " the id property in a BeforeInsert event handler, or ensure the database creates a value and your "
+ + "JDBC driver returns it.";
+
+ private final SqlGeneratorSource sqlGeneratorSource;
+ private final NamedParameterJdbcOperations operations;
+ private final JdbcMappingContext context;
+ private final ConversionService conversions = getDefaultConversionService();
+ private final DataAccessStrategy accessStrategy;
+
+ public DefaultDataAccessStrategy(SqlGeneratorSource sqlGeneratorSource, NamedParameterJdbcOperations operations,
+ JdbcMappingContext context, DataAccessStrategy accessStrategy) {
+
+ this.sqlGeneratorSource = sqlGeneratorSource;
+ this.operations = operations;
+ this.context = context;
+ this.accessStrategy = accessStrategy;
+ }
+
+ /**
+ * creates a {@link DefaultDataAccessStrategy} which references it self for resolution of recursive data accesses.
+ *
+ * Only suitable if this is the only access strategy in use.
+ */
+ public DefaultDataAccessStrategy(SqlGeneratorSource sqlGeneratorSource, NamedParameterJdbcOperations operations,
+ JdbcMappingContext context) {
+
+ this.sqlGeneratorSource = sqlGeneratorSource;
+ this.operations = operations;
+ this.context = context;
+ this.accessStrategy = this;
+ }
+
+ @Override
+ public void insert(T instance, Class domainType, Map additionalParameters) {
+
+ KeyHolder holder = new GeneratedKeyHolder();
+ JdbcPersistentEntity persistentEntity = getRequiredPersistentEntity(domainType);
+ JdbcPersistentEntityInformation entityInformation = context
+ .getRequiredPersistentEntityInformation(domainType);
+
+ MapSqlParameterSource parameterSource = getPropertyMap(instance, persistentEntity);
+
+ Object idValue = getIdValueOrNull(instance, persistentEntity);
+ JdbcPersistentProperty idProperty = persistentEntity.getRequiredIdProperty();
+ parameterSource.addValue(idProperty.getColumnName(), convert(idValue, idProperty.getColumnType()),
+ JdbcUtil.sqlTypeFor(idProperty.getColumnType()));
+
+ additionalParameters.forEach(parameterSource::addValue);
+
+ operations.update(sql(domainType).getInsert(idValue == null, additionalParameters.keySet()), parameterSource,
+ holder);
+
+ setIdFromJdbc(instance, holder, persistentEntity);
+
+ if (entityInformation.isNew(instance)) {
+ throw new IllegalStateException(String.format(ENTITY_NEW_AFTER_INSERT, persistentEntity));
+ }
+
+ }
+
+ @Override
+ public void update(S instance, Class domainType) {
+
+ JdbcPersistentEntity persistentEntity = getRequiredPersistentEntity(domainType);
+
+ operations.update(sql(domainType).getUpdate(), getPropertyMap(instance, persistentEntity));
+ }
+
+ @Override
+ public void delete(Object id, Class> domainType) {
+
+ String deleteByIdSql = sql(domainType).getDeleteById();
+ MapSqlParameterSource parameter = createIdParameterSource(id, domainType);
+
+ operations.update(deleteByIdSql, parameter);
+ }
+
+ @Override
+ public void delete(Object rootId, PropertyPath propertyPath) {
+
+ JdbcPersistentEntity> rootEntity = context.getRequiredPersistentEntity(propertyPath.getOwningType());
+
+ JdbcPersistentProperty referencingProperty = rootEntity.getRequiredPersistentProperty(propertyPath.getSegment());
+ Assert.notNull(referencingProperty, "No property found matching the PropertyPath " + propertyPath);
+
+ String format = sql(rootEntity.getType()).createDeleteByPath(propertyPath);
+
+ HashMap parameters = new HashMap<>();
+ parameters.put("rootId", rootId);
+ operations.update(format, parameters);
+
+ }
+
+ @Override
+ public void deleteAll(Class domainType) {
+
+ operations.getJdbcOperations().update(sql(domainType).createDeleteAllSql(null));
+ }
+
+ @Override
+ public void deleteAll(PropertyPath propertyPath) {
+
+ operations.getJdbcOperations().update(sql(propertyPath.getOwningType().getType()).createDeleteAllSql(propertyPath));
+ }
+
+ @SuppressWarnings("ConstantConditions")
+ @Override
+ public long count(Class> domainType) {
+ return operations.getJdbcOperations().queryForObject(sql(domainType).getCount(), Long.class);
+ }
+
+ @Override
+ public T findById(Object id, Class domainType) {
+
+ String findOneSql = sql(domainType).getFindOne();
+ MapSqlParameterSource parameter = createIdParameterSource(id, domainType);
+ try {
+ return operations.queryForObject(findOneSql, parameter, getEntityRowMapper(domainType));
+ } catch (EmptyResultDataAccessException e) {
+ return null;
+ }
+ }
+
+ @Override
+ public Iterable findAll(Class domainType) {
+ return operations.query(sql(domainType).getFindAll(), getEntityRowMapper(domainType));
+ }
+
+ @Override
+ public Iterable findAllById(Iterable> ids, Class domainType) {
+
+ String findAllInListSql = sql(domainType).getFindAllInList();
+ Class> targetType = getRequiredPersistentEntity(domainType).getRequiredIdProperty().getColumnType();
+
+ MapSqlParameterSource parameter = new MapSqlParameterSource( //
+ "ids", //
+ StreamSupport.stream(ids.spliterator(), false) //
+ .map(id -> convert(id, targetType)) //
+ .collect(Collectors.toList()) //
+ );
+
+ return operations.query(findAllInListSql, parameter, getEntityRowMapper(domainType));
+ }
+
+ @Override
+ public Iterable findAllByProperty(Object rootId, JdbcPersistentProperty property) {
+
+ Class> actualType = property.getActualType();
+ String findAllByProperty = sql(actualType).getFindAllByProperty(property.getReverseColumnName());
+
+ MapSqlParameterSource parameter = new MapSqlParameterSource(property.getReverseColumnName(), rootId);
+
+ return (Iterable) operations.query(findAllByProperty, parameter, getEntityRowMapper(actualType));
+ }
+
+ @Override
+ public boolean existsById(Object id, Class domainType) {
+
+ String existsSql = sql(domainType).getExists();
+ MapSqlParameterSource parameter = createIdParameterSource(id, domainType);
+ return operations.queryForObject(existsSql, parameter, Boolean.class);
+ }
+
+ private static GenericConversionService getDefaultConversionService() {
+
+ DefaultConversionService conversionService = new DefaultConversionService();
+ Jsr310Converters.getConvertersToRegister().forEach(conversionService::addConverter);
+
+ return conversionService;
+ }
+
+ private MapSqlParameterSource getPropertyMap(final S instance, JdbcPersistentEntity persistentEntity) {
+
+ MapSqlParameterSource parameters = new MapSqlParameterSource();
+
+ persistentEntity.doWithProperties((PropertyHandler) property -> {
+ if (!property.isEntity()) {
+ Object value = persistentEntity.getPropertyAccessor(instance).getProperty(property);
+
+ Object convertedValue = convert(value, property.getColumnType());
+ parameters.addValue(property.getColumnName(), convertedValue, JdbcUtil.sqlTypeFor(property.getColumnType()));
+ }
+ });
+
+ return parameters;
+ }
+
+ private ID getIdValueOrNull(S instance, JdbcPersistentEntity persistentEntity) {
+
+ EntityInformation entityInformation = new BasicJdbcPersistentEntityInformation<>(persistentEntity);
+
+ ID idValue = entityInformation.getId(instance);
+
+ return isIdPropertySimpleTypeAndValueZero(idValue, persistentEntity) ? null : idValue;
+ }
+
+ private boolean isIdPropertySimpleTypeAndValueZero(ID idValue, JdbcPersistentEntity persistentEntity) {
+
+ JdbcPersistentProperty idProperty = persistentEntity.getIdProperty();
+ return idValue == null //
+ || idProperty == null //
+ || (idProperty.getType() == int.class && idValue.equals(0)) //
+ || (idProperty.getType() == long.class && idValue.equals(0L));
+ }
+
+ private void setIdFromJdbc(S instance, KeyHolder holder, JdbcPersistentEntity persistentEntity) {
+
+ JdbcPersistentEntityInformation entityInformation = new BasicJdbcPersistentEntityInformation<>(
+ persistentEntity);
+
+ try {
+
+ getIdFromHolder(holder, persistentEntity).ifPresent(it -> {
+
+ Class> targetType = persistentEntity.getRequiredIdProperty().getType();
+ Object converted = convert(it, targetType);
+ entityInformation.setId(instance, converted);
+ });
+
+ } catch (NonTransientDataAccessException e) {
+ throw new UnableToSetId("Unable to set id of " + instance, e);
+ }
+ }
+
+ private Optional