Browse Source
If MyBatis-spring is available and a SqlSessionFactory in the Application Context we look for matching mapped sql statements instead of using the default generated SQL. Introduced DataAccessStrategy as a new abstraction level, operating on a single entity. JdbcEntityOperations only contain operations related to complete Aggregates. Thereby also solving DATAJDBC-132. Integration tests ending in HsqlIntegrationTest will only get executed using HsqlDb. Related issue: DATAJDBC-132.pull/18/head
30 changed files with 2013 additions and 568 deletions
@ -1,243 +1,263 @@
@@ -1,243 +1,263 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?> |
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" |
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> |
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd"> |
||||
|
||||
<modelVersion>4.0.0</modelVersion> |
||||
<modelVersion>4.0.0</modelVersion> |
||||
|
||||
<groupId>org.springframework.data</groupId> |
||||
<artifactId>spring-data-jdbc</artifactId> |
||||
<version>1.0.0.BUILD-SNAPSHOT</version> |
||||
|
||||
<name>Spring Data JDBC</name> |
||||
<description>Spring Data module for JDBC repositories.</description> |
||||
<url>http://projects.spring.io/spring-data-jdbc</url> |
||||
|
||||
<parent> |
||||
<groupId>org.springframework.data.build</groupId> |
||||
<artifactId>spring-data-parent</artifactId> |
||||
<version>2.0.0.BUILD-SNAPSHOT</version> |
||||
</parent> |
||||
|
||||
<properties> |
||||
|
||||
<dist.key>DATAJDBC</dist.key> |
||||
|
||||
<springdata.commons>2.0.0.BUILD-SNAPSHOT</springdata.commons> |
||||
|
||||
<sonar.dynamicAnalysis>reuseReports</sonar.dynamicAnalysis> |
||||
<hsqldb1>1.8.0.10</hsqldb1> |
||||
|
||||
</properties> |
||||
|
||||
<profiles> |
||||
<profile> |
||||
<id>release</id> |
||||
<build> |
||||
<plugins> |
||||
<plugin> |
||||
<groupId>org.jfrog.buildinfo</groupId> |
||||
<artifactId>artifactory-maven-plugin</artifactId> |
||||
<inherited>false</inherited> |
||||
</plugin> |
||||
</plugins> |
||||
</build> |
||||
</profile> |
||||
|
||||
<profile> |
||||
<id>all-dbs</id> |
||||
<build> |
||||
<plugins> |
||||
<plugin> |
||||
<groupId>org.apache.maven.plugins</groupId> |
||||
<artifactId>maven-surefire-plugin</artifactId> |
||||
<executions> |
||||
<execution> |
||||
<id>mysql-test</id> |
||||
<phase>test</phase> |
||||
<goals> |
||||
<goal>test</goal> |
||||
</goals> |
||||
<configuration> |
||||
<includes> |
||||
<include>**/*IntegrationTests.java</include> |
||||
</includes> |
||||
<systemPropertyVariables> |
||||
<spring.profiles.active>mysql</spring.profiles.active> |
||||
</systemPropertyVariables> |
||||
</configuration> |
||||
</execution> |
||||
<execution> |
||||
<id>postgres-test</id> |
||||
<phase>test</phase> |
||||
<goals> |
||||
<goal>test</goal> |
||||
</goals> |
||||
<configuration> |
||||
<includes> |
||||
<include>**/*IntegrationTests.java</include> |
||||
</includes> |
||||
<systemPropertyVariables> |
||||
<spring.profiles.active>postgres</spring.profiles.active> |
||||
</systemPropertyVariables> |
||||
</configuration> |
||||
</execution> |
||||
</executions> |
||||
</plugin> |
||||
</plugins> |
||||
</build> |
||||
</profile> |
||||
</profiles> |
||||
|
||||
<dependencies> |
||||
|
||||
<dependency> |
||||
<groupId>${project.groupId}</groupId> |
||||
<artifactId>spring-data-commons</artifactId> |
||||
<version>${springdata.commons}</version> |
||||
</dependency> |
||||
|
||||
<dependency> |
||||
<groupId>org.springframework</groupId> |
||||
<artifactId>spring-tx</artifactId> |
||||
</dependency> |
||||
|
||||
<dependency> |
||||
<groupId>org.springframework</groupId> |
||||
<artifactId>spring-context</artifactId> |
||||
</dependency> |
||||
|
||||
<dependency> |
||||
<groupId>org.springframework</groupId> |
||||
<artifactId>spring-beans</artifactId> |
||||
</dependency> |
||||
|
||||
<dependency> |
||||
<groupId>org.springframework</groupId> |
||||
<artifactId>spring-jdbc</artifactId> |
||||
</dependency> |
||||
|
||||
<dependency> |
||||
<groupId>org.springframework</groupId> |
||||
<artifactId>spring-core</artifactId> |
||||
<exclusions> |
||||
<exclusion> |
||||
<groupId>commons-logging</groupId> |
||||
<artifactId>commons-logging</artifactId> |
||||
</exclusion> |
||||
</exclusions> |
||||
</dependency> |
||||
|
||||
<dependency> |
||||
<groupId>org.hsqldb</groupId> |
||||
<artifactId>hsqldb</artifactId> |
||||
<version>2.2.8</version> |
||||
<scope>test</scope> |
||||
</dependency> |
||||
|
||||
<dependency> |
||||
<groupId>org.assertj</groupId> |
||||
<artifactId>assertj-core</artifactId> |
||||
<version>3.6.2</version> |
||||
<scope>test</scope> |
||||
</dependency> |
||||
|
||||
<dependency> |
||||
<groupId>mysql</groupId> |
||||
<artifactId>mysql-connector-java</artifactId> |
||||
<version>5.1.41</version> |
||||
<scope>test</scope> |
||||
</dependency> |
||||
|
||||
<dependency> |
||||
<groupId>org.postgresql</groupId> |
||||
<artifactId>postgresql</artifactId> |
||||
<version>42.0.0</version> |
||||
<scope>test</scope> |
||||
</dependency> |
||||
|
||||
<dependency> |
||||
<groupId>de.schauderhaft.degraph</groupId> |
||||
<artifactId>degraph-check</artifactId> |
||||
<version>0.1.4</version> |
||||
<scope>test</scope> |
||||
</dependency> |
||||
|
||||
</dependencies> |
||||
|
||||
<build> |
||||
<pluginManagement> |
||||
<plugins> |
||||
<plugin> |
||||
<groupId>org.apache.maven.plugins</groupId> |
||||
<artifactId>maven-surefire-plugin</artifactId> |
||||
<version>2.12</version> |
||||
</plugin> |
||||
</plugins> |
||||
</pluginManagement> |
||||
<plugins> |
||||
|
||||
<!-- |
||||
Jacoco plugin redeclared to make sure it's downloaded and |
||||
the agents can be explicitly added to the test executions. |
||||
--> |
||||
<plugin> |
||||
<groupId>org.jacoco</groupId> |
||||
<artifactId>jacoco-maven-plugin</artifactId> |
||||
<version>${jacoco}</version> |
||||
<configuration> |
||||
<destFile>${jacoco.destfile}</destFile> |
||||
</configuration> |
||||
<executions> |
||||
<execution> |
||||
<id>jacoco-initialize</id> |
||||
<goals> |
||||
<goal>prepare-agent</goal> |
||||
</goals> |
||||
</execution> |
||||
</executions> |
||||
</plugin> |
||||
|
||||
<plugin> |
||||
<groupId>org.apache.maven.plugins</groupId> |
||||
<artifactId>maven-surefire-plugin</artifactId> |
||||
<executions> |
||||
<execution> |
||||
<id>default-test</id> |
||||
<configuration> |
||||
<includes> |
||||
<include>**/*Tests.java</include> |
||||
</includes> |
||||
</configuration> |
||||
</execution> |
||||
</executions> |
||||
</plugin> |
||||
<plugin> |
||||
<groupId>org.apache.maven.plugins</groupId> |
||||
<artifactId>maven-assembly-plugin</artifactId> |
||||
</plugin> |
||||
<plugin> |
||||
<groupId>org.codehaus.mojo</groupId> |
||||
<artifactId>wagon-maven-plugin</artifactId> |
||||
</plugin> |
||||
<plugin> |
||||
<groupId>org.asciidoctor</groupId> |
||||
<artifactId>asciidoctor-maven-plugin</artifactId> |
||||
</plugin> |
||||
</plugins> |
||||
</build> |
||||
|
||||
<repositories> |
||||
<repository> |
||||
<id>spring-libs-snapshot</id> |
||||
<url>https://repo.spring.io/libs-snapshot</url> |
||||
</repository> |
||||
</repositories> |
||||
|
||||
<pluginRepositories> |
||||
<pluginRepository> |
||||
<id>spring-plugins-snapshot</id> |
||||
<url>https://repo.spring.io/plugins-snapshot</url> |
||||
</pluginRepository> |
||||
</pluginRepositories> |
||||
<name>Spring Data JDBC</name> |
||||
<description>Spring Data module for JDBC repositories.</description> |
||||
<url>http://projects.spring.io/spring-data-jdbc</url> |
||||
|
||||
<parent> |
||||
<groupId>org.springframework.data.build</groupId> |
||||
<artifactId>spring-data-parent</artifactId> |
||||
<version>2.0.0.BUILD-SNAPSHOT</version> |
||||
</parent> |
||||
|
||||
<properties> |
||||
|
||||
<dist.key>DATAJDBC</dist.key> |
||||
|
||||
<springdata.commons>2.0.0.BUILD-SNAPSHOT</springdata.commons> |
||||
|
||||
<sonar.dynamicAnalysis>reuseReports</sonar.dynamicAnalysis> |
||||
<hsqldb1>1.8.0.10</hsqldb1> |
||||
|
||||
</properties> |
||||
|
||||
<profiles> |
||||
<profile> |
||||
<id>release</id> |
||||
<build> |
||||
<plugins> |
||||
<plugin> |
||||
<groupId>org.jfrog.buildinfo</groupId> |
||||
<artifactId>artifactory-maven-plugin</artifactId> |
||||
<inherited>false</inherited> |
||||
</plugin> |
||||
</plugins> |
||||
</build> |
||||
</profile> |
||||
|
||||
<profile> |
||||
<id>all-dbs</id> |
||||
<build> |
||||
<plugins> |
||||
<plugin> |
||||
<groupId>org.apache.maven.plugins</groupId> |
||||
<artifactId>maven-surefire-plugin</artifactId> |
||||
<executions> |
||||
<execution> |
||||
<id>mysql-test</id> |
||||
<phase>test</phase> |
||||
<goals> |
||||
<goal>test</goal> |
||||
</goals> |
||||
<configuration> |
||||
<includes> |
||||
<include>**/*IntegrationTests.java</include> |
||||
</includes> |
||||
<excludes> |
||||
<exclude>**/*HsqlIntegrationTests.java</exclude> |
||||
</excludes> |
||||
<systemPropertyVariables> |
||||
<spring.profiles.active>mysql</spring.profiles.active> |
||||
</systemPropertyVariables> |
||||
</configuration> |
||||
</execution> |
||||
<execution> |
||||
<id>postgres-test</id> |
||||
<phase>test</phase> |
||||
<goals> |
||||
<goal>test</goal> |
||||
</goals> |
||||
<configuration> |
||||
<includes> |
||||
<include>**/*IntegrationTests.java</include> |
||||
</includes> |
||||
<excludes> |
||||
<exclude>**/*HsqlIntegrationTests.java</exclude> |
||||
</excludes> |
||||
<systemPropertyVariables> |
||||
<spring.profiles.active>postgres</spring.profiles.active> |
||||
</systemPropertyVariables> |
||||
</configuration> |
||||
</execution> |
||||
</executions> |
||||
</plugin> |
||||
</plugins> |
||||
</build> |
||||
</profile> |
||||
</profiles> |
||||
|
||||
<dependencies> |
||||
|
||||
<dependency> |
||||
<groupId>${project.groupId}</groupId> |
||||
<artifactId>spring-data-commons</artifactId> |
||||
<version>${springdata.commons}</version> |
||||
</dependency> |
||||
|
||||
<dependency> |
||||
<groupId>org.springframework</groupId> |
||||
<artifactId>spring-tx</artifactId> |
||||
</dependency> |
||||
|
||||
<dependency> |
||||
<groupId>org.springframework</groupId> |
||||
<artifactId>spring-context</artifactId> |
||||
</dependency> |
||||
|
||||
<dependency> |
||||
<groupId>org.springframework</groupId> |
||||
<artifactId>spring-beans</artifactId> |
||||
</dependency> |
||||
|
||||
<dependency> |
||||
<groupId>org.springframework</groupId> |
||||
<artifactId>spring-jdbc</artifactId> |
||||
</dependency> |
||||
|
||||
<dependency> |
||||
<groupId>org.springframework</groupId> |
||||
<artifactId>spring-core</artifactId> |
||||
<exclusions> |
||||
<exclusion> |
||||
<groupId>commons-logging</groupId> |
||||
<artifactId>commons-logging</artifactId> |
||||
</exclusion> |
||||
</exclusions> |
||||
</dependency> |
||||
|
||||
<dependency> |
||||
<groupId>org.mybatis</groupId> |
||||
<artifactId>mybatis-spring</artifactId> |
||||
<version>1.3.1</version> |
||||
<optional>true</optional> |
||||
</dependency> |
||||
|
||||
<dependency> |
||||
<groupId>org.mybatis</groupId> |
||||
<artifactId>mybatis</artifactId> |
||||
<version>3.4.4</version> |
||||
<optional>true</optional> |
||||
</dependency> |
||||
|
||||
<dependency> |
||||
<groupId>org.hsqldb</groupId> |
||||
<artifactId>hsqldb</artifactId> |
||||
<version>2.2.8</version> |
||||
<scope>test</scope> |
||||
</dependency> |
||||
|
||||
<dependency> |
||||
<groupId>org.assertj</groupId> |
||||
<artifactId>assertj-core</artifactId> |
||||
<version>3.6.2</version> |
||||
<scope>test</scope> |
||||
</dependency> |
||||
|
||||
<dependency> |
||||
<groupId>mysql</groupId> |
||||
<artifactId>mysql-connector-java</artifactId> |
||||
<version>5.1.41</version> |
||||
<scope>test</scope> |
||||
</dependency> |
||||
|
||||
<dependency> |
||||
<groupId>org.postgresql</groupId> |
||||
<artifactId>postgresql</artifactId> |
||||
<version>42.0.0</version> |
||||
<scope>test</scope> |
||||
</dependency> |
||||
|
||||
<dependency> |
||||
<groupId>de.schauderhaft.degraph</groupId> |
||||
<artifactId>degraph-check</artifactId> |
||||
<version>0.1.4</version> |
||||
<scope>test</scope> |
||||
</dependency> |
||||
|
||||
</dependencies> |
||||
|
||||
<build> |
||||
<pluginManagement> |
||||
<plugins> |
||||
<plugin> |
||||
<groupId>org.apache.maven.plugins</groupId> |
||||
<artifactId>maven-surefire-plugin</artifactId> |
||||
<version>2.12</version> |
||||
</plugin> |
||||
</plugins> |
||||
</pluginManagement> |
||||
<plugins> |
||||
|
||||
<!-- |
||||
Jacoco plugin redeclared to make sure it's downloaded and |
||||
the agents can be explicitly added to the test executions. |
||||
--> |
||||
<plugin> |
||||
<groupId>org.jacoco</groupId> |
||||
<artifactId>jacoco-maven-plugin</artifactId> |
||||
<version>${jacoco}</version> |
||||
<configuration> |
||||
<destFile>${jacoco.destfile}</destFile> |
||||
</configuration> |
||||
<executions> |
||||
<execution> |
||||
<id>jacoco-initialize</id> |
||||
<goals> |
||||
<goal>prepare-agent</goal> |
||||
</goals> |
||||
</execution> |
||||
</executions> |
||||
</plugin> |
||||
|
||||
<plugin> |
||||
<groupId>org.apache.maven.plugins</groupId> |
||||
<artifactId>maven-surefire-plugin</artifactId> |
||||
<executions> |
||||
<execution> |
||||
<id>default-test</id> |
||||
<configuration> |
||||
<includes> |
||||
<include>**/*Tests.java</include> |
||||
</includes> |
||||
</configuration> |
||||
</execution> |
||||
</executions> |
||||
</plugin> |
||||
<plugin> |
||||
<groupId>org.apache.maven.plugins</groupId> |
||||
<artifactId>maven-assembly-plugin</artifactId> |
||||
</plugin> |
||||
<plugin> |
||||
<groupId>org.codehaus.mojo</groupId> |
||||
<artifactId>wagon-maven-plugin</artifactId> |
||||
</plugin> |
||||
<plugin> |
||||
<groupId>org.asciidoctor</groupId> |
||||
<artifactId>asciidoctor-maven-plugin</artifactId> |
||||
</plugin> |
||||
</plugins> |
||||
</build> |
||||
|
||||
<repositories> |
||||
<repository> |
||||
<id>spring-libs-snapshot</id> |
||||
<url>https://repo.spring.io/libs-snapshot</url> |
||||
</repository> |
||||
</repositories> |
||||
|
||||
<pluginRepositories> |
||||
<pluginRepository> |
||||
<id>spring-plugins-snapshot</id> |
||||
<url>https://repo.spring.io/plugins-snapshot</url> |
||||
</pluginRepository> |
||||
</pluginRepositories> |
||||
|
||||
</project> |
||||
@ -0,0 +1,113 @@
@@ -0,0 +1,113 @@
|
||||
/* |
||||
* Copyright 2017 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package org.springframework.data.jdbc.core; |
||||
|
||||
import java.util.ArrayList; |
||||
import java.util.List; |
||||
import java.util.Map; |
||||
import java.util.function.Consumer; |
||||
import java.util.function.Function; |
||||
|
||||
import org.springframework.data.jdbc.mapping.model.JdbcPersistentProperty; |
||||
import org.springframework.data.mapping.PropertyPath; |
||||
|
||||
/** |
||||
* Delegates each methods to the {@link DataAccessStrategy}s passed to the constructor in turn until the first that does |
||||
* not throw an exception. |
||||
* |
||||
* @author Jens Schauder |
||||
*/ |
||||
public class CascadingDataAccessStrategy implements DataAccessStrategy { |
||||
|
||||
private final List<DataAccessStrategy> strategies; |
||||
|
||||
public CascadingDataAccessStrategy(List<DataAccessStrategy> strategies) { |
||||
this.strategies = new ArrayList<>(strategies); |
||||
} |
||||
|
||||
@Override |
||||
public <T> void insert(T instance, Class<T> domainType, Map<String, Object> additionalParameters) { |
||||
collectVoid(das -> das.insert(instance, domainType, additionalParameters)); |
||||
} |
||||
|
||||
@Override |
||||
public <S> void update(S instance, Class<S> domainType) { |
||||
collectVoid(das -> das.update(instance, domainType)); |
||||
} |
||||
|
||||
@Override |
||||
public void delete(Object id, Class<?> domainType) { |
||||
collectVoid(das -> das.delete(id, domainType)); |
||||
} |
||||
|
||||
@Override |
||||
public void delete(Object rootId, PropertyPath propertyPath) { |
||||
collectVoid(das -> das.delete(rootId, propertyPath)); |
||||
} |
||||
|
||||
@Override |
||||
public <T> void deleteAll(Class<T> domainType) { |
||||
collectVoid(das -> das.deleteAll(domainType)); |
||||
} |
||||
|
||||
@Override |
||||
public <T> void deleteAll(PropertyPath propertyPath) { |
||||
collectVoid(das -> das.deleteAll(propertyPath)); |
||||
} |
||||
|
||||
@Override |
||||
public long count(Class<?> domainType) { |
||||
return collect(das -> das.count(domainType)); |
||||
} |
||||
|
||||
@Override |
||||
public <T> T findById(Object id, Class<T> domainType) { |
||||
return collect(das -> das.findById(id, domainType)); |
||||
} |
||||
|
||||
@Override |
||||
public <T> Iterable<T> findAll(Class<T> domainType) { |
||||
return collect(das -> das.findAll(domainType)); |
||||
} |
||||
|
||||
@Override |
||||
public <T> Iterable<T> findAllById(Iterable<?> ids, Class<T> domainType) { |
||||
return collect(das -> das.findAllById(ids, domainType)); |
||||
} |
||||
|
||||
@Override |
||||
public <T> Iterable<T> findAllByProperty(Object rootId, JdbcPersistentProperty property) { |
||||
return collect(das -> das.findAllByProperty(rootId, property)); |
||||
} |
||||
|
||||
@Override |
||||
public <T> boolean existsById(Object id, Class<T> domainType) { |
||||
return collect(das -> das.existsById(id, domainType)); |
||||
} |
||||
|
||||
private <T> T collect(Function<DataAccessStrategy, T> function) { |
||||
return strategies.stream().collect(new FunctionCollector<>(function)); |
||||
} |
||||
|
||||
private void collectVoid(Consumer<DataAccessStrategy> consumer) { |
||||
|
||||
collect(das -> { |
||||
consumer.accept(das); |
||||
return null; |
||||
}); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,69 @@
@@ -0,0 +1,69 @@
|
||||
/* |
||||
* Copyright 2017 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package org.springframework.data.jdbc.core; |
||||
|
||||
import java.util.Map; |
||||
|
||||
import org.springframework.data.jdbc.mapping.model.JdbcPersistentProperty; |
||||
import org.springframework.data.mapping.PropertyPath; |
||||
|
||||
/** |
||||
* Abstraction for accesses to the database that should be implementable with a single SQL statement and relates to a single entity as opposed to {@link JdbcEntityOperations} which provides interactions related to complete aggregates. |
||||
* |
||||
* @author Jens Schauder |
||||
*/ |
||||
public interface DataAccessStrategy { |
||||
|
||||
<T> void insert(T instance, Class<T> domainType, Map<String, Object> additionalParameters); |
||||
|
||||
<S> void update(S instance, Class<S> domainType); |
||||
|
||||
void delete(Object id, Class<?> domainType); |
||||
|
||||
/** Deletes all entities reachable via {@literal propertyPath} from the instance identified by {@literal rootId}. |
||||
* |
||||
* @param rootId Id of the root object on which the {@literal propertyPath} is based. |
||||
* @param propertyPath Leading from the root object to the entities to be deleted. |
||||
*/ |
||||
void delete(Object rootId, PropertyPath propertyPath); |
||||
|
||||
<T> void deleteAll(Class<T> domainType); |
||||
|
||||
/** Deletes all entities reachable via {@literal propertyPath} from any instance. |
||||
* |
||||
* @param propertyPath Leading from the root object to the entities to be deleted. |
||||
*/ |
||||
<T> void deleteAll(PropertyPath propertyPath); |
||||
|
||||
long count(Class<?> domainType); |
||||
|
||||
<T> T findById(Object id, Class<T> domainType); |
||||
|
||||
<T> Iterable<T> findAll(Class<T> domainType); |
||||
|
||||
<T> Iterable<T> findAllById(Iterable<?> ids, Class<T> domainType); |
||||
|
||||
/** |
||||
* Finds all entities reachable via {@literal property} from the instance identified by {@literal rootId}. |
||||
* |
||||
* @param rootId Id of the root object on which the {@literal propertyPath} is based. |
||||
* @param property Leading from the root object to the entities to be found. |
||||
*/ |
||||
<T> Iterable<T> findAllByProperty(Object rootId, JdbcPersistentProperty property); |
||||
|
||||
<T> boolean existsById(Object id, Class<T> domainType); |
||||
|
||||
} |
||||
@ -0,0 +1,320 @@
@@ -0,0 +1,320 @@
|
||||
/* |
||||
* Copyright 2017 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package org.springframework.data.jdbc.core; |
||||
|
||||
import java.util.HashMap; |
||||
import java.util.Map; |
||||
import java.util.Optional; |
||||
import java.util.stream.Collectors; |
||||
import java.util.stream.StreamSupport; |
||||
|
||||
import org.springframework.core.convert.ConversionService; |
||||
import org.springframework.core.convert.support.DefaultConversionService; |
||||
import org.springframework.core.convert.support.GenericConversionService; |
||||
import org.springframework.dao.EmptyResultDataAccessException; |
||||
import org.springframework.dao.InvalidDataAccessApiUsageException; |
||||
import org.springframework.dao.NonTransientDataAccessException; |
||||
import org.springframework.data.convert.Jsr310Converters; |
||||
import org.springframework.data.jdbc.mapping.model.BasicJdbcPersistentEntityInformation; |
||||
import org.springframework.data.jdbc.mapping.model.JdbcMappingContext; |
||||
import org.springframework.data.jdbc.mapping.model.JdbcPersistentEntity; |
||||
import org.springframework.data.jdbc.mapping.model.JdbcPersistentEntityInformation; |
||||
import org.springframework.data.jdbc.mapping.model.JdbcPersistentProperty; |
||||
import org.springframework.data.jdbc.support.JdbcUtil; |
||||
import org.springframework.data.mapping.PropertyHandler; |
||||
import org.springframework.data.mapping.PropertyPath; |
||||
import org.springframework.data.repository.core.EntityInformation; |
||||
import org.springframework.jdbc.core.namedparam.MapSqlParameterSource; |
||||
import org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations; |
||||
import org.springframework.jdbc.support.GeneratedKeyHolder; |
||||
import org.springframework.jdbc.support.KeyHolder; |
||||
import org.springframework.util.Assert; |
||||
|
||||
/** |
||||
* Generates and executes actual SQL statements. |
||||
* |
||||
* @author Jens Schauder |
||||
*/ |
||||
public class DefaultDataAccessStrategy implements DataAccessStrategy { |
||||
|
||||
private static final String ENTITY_NEW_AFTER_INSERT = "Entity [%s] still 'new' after insert. Please set either" |
||||
+ " the id property in a BeforeInsert event handler, or ensure the database creates a value and your " |
||||
+ "JDBC driver returns it."; |
||||
|
||||
private final SqlGeneratorSource sqlGeneratorSource; |
||||
private final NamedParameterJdbcOperations operations; |
||||
private final JdbcMappingContext context; |
||||
private final ConversionService conversions = getDefaultConversionService(); |
||||
private final DataAccessStrategy accessStrategy; |
||||
|
||||
public DefaultDataAccessStrategy(SqlGeneratorSource sqlGeneratorSource, NamedParameterJdbcOperations operations, |
||||
JdbcMappingContext context, DataAccessStrategy accessStrategy) { |
||||
|
||||
this.sqlGeneratorSource = sqlGeneratorSource; |
||||
this.operations = operations; |
||||
this.context = context; |
||||
this.accessStrategy = accessStrategy; |
||||
} |
||||
|
||||
/** |
||||
* creates a {@link DefaultDataAccessStrategy} which references it self for resolution of recursive data accesses. |
||||
* |
||||
* Only suitable if this is the only access strategy in use. |
||||
*/ |
||||
public DefaultDataAccessStrategy(SqlGeneratorSource sqlGeneratorSource, NamedParameterJdbcOperations operations, |
||||
JdbcMappingContext context) { |
||||
|
||||
this.sqlGeneratorSource = sqlGeneratorSource; |
||||
this.operations = operations; |
||||
this.context = context; |
||||
this.accessStrategy = this; |
||||
} |
||||
|
||||
@Override |
||||
public <T> void insert(T instance, Class<T> domainType, Map<String, Object> additionalParameters) { |
||||
|
||||
KeyHolder holder = new GeneratedKeyHolder(); |
||||
JdbcPersistentEntity<T> persistentEntity = getRequiredPersistentEntity(domainType); |
||||
JdbcPersistentEntityInformation<T, ?> entityInformation = context |
||||
.getRequiredPersistentEntityInformation(domainType); |
||||
|
||||
MapSqlParameterSource parameterSource = getPropertyMap(instance, persistentEntity); |
||||
|
||||
Object idValue = getIdValueOrNull(instance, persistentEntity); |
||||
JdbcPersistentProperty idProperty = persistentEntity.getRequiredIdProperty(); |
||||
parameterSource.addValue(idProperty.getColumnName(), convert(idValue, idProperty.getColumnType()), |
||||
JdbcUtil.sqlTypeFor(idProperty.getColumnType())); |
||||
|
||||
additionalParameters.forEach(parameterSource::addValue); |
||||
|
||||
operations.update(sql(domainType).getInsert(idValue == null, additionalParameters.keySet()), parameterSource, |
||||
holder); |
||||
|
||||
setIdFromJdbc(instance, holder, persistentEntity); |
||||
|
||||
if (entityInformation.isNew(instance)) { |
||||
throw new IllegalStateException(String.format(ENTITY_NEW_AFTER_INSERT, persistentEntity)); |
||||
} |
||||
|
||||
} |
||||
|
||||
@Override |
||||
public <S> void update(S instance, Class<S> domainType) { |
||||
|
||||
JdbcPersistentEntity<S> persistentEntity = getRequiredPersistentEntity(domainType); |
||||
|
||||
operations.update(sql(domainType).getUpdate(), getPropertyMap(instance, persistentEntity)); |
||||
} |
||||
|
||||
@Override |
||||
public void delete(Object id, Class<?> domainType) { |
||||
|
||||
String deleteByIdSql = sql(domainType).getDeleteById(); |
||||
MapSqlParameterSource parameter = createIdParameterSource(id, domainType); |
||||
|
||||
operations.update(deleteByIdSql, parameter); |
||||
} |
||||
|
||||
@Override |
||||
public void delete(Object rootId, PropertyPath propertyPath) { |
||||
|
||||
JdbcPersistentEntity<?> rootEntity = context.getRequiredPersistentEntity(propertyPath.getOwningType()); |
||||
|
||||
JdbcPersistentProperty referencingProperty = rootEntity.getRequiredPersistentProperty(propertyPath.getSegment()); |
||||
Assert.notNull(referencingProperty, "No property found matching the PropertyPath " + propertyPath); |
||||
|
||||
String format = sql(rootEntity.getType()).createDeleteByPath(propertyPath); |
||||
|
||||
HashMap<String, Object> parameters = new HashMap<>(); |
||||
parameters.put("rootId", rootId); |
||||
operations.update(format, parameters); |
||||
|
||||
} |
||||
|
||||
@Override |
||||
public <T> void deleteAll(Class<T> domainType) { |
||||
|
||||
operations.getJdbcOperations().update(sql(domainType).createDeleteAllSql(null)); |
||||
} |
||||
|
||||
@Override |
||||
public <T> void deleteAll(PropertyPath propertyPath) { |
||||
|
||||
operations.getJdbcOperations().update(sql(propertyPath.getOwningType().getType()).createDeleteAllSql(propertyPath)); |
||||
} |
||||
|
||||
@SuppressWarnings("ConstantConditions") |
||||
@Override |
||||
public long count(Class<?> domainType) { |
||||
return operations.getJdbcOperations().queryForObject(sql(domainType).getCount(), Long.class); |
||||
} |
||||
|
||||
@Override |
||||
public <T> T findById(Object id, Class<T> domainType) { |
||||
|
||||
String findOneSql = sql(domainType).getFindOne(); |
||||
MapSqlParameterSource parameter = createIdParameterSource(id, domainType); |
||||
try { |
||||
return operations.queryForObject(findOneSql, parameter, getEntityRowMapper(domainType)); |
||||
} catch (EmptyResultDataAccessException e) { |
||||
return null; |
||||
} |
||||
} |
||||
|
||||
@Override |
||||
public <T> Iterable<T> findAll(Class<T> domainType) { |
||||
return operations.query(sql(domainType).getFindAll(), getEntityRowMapper(domainType)); |
||||
} |
||||
|
||||
@Override |
||||
public <T> Iterable<T> findAllById(Iterable<?> ids, Class<T> domainType) { |
||||
|
||||
String findAllInListSql = sql(domainType).getFindAllInList(); |
||||
Class<?> targetType = getRequiredPersistentEntity(domainType).getRequiredIdProperty().getColumnType(); |
||||
|
||||
MapSqlParameterSource parameter = new MapSqlParameterSource( //
|
||||
"ids", //
|
||||
StreamSupport.stream(ids.spliterator(), false) //
|
||||
.map(id -> convert(id, targetType)) //
|
||||
.collect(Collectors.toList()) //
|
||||
); |
||||
|
||||
return operations.query(findAllInListSql, parameter, getEntityRowMapper(domainType)); |
||||
} |
||||
|
||||
@Override |
||||
public <T> Iterable<T> findAllByProperty(Object rootId, JdbcPersistentProperty property) { |
||||
|
||||
Class<?> actualType = property.getActualType(); |
||||
String findAllByProperty = sql(actualType).getFindAllByProperty(property.getReverseColumnName()); |
||||
|
||||
MapSqlParameterSource parameter = new MapSqlParameterSource(property.getReverseColumnName(), rootId); |
||||
|
||||
return (Iterable<T>) operations.query(findAllByProperty, parameter, getEntityRowMapper(actualType)); |
||||
} |
||||
|
||||
@Override |
||||
public <T> boolean existsById(Object id, Class<T> domainType) { |
||||
|
||||
String existsSql = sql(domainType).getExists(); |
||||
MapSqlParameterSource parameter = createIdParameterSource(id, domainType); |
||||
return operations.queryForObject(existsSql, parameter, Boolean.class); |
||||
} |
||||
|
||||
private static GenericConversionService getDefaultConversionService() { |
||||
|
||||
DefaultConversionService conversionService = new DefaultConversionService(); |
||||
Jsr310Converters.getConvertersToRegister().forEach(conversionService::addConverter); |
||||
|
||||
return conversionService; |
||||
} |
||||
|
||||
private <S> MapSqlParameterSource getPropertyMap(final S instance, JdbcPersistentEntity<S> persistentEntity) { |
||||
|
||||
MapSqlParameterSource parameters = new MapSqlParameterSource(); |
||||
|
||||
persistentEntity.doWithProperties((PropertyHandler<JdbcPersistentProperty>) property -> { |
||||
if (!property.isEntity()) { |
||||
Object value = persistentEntity.getPropertyAccessor(instance).getProperty(property); |
||||
|
||||
Object convertedValue = convert(value, property.getColumnType()); |
||||
parameters.addValue(property.getColumnName(), convertedValue, JdbcUtil.sqlTypeFor(property.getColumnType())); |
||||
} |
||||
}); |
||||
|
||||
return parameters; |
||||
} |
||||
|
||||
private <S, ID> ID getIdValueOrNull(S instance, JdbcPersistentEntity<S> persistentEntity) { |
||||
|
||||
EntityInformation<S, ID> entityInformation = new BasicJdbcPersistentEntityInformation<>(persistentEntity); |
||||
|
||||
ID idValue = entityInformation.getId(instance); |
||||
|
||||
return isIdPropertySimpleTypeAndValueZero(idValue, persistentEntity) ? null : idValue; |
||||
} |
||||
|
||||
private <S, ID> boolean isIdPropertySimpleTypeAndValueZero(ID idValue, JdbcPersistentEntity<S> persistentEntity) { |
||||
|
||||
JdbcPersistentProperty idProperty = persistentEntity.getIdProperty(); |
||||
return idValue == null //
|
||||
|| idProperty == null //
|
||||
|| (idProperty.getType() == int.class && idValue.equals(0)) //
|
||||
|| (idProperty.getType() == long.class && idValue.equals(0L)); |
||||
} |
||||
|
||||
private <S> void setIdFromJdbc(S instance, KeyHolder holder, JdbcPersistentEntity<S> persistentEntity) { |
||||
|
||||
JdbcPersistentEntityInformation<S, ?> entityInformation = new BasicJdbcPersistentEntityInformation<>( |
||||
persistentEntity); |
||||
|
||||
try { |
||||
|
||||
getIdFromHolder(holder, persistentEntity).ifPresent(it -> { |
||||
|
||||
Class<?> targetType = persistentEntity.getRequiredIdProperty().getType(); |
||||
Object converted = convert(it, targetType); |
||||
entityInformation.setId(instance, converted); |
||||
}); |
||||
|
||||
} catch (NonTransientDataAccessException e) { |
||||
throw new UnableToSetId("Unable to set id of " + instance, e); |
||||
} |
||||
} |
||||
|
||||
private <S> Optional<Object> getIdFromHolder(KeyHolder holder, JdbcPersistentEntity<S> persistentEntity) { |
||||
|
||||
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 <T> EntityRowMapper<T> getEntityRowMapper(Class<T> domainType) { |
||||
return new EntityRowMapper<>(getRequiredPersistentEntity(domainType), conversions, context, accessStrategy); |
||||
} |
||||
|
||||
private <T> MapSqlParameterSource createIdParameterSource(Object id, Class<T> domainType) { |
||||
return new MapSqlParameterSource("id", |
||||
convert(id, getRequiredPersistentEntity(domainType).getRequiredIdProperty().getColumnType())); |
||||
} |
||||
|
||||
@SuppressWarnings("unchecked") |
||||
private <S> JdbcPersistentEntity<S> getRequiredPersistentEntity(Class<S> domainType) { |
||||
return (JdbcPersistentEntity<S>) context.getRequiredPersistentEntity(domainType); |
||||
} |
||||
|
||||
private <V> V convert(Object from, Class<V> to) { |
||||
|
||||
if (from == null) { |
||||
return null; |
||||
} |
||||
|
||||
JdbcPersistentEntity<?> persistentEntity = context.getPersistentEntity(from.getClass()); |
||||
|
||||
Object id = persistentEntity == null ? null : persistentEntity.getIdentifierAccessor(from).getIdentifier(); |
||||
|
||||
return conversions.convert(id == null ? from : id, to); |
||||
} |
||||
|
||||
private SqlGenerator sql(Class<?> domainType) { |
||||
return sqlGeneratorSource.getSqlGenerator(domainType); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,112 @@
@@ -0,0 +1,112 @@
|
||||
/* |
||||
* Copyright 2017 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package org.springframework.data.jdbc.core; |
||||
|
||||
import java.util.Map; |
||||
|
||||
import org.springframework.data.jdbc.mapping.model.JdbcPersistentProperty; |
||||
import org.springframework.data.mapping.PropertyPath; |
||||
import org.springframework.util.Assert; |
||||
|
||||
/** |
||||
* delegates all method calls to an instance set after construction. This is useful for {@link DataAccessStrategy}s with |
||||
* cyclical dependencies. |
||||
* |
||||
* @author Jens Schauder |
||||
*/ |
||||
public class DelegatingDataAccessStrategy implements DataAccessStrategy { |
||||
|
||||
private DataAccessStrategy delegate; |
||||
|
||||
@Override |
||||
public <T> void insert(T instance, Class<T> domainType, Map<String, Object> additionalParameters) { |
||||
delegate.insert(instance, domainType, additionalParameters); |
||||
} |
||||
|
||||
@Override |
||||
public <S> void update(S instance, Class<S> domainType) { |
||||
delegate.update(instance, domainType); |
||||
} |
||||
|
||||
@Override |
||||
public void delete(Object rootId, PropertyPath propertyPath) { |
||||
delegate.delete(rootId, propertyPath); |
||||
} |
||||
|
||||
@Override |
||||
public void delete(Object id, Class<?> domainType) { |
||||
delegate.delete(id, domainType); |
||||
} |
||||
|
||||
@Override |
||||
public <T> void deleteAll(Class<T> domainType) { |
||||
delegate.deleteAll(domainType); |
||||
} |
||||
|
||||
@Override |
||||
public <T> void deleteAll(PropertyPath propertyPath) { |
||||
delegate.deleteAll(propertyPath); |
||||
} |
||||
|
||||
@Override |
||||
public long count(Class<?> domainType) { |
||||
return delegate.count(domainType); |
||||
} |
||||
|
||||
@Override |
||||
public <T> T findById(Object id, Class<T> domainType) { |
||||
|
||||
Assert.notNull(delegate, "Delegate is null"); |
||||
|
||||
return delegate.findById(id, domainType); |
||||
} |
||||
|
||||
@Override |
||||
public <T> Iterable<T> findAll(Class<T> domainType) { |
||||
return delegate.findAll(domainType); |
||||
} |
||||
|
||||
@Override |
||||
public <T> Iterable<T> findAllById(Iterable<?> ids, Class<T> domainType) { |
||||
return delegate.findAllById(ids, domainType); |
||||
} |
||||
|
||||
@Override |
||||
public <T> Iterable<T> findAllByProperty(Object rootId, JdbcPersistentProperty property) { |
||||
|
||||
Assert.notNull(delegate, "Delegate is null"); |
||||
|
||||
return delegate.findAllByProperty(rootId, property); |
||||
} |
||||
|
||||
@Override |
||||
public <T> boolean existsById(Object id, Class<T> domainType) { |
||||
return delegate.existsById(id, domainType); |
||||
} |
||||
|
||||
/** |
||||
* Must be called exactly once before calling any of the other methods. |
||||
* |
||||
* @param delegate Must not be {@literal null} |
||||
*/ |
||||
public void setDelegate(DataAccessStrategy delegate) { |
||||
|
||||
Assert.isNull(this.delegate, "The delegate must be set exactly once"); |
||||
Assert.notNull(delegate, "The delegate must not be set to null"); |
||||
|
||||
this.delegate = delegate; |
||||
} |
||||
} |
||||
@ -0,0 +1,127 @@
@@ -0,0 +1,127 @@
|
||||
/* |
||||
* Copyright 2017 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package org.springframework.data.jdbc.core; |
||||
|
||||
import java.util.Collections; |
||||
import java.util.LinkedList; |
||||
import java.util.List; |
||||
import java.util.Set; |
||||
import java.util.function.BiConsumer; |
||||
import java.util.function.BinaryOperator; |
||||
import java.util.function.Function; |
||||
import java.util.function.Supplier; |
||||
import java.util.stream.Collector; |
||||
import java.util.stream.Collectors; |
||||
|
||||
import org.springframework.dao.DataAccessException; |
||||
|
||||
/** |
||||
* {@link Collector} which invokes functions on the elements of a {@link java.util.stream.Stream} containing |
||||
* {@link DataAccessStrategy}s until one function completes without throwing an exception. If all invocations throw |
||||
* exceptions this {@link Collector} throws itself an exception, gathering all exceptions thrown. |
||||
* |
||||
* @author Jens Schauder |
||||
*/ |
||||
class FunctionCollector<T> implements Collector<DataAccessStrategy, FunctionCollector<T>.ResultOrException, T> { |
||||
|
||||
private final Function<DataAccessStrategy, T> method; |
||||
|
||||
FunctionCollector(Function<DataAccessStrategy, T> method) { |
||||
this.method = method; |
||||
} |
||||
|
||||
@Override |
||||
public Supplier<ResultOrException> supplier() { |
||||
return ResultOrException::new; |
||||
} |
||||
|
||||
@Override |
||||
public BiConsumer<ResultOrException, DataAccessStrategy> accumulator() { |
||||
|
||||
return (roe, das) -> { |
||||
|
||||
if (!roe.hasResult()) { |
||||
|
||||
try { |
||||
roe.setResult(method.apply(das)); |
||||
} catch (Exception ex) { |
||||
roe.add(ex); |
||||
} |
||||
} |
||||
}; |
||||
} |
||||
|
||||
@Override |
||||
public BinaryOperator<ResultOrException> combiner() { |
||||
|
||||
return (roe1, roe2) -> { |
||||
throw new UnsupportedOperationException("Can't combine method calls"); |
||||
}; |
||||
} |
||||
|
||||
@Override |
||||
public Function<ResultOrException, T> finisher() { |
||||
|
||||
return roe -> { |
||||
|
||||
if (roe.hasResult) |
||||
return roe.result; |
||||
else |
||||
throw new CombinedDataAccessException("Failed to perform data access with all available strategies", |
||||
Collections.unmodifiableList(roe.exceptions)); |
||||
}; |
||||
} |
||||
|
||||
@Override |
||||
public Set<Characteristics> characteristics() { |
||||
return Collections.emptySet(); |
||||
} |
||||
|
||||
/** |
||||
* Stores intermediate results. I.e. a list of exceptions caught so far, any actual result and the fact, if there |
||||
* actually is an result. |
||||
*/ |
||||
class ResultOrException { |
||||
|
||||
private T result; |
||||
private final List<Exception> exceptions = new LinkedList<>(); |
||||
private boolean hasResult = false; |
||||
|
||||
private boolean hasResult() { |
||||
return hasResult; |
||||
} |
||||
|
||||
private void setResult(T result) { |
||||
this.result = result; |
||||
hasResult = true; |
||||
} |
||||
|
||||
public void add(Exception ex) { |
||||
exceptions.add(ex); |
||||
} |
||||
} |
||||
|
||||
static class CombinedDataAccessException extends DataAccessException { |
||||
|
||||
CombinedDataAccessException(String message, List<Exception> exceptions) { |
||||
super(combineMessage(message, exceptions), exceptions.get(exceptions.size() - 1)); |
||||
} |
||||
|
||||
private static String combineMessage(String message, List<Exception> exceptions) { |
||||
return message + exceptions.stream().map(Exception::getMessage).collect(Collectors.joining("\n\t", "\n\t", "")); |
||||
} |
||||
} |
||||
} |
||||
@ -0,0 +1,56 @@
@@ -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.mybatis; |
||||
|
||||
import java.util.Map; |
||||
|
||||
/** |
||||
* {@link MyBatisContext} instances get passed to MyBatis mapped statements as arguments, making Ids, instances, domainType and other attributes available to the statements. |
||||
* |
||||
* All methods might return {@literal null} depending on the kind of values available on invocation. |
||||
* @author Jens Schauder |
||||
*/ |
||||
public class MyBatisContext { |
||||
|
||||
private final Object id; |
||||
private final Object instance; |
||||
private final Class domainType; |
||||
private final Map<String, Object> additonalValues; |
||||
|
||||
public MyBatisContext(Object id, Object instance, Class domainType, Map<String, Object> additonalValues) { |
||||
|
||||
this.id = id; |
||||
this.instance = instance; |
||||
this.domainType = domainType; |
||||
this.additonalValues = additonalValues; |
||||
} |
||||
|
||||
public Object getId() { |
||||
return id; |
||||
} |
||||
|
||||
public Object getInstance() { |
||||
return instance; |
||||
} |
||||
|
||||
public Class getDomainType() { |
||||
return domainType; |
||||
} |
||||
|
||||
public Object get(String key) { |
||||
return additonalValues.get(key); |
||||
} |
||||
} |
||||
@ -0,0 +1,144 @@
@@ -0,0 +1,144 @@
|
||||
/* |
||||
* 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.mybatis; |
||||
|
||||
import java.util.Collections; |
||||
import java.util.Map; |
||||
|
||||
import org.apache.ibatis.session.SqlSession; |
||||
import org.apache.ibatis.session.SqlSessionFactory; |
||||
import org.springframework.data.jdbc.core.DataAccessStrategy; |
||||
import org.springframework.data.jdbc.mapping.model.JdbcPersistentProperty; |
||||
import org.springframework.data.mapping.PropertyPath; |
||||
|
||||
/** |
||||
* {@link DataAccessStrategy} implementation based on MyBatis. |
||||
* |
||||
* Each method gets mapped to a statement. The name of the statement gets constructed as follows: |
||||
* |
||||
* The namespace is based on the class of the entity plus the suffix "Mapper". This is then followed by the method name separated by a dot. |
||||
* |
||||
* For methods taking a {@link PropertyPath} as argument, the relevant entity is that of the root of the path, and the path itself gets as dot separated String appended to the statement name. |
||||
* |
||||
* Each statement gets an instance of {@link MyBatisContext}, which at least has the entityType set. |
||||
* |
||||
* For methods taking a {@link PropertyPath} the entityTyoe if the context is set to the class of the leaf type. |
||||
* |
||||
* @author Jens Schauder |
||||
*/ |
||||
public class MyBatisDataAccessStrategy implements DataAccessStrategy { |
||||
|
||||
private static final String MAPPER_SUFFIX = "Mapper"; |
||||
|
||||
private final SqlSessionFactory sqlSessionFactory; |
||||
|
||||
public MyBatisDataAccessStrategy(SqlSessionFactory sqlSessionFactory) { |
||||
|
||||
this.sqlSessionFactory = sqlSessionFactory; |
||||
} |
||||
|
||||
@Override |
||||
public <T> void insert(T instance, Class<T> domainType, Map<String, Object> additionalParameters) { |
||||
sqlSession().insert(mapper(domainType) + ".insert", |
||||
new MyBatisContext(null, instance, domainType, additionalParameters)); |
||||
} |
||||
|
||||
@Override |
||||
public <S> void update(S instance, Class<S> domainType) { |
||||
|
||||
sqlSession().update(mapper(domainType) + ".update", |
||||
new MyBatisContext(null, instance, domainType, Collections.emptyMap())); |
||||
} |
||||
|
||||
@Override |
||||
public void delete(Object id, Class<?> domainType) { |
||||
|
||||
sqlSession().delete(mapper(domainType) + ".delete", |
||||
new MyBatisContext(id, null, domainType, Collections.emptyMap())); |
||||
} |
||||
|
||||
@Override |
||||
public void delete(Object rootId, PropertyPath propertyPath) { |
||||
|
||||
sqlSession().delete(mapper(propertyPath.getOwningType().getType()) + ".delete." + propertyPath.toDotPath(), |
||||
new MyBatisContext(rootId, null, propertyPath.getLeafProperty().getTypeInformation().getType(), |
||||
Collections.emptyMap())); |
||||
} |
||||
|
||||
@Override |
||||
public <T> void deleteAll(Class<T> domainType) { |
||||
|
||||
sqlSession().delete( //
|
||||
mapper(domainType) + ".deleteAll", //
|
||||
new MyBatisContext(null, null, domainType, Collections.emptyMap()) //
|
||||
); |
||||
} |
||||
|
||||
@Override |
||||
public <T> void deleteAll(PropertyPath propertyPath) { |
||||
|
||||
Class baseType = propertyPath.getOwningType().getType(); |
||||
Class leaveType = propertyPath.getLeafProperty().getTypeInformation().getType(); |
||||
|
||||
sqlSession().delete( //
|
||||
mapper(baseType) + ".deleteAll." + propertyPath.toDotPath(), //
|
||||
new MyBatisContext(null, null, leaveType, Collections.emptyMap()) //
|
||||
); |
||||
} |
||||
|
||||
@Override |
||||
public <T> T findById(Object id, Class<T> domainType) { |
||||
return sqlSession().selectOne(mapper(domainType) + ".findById", |
||||
new MyBatisContext(id, null, domainType, Collections.emptyMap())); |
||||
} |
||||
|
||||
@Override |
||||
public <T> Iterable<T> findAll(Class<T> domainType) { |
||||
return sqlSession().selectList(mapper(domainType) + ".findAll", |
||||
new MyBatisContext(null, null, domainType, Collections.emptyMap())); |
||||
} |
||||
|
||||
@Override |
||||
public <T> Iterable<T> findAllById(Iterable<?> ids, Class<T> domainType) { |
||||
return sqlSession().selectList(mapper(domainType) + ".findAllById", |
||||
new MyBatisContext(ids, null, domainType, Collections.emptyMap())); |
||||
} |
||||
|
||||
@Override |
||||
public <T> Iterable<T> findAllByProperty(Object rootId, JdbcPersistentProperty property) { |
||||
return sqlSession().selectList(mapper(property.getOwner().getType()) + ".findAllByProperty." + property.getName(), |
||||
new MyBatisContext(rootId, null, property.getType(), Collections.emptyMap())); |
||||
} |
||||
|
||||
@Override |
||||
public <T> boolean existsById(Object id, Class<T> domainType) { |
||||
return sqlSession().selectOne(mapper(domainType) + ".existsById", |
||||
new MyBatisContext(id, null, domainType, Collections.emptyMap())); |
||||
} |
||||
|
||||
@Override |
||||
public long count(Class<?> domainType) { |
||||
return sqlSession().selectOne(mapper(domainType) + ".count"); |
||||
} |
||||
|
||||
private String mapper(Class<?> domainType) { |
||||
return domainType.getName() + MAPPER_SUFFIX; |
||||
} |
||||
|
||||
private SqlSession sqlSession() { |
||||
return sqlSessionFactory.openSession(); |
||||
} |
||||
} |
||||
@ -0,0 +1,86 @@
@@ -0,0 +1,86 @@
|
||||
/* |
||||
* Copyright 2017 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package org.springframework.data.jdbc.core; |
||||
|
||||
import static java.util.Arrays.*; |
||||
import static org.assertj.core.api.Assertions.*; |
||||
import static org.mockito.Mockito.*; |
||||
|
||||
import junit.framework.AssertionFailedError; |
||||
|
||||
import java.util.Collections; |
||||
|
||||
import org.junit.Test; |
||||
import org.springframework.data.jdbc.core.FunctionCollector.CombinedDataAccessException; |
||||
import org.springframework.data.jdbc.mapping.model.JdbcPersistentProperty; |
||||
|
||||
/** |
||||
* Unit tests for {@link CascadingDataAccessStrategy}. |
||||
* |
||||
* @author Jens Schauder |
||||
*/ |
||||
public class CascadingDataAccessStrategyUnitTests { |
||||
|
||||
int errorIndex = 1; |
||||
String[] errorMessages = {"Sorry I don't support this method. Please try again later", "Still no luck"}; |
||||
|
||||
DataAccessStrategy alwaysFails = mock(DataAccessStrategy.class, i -> { |
||||
errorIndex ++; |
||||
errorIndex %=2; |
||||
throw new UnsupportedOperationException(errorMessages[errorIndex]); |
||||
}); |
||||
DataAccessStrategy succeeds = mock(DataAccessStrategy.class); |
||||
DataAccessStrategy mayNotCall = mock(DataAccessStrategy.class, i -> { |
||||
throw new AssertionFailedError("this shouldn't have get called"); |
||||
}); |
||||
|
||||
|
||||
@Test // DATAJDBC-123
|
||||
public void findByReturnsFirstSuccess() { |
||||
|
||||
doReturn("success").when(succeeds).findById(23L, String.class); |
||||
CascadingDataAccessStrategy access = new CascadingDataAccessStrategy(asList(alwaysFails, succeeds, mayNotCall)); |
||||
|
||||
String byId = access.findById(23L, String.class); |
||||
|
||||
assertThat(byId).isEqualTo("success"); |
||||
} |
||||
|
||||
@Test // DATAJDBC-123
|
||||
public void findByFailsIfAllStrategiesFail() { |
||||
|
||||
CascadingDataAccessStrategy access = new CascadingDataAccessStrategy(asList(alwaysFails, alwaysFails)); |
||||
|
||||
assertThatExceptionOfType(CombinedDataAccessException.class) //
|
||||
.isThrownBy(() -> access.findById(23L, String.class)) //
|
||||
.withMessageContaining("Failed to perform data access with all available strategies") //
|
||||
.withMessageContaining("Sorry I don't support this method") //
|
||||
.withMessageContaining("Still no luck"); |
||||
|
||||
} |
||||
|
||||
@Test // DATAJDBC-123
|
||||
public void findByPropertyReturnsFirstSuccess() { |
||||
|
||||
doReturn(Collections.singletonList("success")).when(succeeds).findAllByProperty(eq(23L), any(JdbcPersistentProperty.class)); |
||||
CascadingDataAccessStrategy access = new CascadingDataAccessStrategy(asList(alwaysFails, succeeds, mayNotCall)); |
||||
|
||||
Iterable<Object> findAll = access.findAllByProperty(23L, mock(JdbcPersistentProperty.class)); |
||||
|
||||
assertThat(findAll).containsExactly("success"); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,299 @@
@@ -0,0 +1,299 @@
|
||||
/* |
||||
* Copyright 2017 the original author or authors. |
||||
* |
||||
* Licensed under the Apache License, Version 2.0 (the "License"); |
||||
* you may not use this file except in compliance with the License. |
||||
* You may obtain a copy of the License at |
||||
* |
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
* |
||||
* Unless required by applicable law or agreed to in writing, software |
||||
* distributed under the License is distributed on an "AS IS" BASIS, |
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
||||
* See the License for the specific language governing permissions and |
||||
* limitations under the License. |
||||
*/ |
||||
package org.springframework.data.jdbc.core; |
||||
|
||||
import static java.util.Arrays.*; |
||||
import static org.assertj.core.api.Assertions.*; |
||||
import static org.mockito.Mockito.*; |
||||
|
||||
import java.util.Collections; |
||||
|
||||
import org.apache.ibatis.session.SqlSession; |
||||
import org.apache.ibatis.session.SqlSessionFactory; |
||||
import org.junit.Test; |
||||
import org.mockito.ArgumentCaptor; |
||||
import org.mockito.Mockito; |
||||
import org.springframework.data.jdbc.mapping.model.JdbcPersistentProperty; |
||||
import org.springframework.data.jdbc.mybatis.MyBatisContext; |
||||
import org.springframework.data.jdbc.mybatis.MyBatisDataAccessStrategy; |
||||
import org.springframework.data.mapping.PropertyPath; |
||||
|
||||
/** |
||||
* Unit tests for the {@link MyBatisDataAccessStrategy}, mainly ensuring that the correct statements get's looked up. |
||||
* |
||||
* @author Jens Schauder |
||||
*/ |
||||
public class MyBatisDataAccessStrategyUnitTests { |
||||
|
||||
SqlSessionFactory sessionFactory = mock(SqlSessionFactory.class); |
||||
SqlSession session = mock(SqlSession.class); |
||||
ArgumentCaptor<MyBatisContext> captor = ArgumentCaptor.forClass(MyBatisContext.class); |
||||
|
||||
MyBatisDataAccessStrategy accessStrategy = new MyBatisDataAccessStrategy(sessionFactory); |
||||
|
||||
{ |
||||
|
||||
doReturn(session).when(sessionFactory).openSession(); |
||||
doReturn(false).when(session).selectOne(any(), any()); |
||||
} |
||||
|
||||
@Test // DATAJDBC-123
|
||||
public void insert() { |
||||
|
||||
accessStrategy.insert("x", String.class, Collections.singletonMap("key", "value")); |
||||
|
||||
verify(session).insert(eq("java.lang.StringMapper.insert"), captor.capture()); |
||||
|
||||
assertThat(captor.getValue()) //
|
||||
.isNotNull() //
|
||||
.extracting( //
|
||||
MyBatisContext::getInstance, //
|
||||
MyBatisContext::getId, //
|
||||
MyBatisContext::getDomainType, //
|
||||
c -> c.get("key") //
|
||||
).containsExactly( //
|
||||
"x", //
|
||||
null, //
|
||||
String.class, //
|
||||
"value" //
|
||||
); |
||||
} |
||||
|
||||
@Test // DATAJDBC-123
|
||||
public void update() { |
||||
|
||||
accessStrategy.update("x", String.class); |
||||
|
||||
verify(session).update(eq("java.lang.StringMapper.update"), captor.capture()); |
||||
|
||||
assertThat(captor.getValue()) //
|
||||
.isNotNull() //
|
||||
.extracting( //
|
||||
MyBatisContext::getInstance, //
|
||||
MyBatisContext::getId, //
|
||||
MyBatisContext::getDomainType, //
|
||||
c -> c.get("key") //
|
||||
).containsExactly( //
|
||||
"x", //
|
||||
null, //
|
||||
String.class, //
|
||||
null //
|
||||
); |
||||
} |
||||
|
||||
@Test // DATAJDBC-123
|
||||
public void delete() { |
||||
|
||||
accessStrategy.delete("an-id", String.class); |
||||
|
||||
verify(session).delete(eq("java.lang.StringMapper.delete"), captor.capture()); |
||||
|
||||
assertThat(captor.getValue()) //
|
||||
.isNotNull() //
|
||||
.extracting( //
|
||||
MyBatisContext::getInstance, //
|
||||
MyBatisContext::getId, //
|
||||
MyBatisContext::getDomainType, //
|
||||
c -> c.get("key") //
|
||||
).containsExactly( //
|
||||
null, //
|
||||
"an-id", //
|
||||
String.class, //
|
||||
null //
|
||||
); |
||||
} |
||||
|
||||
@Test // DATAJDBC-123
|
||||
public void deleteAllByPath() { |
||||
|
||||
accessStrategy.deleteAll(PropertyPath.from("class.name.bytes", String.class)); |
||||
|
||||
verify(session).delete(eq("java.lang.StringMapper.deleteAll.class.name.bytes"), captor.capture()); |
||||
|
||||
assertThat(captor.getValue()) //
|
||||
.isNotNull() //
|
||||
.extracting( //
|
||||
MyBatisContext::getInstance, //
|
||||
MyBatisContext::getId, //
|
||||
MyBatisContext::getDomainType, //
|
||||
c -> c.get("key") //
|
||||
).containsExactly( //
|
||||
null, //
|
||||
null, //
|
||||
byte[].class, //
|
||||
null //
|
||||
); |
||||
} |
||||
|
||||
@Test // DATAJDBC-123
|
||||
public void deleteAllByType() { |
||||
|
||||
accessStrategy.deleteAll(String.class); |
||||
|
||||
verify(session).delete(eq("java.lang.StringMapper.deleteAll"), captor.capture()); |
||||
|
||||
assertThat(captor.getValue()) //
|
||||
.isNotNull() //
|
||||
.extracting( //
|
||||
MyBatisContext::getInstance, //
|
||||
MyBatisContext::getId, //
|
||||
MyBatisContext::getDomainType, //
|
||||
c -> c.get("key") //
|
||||
).containsExactly( //
|
||||
null, //
|
||||
null, //
|
||||
String.class, //
|
||||
null //
|
||||
); |
||||
} |
||||
|
||||
@Test // DATAJDBC-123
|
||||
public void deleteByPath() { |
||||
|
||||
accessStrategy.delete("rootid", PropertyPath.from("class.name.bytes", String.class)); |
||||
|
||||
verify(session).delete(eq("java.lang.StringMapper.delete.class.name.bytes"), captor.capture()); |
||||
|
||||
assertThat(captor.getValue()) //
|
||||
.isNotNull() //
|
||||
.extracting( //
|
||||
MyBatisContext::getInstance, //
|
||||
MyBatisContext::getId, //
|
||||
MyBatisContext::getDomainType, //
|
||||
c -> c.get("key") //
|
||||
).containsExactly( //
|
||||
null, "rootid", //
|
||||
byte[].class, //
|
||||
null //
|
||||
); |
||||
} |
||||
|
||||
@Test // DATAJDBC-123
|
||||
public void findById() { |
||||
|
||||
accessStrategy.findById("an-id", String.class); |
||||
|
||||
verify(session).selectOne(eq("java.lang.StringMapper.findById"), captor.capture()); |
||||
|
||||
assertThat(captor.getValue()) //
|
||||
.isNotNull() //
|
||||
.extracting( //
|
||||
MyBatisContext::getInstance, //
|
||||
MyBatisContext::getId, //
|
||||
MyBatisContext::getDomainType, //
|
||||
c -> c.get("key") //
|
||||
).containsExactly( //
|
||||
null, "an-id", //
|
||||
String.class, //
|
||||
null //
|
||||
); |
||||
} |
||||
|
||||
@Test // DATAJDBC-123
|
||||
public void findAll() { |
||||
|
||||
accessStrategy.findAll(String.class); |
||||
|
||||
verify(session).selectList(eq("java.lang.StringMapper.findAll"), captor.capture()); |
||||
|
||||
assertThat(captor.getValue()) //
|
||||
.isNotNull() //
|
||||
.extracting( //
|
||||
MyBatisContext::getInstance, //
|
||||
MyBatisContext::getId, //
|
||||
MyBatisContext::getDomainType, //
|
||||
c -> c.get("key") //
|
||||
).containsExactly( //
|
||||
null, //
|
||||
null, //
|
||||
String.class, //
|
||||
null //
|
||||
); |
||||
} |
||||
|
||||
@Test // DATAJDBC-123
|
||||
public void findAllById() { |
||||
|
||||
accessStrategy.findAllById(asList("id1", "id2"), String.class); |
||||
|
||||
verify(session).selectList(eq("java.lang.StringMapper.findAllById"), captor.capture()); |
||||
|
||||
assertThat(captor.getValue()) //
|
||||
.isNotNull() //
|
||||
.extracting( //
|
||||
MyBatisContext::getInstance, //
|
||||
MyBatisContext::getId, //
|
||||
MyBatisContext::getDomainType, //
|
||||
c -> c.get("key") //
|
||||
).containsExactly( //
|
||||
null, //
|
||||
asList("id1", "id2"), //
|
||||
String.class, //
|
||||
null //
|
||||
); |
||||
} |
||||
|
||||
@Test // DATAJDBC-123
|
||||
public void findAllByProperty() { |
||||
|
||||
JdbcPersistentProperty property = mock(JdbcPersistentProperty.class, Mockito.RETURNS_DEEP_STUBS); |
||||
|
||||
when(property.getOwner().getType()).thenReturn((Class) String.class); |
||||
doReturn(Number.class).when(property).getType(); |
||||
doReturn("propertyName").when(property).getName(); |
||||
|
||||
accessStrategy.findAllByProperty("id", property); |
||||
|
||||
verify(session).selectList(eq("java.lang.StringMapper.findAllByProperty.propertyName"), captor.capture()); |
||||
|
||||
assertThat(captor.getValue()) //
|
||||
.isNotNull() //
|
||||
.extracting( //
|
||||
MyBatisContext::getInstance, //
|
||||
MyBatisContext::getId, //
|
||||
MyBatisContext::getDomainType, //
|
||||
c -> c.get("key") //
|
||||
).containsExactly( //
|
||||
null, //
|
||||
"id", //
|
||||
Number.class, //
|
||||
null //
|
||||
); |
||||
} |
||||
|
||||
@Test // DATAJDBC-123
|
||||
public void existsById() { |
||||
|
||||
accessStrategy.existsById("id", String.class); |
||||
|
||||
verify(session).selectOne(eq("java.lang.StringMapper.existsById"), captor.capture()); |
||||
|
||||
assertThat(captor.getValue()) //
|
||||
.isNotNull() //
|
||||
.extracting( //
|
||||
MyBatisContext::getInstance, //
|
||||
MyBatisContext::getId, //
|
||||
MyBatisContext::getDomainType, //
|
||||
c -> c.get("key") //
|
||||
).containsExactly( //
|
||||
null, //
|
||||
"id", //
|
||||
String.class, //
|
||||
null //
|
||||
); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,34 @@
@@ -0,0 +1,34 @@
|
||||
/* |
||||
* 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.mybatis; |
||||
|
||||
import org.apache.ibatis.type.Alias; |
||||
import org.springframework.data.annotation.Id; |
||||
|
||||
/** |
||||
* @author Jens Schauder |
||||
*/ |
||||
@Alias("DummyEntity") |
||||
class DummyEntity { |
||||
|
||||
@Id final Long id; |
||||
final String name; |
||||
|
||||
public DummyEntity(Long id, String name) { |
||||
this.id = id; |
||||
this.name = name; |
||||
} |
||||
} |
||||
@ -0,0 +1,23 @@
@@ -0,0 +1,23 @@
|
||||
/* |
||||
* 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.mybatis; |
||||
|
||||
/** |
||||
* @author Jens Schauder |
||||
*/ |
||||
public interface DummyEntityMapper { |
||||
|
||||
} |
||||
@ -0,0 +1,111 @@
@@ -0,0 +1,111 @@
|
||||
/* |
||||
* 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.mybatis; |
||||
|
||||
import static org.assertj.core.api.Assertions.*; |
||||
|
||||
import junit.framework.AssertionFailedError; |
||||
|
||||
import org.apache.ibatis.session.Configuration; |
||||
import org.apache.ibatis.session.SqlSession; |
||||
import org.apache.ibatis.session.SqlSessionFactory; |
||||
import org.junit.ClassRule; |
||||
import org.junit.Rule; |
||||
import org.junit.Test; |
||||
import org.mybatis.spring.SqlSessionFactoryBean; |
||||
import org.springframework.beans.factory.annotation.Autowired; |
||||
import org.springframework.context.annotation.Bean; |
||||
import org.springframework.context.annotation.Import; |
||||
import org.springframework.data.jdbc.repository.JdbcRepositoryIdGenerationIntegrationTests.TestConfiguration; |
||||
import org.springframework.data.jdbc.repository.config.EnableJdbcRepositories; |
||||
import org.springframework.data.jdbc.repository.support.JdbcRepositoryFactory; |
||||
import org.springframework.data.repository.CrudRepository; |
||||
import org.springframework.jdbc.datasource.embedded.EmbeddedDatabase; |
||||
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.transaction.annotation.Transactional; |
||||
|
||||
/** |
||||
* Tests the integration with Mybatis. |
||||
* |
||||
* @author Jens Schauder |
||||
*/ |
||||
@ContextConfiguration |
||||
@Transactional |
||||
public class MyBatisHsqlIntegrationTests { |
||||
|
||||
@org.springframework.context.annotation.Configuration |
||||
@Import(TestConfiguration.class) |
||||
|
||||
@EnableJdbcRepositories(considerNestedRepositories = true) |
||||
static class Config { |
||||
|
||||
@Autowired JdbcRepositoryFactory factory; |
||||
|
||||
@Bean |
||||
Class<?> testClass() { |
||||
return MyBatisHsqlIntegrationTests.class; |
||||
} |
||||
|
||||
@Bean |
||||
SqlSessionFactoryBean createSessionFactory(EmbeddedDatabase db) { |
||||
|
||||
Configuration configuration = new Configuration(); |
||||
configuration.getTypeAliasRegistry().registerAlias("MyBatisContext", MyBatisContext.class); |
||||
|
||||
configuration.getTypeAliasRegistry().registerAlias(DummyEntity.class); |
||||
configuration.addMapper(DummyEntityMapper.class); |
||||
|
||||
SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean(); |
||||
sqlSessionFactoryBean.setDataSource(db); |
||||
sqlSessionFactoryBean.setConfiguration(configuration); |
||||
|
||||
return sqlSessionFactoryBean; |
||||
} |
||||
} |
||||
|
||||
@ClassRule public static final SpringClassRule classRule = new SpringClassRule(); |
||||
@Rule public SpringMethodRule methodRule = new SpringMethodRule(); |
||||
|
||||
@Autowired SqlSessionFactory sqlSessionFactory; |
||||
@Autowired DummyEntityRepository repository; |
||||
|
||||
@Test // DATAJDBC-123
|
||||
public void mybatisSelfTest() { |
||||
|
||||
SqlSession session = sqlSessionFactory.openSession(); |
||||
|
||||
session.selectList("org.springframework.data.jdbc.mybatis.DummyEntityMapper.findById"); |
||||
} |
||||
|
||||
@Test // DATAJDBC-123
|
||||
public void myBatisGetsUsedForInsertAndSelect() { |
||||
|
||||
DummyEntity entity = new DummyEntity(null, "some name"); |
||||
DummyEntity saved = repository.save(entity); |
||||
|
||||
assertThat(saved.id).isNotNull(); |
||||
|
||||
DummyEntity reloaded = repository.findById(saved.id).orElseThrow(AssertionFailedError::new); |
||||
|
||||
assertThat(reloaded).isNotNull().extracting(e -> e.id, e -> e.name); |
||||
} |
||||
|
||||
interface DummyEntityRepository extends CrudRepository<DummyEntity, Long> { |
||||
|
||||
} |
||||
} |
||||
@ -0,0 +1 @@
@@ -0,0 +1 @@
|
||||
CREATE TABLE dummyentity(id BIGINT GENERATED BY DEFAULT AS IDENTITY(START WITH 1) PRIMARY KEY); |
||||
@ -0,0 +1,22 @@
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?> |
||||
<!DOCTYPE mapper |
||||
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" |
||||
"http://mybatis.org/dtd/mybatis-3-mapper.dtd"> |
||||
<mapper namespace="org.springframework.data.jdbc.mybatis.DummyEntityMapper"> |
||||
<resultMap id="dummyEntityMap" type="DummyEntity"> |
||||
<constructor> |
||||
<idArg column="id" javaType="long"/> |
||||
<arg column="name" javaType="String"/> |
||||
</constructor> |
||||
</resultMap> |
||||
<insert id="insert" parameterType="MyBatisContext" useGeneratedKeys="true" keyProperty="instance.id" keyColumn="ID"> |
||||
INSERT INTO DummyEntity (id) VALUES (DEFAULT) |
||||
</insert> |
||||
<select id="findById" resultType="MyBatisContext" resultMap="dummyEntityMap"> |
||||
SELECT |
||||
id, |
||||
'Name based on an id' || id AS name |
||||
FROM DummyEntity |
||||
WHERE id = #{id} |
||||
</select> |
||||
</mapper> |
||||
Loading…
Reference in new issue