diff --git a/lombok.config b/lombok.config
new file mode 100644
index 0000000000..e50c7ea439
--- /dev/null
+++ b/lombok.config
@@ -0,0 +1,2 @@
+lombok.nonNull.exceptionType = IllegalArgumentException
+lombok.log.fieldName = LOG
diff --git a/pom.xml b/pom.xml
new file mode 100644
index 0000000000..a0bb887d5d
--- /dev/null
+++ b/pom.xml
@@ -0,0 +1,237 @@
+
+
+
+ 4.0.0
+
+ org.springframework.data
+ spring-data-jdbc
+ 1.0.0.DATAJDBC-99-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
+
+
+
+
+
+
+
+
+
+ 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
+
+
+
+
diff --git a/readme.md b/readme.md
index 62f3e743f9..e2beccbb41 100644
--- a/readme.md
+++ b/readme.md
@@ -9,6 +9,35 @@ The primary goal of the [Spring Data](http://projects.spring.io/spring-data) pro
## Quick Start ##
+## Execute Tests ##
+
+### Fast running tests
+
+Fast running tests can executed with a simple
+
+ mvn test
+
+This will execute unit tests and integration tests using an in-memory database.
+
+### Running tests with a real database
+
+To run the integration tests against a specific database you nned to have the database running on your local machine and then execute.
+
+ mvn test -Dspring.profiles.active=
+
+This will also execute the unit tests.
+
+Currently the following *databasetypes* are available:
+
+* hsql (default, does not need to be running)
+* mysql
+
+### Run tests with all databases
+
+ mvn test -Pall-dbs
+
+This will execute the unit tests, and all the integration tests with all the databases we currently support for testing. The databases must be running.
+
## Contributing to Spring Data JDBC ##
Here are some ways for you to get involved in the community:
diff --git a/run-tests-against-all-dbs.sh b/run-tests-against-all-dbs.sh
new file mode 100755
index 0000000000..9ef6389c1c
--- /dev/null
+++ b/run-tests-against-all-dbs.sh
@@ -0,0 +1,3 @@
+#!/bin/sh
+
+./start-all-dbs.sh && mvn clean install -Pall-dbs && ./stop-all-dbs.sh
diff --git a/src/main/java/org/springframework/data/jdbc/mapping/context/JdbcMappingContext.java b/src/main/java/org/springframework/data/jdbc/mapping/context/JdbcMappingContext.java
new file mode 100644
index 0000000000..9d228ce780
--- /dev/null
+++ b/src/main/java/org/springframework/data/jdbc/mapping/context/JdbcMappingContext.java
@@ -0,0 +1,53 @@
+/*
+ * 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.mapping.context;
+
+import org.springframework.data.jdbc.mapping.model.BasicJdbcPersistentProperty;
+import org.springframework.data.jdbc.mapping.model.JdbcPersistentEntityImpl;
+import org.springframework.data.jdbc.mapping.model.JdbcPersistentProperty;
+import org.springframework.data.mapping.context.AbstractMappingContext;
+import org.springframework.data.mapping.context.MappingContext;
+import org.springframework.data.mapping.model.Property;
+import org.springframework.data.mapping.model.SimpleTypeHolder;
+import org.springframework.data.util.TypeInformation;
+
+/**
+ * {@link MappingContext} implementation for JDBC.
+ *
+ * @author Jens Schauder
+ * @since 2.0
+ */
+public class JdbcMappingContext extends AbstractMappingContext, JdbcPersistentProperty> {
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.mapping.context.AbstractMappingContext#createPersistentEntity(org.springframework.data.util.TypeInformation)
+ */
+ @Override
+ protected JdbcPersistentEntityImpl> createPersistentEntity(TypeInformation typeInformation) {
+ return new JdbcPersistentEntityImpl<>(typeInformation);
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.mapping.context.AbstractMappingContext#createPersistentProperty(org.springframework.data.mapping.model.Property, org.springframework.data.mapping.model.MutablePersistentEntity, org.springframework.data.mapping.model.SimpleTypeHolder)
+ */
+ @Override
+ protected JdbcPersistentProperty createPersistentProperty(Property property, JdbcPersistentEntityImpl> owner,
+ SimpleTypeHolder simpleTypeHolder) {
+ return new BasicJdbcPersistentProperty(property, owner, simpleTypeHolder);
+ }
+}
diff --git a/src/main/java/org/springframework/data/jdbc/mapping/event/AfterCreation.java b/src/main/java/org/springframework/data/jdbc/mapping/event/AfterCreation.java
new file mode 100644
index 0000000000..b0f2e8edd6
--- /dev/null
+++ b/src/main/java/org/springframework/data/jdbc/mapping/event/AfterCreation.java
@@ -0,0 +1,38 @@
+/*
+ * 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.mapping.event;
+
+import org.springframework.data.jdbc.mapping.event.Identifier.Specified;
+
+/**
+ * Gets published after instantiation and setting of all the properties of an entity. This allows to do some
+ * postprocessing of entities.
+ *
+ * @author Jens Schauder
+ * @since 2.0
+ */
+public class AfterCreation extends JdbcEventWithIdAndEntity {
+
+ private static final long serialVersionUID = -4185777271143436728L;
+
+ /**
+ * @param id of the entity
+ * @param entity the newly instantiated entity.
+ */
+ public AfterCreation(Specified id, Object entity) {
+ super(id, entity);
+ }
+}
diff --git a/src/main/java/org/springframework/data/jdbc/mapping/event/AfterDelete.java b/src/main/java/org/springframework/data/jdbc/mapping/event/AfterDelete.java
new file mode 100644
index 0000000000..f1f24d1952
--- /dev/null
+++ b/src/main/java/org/springframework/data/jdbc/mapping/event/AfterDelete.java
@@ -0,0 +1,40 @@
+/*
+ * 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.mapping.event;
+
+import java.util.Optional;
+
+import org.springframework.data.jdbc.mapping.event.Identifier.Specified;
+
+/**
+ * Gets published after deletion of an entity. It will have a {@link Specified} identifier. If the entity is empty or
+ * not depends on the delete method used.
+ *
+ * @author Jens Schauder
+ * @since 2.0
+ */
+public class AfterDelete extends JdbcEventWithId {
+
+ private static final long serialVersionUID = 3594807189931141582L;
+
+ /**
+ * @param id of the entity.
+ * @param instance the deleted entity if it is available.
+ */
+ public AfterDelete(Specified id, Optional instance) {
+ super(id, instance);
+ }
+}
diff --git a/src/main/java/org/springframework/data/jdbc/mapping/event/AfterInsert.java b/src/main/java/org/springframework/data/jdbc/mapping/event/AfterInsert.java
new file mode 100644
index 0000000000..5b82bd3c93
--- /dev/null
+++ b/src/main/java/org/springframework/data/jdbc/mapping/event/AfterInsert.java
@@ -0,0 +1,37 @@
+/*
+ * 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.mapping.event;
+
+import org.springframework.data.jdbc.mapping.event.Identifier.Specified;
+
+/**
+ * Gets published after an entity got inserted into the database.
+ *
+ * @author Jens Schauder
+ * @since 2.0
+ */
+public class AfterInsert extends AfterSave {
+
+ private static final long serialVersionUID = 1312645717283677063L;
+
+ /**
+ * @param id identifier of the entity triggering the event.
+ * @param instance the newly inserted entity.
+ */
+ public AfterInsert(Specified id, Object instance) {
+ super(id, instance);
+ }
+}
diff --git a/src/main/java/org/springframework/data/jdbc/mapping/event/AfterSave.java b/src/main/java/org/springframework/data/jdbc/mapping/event/AfterSave.java
new file mode 100644
index 0000000000..9c79e21398
--- /dev/null
+++ b/src/main/java/org/springframework/data/jdbc/mapping/event/AfterSave.java
@@ -0,0 +1,37 @@
+/*
+ * 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.mapping.event;
+
+import org.springframework.data.jdbc.mapping.event.Identifier.Specified;
+
+/**
+ * Subclasses of this get published after a new instance or a changed instance was saved in the database.
+ *
+ * @author Jens Schauder
+ * @since 2.0
+ */
+public class AfterSave extends JdbcEventWithIdAndEntity {
+
+ private static final long serialVersionUID = 8982085767296982848L;
+
+ /**
+ * @param id identifier of
+ * @param instance the newly saved entity.
+ */
+ AfterSave(Specified id, Object instance) {
+ super(id, instance);
+ }
+}
diff --git a/src/main/java/org/springframework/data/jdbc/mapping/event/AfterUpdate.java b/src/main/java/org/springframework/data/jdbc/mapping/event/AfterUpdate.java
new file mode 100644
index 0000000000..cda83304b2
--- /dev/null
+++ b/src/main/java/org/springframework/data/jdbc/mapping/event/AfterUpdate.java
@@ -0,0 +1,37 @@
+/*
+ * 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.mapping.event;
+
+import org.springframework.data.jdbc.mapping.event.Identifier.Specified;
+
+/**
+ * Gets published after an entity was updated in the database.
+ *
+ * @author Jens Schauder
+ * @since 2.0
+ */
+public class AfterUpdate extends AfterSave {
+
+ private static final long serialVersionUID = -1765706904721563399L;
+
+ /**
+ * @param id of the entity
+ * @param instance the updated entity.
+ */
+ public AfterUpdate(Specified id, Object instance) {
+ super(id, instance);
+ }
+}
diff --git a/src/main/java/org/springframework/data/jdbc/mapping/event/BeforeDelete.java b/src/main/java/org/springframework/data/jdbc/mapping/event/BeforeDelete.java
new file mode 100644
index 0000000000..ef7d1c0f12
--- /dev/null
+++ b/src/main/java/org/springframework/data/jdbc/mapping/event/BeforeDelete.java
@@ -0,0 +1,39 @@
+/*
+ * 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.mapping.event;
+
+import java.util.Optional;
+
+import org.springframework.data.jdbc.mapping.event.Identifier.Specified;
+
+/**
+ * Gets published when an entity is about to get deleted.
+ *
+ * @author Jens Schauder
+ * @since 2.0
+ */
+public class BeforeDelete extends JdbcEventWithId {
+
+ private static final long serialVersionUID = -5483432053368496651L;
+
+ /**
+ * @param id the id of the entity
+ * @param entity the entity about to get deleted. Might be empty.
+ */
+ public BeforeDelete(Specified id, Optional entity) {
+ super(id, entity);
+ }
+}
diff --git a/src/main/java/org/springframework/data/jdbc/mapping/event/BeforeInsert.java b/src/main/java/org/springframework/data/jdbc/mapping/event/BeforeInsert.java
new file mode 100644
index 0000000000..37d2227034
--- /dev/null
+++ b/src/main/java/org/springframework/data/jdbc/mapping/event/BeforeInsert.java
@@ -0,0 +1,36 @@
+/*
+ * 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.mapping.event;
+
+/**
+ * Gets published before an entity gets inserted into the database. When the id-property of the entity must get set
+ * manually, an event listener for this event may do so.
+ * The {@link Identifier} is {@link Unset#UNSET}
+ *
+ * @author Jens Schauder
+ * @since 2.0
+ */
+public class BeforeInsert extends BeforeSave {
+
+ private static final long serialVersionUID = -5350552051337308870L;
+
+ /**
+ * @param instance the entity about to get inserted.
+ */
+ public BeforeInsert(Object instance) {
+ super(Unset.UNSET, instance);
+ }
+}
diff --git a/src/main/java/org/springframework/data/jdbc/mapping/event/BeforeSave.java b/src/main/java/org/springframework/data/jdbc/mapping/event/BeforeSave.java
new file mode 100644
index 0000000000..ae100d39a4
--- /dev/null
+++ b/src/main/java/org/springframework/data/jdbc/mapping/event/BeforeSave.java
@@ -0,0 +1,35 @@
+/*
+ * 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.mapping.event;
+
+/**
+ * Subclasses of this get published before an entity gets saved to the database.
+ *
+ * @author Jens Schauder
+ * @since 2.0
+ */
+public class BeforeSave extends JdbcEventWithEntity {
+
+ private static final long serialVersionUID = -6996874391990315443L;
+
+ /**
+ * @param id of the entity to be saved.
+ * @param instance the entity about to get saved.
+ */
+ BeforeSave(Identifier id, Object instance) {
+ super(id, instance);
+ }
+}
diff --git a/src/main/java/org/springframework/data/jdbc/mapping/event/BeforeUpdate.java b/src/main/java/org/springframework/data/jdbc/mapping/event/BeforeUpdate.java
new file mode 100644
index 0000000000..57f7a330e1
--- /dev/null
+++ b/src/main/java/org/springframework/data/jdbc/mapping/event/BeforeUpdate.java
@@ -0,0 +1,51 @@
+/*
+ * 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.mapping.event;
+
+import org.springframework.data.jdbc.mapping.event.Identifier.Specified;
+
+/**
+ * Gets published before an entity gets updated in the database.
+ *
+ * @author Jens Schauder
+ * @since 2.0
+ */
+public class BeforeUpdate extends BeforeSave implements WithId {
+
+ private static final long serialVersionUID = 794561215071613972L;
+
+ private final Specified id;
+
+ /**
+ * @param id of the entity about to get updated
+ * @param instance the entity about to get updated.
+ */
+ public BeforeUpdate(Specified id, Object instance) {
+
+ super(id, instance);
+
+ this.id = id;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.jdbc.mapping.event.JdbcEvent#getId()
+ */
+ @Override
+ public Specified getId() {
+ return id;
+ }
+}
diff --git a/src/main/java/org/springframework/data/jdbc/mapping/event/Identifier.java b/src/main/java/org/springframework/data/jdbc/mapping/event/Identifier.java
new file mode 100644
index 0000000000..0db4664628
--- /dev/null
+++ b/src/main/java/org/springframework/data/jdbc/mapping/event/Identifier.java
@@ -0,0 +1,79 @@
+/*
+ * 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.mapping.event;
+
+import java.util.Optional;
+
+import org.springframework.util.Assert;
+
+/**
+ * Wrapper for an identifier of an entity. Might either be a {@link Specified} or {@link Unset#UNSET}
+ *
+ * @author Jens Schauder
+ * @since 2.0
+ */
+public interface Identifier {
+
+ /**
+ * Creates a new {@link Specified} identifier for the given, non-null value.
+ *
+ * @param identifier must not be {@literal null}.
+ * @return will never be {@literal null}.
+ */
+ static Specified of(Object identifier) {
+
+ Assert.notNull(identifier, "Identifier must not be null!");
+
+ return SpecifiedIdentifier.of(identifier);
+ }
+
+ /**
+ * Creates a new {@link Identifier} for the given optional source value.
+ *
+ * @param identifier must not be {@literal null}.
+ * @return
+ */
+ static Identifier of(Optional extends Object> identifier) {
+
+ Assert.notNull(identifier, "Identifier must not be null!");
+
+ return identifier.map(it -> (Identifier) Identifier.of(it)).orElse(Unset.UNSET);
+ }
+
+ /**
+ * Returns the identifier value.
+ *
+ * @return will never be {@literal null}.
+ */
+ Optional extends Object> getOptionalValue();
+
+ /**
+ * A specified identifier that exposes a definitely present identifier value.
+ *
+ * @author Oliver Gierke
+ */
+ interface Specified extends Identifier {
+
+ /**
+ * Returns the identifier value.
+ *
+ * @return will never be {@literal null}.
+ */
+ default Object getValue() {
+ return getOptionalValue().orElseThrow(() -> new IllegalStateException("Should not happen!"));
+ }
+ }
+}
diff --git a/src/main/java/org/springframework/data/jdbc/mapping/event/JdbcEvent.java b/src/main/java/org/springframework/data/jdbc/mapping/event/JdbcEvent.java
new file mode 100644
index 0000000000..d862517a34
--- /dev/null
+++ b/src/main/java/org/springframework/data/jdbc/mapping/event/JdbcEvent.java
@@ -0,0 +1,40 @@
+/*
+ * 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.mapping.event;
+
+import java.util.Optional;
+
+/**
+ *
+ * @author Oliver Gierke
+ */
+public interface JdbcEvent {
+
+ /**
+ * The identifier of the entity, triggering this event. Also available via {@link #getSource()}.
+ *
+ * @return the source of the event as an {@link Identifier}.
+ */
+ Identifier getId();
+
+ /**
+ * Returns the entity the event was triggered for.
+ *
+ * @return will never be {@literal null}.
+ */
+ Optional getOptionalEntity();
+
+}
diff --git a/src/main/java/org/springframework/data/jdbc/mapping/event/JdbcEventWithEntity.java b/src/main/java/org/springframework/data/jdbc/mapping/event/JdbcEventWithEntity.java
new file mode 100644
index 0000000000..1d325f6bd5
--- /dev/null
+++ b/src/main/java/org/springframework/data/jdbc/mapping/event/JdbcEventWithEntity.java
@@ -0,0 +1,33 @@
+/*
+ * 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.mapping.event;
+
+import java.util.Optional;
+
+/**
+ * A {@link SimpleJdbcEvent} which is guaranteed to have an entity.
+ *
+ * @author Jens Schauder
+ * @since 2.0
+ */
+public class JdbcEventWithEntity extends SimpleJdbcEvent implements WithEntity {
+
+ private static final long serialVersionUID = 4891455396602090638L;
+
+ public JdbcEventWithEntity(Identifier id, Object entity) {
+ super(id, Optional.of(entity));
+ }
+}
diff --git a/src/main/java/org/springframework/data/jdbc/mapping/event/JdbcEventWithId.java b/src/main/java/org/springframework/data/jdbc/mapping/event/JdbcEventWithId.java
new file mode 100644
index 0000000000..d5c1ded08a
--- /dev/null
+++ b/src/main/java/org/springframework/data/jdbc/mapping/event/JdbcEventWithId.java
@@ -0,0 +1,49 @@
+/*
+ * 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.mapping.event;
+
+import java.util.Optional;
+
+import org.springframework.data.jdbc.mapping.event.Identifier.Specified;
+
+/**
+ * A {@link SimpleJdbcEvent} guaranteed to have an identifier.
+ *
+ * @author Jens Schauder
+ * @since 2.0
+ */
+public class JdbcEventWithId extends SimpleJdbcEvent implements WithId {
+
+ private static final long serialVersionUID = -8071323168471611098L;
+
+ private final Specified id;
+
+ public JdbcEventWithId(Specified id, Optional entity) {
+
+ super(id, entity);
+
+ this.id = id;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.jdbc.mapping.event.JdbcEvent#getId()
+ */
+ @Override
+ public Specified getId() {
+ return id;
+ }
+}
diff --git a/src/main/java/org/springframework/data/jdbc/mapping/event/JdbcEventWithIdAndEntity.java b/src/main/java/org/springframework/data/jdbc/mapping/event/JdbcEventWithIdAndEntity.java
new file mode 100644
index 0000000000..e9268bf4b6
--- /dev/null
+++ b/src/main/java/org/springframework/data/jdbc/mapping/event/JdbcEventWithIdAndEntity.java
@@ -0,0 +1,38 @@
+/*
+ * 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.mapping.event;
+
+import lombok.Getter;
+
+import java.util.Optional;
+
+import org.springframework.data.jdbc.mapping.event.Identifier.Specified;
+
+/**
+ * A {@link SimpleJdbcEvent} which is guaranteed to have an identifier and an entity.
+ *
+ * @author Jens Schauder
+ * @since 2.0
+ */
+@Getter
+public class JdbcEventWithIdAndEntity extends JdbcEventWithId implements WithEntity {
+
+ private static final long serialVersionUID = -3194462549552515519L;
+
+ public JdbcEventWithIdAndEntity(Specified id, Object entity) {
+ super(id, Optional.of(entity));
+ }
+}
diff --git a/src/main/java/org/springframework/data/jdbc/mapping/event/SimpleJdbcEvent.java b/src/main/java/org/springframework/data/jdbc/mapping/event/SimpleJdbcEvent.java
new file mode 100644
index 0000000000..bf5df81afa
--- /dev/null
+++ b/src/main/java/org/springframework/data/jdbc/mapping/event/SimpleJdbcEvent.java
@@ -0,0 +1,60 @@
+/*
+ * 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.mapping.event;
+
+import java.util.Optional;
+
+import org.springframework.context.ApplicationEvent;
+
+/**
+ * The common superclass for all events published by JDBC repositories. {@link #getSource} contains the
+ * {@link Identifier} of the entity triggering the event.
+ *
+ * @author Jens Schauder
+ * @author Oliver Gierke
+ * @since 2.0
+ */
+class SimpleJdbcEvent extends ApplicationEvent implements JdbcEvent {
+
+ private static final long serialVersionUID = -1798807778668751659L;
+
+ private final Object entity;
+
+ SimpleJdbcEvent(Identifier id, Optional entity) {
+
+ super(id);
+
+ this.entity = entity.orElse(null);
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.jdbc.mapping.event.JdbcEvent#getId()
+ */
+ @Override
+ public Identifier getId() {
+ return (Identifier) getSource();
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.jdbc.mapping.event.JdbcEvent#getOptionalEntity()
+ */
+ @Override
+ public Optional getOptionalEntity() {
+ return Optional.ofNullable(entity);
+ }
+}
diff --git a/src/main/java/org/springframework/data/jdbc/mapping/event/SpecifiedIdentifier.java b/src/main/java/org/springframework/data/jdbc/mapping/event/SpecifiedIdentifier.java
new file mode 100644
index 0000000000..a68c97b24e
--- /dev/null
+++ b/src/main/java/org/springframework/data/jdbc/mapping/event/SpecifiedIdentifier.java
@@ -0,0 +1,44 @@
+/*
+ * 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.mapping.event;
+
+import lombok.Value;
+
+import java.util.Optional;
+
+import org.springframework.data.jdbc.mapping.event.Identifier.Specified;
+
+/**
+ * Simple value object for {@link Specified}.
+ *
+ * @author Jens Schauder
+ * @author Oliver Gierke
+ * @since 2.0
+ */
+@Value(staticConstructor = "of")
+class SpecifiedIdentifier implements Specified {
+
+ Object value;
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.jdbc.mapping.event.Identifier#getOptionalValue()
+ */
+ @Override
+ public Optional extends Object> getOptionalValue() {
+ return Optional.of(value);
+ }
+}
diff --git a/src/main/java/org/springframework/data/jdbc/mapping/event/Unset.java b/src/main/java/org/springframework/data/jdbc/mapping/event/Unset.java
new file mode 100644
index 0000000000..0fd30e4030
--- /dev/null
+++ b/src/main/java/org/springframework/data/jdbc/mapping/event/Unset.java
@@ -0,0 +1,39 @@
+/*
+ * 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.mapping.event;
+
+import java.util.Optional;
+
+/**
+ * An unset identifier. Always returns {@link Optional#empty()} as value.
+ *
+ * @author Jens Schaude
+ * @author Oliver Gierke
+ * @since 2.0
+ */
+enum Unset implements Identifier {
+
+ UNSET;
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.jdbc.mapping.event.Identifier#getOptionalValue()
+ */
+ @Override
+ public Optional getOptionalValue() {
+ return Optional.empty();
+ }
+}
diff --git a/src/main/java/org/springframework/data/jdbc/mapping/event/WithEntity.java b/src/main/java/org/springframework/data/jdbc/mapping/event/WithEntity.java
new file mode 100644
index 0000000000..568a84376f
--- /dev/null
+++ b/src/main/java/org/springframework/data/jdbc/mapping/event/WithEntity.java
@@ -0,0 +1,33 @@
+/*
+ * 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.mapping.event;
+
+/**
+ * Interface for {@link SimpleJdbcEvent}s which are guaranteed to have an entity. Allows direct access to that entity,
+ * without going through an {@link java.util.Optional}
+ *
+ * @author Jens Schauder
+ * @since 2.0
+ */
+public interface WithEntity extends JdbcEvent {
+
+ /**
+ * @return will never be {@literal null}.
+ */
+ default Object getEntity() {
+ return getOptionalEntity().orElseThrow(() -> new IllegalStateException("Entity must not be NULL"));
+ }
+}
diff --git a/src/main/java/org/springframework/data/jdbc/mapping/event/WithId.java b/src/main/java/org/springframework/data/jdbc/mapping/event/WithId.java
new file mode 100644
index 0000000000..f5c857dcc0
--- /dev/null
+++ b/src/main/java/org/springframework/data/jdbc/mapping/event/WithId.java
@@ -0,0 +1,33 @@
+/*
+ * 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.mapping.event;
+
+import org.springframework.data.jdbc.mapping.event.Identifier.Specified;
+
+/**
+ * Interface for {@link SimpleJdbcEvent}s which are guaranteed to have a {@link Specified} identifier. Offers direct
+ * access to the {@link Specified} identifier.
+ *
+ * @author Jens Schauder
+ * @since 2.0
+ */
+public interface WithId extends JdbcEvent {
+
+ /**
+ * Events with an identifier will always return a {@link Specified} one.
+ */
+ Specified getId();
+}
diff --git a/src/main/java/org/springframework/data/jdbc/mapping/model/BasicJdbcPersistentProperty.java b/src/main/java/org/springframework/data/jdbc/mapping/model/BasicJdbcPersistentProperty.java
new file mode 100644
index 0000000000..cda2801930
--- /dev/null
+++ b/src/main/java/org/springframework/data/jdbc/mapping/model/BasicJdbcPersistentProperty.java
@@ -0,0 +1,61 @@
+/*
+ * 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.mapping.model;
+
+import org.springframework.data.mapping.Association;
+import org.springframework.data.mapping.PersistentEntity;
+import org.springframework.data.mapping.model.AnnotationBasedPersistentProperty;
+import org.springframework.data.mapping.model.Property;
+import org.springframework.data.mapping.model.SimpleTypeHolder;
+
+/**
+ * Meta data about a property to be used by repository implementations.
+ *
+ * @author Jens Schauder
+ * @since 2.0
+ */
+public class BasicJdbcPersistentProperty extends AnnotationBasedPersistentProperty
+ implements JdbcPersistentProperty {
+
+ /**
+ * Creates a new {@link AnnotationBasedPersistentProperty}.
+ *
+ * @param property must not be {@literal null}.
+ * @param owner must not be {@literal null}.
+ * @param simpleTypeHolder must not be {@literal null}.
+ */
+ public BasicJdbcPersistentProperty(Property property, PersistentEntity, JdbcPersistentProperty> owner,
+ SimpleTypeHolder simpleTypeHolder) {
+ super(property, owner, simpleTypeHolder);
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.mapping.model.AbstractPersistentProperty#createAssociation()
+ */
+ @Override
+ protected Association createAssociation() {
+ throw new UnsupportedOperationException();
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.jdbc.mapping.model.JdbcPersistentProperty#getColumnName()
+ */
+ public String getColumnName() {
+ return getName();
+ }
+}
diff --git a/src/main/java/org/springframework/data/jdbc/mapping/model/JdbcPersistentEntity.java b/src/main/java/org/springframework/data/jdbc/mapping/model/JdbcPersistentEntity.java
new file mode 100644
index 0000000000..ee7a75d340
--- /dev/null
+++ b/src/main/java/org/springframework/data/jdbc/mapping/model/JdbcPersistentEntity.java
@@ -0,0 +1,40 @@
+/*
+ * 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.mapping.model;
+
+import org.springframework.data.mapping.PersistentEntity;
+
+/**
+ * @author Jens Schauder
+ * @author Oliver Gierke
+ * @since 2.0
+ */
+public interface JdbcPersistentEntity extends PersistentEntity {
+
+ /**
+ * Returns the name of the table backing the given entity.
+ *
+ * @return
+ */
+ String getTableName();
+
+ /**
+ * Returns the column representing the identifier.
+ *
+ * @return will never be {@literal null}.
+ */
+ String getIdColumn();
+}
diff --git a/src/main/java/org/springframework/data/jdbc/mapping/model/JdbcPersistentEntityImpl.java b/src/main/java/org/springframework/data/jdbc/mapping/model/JdbcPersistentEntityImpl.java
new file mode 100644
index 0000000000..744423f10b
--- /dev/null
+++ b/src/main/java/org/springframework/data/jdbc/mapping/model/JdbcPersistentEntityImpl.java
@@ -0,0 +1,54 @@
+/*
+ * 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.mapping.model;
+
+import lombok.Getter;
+
+import org.springframework.data.mapping.model.BasicPersistentEntity;
+import org.springframework.data.util.TypeInformation;
+
+/**
+ * Meta data a repository might need for implementing persistence operations for instances of type {@code T}
+ *
+ * @author Jens Schauder
+ * @since 2.0
+ */
+public class JdbcPersistentEntityImpl extends BasicPersistentEntity
+ implements JdbcPersistentEntity {
+
+ private final @Getter String tableName;
+
+ /**
+ * Creates a new {@link JdbcPersistentEntityImpl} for the given {@link TypeInformation}.
+ *
+ * @param information must not be {@literal null}.
+ */
+ public JdbcPersistentEntityImpl(TypeInformation information) {
+
+ super(information);
+
+ tableName = getType().getSimpleName();
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.jdbc.mapping.model.JdbcPersistentEntity#getIdColumn()
+ */
+ @Override
+ public String getIdColumn() {
+ return getRequiredIdProperty().getName();
+ }
+}
diff --git a/src/main/java/org/springframework/data/jdbc/mapping/model/JdbcPersistentProperty.java b/src/main/java/org/springframework/data/jdbc/mapping/model/JdbcPersistentProperty.java
new file mode 100644
index 0000000000..7a8ffc634e
--- /dev/null
+++ b/src/main/java/org/springframework/data/jdbc/mapping/model/JdbcPersistentProperty.java
@@ -0,0 +1,35 @@
+/*
+ * 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.mapping.model;
+
+import org.springframework.data.mapping.PersistentProperty;
+
+/**
+ * A {@link PersistentProperty} for JDBC.
+ *
+ * @author Jens Schauder
+ * @author Oliver Gierke
+ * @since 2.0
+ */
+public interface JdbcPersistentProperty extends PersistentProperty {
+
+ /**
+ * Returns the name of the column backing that property.
+ *
+ * @return
+ */
+ String getColumnName();
+}
diff --git a/src/main/java/org/springframework/data/jdbc/repository/EntityRowMapper.java b/src/main/java/org/springframework/data/jdbc/repository/EntityRowMapper.java
new file mode 100644
index 0000000000..b529d9187a
--- /dev/null
+++ b/src/main/java/org/springframework/data/jdbc/repository/EntityRowMapper.java
@@ -0,0 +1,109 @@
+/*
+ * 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.repository;
+
+import lombok.RequiredArgsConstructor;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Optional;
+
+import org.springframework.core.convert.ConversionService;
+import org.springframework.core.convert.support.DefaultConversionService;
+import org.springframework.data.convert.ClassGeneratingEntityInstantiator;
+import org.springframework.data.convert.EntityInstantiator;
+import org.springframework.data.jdbc.mapping.model.JdbcPersistentEntity;
+import org.springframework.data.jdbc.mapping.model.JdbcPersistentProperty;
+import org.springframework.data.mapping.PersistentProperty;
+import org.springframework.data.mapping.PersistentPropertyAccessor;
+import org.springframework.data.mapping.PreferredConstructor.Parameter;
+import org.springframework.data.mapping.PropertyHandler;
+import org.springframework.data.mapping.model.ConvertingPropertyAccessor;
+import org.springframework.data.mapping.model.MappingException;
+import org.springframework.data.mapping.model.ParameterValueProvider;
+import org.springframework.jdbc.core.RowMapper;
+
+/**
+ * Maps a ResultSet to an entity of type {@code T}
+ *
+ * @author Jens Schauder
+ * @author Oliver Gierke
+ * @since 2.0
+ */
+@RequiredArgsConstructor
+class EntityRowMapper implements RowMapper {
+
+ private final JdbcPersistentEntity entity;
+ private final EntityInstantiator instantiator = new ClassGeneratingEntityInstantiator();
+ private final ConversionService conversions = new DefaultConversionService();
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.jdbc.core.RowMapper#mapRow(java.sql.ResultSet, int)
+ */
+ @Override
+ public T mapRow(ResultSet resultSet, int rowNumber) throws SQLException {
+
+ T result = createInstance(resultSet);
+
+ entity.doWithProperties((PropertyHandler) property -> {
+
+ PersistentPropertyAccessor accessor = entity.getPropertyAccessor(result);
+ ConvertingPropertyAccessor propertyAccessor = new ConvertingPropertyAccessor(accessor, conversions);
+
+ propertyAccessor.setProperty(property, readFrom(resultSet, property));
+ });
+
+ return result;
+ }
+
+ private T createInstance(ResultSet rs) {
+ return instantiator.createInstance(entity, ResultSetParameterValueProvider.of(rs, conversions));
+ }
+
+ private static Optional readFrom(ResultSet resultSet, PersistentProperty> property) {
+
+ try {
+ return Optional.ofNullable(resultSet.getObject(property.getName()));
+ } catch (SQLException o_O) {
+ throw new MappingException(String.format("Could not read property %s from result set!", property), o_O);
+ }
+ }
+
+ @RequiredArgsConstructor(staticName = "of")
+ private static class ResultSetParameterValueProvider implements ParameterValueProvider {
+
+ private final ResultSet resultSet;
+ private final ConversionService conversionService;
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.mapping.model.ParameterValueProvider#getParameterValue(org.springframework.data.mapping.PreferredConstructor.Parameter)
+ */
+ @Override
+ public Optional getParameterValue(Parameter parameter) {
+
+ return parameter.getName().map(name -> {
+
+ try {
+ return conversionService.convert(resultSet.getObject(name), parameter.getType().getType());
+ } catch (SQLException o_O) {
+ throw new MappingException(String.format("Couldn't read column %s from ResultSet.", name), o_O);
+ }
+ });
+ }
+ }
+}
diff --git a/src/main/java/org/springframework/data/jdbc/repository/EventPublishingEntityRowMapper.java b/src/main/java/org/springframework/data/jdbc/repository/EventPublishingEntityRowMapper.java
new file mode 100644
index 0000000000..24723ff04a
--- /dev/null
+++ b/src/main/java/org/springframework/data/jdbc/repository/EventPublishingEntityRowMapper.java
@@ -0,0 +1,57 @@
+/*
+ * 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.repository;
+
+import lombok.NonNull;
+import lombok.RequiredArgsConstructor;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+
+import org.springframework.context.ApplicationEventPublisher;
+import org.springframework.data.jdbc.mapping.event.AfterCreation;
+import org.springframework.data.jdbc.mapping.event.Identifier;
+import org.springframework.data.jdbc.repository.support.JdbcPersistentEntityInformation;
+import org.springframework.jdbc.core.RowMapper;
+
+/**
+ * A {@link RowMapper} that publishes events after a delegate, did the actual work of mapping a {@link ResultSet} to an
+ * entityInformation.
+ *
+ * @author Jens Schauder
+ * @since 2.0
+ */
+@RequiredArgsConstructor
+public class EventPublishingEntityRowMapper implements RowMapper {
+
+ private final @NonNull RowMapper delegate;
+ private final @NonNull JdbcPersistentEntityInformation entityInformation;
+ private final @NonNull ApplicationEventPublisher publisher;
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.jdbc.core.RowMapper#mapRow(java.sql.ResultSet, int)
+ */
+ @Override
+ public T mapRow(ResultSet resultSet, int i) throws SQLException {
+
+ T instance = delegate.mapRow(resultSet, i);
+
+ publisher.publishEvent(new AfterCreation(Identifier.of(entityInformation.getRequiredId(instance)), instance));
+
+ return instance;
+ }
+}
diff --git a/src/main/java/org/springframework/data/jdbc/repository/SimpleJdbcRepository.java b/src/main/java/org/springframework/data/jdbc/repository/SimpleJdbcRepository.java
new file mode 100644
index 0000000000..db0e9cf4bf
--- /dev/null
+++ b/src/main/java/org/springframework/data/jdbc/repository/SimpleJdbcRepository.java
@@ -0,0 +1,300 @@
+/*
+ * 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.repository;
+
+import java.io.Serializable;
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.stream.Collectors;
+
+import org.springframework.context.ApplicationEventPublisher;
+import org.springframework.core.convert.ConversionService;
+import org.springframework.core.convert.support.DefaultConversionService;
+import org.springframework.dao.InvalidDataAccessApiUsageException;
+import org.springframework.dao.NonTransientDataAccessException;
+import org.springframework.data.jdbc.mapping.event.AfterDelete;
+import org.springframework.data.jdbc.mapping.event.AfterInsert;
+import org.springframework.data.jdbc.mapping.event.AfterUpdate;
+import org.springframework.data.jdbc.mapping.event.BeforeDelete;
+import org.springframework.data.jdbc.mapping.event.BeforeInsert;
+import org.springframework.data.jdbc.mapping.event.BeforeUpdate;
+import org.springframework.data.jdbc.mapping.event.Identifier;
+import org.springframework.data.jdbc.mapping.event.Identifier.Specified;
+import org.springframework.data.jdbc.mapping.model.JdbcPersistentEntity;
+import org.springframework.data.jdbc.mapping.model.JdbcPersistentEntityImpl;
+import org.springframework.data.jdbc.mapping.model.JdbcPersistentProperty;
+import org.springframework.data.jdbc.repository.support.BasicJdbcPersistentEntityInformation;
+import org.springframework.data.jdbc.repository.support.JdbcPersistentEntityInformation;
+import org.springframework.data.mapping.PropertyHandler;
+import org.springframework.data.repository.CrudRepository;
+import org.springframework.data.util.Streamable;
+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;
+
+/**
+ * @author Jens Schauder
+ * @since 2.0
+ */
+public class SimpleJdbcRepository implements CrudRepository {
+
+ private static final String ENTITY_NEW_AFTER_INSERT = "Entity [%s] still 'new' after insert. Please set either"
+ + " the id property in a before insert event handler, or ensure the database creates a value and your "
+ + "JDBC driver returns it.";
+
+ private final JdbcPersistentEntity persistentEntity;
+ private final JdbcPersistentEntityInformation entityInformation;
+ private final NamedParameterJdbcOperations operations;
+ private final SqlGenerator sql;
+ private final EntityRowMapper entityRowMapper;
+ private final ApplicationEventPublisher publisher;
+ private final ConversionService conversions = new DefaultConversionService();
+
+ /**
+ * Creates a new {@link SimpleJdbcRepository} for the given {@link JdbcPersistentEntityImpl}
+ *
+ * @param persistentEntity
+ * @param jdbcOperations
+ * @param publisher
+ */
+ public SimpleJdbcRepository(JdbcPersistentEntity persistentEntity, NamedParameterJdbcOperations jdbcOperations,
+ ApplicationEventPublisher publisher) {
+
+ Assert.notNull(persistentEntity, "PersistentEntity must not be null.");
+ Assert.notNull(jdbcOperations, "JdbcOperations must not be null.");
+ Assert.notNull(publisher, "Publisher must not be null.");
+
+ this.persistentEntity = persistentEntity;
+ this.entityInformation = new BasicJdbcPersistentEntityInformation<>(persistentEntity);
+ this.operations = jdbcOperations;
+ this.publisher = publisher;
+
+ this.entityRowMapper = new EntityRowMapper<>(persistentEntity);
+ this.sql = new SqlGenerator(persistentEntity);
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.repository.CrudRepository#save(S)
+ */
+ @Override
+ public S save(S instance) {
+
+ if (entityInformation.isNew(instance)) {
+ doInsert(instance);
+ } else {
+ doUpdate(instance);
+ }
+
+ return instance;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.repository.CrudRepository#save(java.lang.Iterable)
+ */
+ @Override
+ public Iterable saveAll(Iterable entities) {
+
+ List savedEntities = new ArrayList<>();
+ entities.forEach(e -> savedEntities.add(save(e)));
+ return savedEntities;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.repository.CrudRepository#findOne(java.io.Serializable)
+ */
+ @Override
+ public Optional findById(ID id) {
+
+ return Optional
+ .ofNullable(operations.queryForObject(sql.getFindOne(), new MapSqlParameterSource("id", id), entityRowMapper));
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.repository.CrudRepository#exists(java.io.Serializable)
+ */
+ @Override
+ public boolean existsById(ID id) {
+ return operations.queryForObject(sql.getExists(), new MapSqlParameterSource("id", id), Boolean.class);
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.repository.CrudRepository#findAll()
+ */
+ @Override
+ public Iterable findAll() {
+ return operations.query(sql.getFindAll(), entityRowMapper);
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.repository.CrudRepository#findAll(java.lang.Iterable)
+ */
+ @Override
+ public Iterable findAllById(Iterable ids) {
+ return operations.query(sql.getFindAllInList(), new MapSqlParameterSource("ids", ids), entityRowMapper);
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.repository.CrudRepository#count()
+ */
+ @Override
+ public long count() {
+ return operations.getJdbcOperations().queryForObject(sql.getCount(), Long.class);
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.repository.CrudRepository#delete(java.io.Serializable)
+ */
+ @Override
+ public void deleteById(ID id) {
+ doDelete(Identifier.of(id), Optional.empty());
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.repository.CrudRepository#delete(java.lang.Object)
+ */
+ @Override
+ public void delete(T instance) {
+ doDelete(Identifier.of(entityInformation.getRequiredId(instance)), Optional.of(instance));
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.repository.CrudRepository#delete(java.lang.Iterable)
+ */
+ @Override
+ public void deleteAll(Iterable extends T> entities) {
+
+ List idList = Streamable.of(entities).stream() //
+ .map(e -> entityInformation.getRequiredId(e)) //
+ .collect(Collectors.toList());
+
+ MapSqlParameterSource sqlParameterSource = new MapSqlParameterSource("ids", idList);
+ operations.update(sql.getDeleteByList(), sqlParameterSource);
+ }
+
+ @Override
+ public void deleteAll() {
+ operations.getJdbcOperations().update(sql.getDeleteAll());
+ }
+
+ private Map getPropertyMap(final S instance) {
+
+ Map parameters = new HashMap<>();
+
+ this.persistentEntity.doWithProperties((PropertyHandler) property -> {
+
+ Optional value = persistentEntity.getPropertyAccessor(instance).getProperty(property);
+ parameters.put(property.getColumnName(), value.orElse(null));
+ });
+
+ return parameters;
+ }
+
+ private void doInsert(S instance) {
+
+ publisher.publishEvent(new BeforeInsert(instance));
+
+ KeyHolder holder = new GeneratedKeyHolder();
+
+ Map propertyMap = getPropertyMap(instance);
+ propertyMap.put(persistentEntity.getRequiredIdProperty().getColumnName(), getIdValueOrNull(instance));
+
+ operations.update(sql.getInsert(), new MapSqlParameterSource(propertyMap), holder);
+ setIdFromJdbc(instance, holder);
+
+ if (entityInformation.isNew(instance)) {
+ throw new IllegalStateException(String.format(ENTITY_NEW_AFTER_INSERT, persistentEntity));
+ }
+
+ publisher.publishEvent(new AfterInsert(Identifier.of(entityInformation.getRequiredId(instance)), instance));
+ }
+
+ private ID getIdValueOrNull(S instance) {
+
+ Optional idValue = entityInformation.getId(instance);
+ return isIdPropertySimpleTypeAndValueZero(idValue) ? null : idValue.get();
+ }
+
+ private boolean isIdPropertySimpleTypeAndValueZero(Optional idValue) {
+
+ Optional idProperty = persistentEntity.getIdProperty();
+ return !idValue.isPresent() //
+ || !idProperty.isPresent() //
+ || (((Optional) idProperty).get().getType() == int.class && idValue.equals(0)) //
+ || (((Optional) idProperty).get().getType() == long.class && idValue.equals(0L));
+ }
+
+ private void setIdFromJdbc(S instance, KeyHolder holder) {
+
+ try {
+
+ getIdFromHolder(holder).ifPresent(it -> {
+
+ Class> targetType = persistentEntity.getRequiredIdProperty().getType();
+ Object converted = convert(it, targetType);
+ entityInformation.setId(instance, Optional.of(converted));
+ });
+
+ } catch (NonTransientDataAccessException e) {
+ throw new UnableToSetId("Unable to set id of " + instance, e);
+ }
+ }
+
+ private Optional getIdFromHolder(KeyHolder holder) {
+
+ try {
+ // MySQL just returns one value with a special name
+ return Optional.ofNullable(holder.getKey());
+ } catch (InvalidDataAccessApiUsageException e) {
+ // Postgres returns a value for each column
+ return Optional.ofNullable(holder.getKeys().get(persistentEntity.getIdColumn()));
+ }
+
+ }
+
+ private V convert(Object idValueFromJdbc, Class targetType) {
+ return conversions.convert(idValueFromJdbc, targetType);
+ }
+
+ private void doDelete(Specified specifiedId, Optional optionalEntity) {
+
+ publisher.publishEvent(new BeforeDelete(specifiedId, optionalEntity));
+ operations.update(sql.getDeleteById(), new MapSqlParameterSource("id", specifiedId.getValue()));
+ publisher.publishEvent(new AfterDelete(specifiedId, optionalEntity));
+ }
+
+ private void doUpdate(S instance) {
+
+ Specified specifiedId = Identifier.of(entityInformation.getRequiredId(instance));
+ publisher.publishEvent(new BeforeUpdate(specifiedId, instance));
+ operations.update(sql.getUpdate(), getPropertyMap(instance));
+ publisher.publishEvent(new AfterUpdate(specifiedId, instance));
+ }
+}
diff --git a/src/main/java/org/springframework/data/jdbc/repository/SqlGenerator.java b/src/main/java/org/springframework/data/jdbc/repository/SqlGenerator.java
new file mode 100644
index 0000000000..89ad5918d6
--- /dev/null
+++ b/src/main/java/org/springframework/data/jdbc/repository/SqlGenerator.java
@@ -0,0 +1,176 @@
+/*
+ * 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.repository;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.stream.Collectors;
+
+import org.springframework.data.jdbc.mapping.model.JdbcPersistentEntity;
+import org.springframework.data.jdbc.mapping.model.JdbcPersistentProperty;
+import org.springframework.data.mapping.PropertyHandler;
+
+/**
+ * Generates SQL statements to be used by {@Link SimpleJdbcRepository}
+ *
+ * @author Jens Schauder
+ * @since 2.0
+ */
+class SqlGenerator {
+
+ private final String findOneSql;
+ private final String findAllSql;
+ private final String findAllInListSql;
+
+ private final String existsSql;
+ private final String countSql;
+
+ private final String insertSql;
+
+ private final String updateSql;
+
+ private final String deleteByIdSql;
+ private final String deleteAllSql;
+ private final String deleteByListSql;
+
+ private final JdbcPersistentEntity> entity;
+ private final List propertyNames = new ArrayList<>();
+ private final List nonIdPropertyNames = new ArrayList<>();
+
+ SqlGenerator(JdbcPersistentEntity> entity) {
+
+ this.entity = entity;
+
+ initPropertyNames();
+
+ findOneSql = createFindOneSelectSql();
+ findAllSql = createFindAllSql();
+ findAllInListSql = createFindAllInListSql();
+
+ existsSql = createExistsSql();
+ countSql = createCountSql();
+
+ insertSql = createInsertSql();
+
+ updateSql = createUpdateSql();
+
+ deleteByIdSql = createDeleteSql();
+ deleteAllSql = createDeleteAllSql();
+ deleteByListSql = createDeleteByListSql();
+ }
+
+ private void initPropertyNames() {
+
+ entity.doWithProperties((PropertyHandler) p -> {
+ propertyNames.add(p.getName());
+ if (!entity.isIdProperty(p)) {
+ nonIdPropertyNames.add(p.getName());
+ }
+ });
+ }
+
+ String getFindAllInList() {
+ return findAllInListSql;
+ }
+
+ String getFindAll() {
+ return findAllSql;
+ }
+
+ String getExists() {
+ return existsSql;
+ }
+
+ String getFindOne() {
+ return findOneSql;
+ }
+
+ String getInsert() {
+ return insertSql;
+ }
+
+ String getUpdate() {
+ return updateSql;
+ }
+
+ String getCount() {
+ return countSql;
+ }
+
+ String getDeleteById() {
+ return deleteByIdSql;
+ }
+
+ String getDeleteAll() {
+ return deleteAllSql;
+ }
+
+ String getDeleteByList() {
+ return deleteByListSql;
+ }
+
+ private String createFindOneSelectSql() {
+ return String.format("select * from %s where %s = :id", entity.getTableName(), entity.getIdColumn());
+ }
+
+ private String createFindAllSql() {
+ return String.format("select * from %s", entity.getTableName());
+ }
+
+ private String createFindAllInListSql() {
+ return String.format("select * from %s where %s in (:ids)", entity.getTableName(), entity.getIdColumn());
+ }
+
+ private String createExistsSql() {
+ return String.format("select count(*) from %s where %s = :id", entity.getTableName(), entity.getIdColumn());
+ }
+
+ private String createCountSql() {
+ return String.format("select count(*) from %s", entity.getTableName());
+ }
+
+ private String createInsertSql() {
+
+ String insertTemplate = "insert into %s (%s) values (%s)";
+ String tableColumns = String.join(", ", nonIdPropertyNames);
+ String parameterNames = nonIdPropertyNames.stream().collect(Collectors.joining(", :", ":", ""));
+
+ return String.format(insertTemplate, entity.getTableName(), tableColumns, parameterNames);
+ }
+
+ private String createUpdateSql() {
+
+ String updateTemplate = "update %s set %s where %s = :%s";
+
+ String setClause = propertyNames.stream()//
+ .map(n -> String.format("%s = :%s", n, n))//
+ .collect(Collectors.joining(", "));
+
+ return String.format(updateTemplate, entity.getTableName(), setClause, entity.getIdColumn(), entity.getIdColumn());
+ }
+
+ private String createDeleteSql() {
+ return String.format("delete from %s where %s = :id", entity.getTableName(), entity.getIdColumn());
+ }
+
+ private String createDeleteAllSql() {
+ return String.format("delete from %s", entity.getTableName());
+ }
+
+ private String createDeleteByListSql() {
+ return String.format("delete from %s where %s in (:ids)", entity.getTableName(), entity.getIdColumn());
+ }
+}
diff --git a/src/main/java/org/springframework/data/jdbc/repository/UnableToSetId.java b/src/main/java/org/springframework/data/jdbc/repository/UnableToSetId.java
new file mode 100644
index 0000000000..e6e22c1bc9
--- /dev/null
+++ b/src/main/java/org/springframework/data/jdbc/repository/UnableToSetId.java
@@ -0,0 +1,33 @@
+/*
+ * 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.repository;
+
+import org.springframework.dao.NonTransientDataAccessException;
+
+/**
+ * Signals failure to set the id property of an entity.
+ *
+ * @author Jens Schauder
+ * @since 2.0
+ */
+public class UnableToSetId extends NonTransientDataAccessException {
+
+ private static final long serialVersionUID = 3285001352389420376L;
+
+ public UnableToSetId(String message, Throwable cause) {
+ super(message, cause);
+ }
+}
diff --git a/src/main/java/org/springframework/data/jdbc/repository/support/BasicJdbcPersistentEntityInformation.java b/src/main/java/org/springframework/data/jdbc/repository/support/BasicJdbcPersistentEntityInformation.java
new file mode 100644
index 0000000000..6e994c3315
--- /dev/null
+++ b/src/main/java/org/springframework/data/jdbc/repository/support/BasicJdbcPersistentEntityInformation.java
@@ -0,0 +1,48 @@
+/*
+ * 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.repository.support;
+
+import java.io.Serializable;
+import java.util.Optional;
+
+import org.springframework.data.jdbc.mapping.model.JdbcPersistentEntity;
+import org.springframework.data.repository.core.support.PersistentEntityInformation;
+
+/**
+ * @author Jens Schauder
+ * @since 2.0
+ */
+public class BasicJdbcPersistentEntityInformation extends PersistentEntityInformation
+ implements JdbcPersistentEntityInformation {
+
+ private final JdbcPersistentEntity persistentEntity;
+
+ public BasicJdbcPersistentEntityInformation(JdbcPersistentEntity persistentEntity) {
+
+ super(persistentEntity);
+
+ this.persistentEntity = persistentEntity;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.jdbc.repository.support.JdbcPersistentEntityInformation#setId(java.lang.Object, java.util.Optional)
+ */
+ @Override
+ public void setId(T instance, Optional value) {
+ persistentEntity.getPropertyAccessor(instance).setProperty(persistentEntity.getRequiredIdProperty(), value);
+ }
+}
diff --git a/src/main/java/org/springframework/data/jdbc/repository/support/JdbcPersistentEntityInformation.java b/src/main/java/org/springframework/data/jdbc/repository/support/JdbcPersistentEntityInformation.java
new file mode 100644
index 0000000000..b1936fa152
--- /dev/null
+++ b/src/main/java/org/springframework/data/jdbc/repository/support/JdbcPersistentEntityInformation.java
@@ -0,0 +1,42 @@
+/*
+ * 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.repository.support;
+
+import java.io.Serializable;
+import java.util.Optional;
+
+import org.springframework.data.repository.core.EntityInformation;
+
+/**
+ * @author Jens Schauder
+ * @since 2.0
+ */
+public interface JdbcPersistentEntityInformation extends EntityInformation {
+
+ void setId(T instance, Optional value);
+
+ /**
+ * Returns the identifier of the given entity or throws and exception if it can't be obtained.
+ *
+ * @param entity must not be {@literal null}.
+ * @return the identifier of the given entity
+ * @throws IllegalArgumentException in case no identifier can be obtained for the given entity.
+ */
+ default ID getRequiredId(T entity) {
+ return getId(entity).orElseThrow(() -> new IllegalArgumentException(
+ String.format("Could not obtain required identifier from entity %s!", entity)));
+ }
+}
diff --git a/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactory.java b/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactory.java
new file mode 100644
index 0000000000..be2160d067
--- /dev/null
+++ b/src/main/java/org/springframework/data/jdbc/repository/support/JdbcRepositoryFactory.java
@@ -0,0 +1,64 @@
+/*
+ * 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.repository.support;
+
+import lombok.RequiredArgsConstructor;
+
+import java.io.Serializable;
+
+import org.springframework.context.ApplicationEventPublisher;
+import org.springframework.data.jdbc.mapping.context.JdbcMappingContext;
+import org.springframework.data.jdbc.mapping.model.JdbcPersistentEntity;
+import org.springframework.data.jdbc.repository.SimpleJdbcRepository;
+import org.springframework.data.repository.core.EntityInformation;
+import org.springframework.data.repository.core.RepositoryInformation;
+import org.springframework.data.repository.core.RepositoryMetadata;
+import org.springframework.data.repository.core.support.RepositoryFactorySupport;
+import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
+
+/**
+ * @author Jens Schauder
+ * @since 2.0
+ */
+@RequiredArgsConstructor
+public class JdbcRepositoryFactory extends RepositoryFactorySupport {
+
+ private final JdbcMappingContext context = new JdbcMappingContext();
+ private final NamedParameterJdbcOperations jdbcOperations;
+ private final ApplicationEventPublisher publisher;
+
+ @SuppressWarnings("unchecked")
+ @Override
+ public EntityInformation getEntityInformation(Class aClass) {
+
+ return context.getPersistentEntity(aClass)
+ .map(e -> new BasicJdbcPersistentEntityInformation((JdbcPersistentEntity) e)).orElseGet(null);
+ }
+
+ @Override
+ protected Object getTargetRepository(RepositoryInformation repositoryInformation) {
+
+ JdbcPersistentEntity> persistentEntity = context //
+ .getPersistentEntity(repositoryInformation.getDomainType()) //
+ .orElseThrow(() -> new IllegalArgumentException("%s does not represent a persistent entity")); //
+ return new SimpleJdbcRepository<>(persistentEntity, jdbcOperations, publisher);
+ }
+
+ @Override
+ protected Class> getRepositoryBaseClass(RepositoryMetadata repositoryMetadata) {
+ return SimpleJdbcRepository.class;
+ }
+}
diff --git a/src/test/java/org/springframework/data/jdbc/repository/EventPublishingEntityRowMapperTest.java b/src/test/java/org/springframework/data/jdbc/repository/EventPublishingEntityRowMapperTest.java
new file mode 100644
index 0000000000..fe6f94bad7
--- /dev/null
+++ b/src/test/java/org/springframework/data/jdbc/repository/EventPublishingEntityRowMapperTest.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.repository;
+
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.*;
+
+import lombok.Value;
+
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.Optional;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnitRunner;
+import org.springframework.context.ApplicationEventPublisher;
+import org.springframework.data.annotation.Id;
+import org.springframework.data.jdbc.mapping.event.AfterCreation;
+import org.springframework.data.jdbc.repository.support.JdbcPersistentEntityInformation;
+import org.springframework.jdbc.core.RowMapper;
+
+/**
+ * Unit tests for {@link EventPublishingEntityRowMapper}.
+ *
+ * @author Jens Schauder
+ * @author Oliver Gierke
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class EventPublishingEntityRowMapperTest {
+
+ @Mock RowMapper rowMapperDelegate;
+ @Mock JdbcPersistentEntityInformation entityInformation;
+ @Mock ApplicationEventPublisher publisher;
+
+ @Test // DATAJDBC-99
+ public void eventGetsPublishedAfterInstantiation() throws SQLException {
+
+ when(rowMapperDelegate.mapRow(any(ResultSet.class), anyInt())).thenReturn(new DummyEntity(1L));
+ when(entityInformation.getId(any())).thenReturn(Optional.of(1L));
+
+ EventPublishingEntityRowMapper> rowMapper = new EventPublishingEntityRowMapper<>(rowMapperDelegate,
+ entityInformation, publisher);
+
+ ResultSet resultSet = mock(ResultSet.class);
+ rowMapper.mapRow(resultSet, 1);
+
+ verify(publisher).publishEvent(isA(AfterCreation.class));
+ }
+
+ @Value
+ static class DummyEntity {
+ @Id Long Id;
+ }
+}
diff --git a/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIdGenerationIntegrationTests.java b/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIdGenerationIntegrationTests.java
new file mode 100644
index 0000000000..2c0ab66bbc
--- /dev/null
+++ b/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIdGenerationIntegrationTests.java
@@ -0,0 +1,124 @@
+/*
+ * 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.repository;
+
+import static org.assertj.core.api.Assertions.*;
+
+import lombok.Data;
+import lombok.Value;
+
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Import;
+import org.springframework.data.annotation.Id;
+import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactory;
+import org.springframework.data.jdbc.testing.TestConfiguration;
+import org.springframework.data.repository.CrudRepository;
+import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.rules.SpringClassRule;
+import org.springframework.test.context.junit4.rules.SpringMethodRule;
+
+/**
+ * Testing special cases for id generation with {@link SimpleJdbcRepository}.
+ *
+ * @author Jens Schauder
+ */
+@ContextConfiguration
+public class JdbcRepositoryIdGenerationIntegrationTests {
+
+ @Configuration
+ @Import(TestConfiguration.class)
+ static class Config {
+
+ @Autowired JdbcRepositoryFactory factory;
+
+ @Bean
+ Class> testClass() {
+ return JdbcRepositoryIdGenerationIntegrationTests.class;
+ }
+
+ @Bean
+ ReadOnlyIdEntityRepository readOnlyIdRepository() {
+ return factory.getRepository(ReadOnlyIdEntityRepository.class);
+ }
+
+ @Bean
+ PrimitiveIdEntityRepository primitiveIdRepository() {
+ return factory.getRepository(PrimitiveIdEntityRepository.class);
+ }
+ }
+
+ @ClassRule public static final SpringClassRule classRule = new SpringClassRule();
+ @Rule public SpringMethodRule methodRule = new SpringMethodRule();
+
+ @Autowired NamedParameterJdbcTemplate template;
+ @Autowired ReadOnlyIdEntityRepository readOnlyIdrepository;
+ @Autowired PrimitiveIdEntityRepository primitiveIdRepository;
+
+ @Test // DATAJDBC-98
+ public void idWithoutSetterGetsSet() {
+
+ ReadOnlyIdEntity entity = readOnlyIdrepository.save(new ReadOnlyIdEntity(null, "Entity Name"));
+
+ assertThat(entity.getId()).isNotNull();
+
+ assertThat(readOnlyIdrepository.findById(entity.getId())).hasValueSatisfying(it -> {
+
+ assertThat(it.getId()).isEqualTo(entity.getId());
+ assertThat(it.getName()).isEqualTo(entity.getName());
+ });
+ }
+
+ @Test // DATAJDBC-98
+ public void primitiveIdGetsSet() {
+
+ PrimitiveIdEntity entity = new PrimitiveIdEntity(0);
+ entity.setName("Entity Name");
+
+ PrimitiveIdEntity saved = primitiveIdRepository.save(entity);
+
+ assertThat(saved.getId()).isNotEqualTo(0L);
+
+ assertThat(primitiveIdRepository.findById(saved.getId())).hasValueSatisfying(it -> {
+
+ assertThat(it.getId()).isEqualTo(saved.getId());
+ assertThat(it.getName()).isEqualTo(saved.getName());
+ });
+ }
+
+ private interface PrimitiveIdEntityRepository extends CrudRepository {}
+
+ public interface ReadOnlyIdEntityRepository extends CrudRepository {}
+
+ @Value
+ static class ReadOnlyIdEntity {
+
+ @Id Long id;
+ String name;
+ }
+
+ @Data
+ static class PrimitiveIdEntity {
+
+ @Id private final long id;
+ String name;
+ }
+}
diff --git a/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIntegrationTests.java b/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIntegrationTests.java
new file mode 100644
index 0000000000..c7e9d67d5e
--- /dev/null
+++ b/src/test/java/org/springframework/data/jdbc/repository/JdbcRepositoryIntegrationTests.java
@@ -0,0 +1,251 @@
+/*
+ * 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.repository;
+
+import static java.util.Arrays.*;
+import static org.assertj.core.api.Assertions.*;
+
+import lombok.Data;
+
+import org.junit.ClassRule;
+import org.junit.Rule;
+import org.junit.Test;
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Import;
+import org.springframework.data.annotation.Id;
+import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactory;
+import org.springframework.data.jdbc.testing.TestConfiguration;
+import org.springframework.data.repository.CrudRepository;
+import org.springframework.jdbc.core.JdbcTemplate;
+import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
+import org.springframework.test.context.ContextConfiguration;
+import org.springframework.test.context.junit4.rules.SpringClassRule;
+import org.springframework.test.context.junit4.rules.SpringMethodRule;
+import org.springframework.test.jdbc.JdbcTestUtils;
+import org.springframework.transaction.annotation.Transactional;
+
+/**
+ * Very simple use cases for creation and usage of JdbcRepositories.
+ *
+ * @author Jens Schauder
+ */
+@ContextConfiguration
+@Transactional
+public class JdbcRepositoryIntegrationTests {
+
+ @Configuration
+ @Import(TestConfiguration.class)
+ static class Config {
+
+ @Autowired JdbcRepositoryFactory factory;
+
+ @Bean
+ Class> testClass() {
+ return JdbcRepositoryIntegrationTests.class;
+ }
+
+ @Bean
+ DummyEntityRepository dummyEntityRepository() {
+ return factory.getRepository(DummyEntityRepository.class);
+ }
+ }
+
+ @ClassRule public static final SpringClassRule classRule = new SpringClassRule();
+ @Rule public SpringMethodRule methodRule = new SpringMethodRule();
+
+ @Autowired NamedParameterJdbcTemplate template;
+ @Autowired DummyEntityRepository repository;
+
+ @Test // DATAJDBC-95
+ public void savesAnEntity() {
+
+ DummyEntity entity = repository.save(createDummyEntity());
+
+ assertThat(JdbcTestUtils.countRowsInTableWhere((JdbcTemplate) template.getJdbcOperations(), "dummyentity",
+ "idProp = " + entity.getIdProp())).isEqualTo(1);
+ }
+
+ @Test // DATAJDBC-95
+ public void saveAndLoadAnEntity() {
+
+ DummyEntity entity = repository.save(createDummyEntity());
+
+ assertThat(repository.findById(entity.getIdProp())).hasValueSatisfying(it -> {
+
+ assertThat(it.getIdProp()).isEqualTo(entity.getIdProp());
+ assertThat(it.getName()).isEqualTo(entity.getName());
+ });
+ }
+
+ @Test // DATAJDBC-97
+ public void savesManyEntities() {
+
+ DummyEntity entity = createDummyEntity();
+ DummyEntity other = createDummyEntity();
+
+ repository.saveAll(asList(entity, other));
+
+ assertThat(repository.findAll()) //
+ .extracting(DummyEntity::getIdProp) //
+ .containsExactlyInAnyOrder(entity.getIdProp(), other.getIdProp());
+ }
+
+ @Test // DATAJDBC-97
+ public void existsReturnsTrueIffEntityExists() {
+
+ DummyEntity entity = repository.save(createDummyEntity());
+
+ assertThat(repository.existsById(entity.getIdProp())).isTrue();
+ assertThat(repository.existsById(entity.getIdProp() + 1)).isFalse();
+ }
+
+ @Test // DATAJDBC-97
+ public void findAllFindsAllEntities() {
+
+ DummyEntity entity = repository.save(createDummyEntity());
+ DummyEntity other = repository.save(createDummyEntity());
+
+ Iterable all = repository.findAll();
+
+ assertThat(all)//
+ .extracting(DummyEntity::getIdProp)//
+ .containsExactlyInAnyOrder(entity.getIdProp(), other.getIdProp());
+ }
+
+ @Test // DATAJDBC-97
+ public void findAllFindsAllSpecifiedEntities() {
+
+ DummyEntity entity = repository.save(createDummyEntity());
+ DummyEntity other = repository.save(createDummyEntity());
+
+ assertThat(repository.findAllById(asList(entity.getIdProp(), other.getIdProp())))//
+ .extracting(DummyEntity::getIdProp)//
+ .containsExactlyInAnyOrder(entity.getIdProp(), other.getIdProp());
+ }
+
+ @Test // DATAJDBC-97
+ public void countsEntities() {
+
+ repository.save(createDummyEntity());
+ repository.save(createDummyEntity());
+ repository.save(createDummyEntity());
+
+ assertThat(repository.count()).isEqualTo(3L);
+ }
+
+ @Test // DATAJDBC-97
+ public void deleteById() {
+
+ DummyEntity one = repository.save(createDummyEntity());
+ DummyEntity two = repository.save(createDummyEntity());
+ DummyEntity three = repository.save(createDummyEntity());
+
+ repository.deleteById(two.getIdProp());
+
+ assertThat(repository.findAll()) //
+ .extracting(DummyEntity::getIdProp) //
+ .containsExactlyInAnyOrder(one.getIdProp(), three.getIdProp());
+ }
+
+ @Test // DATAJDBC-97
+ public void deleteByEntity() {
+
+ DummyEntity one = repository.save(createDummyEntity());
+ DummyEntity two = repository.save(createDummyEntity());
+ DummyEntity three = repository.save(createDummyEntity());
+
+ repository.delete(one);
+
+ assertThat(repository.findAll()) //
+ .extracting(DummyEntity::getIdProp) //
+ .containsExactlyInAnyOrder(two.getIdProp(), three.getIdProp());
+ }
+
+ @Test // DATAJDBC-97
+ public void deleteByList() {
+
+ DummyEntity one = repository.save(createDummyEntity());
+ DummyEntity two = repository.save(createDummyEntity());
+ DummyEntity three = repository.save(createDummyEntity());
+
+ repository.deleteAll(asList(one, three));
+
+ assertThat(repository.findAll()) //
+ .extracting(DummyEntity::getIdProp) //
+ .containsExactlyInAnyOrder(two.getIdProp());
+ }
+
+ @Test // DATAJDBC-97
+ public void deleteAll() {
+
+ repository.save(createDummyEntity());
+ repository.save(createDummyEntity());
+ repository.save(createDummyEntity());
+
+ assertThat(repository.findAll()).isNotEmpty();
+
+ repository.deleteAll();
+
+ assertThat(repository.findAll()).isEmpty();
+ }
+
+ @Test // DATAJDBC-98
+ public void update() {
+
+ DummyEntity entity = repository.save(createDummyEntity());
+
+ entity.setName("something else");
+ DummyEntity saved = repository.save(entity);
+
+ assertThat(repository.findById(entity.getIdProp())).hasValueSatisfying(it -> {
+ assertThat(it.getName()).isEqualTo(saved.getName());
+ });
+ }
+
+ @Test // DATAJDBC-98
+ public void updateMany() {
+
+ DummyEntity entity = repository.save(createDummyEntity());
+ DummyEntity other = repository.save(createDummyEntity());
+
+ entity.setName("something else");
+ other.setName("others Name");
+
+ repository.saveAll(asList(entity, other));
+
+ assertThat(repository.findAll()) //
+ .extracting(DummyEntity::getName) //
+ .containsExactlyInAnyOrder(entity.getName(), other.getName());
+ }
+
+ private static DummyEntity createDummyEntity() {
+
+ DummyEntity entity = new DummyEntity();
+ entity.setName("Entity Name");
+ return entity;
+ }
+
+ interface DummyEntityRepository extends CrudRepository {}
+
+ @Data
+ static class DummyEntity {
+
+ String name;
+ @Id private Long idProp;
+ }
+}
diff --git a/src/test/java/org/springframework/data/jdbc/repository/SimpleJdbcRepositoryEventsUnitTests.java b/src/test/java/org/springframework/data/jdbc/repository/SimpleJdbcRepositoryEventsUnitTests.java
new file mode 100644
index 0000000000..982152d019
--- /dev/null
+++ b/src/test/java/org/springframework/data/jdbc/repository/SimpleJdbcRepositoryEventsUnitTests.java
@@ -0,0 +1,135 @@
+package org.springframework.data.jdbc.repository;
+
+import static java.util.Arrays.*;
+import static org.assertj.core.api.Assertions.*;
+import static org.mockito.ArgumentMatchers.*;
+import static org.mockito.Mockito.*;
+
+import lombok.Data;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.mockito.stubbing.Answer;
+import org.springframework.context.ApplicationEventPublisher;
+import org.springframework.data.annotation.Id;
+import org.springframework.data.jdbc.mapping.event.AfterDelete;
+import org.springframework.data.jdbc.mapping.event.AfterInsert;
+import org.springframework.data.jdbc.mapping.event.AfterUpdate;
+import org.springframework.data.jdbc.mapping.event.BeforeDelete;
+import org.springframework.data.jdbc.mapping.event.BeforeInsert;
+import org.springframework.data.jdbc.mapping.event.BeforeUpdate;
+import org.springframework.data.jdbc.mapping.event.Identifier;
+import org.springframework.data.jdbc.mapping.event.JdbcEvent;
+import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactory;
+import org.springframework.data.repository.CrudRepository;
+import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations;
+import org.springframework.jdbc.core.namedparam.SqlParameterSource;
+import org.springframework.jdbc.support.KeyHolder;
+
+/**
+ * @author Jens Schauder
+ */
+public class SimpleJdbcRepositoryEventsUnitTests {
+
+ FakePublisher publisher = new FakePublisher();
+
+ DummyEntityRepository repository;
+
+ @Before
+ public void before() {
+
+ NamedParameterJdbcOperations operations = createIdGeneratingOperations();
+ JdbcRepositoryFactory factory = new JdbcRepositoryFactory(operations, publisher);
+ repository = factory.getRepository(DummyEntityRepository.class);
+ }
+
+ @Test // DATAJDBC-99
+ public void publishesEventsOnSave() {
+
+ DummyEntity entity = new DummyEntity(23L);
+
+ repository.save(entity);
+
+ assertThat(publisher.events.get(0)).isInstanceOf(BeforeUpdate.class);
+ assertThat(publisher.events.get(1)).isInstanceOf(AfterUpdate.class);
+ }
+
+ @Test // DATAJDBC-99
+ public void publishesEventsOnSaveMany() {
+
+ DummyEntity entity1 = new DummyEntity(null);
+ DummyEntity entity2 = new DummyEntity(23L);
+
+ repository.saveAll(asList(entity1, entity2));
+
+ assertThat(publisher.events.get(0)).isInstanceOf(BeforeInsert.class);
+ assertThat(publisher.events.get(1)).isInstanceOf(AfterInsert.class);
+ assertThat(publisher.events.get(2)).isInstanceOf(BeforeUpdate.class);
+ assertThat(publisher.events.get(3)).isInstanceOf(AfterUpdate.class);
+ }
+
+ @Test // DATAJDBC-99
+ public void publishesEventsOnDelete() {
+
+ DummyEntity entity = new DummyEntity(23L);
+
+ repository.delete(entity);
+
+ assertThat(publisher.events.get(0)).isInstanceOf(BeforeDelete.class);
+ assertThat(publisher.events.get(1)).isInstanceOf(AfterDelete.class);
+
+ assertThat(publisher.events.get(0).getOptionalEntity()).hasValue(entity);
+ assertThat(publisher.events.get(1).getOptionalEntity()).hasValue(entity);
+
+ assertThat(publisher.events.get(0).getId()).isEqualTo(Identifier.of(23L));
+ assertThat(publisher.events.get(1).getId()).isEqualTo(Identifier.of(23L));
+ }
+
+ @Test // DATAJDBC-99
+ public void publishesEventsOnDeleteById() {
+
+ repository.deleteById(23L);
+
+ assertThat(publisher.events.get(0)).isInstanceOf(BeforeDelete.class);
+ assertThat(publisher.events.get(1)).isInstanceOf(AfterDelete.class);
+ }
+
+ private static NamedParameterJdbcOperations createIdGeneratingOperations() {
+
+ Answer setIdInKeyHolder = invocation -> {
+
+ HashMap keys = new HashMap<>();
+ keys.put("id", 4711L);
+ KeyHolder keyHolder = invocation.getArgument(2);
+ keyHolder.getKeyList().add(keys);
+
+ return 1;
+ };
+
+ NamedParameterJdbcOperations operations = mock(NamedParameterJdbcOperations.class);
+ when(operations.update(anyString(), any(SqlParameterSource.class), any(KeyHolder.class)))
+ .thenAnswer(setIdInKeyHolder);
+ return operations;
+ }
+
+ interface DummyEntityRepository extends CrudRepository {}
+
+ @Data
+ static class DummyEntity {
+ private final @Id Long id;
+ }
+
+ static class FakePublisher implements ApplicationEventPublisher {
+
+ List events = new ArrayList<>();
+
+ @Override
+ public void publishEvent(Object o) {
+ events.add((JdbcEvent) o);
+ }
+ }
+}
diff --git a/src/test/java/org/springframework/data/jdbc/testing/DataSourceConfiguration.java b/src/test/java/org/springframework/data/jdbc/testing/DataSourceConfiguration.java
new file mode 100644
index 0000000000..01db760c67
--- /dev/null
+++ b/src/test/java/org/springframework/data/jdbc/testing/DataSourceConfiguration.java
@@ -0,0 +1,79 @@
+/*
+ * 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.testing;
+
+import javax.sql.DataSource;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationContext;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.core.env.Environment;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.jdbc.datasource.init.DataSourceInitializer;
+import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
+
+/**
+ * Basic configuration expecting subclasses to provide a {@link DataSource} via {@link #createDataSource()} to be
+ * exposed to the {@link ApplicationContext}.
+ *
+ * @author Jens Schauder
+ * @author Oliver Gierke
+ */
+@Configuration
+abstract class DataSourceConfiguration {
+
+ @Autowired Class> testClass;
+ @Autowired Environment environment;
+
+ @Bean
+ DataSource dataSource() {
+ return createDataSource();
+ }
+
+ @Bean
+ DataSourceInitializer initializer() {
+
+ DataSourceInitializer initializer = new DataSourceInitializer();
+ initializer.setDataSource(dataSource());
+
+ String[] activeProfiles = environment.getActiveProfiles();
+ String profile = activeProfiles.length == 0 ? "" : activeProfiles[0];
+
+ ClassPathResource script = new ClassPathResource(TestUtils.createScriptName(testClass, profile));
+ ResourceDatabasePopulator populator = new ResourceDatabasePopulator(script);
+ customizePopulator(populator);
+ initializer.setDatabasePopulator(populator);
+
+ return initializer;
+ }
+
+ /**
+ * Return the {@link DataSource} to be exposed as a Spring bean.
+ *
+ * @return
+ */
+ protected abstract DataSource createDataSource();
+
+ /**
+ * Callback to customize the {@link ResourceDatabasePopulator} before it will be applied to the {@link DataSource}. It
+ * will be pre-populated with a SQL script derived from the name of the current test class and the activated Spring
+ * profile.
+ *
+ * @param populator will never be {@literal null}.
+ */
+ protected void customizePopulator(ResourceDatabasePopulator populator) {}
+}
diff --git a/src/test/java/org/springframework/data/jdbc/testing/HsqlDataSourceConfiguration.java b/src/test/java/org/springframework/data/jdbc/testing/HsqlDataSourceConfiguration.java
new file mode 100644
index 0000000000..0b92c63cb4
--- /dev/null
+++ b/src/test/java/org/springframework/data/jdbc/testing/HsqlDataSourceConfiguration.java
@@ -0,0 +1,50 @@
+/*
+ * 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.testing;
+
+import javax.sql.DataSource;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Profile;
+import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseBuilder;
+import org.springframework.jdbc.datasource.embedded.EmbeddedDatabaseType;
+
+/**
+ * {@link DataSource} setup for HSQL.
+ *
+ * @author Jens Schauder
+ * @author Oliver Gierke
+ */
+@Configuration
+@Profile({ "hsql", "default" })
+class HsqlDataSourceConfiguration {
+
+ @Autowired Class> context;
+
+ @Bean
+ DataSource dataSource() {
+
+ return new EmbeddedDatabaseBuilder() //
+ .generateUniqueName(true) //
+ .setType(EmbeddedDatabaseType.HSQL) //
+ .setScriptEncoding("UTF-8") //
+ .ignoreFailedDrops(true) //
+ .addScript(TestUtils.createScriptName(context, "hsql")) //
+ .build();
+ }
+}
diff --git a/src/test/java/org/springframework/data/jdbc/testing/MySqlDataSourceConfiguration.java b/src/test/java/org/springframework/data/jdbc/testing/MySqlDataSourceConfiguration.java
new file mode 100644
index 0000000000..2bd81a504f
--- /dev/null
+++ b/src/test/java/org/springframework/data/jdbc/testing/MySqlDataSourceConfiguration.java
@@ -0,0 +1,66 @@
+/*
+ * 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.testing;
+
+import javax.annotation.PostConstruct;
+import javax.sql.DataSource;
+
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Profile;
+import org.springframework.core.io.ClassPathResource;
+import org.springframework.jdbc.datasource.init.DataSourceInitializer;
+import org.springframework.jdbc.datasource.init.DatabasePopulator;
+import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
+
+import com.mysql.jdbc.jdbc2.optional.MysqlDataSource;
+
+/**
+ * {@link DataSource} setup for MySQL.
+ *
+ * @author Jens Schauder
+ * @author Oliver Gierke
+ */
+@Configuration
+@Profile("mysql")
+class MySqlDataSourceConfiguration extends DataSourceConfiguration {
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.jdbc.testing.DataSourceConfiguration#createDataSource()
+ */
+ protected DataSource createDataSource() {
+
+ MysqlDataSource dataSource = new MysqlDataSource();
+ dataSource.setUrl("jdbc:mysql:///test?user=root");
+
+ return dataSource;
+ }
+
+ @PostConstruct
+ public void initDatabase() {
+
+ MysqlDataSource dataSource = new MysqlDataSource();
+ dataSource.setUrl("jdbc:mysql:///?user=root");
+
+ ClassPathResource createScript = new ClassPathResource("create-mysql.sql");
+ DatabasePopulator databasePopulator = new ResourceDatabasePopulator(createScript);
+
+ DataSourceInitializer initializer = new DataSourceInitializer();
+ initializer.setDatabasePopulator(databasePopulator);
+ initializer.setDataSource(dataSource);
+ initializer.afterPropertiesSet();
+ }
+}
diff --git a/src/test/java/org/springframework/data/jdbc/testing/PostgresDataSourceConfiguration.java b/src/test/java/org/springframework/data/jdbc/testing/PostgresDataSourceConfiguration.java
new file mode 100644
index 0000000000..6a7f192500
--- /dev/null
+++ b/src/test/java/org/springframework/data/jdbc/testing/PostgresDataSourceConfiguration.java
@@ -0,0 +1,55 @@
+/*
+ * 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.testing;
+
+import javax.sql.DataSource;
+
+import org.postgresql.ds.PGSimpleDataSource;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.context.annotation.Profile;
+import org.springframework.jdbc.datasource.init.ResourceDatabasePopulator;
+
+/**
+ * {@link DataSource} setup for PostgreSQL
+ *
+ * @author Jens Schauder
+ * @author Oliver Gierke
+ */
+@Configuration
+@Profile("postgres")
+public class PostgresDataSourceConfiguration extends DataSourceConfiguration {
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.jdbc.testing.DataSourceConfiguration#createDataSource()
+ */
+ protected DataSource createDataSource() {
+
+ PGSimpleDataSource ds = new PGSimpleDataSource();
+ ds.setUrl("jdbc:postgresql:///postgres");
+
+ return ds;
+ }
+
+ /*
+ * (non-Javadoc)
+ * @see org.springframework.data.jdbc.testing.DataSourceFactoryBean#customizePopulator(org.springframework.jdbc.datasource.init.ResourceDatabasePopulator)
+ */
+ @Override
+ protected void customizePopulator(ResourceDatabasePopulator populator) {
+ populator.setIgnoreFailedDrops(true);
+ }
+}
diff --git a/src/test/java/org/springframework/data/jdbc/testing/TestConfiguration.java b/src/test/java/org/springframework/data/jdbc/testing/TestConfiguration.java
new file mode 100644
index 0000000000..0c93cc3f6d
--- /dev/null
+++ b/src/test/java/org/springframework/data/jdbc/testing/TestConfiguration.java
@@ -0,0 +1,56 @@
+/*
+ * 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.testing;
+
+import javax.sql.DataSource;
+
+import org.springframework.beans.factory.annotation.Autowired;
+import org.springframework.context.ApplicationEventPublisher;
+import org.springframework.context.annotation.Bean;
+import org.springframework.context.annotation.ComponentScan;
+import org.springframework.context.annotation.Configuration;
+import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactory;
+import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
+import org.springframework.jdbc.datasource.DataSourceTransactionManager;
+import org.springframework.transaction.PlatformTransactionManager;
+
+/**
+ * Infrastructure configuration for integration tests.
+ *
+ * @author Oliver Gierke
+ */
+@Configuration
+@ComponentScan // To pick up configuration classes (per activated profile)
+public class TestConfiguration {
+
+ @Autowired DataSource dataSource;
+ @Autowired ApplicationEventPublisher publisher;
+
+ @Bean
+ JdbcRepositoryFactory jdbcRepositoryFactory() {
+ return new JdbcRepositoryFactory(namedParameterJdbcTemplate(), publisher);
+ }
+
+ @Bean
+ NamedParameterJdbcTemplate namedParameterJdbcTemplate() {
+ return new NamedParameterJdbcTemplate(dataSource);
+ }
+
+ @Bean
+ PlatformTransactionManager transactionManager() {
+ return new DataSourceTransactionManager(dataSource);
+ }
+}
diff --git a/src/test/java/org/springframework/data/jdbc/testing/TestUtils.java b/src/test/java/org/springframework/data/jdbc/testing/TestUtils.java
new file mode 100644
index 0000000000..e9e1a5dc01
--- /dev/null
+++ b/src/test/java/org/springframework/data/jdbc/testing/TestUtils.java
@@ -0,0 +1,42 @@
+/*
+ * 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.testing;
+
+import org.springframework.util.Assert;
+
+/**
+ * Utility methods for testing.
+ *
+ * @author Oliver Gierke
+ */
+public interface TestUtils {
+
+ /**
+ * Returns the name of the SQL script to be loaded for the given test class and database type.
+ *
+ * @param testClass must not be {@literal null}.
+ * @param databaseType must not be {@literal null} or empty.
+ * @return
+ */
+ public static String createScriptName(Class> testClass, String databaseType) {
+
+ Assert.notNull(testClass, "Test class must not be null!");
+ Assert.hasText(databaseType, "Database type must not be null or empty!");
+
+ return String.format("%s/%s-%s.sql", testClass.getPackage().getName(), testClass.getSimpleName(),
+ databaseType.toLowerCase());
+ }
+}
diff --git a/src/test/resources/create-mysql.sql b/src/test/resources/create-mysql.sql
new file mode 100644
index 0000000000..f1afbb0e10
--- /dev/null
+++ b/src/test/resources/create-mysql.sql
@@ -0,0 +1,2 @@
+DROP DATABASE test;
+CREATE DATABASE test;
diff --git a/src/test/resources/logback.xml b/src/test/resources/logback.xml
new file mode 100644
index 0000000000..ad5cbef50a
--- /dev/null
+++ b/src/test/resources/logback.xml
@@ -0,0 +1,16 @@
+
+
+
+
+
+ %d %5p %40.40c:%4L - %m%n
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIdGenerationIntegrationTests-hsql.sql b/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIdGenerationIntegrationTests-hsql.sql
new file mode 100644
index 0000000000..1618e2569b
--- /dev/null
+++ b/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIdGenerationIntegrationTests-hsql.sql
@@ -0,0 +1,4 @@
+-- noinspection SqlNoDataSourceInspectionForFile
+
+CREATE TABLE ReadOnlyIdEntity (ID BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 1) PRIMARY KEY, NAME VARCHAR(100))
+CREATE TABLE PrimitiveIdEntity (ID BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 1) PRIMARY KEY, NAME VARCHAR(100))
diff --git a/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIdGenerationIntegrationTests-mysql.sql b/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIdGenerationIntegrationTests-mysql.sql
new file mode 100644
index 0000000000..b02242b2c9
--- /dev/null
+++ b/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIdGenerationIntegrationTests-mysql.sql
@@ -0,0 +1,2 @@
+CREATE TABLE ReadOnlyIdEntity (ID BIGINT AUTO_INCREMENT PRIMARY KEY, NAME VARCHAR(100));
+CREATE TABLE PrimitiveIdEntity (ID BIGINT AUTO_INCREMENT PRIMARY KEY, NAME VARCHAR(100));
diff --git a/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIdGenerationIntegrationTests-postgres.sql b/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIdGenerationIntegrationTests-postgres.sql
new file mode 100644
index 0000000000..267f0b7aba
--- /dev/null
+++ b/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIdGenerationIntegrationTests-postgres.sql
@@ -0,0 +1,5 @@
+DROP TABLE ReadOnlyIdEntity;
+DROP TABLE PrimitiveIdEntity;
+
+CREATE TABLE ReadOnlyIdEntity (ID SERIAL PRIMARY KEY, NAME VARCHAR(100));
+CREATE TABLE PrimitiveIdEntity (ID SERIAL PRIMARY KEY, NAME VARCHAR(100));
diff --git a/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-hsql.sql b/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-hsql.sql
new file mode 100644
index 0000000000..d6458976a7
--- /dev/null
+++ b/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-hsql.sql
@@ -0,0 +1 @@
+CREATE TABLE dummyentity ( idProp BIGINT GENERATED BY DEFAULT AS IDENTITY ( START WITH 1 ) PRIMARY KEY, NAME VARCHAR(100))
diff --git a/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-mysql.sql b/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-mysql.sql
new file mode 100644
index 0000000000..89763ecdbf
--- /dev/null
+++ b/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-mysql.sql
@@ -0,0 +1 @@
+CREATE TABLE dummyentity (idProp BIGINT AUTO_INCREMENT PRIMARY KEY, NAME VARCHAR(100));
diff --git a/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-postgres.sql b/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-postgres.sql
new file mode 100644
index 0000000000..25e9c426ae
--- /dev/null
+++ b/src/test/resources/org.springframework.data.jdbc.repository/JdbcRepositoryIntegrationTests-postgres.sql
@@ -0,0 +1,2 @@
+DROP TABLE dummyentity;
+CREATE TABLE dummyentity (idProp SERIAL PRIMARY KEY, NAME VARCHAR(100));
diff --git a/start-all-dbs.sh b/start-all-dbs.sh
new file mode 100755
index 0000000000..69ef5d777e
--- /dev/null
+++ b/start-all-dbs.sh
@@ -0,0 +1,7 @@
+#!/bin/sh
+
+# postgres
+pg_ctl -D /usr/local/var/postgres start
+
+# mysql
+mysql.server start
diff --git a/stop-all-dbs.sh b/stop-all-dbs.sh
new file mode 100755
index 0000000000..402d21c35d
--- /dev/null
+++ b/stop-all-dbs.sh
@@ -0,0 +1,7 @@
+#!/bin/sh
+
+# postgres
+pg_ctl -D /usr/local/var/postgres stop
+
+# mysql
+mysql.server stop