BIN
.mvn/wrapper/maven-wrapper.jar
vendored
BIN
.mvn/wrapper/maven-wrapper.jar
vendored
Binary file not shown.
2
.mvn/wrapper/maven-wrapper.properties
vendored
2
.mvn/wrapper/maven-wrapper.properties
vendored
@@ -1,2 +0,0 @@
|
||||
#Mon Oct 11 14:30:22 CEST 2021
|
||||
distributionUrl=https\://repo.maven.apache.org/maven2/org/apache/maven/apache-maven/3.8.3/apache-maven-3.8.3-bin.zip
|
||||
29
CI.adoc
29
CI.adoc
@@ -1,29 +0,0 @@
|
||||
= Continuous Integration
|
||||
|
||||
image:https://jenkins.spring.io/buildStatus/icon?job=spring-data-r2dbc%2Fmain&subject=main["Spring Data R2DBC", link="https://jenkins.spring.io/view/SpringData/job/spring-data-r2dbc/"]
|
||||
|
||||
== Running CI tasks locally
|
||||
|
||||
Since this pipeline is purely Docker-based, it's easy to:
|
||||
|
||||
* Debug what went wrong on your local machine.
|
||||
* Test out a a tweak to your `test.sh` script before sending it out.
|
||||
* Experiment against a new image before submitting your pull request.
|
||||
|
||||
All of these use cases are great reasons to essentially run what the CI server does on your local machine.
|
||||
|
||||
IMPORTANT: To do this you must have Docker installed on your machine.
|
||||
|
||||
1. `docker run -it --mount type=bind,source="$(pwd)",target=/spring-data-r2dbc-github -v /usr/bin/docker:/usr/bin/docker -v /var/run/docker.sock:/var/run/docker.sock adoptopenjdk/openjdk8:latest /bin/bash`
|
||||
+
|
||||
This will launch the Docker image and mount your source code at `spring-data-r2dbc-github`.
|
||||
+
|
||||
2. `cd spring-data-r2dbc-github`
|
||||
+
|
||||
Next, test everything from inside the container:
|
||||
+
|
||||
3. `./mvnw -Pci,all-dbs clean dependency:list test -Dsort -B` (or whatever test configuration you must use)
|
||||
|
||||
Since the container is binding to your source, you can make edits from your IDE and continue to run build jobs.
|
||||
|
||||
NOTE: Docker containers can eat up disk space fast! From time to time, run `docker system prune` to clean out old images.
|
||||
@@ -1,3 +0,0 @@
|
||||
= Spring Data contribution guidelines
|
||||
|
||||
You find the contribution guidelines for Spring Data projects https://github.com/spring-projects/spring-data-build/blob/main/CONTRIBUTING.adoc[here].
|
||||
103
Jenkinsfile
vendored
103
Jenkinsfile
vendored
@@ -1,103 +0,0 @@
|
||||
def p = [:]
|
||||
node {
|
||||
checkout scm
|
||||
p = readProperties interpolate: true, file: 'ci/pipeline.properties'
|
||||
}
|
||||
|
||||
pipeline {
|
||||
agent none
|
||||
|
||||
triggers {
|
||||
pollSCM 'H/10 * * * *'
|
||||
upstream(upstreamProjects: "spring-data-commons/3.0.x,spring-data-jdbc/3.0.x", threshold: hudson.model.Result.SUCCESS)
|
||||
}
|
||||
|
||||
options {
|
||||
disableConcurrentBuilds()
|
||||
buildDiscarder(logRotator(numToKeepStr: '14'))
|
||||
}
|
||||
|
||||
stages {
|
||||
stage("test: baseline (Java 17)") {
|
||||
when {
|
||||
beforeAgent(true)
|
||||
anyOf {
|
||||
branch(pattern: "main|(\\d\\.\\d\\.x)", comparator: "REGEXP")
|
||||
not { triggeredBy 'UpstreamCause' }
|
||||
}
|
||||
}
|
||||
agent {
|
||||
label 'data'
|
||||
}
|
||||
options { timeout(time: 30, unit: 'MINUTES') }
|
||||
|
||||
environment {
|
||||
DOCKER_HUB = credentials('hub.docker.com-springbuildmaster')
|
||||
ARTIFACTORY = credentials('02bd1690-b54f-4c9f-819d-a77cb7a9822c')
|
||||
}
|
||||
|
||||
steps {
|
||||
script {
|
||||
docker.withRegistry(p['docker.registry'], p['docker.credentials']) {
|
||||
docker.image(p['docker.java.main.image']).inside(p['docker.java.inside.docker']) {
|
||||
sh "docker login --username ${DOCKER_HUB_USR} --password ${DOCKER_HUB_PSW}"
|
||||
sh 'PROFILE=ci ci/test.sh'
|
||||
sh "ci/clean.sh"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
stage('Release to artifactory') {
|
||||
when {
|
||||
beforeAgent(true)
|
||||
anyOf {
|
||||
branch(pattern: "main|(\\d\\.\\d\\.x)", comparator: "REGEXP")
|
||||
not { triggeredBy 'UpstreamCause' }
|
||||
}
|
||||
}
|
||||
agent {
|
||||
label 'data'
|
||||
}
|
||||
options { timeout(time: 20, unit: 'MINUTES') }
|
||||
|
||||
environment {
|
||||
ARTIFACTORY = credentials('02bd1690-b54f-4c9f-819d-a77cb7a9822c')
|
||||
}
|
||||
|
||||
steps {
|
||||
script {
|
||||
docker.withRegistry(p['docker.registry'], p['docker.credentials']) {
|
||||
docker.image(p['docker.java.main.image']).inside(p['docker.java.inside.basic']) {
|
||||
sh 'MAVEN_OPTS="-Duser.name=jenkins -Duser.home=/tmp/jenkins-home" ./mvnw -s settings.xml -Pci,artifactory -Dmaven.repo.local=/tmp/jenkins-home/.m2/spring-data-r2dbc-non-root ' +
|
||||
'-Dartifactory.server=https://repo.spring.io ' +
|
||||
"-Dartifactory.username=${ARTIFACTORY_USR} " +
|
||||
"-Dartifactory.password=${ARTIFACTORY_PSW} " +
|
||||
"-Dartifactory.staging-repository=libs-snapshot-local " +
|
||||
"-Dartifactory.build-name=spring-data-r2dbc " +
|
||||
"-Dartifactory.build-number=${BUILD_NUMBER} " +
|
||||
'-Dmaven.test.skip=true clean deploy -U -B'
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
post {
|
||||
changed {
|
||||
script {
|
||||
slackSend(
|
||||
color: (currentBuild.currentResult == 'SUCCESS') ? 'good' : 'danger',
|
||||
channel: '#spring-data-dev',
|
||||
message: "${currentBuild.fullDisplayName} - `${currentBuild.currentResult}`\n${env.BUILD_URL}")
|
||||
emailext(
|
||||
subject: "[${currentBuild.fullDisplayName}] ${currentBuild.currentResult}",
|
||||
mimeType: 'text/html',
|
||||
recipientProviders: [[$class: 'CulpritsRecipientProvider'], [$class: 'RequesterRecipientProvider']],
|
||||
body: "<a href=\"${env.BUILD_URL}\">${currentBuild.fullDisplayName} is reported as ${currentBuild.currentResult}</a>")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
155
README.adoc
155
README.adoc
@@ -1,157 +1,6 @@
|
||||
image:https://spring.io/badges/spring-data-r2dbc/snapshot.svg["Spring Data R2DBC", link="https://spring.io/projects/spring-data-r2dbc#learn"]
|
||||
= Spring Data R2DBC
|
||||
|
||||
= Spring Data R2DBC image:https://jenkins.spring.io/buildStatus/icon?job=spring-data-r2dbc%2Fmain&subject=Build[link=https://jenkins.spring.io/view/SpringData/job/spring-data-r2dbc/] https://gitter.im/spring-projects/spring-data[image:https://badges.gitter.im/spring-projects/spring-data.svg[Gitter]]
|
||||
|
||||
The primary goal of the https://projects.spring.io/spring-data[Spring Data] project is to make it easier to build Spring-powered applications that use data access technologies. *Spring Data R2DBC* offers the popular Repository abstraction based on https://r2dbc.io[R2DBC].
|
||||
|
||||
R2DBC is the abbreviation for https://github.com/r2dbc/[Reactive Relational Database Connectivity], an incubator to integrate relational databases using a reactive driver.
|
||||
|
||||
== This is NOT an ORM
|
||||
|
||||
Spring Data R2DBC aims at being conceptually easy. In order to achieve this it does NOT offer caching, lazy loading, write behind or many other features of ORM frameworks. This makes Spring Data R2DBC a simple, limited, opinionated object mapper.
|
||||
|
||||
== Features
|
||||
|
||||
* Spring configuration support using Java based `@Configuration` classes.
|
||||
* Annotation based mapping metadata.
|
||||
* Automatic implementation of Repository interfaces including support.
|
||||
* Support for Reactive Transactions
|
||||
* Schema and data initialization utilities.
|
||||
|
||||
== Code of Conduct
|
||||
|
||||
This project is governed by the https://github.com/spring-projects/.github/blob/e3cc2ff230d8f1dca06535aa6b5a4a23815861d4/CODE_OF_CONDUCT.md[Spring Code of Conduct]. By participating, you are expected to uphold this code of conduct. Please report unacceptable behavior to spring-code-of-conduct@pivotal.io.
|
||||
|
||||
== Getting Started
|
||||
|
||||
Here is a quick teaser of an application using Spring Data Repositories in Java:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
public interface PersonRepository extends ReactiveCrudRepository<Person, Long> {
|
||||
|
||||
@Query("SELECT * FROM person WHERE lastname = :lastname")
|
||||
Flux<Person> findByLastname(String lastname);
|
||||
|
||||
@Query("SELECT * FROM person WHERE firstname LIKE :firstname")
|
||||
Flux<Person> findByFirstnameLike(String firstname);
|
||||
}
|
||||
|
||||
@Service
|
||||
public class MyService {
|
||||
|
||||
private final PersonRepository repository;
|
||||
|
||||
public MyService(PersonRepository repository) {
|
||||
this.repository = repository;
|
||||
}
|
||||
|
||||
public void doWork() {
|
||||
|
||||
repository.deleteAll().block();
|
||||
|
||||
Person person = new Person();
|
||||
person.setFirstname("Mark");
|
||||
person.setLastname("Paluch");
|
||||
repository.save(person).block();
|
||||
|
||||
Flux<Person> lastNameResults = repository.findByLastname("Paluch");
|
||||
Flux<Person> firstNameResults = repository.findByFirstnameLike("M%");
|
||||
}
|
||||
}
|
||||
|
||||
@Configuration
|
||||
@EnableR2dbcRepositories
|
||||
class ApplicationConfig extends AbstractR2dbcConfiguration {
|
||||
|
||||
@Bean
|
||||
public ConnectionFactory connectionFactory() {
|
||||
return ConnectionFactories.get("r2dbc:h2:mem:///test?options=DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE");
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
=== Maven configuration
|
||||
|
||||
Add the Maven dependency:
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
<dependency>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-r2dbc</artifactId>
|
||||
<version>${version}</version>
|
||||
</dependency>
|
||||
----
|
||||
|
||||
If you'd rather like the latest snapshots of the upcoming major version, use our Maven snapshot repository and declare the appropriate dependency version.
|
||||
|
||||
[source,xml]
|
||||
----
|
||||
<dependency>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-r2dbc</artifactId>
|
||||
<version>${version}-SNAPSHOT</version>
|
||||
</dependency>
|
||||
|
||||
<repository>
|
||||
<id>spring-libs-snapshot</id>
|
||||
<name>Spring Snapshot Repository</name>
|
||||
<url>https://repo.spring.io/libs-snapshot</url>
|
||||
</repository>
|
||||
----
|
||||
|
||||
== Getting Help
|
||||
|
||||
Having trouble with Spring Data? We’d love to help!
|
||||
|
||||
* Check the
|
||||
https://docs.spring.io/spring-data/r2dbc/docs/1.0.x/reference/html/#reference[reference documentation], and https://docs.spring.io/spring-data/r2dbc/docs/1.0.x/api/[Javadocs].
|
||||
* Learn the Spring basics – Spring Data builds on Spring Framework, check the https://spring.io[spring.io] web-site for a wealth of reference documentation.
|
||||
If you are just starting out with Spring, try one of the https://spring.io/guides[guides].
|
||||
* If you are upgrading, check out the https://docs.spring.io/spring-data/r2dbc/docs/current/changelog.txt[changelog] for "`new and noteworthy`" features.
|
||||
* Ask a question - we monitor https://stackoverflow.com[stackoverflow.com] for questions tagged with https://stackoverflow.com/tags/spring-data-r2dbc[`spring-data-r2dbc`].
|
||||
* Report bugs with Spring Data R2DBC at https://github.com/spring-projects/spring-data-r2dbc/issues[github.com/spring-projects/spring-data-r2dbc/issues].
|
||||
|
||||
== Reporting Issues
|
||||
|
||||
Spring Data uses GitHub as issue tracking system to record bugs and feature requests. If you want to raise an issue, please follow the recommendations below:
|
||||
|
||||
* Before you log a bug, please search the
|
||||
https://github.com/spring-projects/spring-data-r2dbc/issues[issue tracker] to see if someone has already reported the problem.
|
||||
* If the issue does not already exist, https://github.com/spring-projects/spring-data-r2dbc/issues/new[create a new issue].
|
||||
* Please provide as much information as possible with the issue report, we like to know the version of Spring Data that you are using and JVM version.
|
||||
* If you need to paste code, or include a stack trace use Markdown +++```+++ escapes before and after your text.
|
||||
* If possible try to create a test-case or project that replicates the issue. Attach a link to your code or a compressed file containing your code.
|
||||
|
||||
== Building from Source
|
||||
|
||||
You don’t need to build from source to use Spring Data (binaries in https://repo.spring.io[repo.spring.io]), but if you want to try out the latest and greatest, Spring Data can be easily built with the https://github.com/takari/maven-wrapper[maven wrapper].
|
||||
You also need JDK 1.8.
|
||||
|
||||
[source,bash]
|
||||
----
|
||||
$ ./mvnw clean install
|
||||
----
|
||||
|
||||
If you want to build with the regular `mvn` command, you will need https://maven.apache.org/run-maven/index.html[Maven v3.5.0 or above].
|
||||
|
||||
_Also see link:CONTRIBUTING.adoc[CONTRIBUTING.adoc] if you wish to submit pull requests, and in particular please sign the https://cla.pivotal.io/sign/spring[Contributor’s Agreement] before your first non-trivial change._
|
||||
|
||||
=== Building reference documentation
|
||||
|
||||
Building the documentation builds also the project without running tests.
|
||||
|
||||
[source,bash]
|
||||
----
|
||||
$ ./mvnw clean install -Pdistribute
|
||||
----
|
||||
|
||||
The generated documentation is available from `target/site/reference/html/index.html`.
|
||||
|
||||
== Examples
|
||||
|
||||
* https://github.com/spring-projects/spring-data-examples/[Spring Data Examples] contains example projects that explain specific features in more detail.
|
||||
This project is merged as of version 3.0 in the https://github.com/spring-projects/spring-data-relational[Spring Data Relational] repository.
|
||||
|
||||
== License
|
||||
|
||||
|
||||
@@ -1,6 +0,0 @@
|
||||
#!/bin/bash -x
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
MAVEN_OPTS="-Duser.name=jenkins -Duser.home=/tmp/jenkins-home" \
|
||||
./mvnw -s settings.xml clean -Dmaven.repo.local=/tmp/jenkins-home/.m2/spring-data-r2dbc
|
||||
@@ -1,24 +0,0 @@
|
||||
# Java versions
|
||||
java.main.tag=17.0.2_8-jdk
|
||||
|
||||
# Docker container images - standard
|
||||
docker.java.main.image=harbor-repo.vmware.com/dockerhub-proxy-cache/library/eclipse-temurin:${java.main.tag}
|
||||
|
||||
# Supported versions of MongoDB
|
||||
docker.mongodb.4.4.version=4.4.12
|
||||
docker.mongodb.5.0.version=5.0.6
|
||||
|
||||
# Supported versions of Redis
|
||||
docker.redis.6.version=6.2.6
|
||||
|
||||
# Supported versions of Cassandra
|
||||
docker.cassandra.3.version=3.11.12
|
||||
|
||||
# Docker environment settings
|
||||
docker.java.inside.basic=-v $HOME:/tmp/jenkins-home
|
||||
docker.java.inside.docker=-u root -v /var/run/docker.sock:/var/run/docker.sock -v /usr/bin/docker:/usr/bin/docker -v $HOME:/tmp/jenkins-home
|
||||
|
||||
# Credentials
|
||||
docker.registry=
|
||||
docker.credentials=hub.docker.com-springbuildmaster
|
||||
artifactory.credentials=02bd1690-b54f-4c9f-819d-a77cb7a9822c
|
||||
10
ci/test.sh
10
ci/test.sh
@@ -1,10 +0,0 @@
|
||||
#!/bin/bash -x
|
||||
|
||||
set -euo pipefail
|
||||
|
||||
mkdir -p /tmp/jenkins-home/.m2/spring-data-r2dbc
|
||||
chown -R 1001:1001 .
|
||||
|
||||
MAVEN_OPTS="-Duser.name=jenkins -Duser.home=/tmp/jenkins-home" \
|
||||
./mvnw -s settings.xml \
|
||||
-P${PROFILE} clean dependency:list test -Dsort -U -B -Dmaven.repo.local=/tmp/jenkins-home/.m2/spring-data-r2dbc
|
||||
BIN
docs/favicon.png
BIN
docs/favicon.png
Binary file not shown.
|
Before Width: | Height: | Size: 1.5 KiB |
@@ -1,11 +0,0 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en-US">
|
||||
<meta charset="utf-8">
|
||||
<title>Redirecting…</title>
|
||||
<link rel="canonical" href="https://spring.io/projects/spring-data-r2dbc">
|
||||
<meta http-equiv="refresh" content="0; url=https://spring.io/projects/spring-data-r2dbc">
|
||||
<meta name="robots" content="noindex">
|
||||
<h1>Redirecting…</h1>
|
||||
<a href="https://spring.io/projects/spring-data-r2dbc">Click here if you are not redirected.</a>
|
||||
<script>location="https://spring.io/projects/spring-data-r2dbc"</script>
|
||||
</html>
|
||||
@@ -1,2 +0,0 @@
|
||||
lombok.nonNull.exceptionType = IllegalArgumentException
|
||||
lombok.log.fieldName = LOG
|
||||
286
mvnw
vendored
286
mvnw
vendored
@@ -1,286 +0,0 @@
|
||||
#!/bin/sh
|
||||
# ----------------------------------------------------------------------------
|
||||
# Licensed to the Apache Software Foundation (ASF) under one
|
||||
# or more contributor license agreements. See the NOTICE file
|
||||
# distributed with this work for additional information
|
||||
# regarding copyright ownership. The ASF licenses this file
|
||||
# to you 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.
|
||||
# ----------------------------------------------------------------------------
|
||||
|
||||
# ----------------------------------------------------------------------------
|
||||
# Maven2 Start Up Batch script
|
||||
#
|
||||
# Required ENV vars:
|
||||
# ------------------
|
||||
# JAVA_HOME - location of a JDK home dir
|
||||
#
|
||||
# Optional ENV vars
|
||||
# -----------------
|
||||
# M2_HOME - location of maven2's installed home dir
|
||||
# MAVEN_OPTS - parameters passed to the Java VM when running Maven
|
||||
# e.g. to debug Maven itself, use
|
||||
# set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
|
||||
# MAVEN_SKIP_RC - flag to disable loading of mavenrc files
|
||||
# ----------------------------------------------------------------------------
|
||||
|
||||
if [ -z "$MAVEN_SKIP_RC" ] ; then
|
||||
|
||||
if [ -f /etc/mavenrc ] ; then
|
||||
. /etc/mavenrc
|
||||
fi
|
||||
|
||||
if [ -f "$HOME/.mavenrc" ] ; then
|
||||
. "$HOME/.mavenrc"
|
||||
fi
|
||||
|
||||
fi
|
||||
|
||||
# OS specific support. $var _must_ be set to either true or false.
|
||||
cygwin=false;
|
||||
darwin=false;
|
||||
mingw=false
|
||||
case "`uname`" in
|
||||
CYGWIN*) cygwin=true ;;
|
||||
MINGW*) mingw=true;;
|
||||
Darwin*) darwin=true
|
||||
# Use /usr/libexec/java_home if available, otherwise fall back to /Library/Java/Home
|
||||
# See https://developer.apple.com/library/mac/qa/qa1170/_index.html
|
||||
if [ -z "$JAVA_HOME" ]; then
|
||||
if [ -x "/usr/libexec/java_home" ]; then
|
||||
export JAVA_HOME="`/usr/libexec/java_home`"
|
||||
else
|
||||
export JAVA_HOME="/Library/Java/Home"
|
||||
fi
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
|
||||
if [ -z "$JAVA_HOME" ] ; then
|
||||
if [ -r /etc/gentoo-release ] ; then
|
||||
JAVA_HOME=`java-config --jre-home`
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -z "$M2_HOME" ] ; then
|
||||
## resolve links - $0 may be a link to maven's home
|
||||
PRG="$0"
|
||||
|
||||
# need this for relative symlinks
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG="`dirname "$PRG"`/$link"
|
||||
fi
|
||||
done
|
||||
|
||||
saveddir=`pwd`
|
||||
|
||||
M2_HOME=`dirname "$PRG"`/..
|
||||
|
||||
# make it fully qualified
|
||||
M2_HOME=`cd "$M2_HOME" && pwd`
|
||||
|
||||
cd "$saveddir"
|
||||
# echo Using m2 at $M2_HOME
|
||||
fi
|
||||
|
||||
# For Cygwin, ensure paths are in UNIX format before anything is touched
|
||||
if $cygwin ; then
|
||||
[ -n "$M2_HOME" ] &&
|
||||
M2_HOME=`cygpath --unix "$M2_HOME"`
|
||||
[ -n "$JAVA_HOME" ] &&
|
||||
JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
|
||||
[ -n "$CLASSPATH" ] &&
|
||||
CLASSPATH=`cygpath --path --unix "$CLASSPATH"`
|
||||
fi
|
||||
|
||||
# For Mingw, ensure paths are in UNIX format before anything is touched
|
||||
if $mingw ; then
|
||||
[ -n "$M2_HOME" ] &&
|
||||
M2_HOME="`(cd "$M2_HOME"; pwd)`"
|
||||
[ -n "$JAVA_HOME" ] &&
|
||||
JAVA_HOME="`(cd "$JAVA_HOME"; pwd)`"
|
||||
# TODO classpath?
|
||||
fi
|
||||
|
||||
if [ -z "$JAVA_HOME" ]; then
|
||||
javaExecutable="`which javac`"
|
||||
if [ -n "$javaExecutable" ] && ! [ "`expr \"$javaExecutable\" : '\([^ ]*\)'`" = "no" ]; then
|
||||
# readlink(1) is not available as standard on Solaris 10.
|
||||
readLink=`which readlink`
|
||||
if [ ! `expr "$readLink" : '\([^ ]*\)'` = "no" ]; then
|
||||
if $darwin ; then
|
||||
javaHome="`dirname \"$javaExecutable\"`"
|
||||
javaExecutable="`cd \"$javaHome\" && pwd -P`/javac"
|
||||
else
|
||||
javaExecutable="`readlink -f \"$javaExecutable\"`"
|
||||
fi
|
||||
javaHome="`dirname \"$javaExecutable\"`"
|
||||
javaHome=`expr "$javaHome" : '\(.*\)/bin'`
|
||||
JAVA_HOME="$javaHome"
|
||||
export JAVA_HOME
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ -z "$JAVACMD" ] ; then
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
fi
|
||||
else
|
||||
JAVACMD="`which java`"
|
||||
fi
|
||||
fi
|
||||
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
echo "Error: JAVA_HOME is not defined correctly." >&2
|
||||
echo " We cannot execute $JAVACMD" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [ -z "$JAVA_HOME" ] ; then
|
||||
echo "Warning: JAVA_HOME environment variable is not set."
|
||||
fi
|
||||
|
||||
CLASSWORLDS_LAUNCHER=org.codehaus.plexus.classworlds.launcher.Launcher
|
||||
|
||||
# traverses directory structure from process work directory to filesystem root
|
||||
# first directory with .mvn subdirectory is considered project base directory
|
||||
find_maven_basedir() {
|
||||
|
||||
if [ -z "$1" ]
|
||||
then
|
||||
echo "Path not specified to find_maven_basedir"
|
||||
return 1
|
||||
fi
|
||||
|
||||
basedir="$1"
|
||||
wdir="$1"
|
||||
while [ "$wdir" != '/' ] ; do
|
||||
if [ -d "$wdir"/.mvn ] ; then
|
||||
basedir=$wdir
|
||||
break
|
||||
fi
|
||||
# workaround for JBEAP-8937 (on Solaris 10/Sparc)
|
||||
if [ -d "${wdir}" ]; then
|
||||
wdir=`cd "$wdir/.."; pwd`
|
||||
fi
|
||||
# end of workaround
|
||||
done
|
||||
echo "${basedir}"
|
||||
}
|
||||
|
||||
# concatenates all lines of a file
|
||||
concat_lines() {
|
||||
if [ -f "$1" ]; then
|
||||
echo "$(tr -s '\n' ' ' < "$1")"
|
||||
fi
|
||||
}
|
||||
|
||||
BASE_DIR=`find_maven_basedir "$(pwd)"`
|
||||
if [ -z "$BASE_DIR" ]; then
|
||||
exit 1;
|
||||
fi
|
||||
|
||||
##########################################################################################
|
||||
# Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
|
||||
# This allows using the maven wrapper in projects that prohibit checking in binary data.
|
||||
##########################################################################################
|
||||
if [ -r "$BASE_DIR/.mvn/wrapper/maven-wrapper.jar" ]; then
|
||||
if [ "$MVNW_VERBOSE" = true ]; then
|
||||
echo "Found .mvn/wrapper/maven-wrapper.jar"
|
||||
fi
|
||||
else
|
||||
if [ "$MVNW_VERBOSE" = true ]; then
|
||||
echo "Couldn't find .mvn/wrapper/maven-wrapper.jar, downloading it ..."
|
||||
fi
|
||||
jarUrl="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar"
|
||||
while IFS="=" read key value; do
|
||||
case "$key" in (wrapperUrl) jarUrl="$value"; break ;;
|
||||
esac
|
||||
done < "$BASE_DIR/.mvn/wrapper/maven-wrapper.properties"
|
||||
if [ "$MVNW_VERBOSE" = true ]; then
|
||||
echo "Downloading from: $jarUrl"
|
||||
fi
|
||||
wrapperJarPath="$BASE_DIR/.mvn/wrapper/maven-wrapper.jar"
|
||||
|
||||
if command -v wget > /dev/null; then
|
||||
if [ "$MVNW_VERBOSE" = true ]; then
|
||||
echo "Found wget ... using wget"
|
||||
fi
|
||||
wget "$jarUrl" -O "$wrapperJarPath"
|
||||
elif command -v curl > /dev/null; then
|
||||
if [ "$MVNW_VERBOSE" = true ]; then
|
||||
echo "Found curl ... using curl"
|
||||
fi
|
||||
curl -o "$wrapperJarPath" "$jarUrl"
|
||||
else
|
||||
if [ "$MVNW_VERBOSE" = true ]; then
|
||||
echo "Falling back to using Java to download"
|
||||
fi
|
||||
javaClass="$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.java"
|
||||
if [ -e "$javaClass" ]; then
|
||||
if [ ! -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
|
||||
if [ "$MVNW_VERBOSE" = true ]; then
|
||||
echo " - Compiling MavenWrapperDownloader.java ..."
|
||||
fi
|
||||
# Compiling the Java class
|
||||
("$JAVA_HOME/bin/javac" "$javaClass")
|
||||
fi
|
||||
if [ -e "$BASE_DIR/.mvn/wrapper/MavenWrapperDownloader.class" ]; then
|
||||
# Running the downloader
|
||||
if [ "$MVNW_VERBOSE" = true ]; then
|
||||
echo " - Running MavenWrapperDownloader.java ..."
|
||||
fi
|
||||
("$JAVA_HOME/bin/java" -cp .mvn/wrapper MavenWrapperDownloader "$MAVEN_PROJECTBASEDIR")
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
fi
|
||||
##########################################################################################
|
||||
# End of extension
|
||||
##########################################################################################
|
||||
|
||||
export MAVEN_PROJECTBASEDIR=${MAVEN_BASEDIR:-"$BASE_DIR"}
|
||||
if [ "$MVNW_VERBOSE" = true ]; then
|
||||
echo $MAVEN_PROJECTBASEDIR
|
||||
fi
|
||||
MAVEN_OPTS="$(concat_lines "$MAVEN_PROJECTBASEDIR/.mvn/jvm.config") $MAVEN_OPTS"
|
||||
|
||||
# For Cygwin, switch paths to Windows format before running java
|
||||
if $cygwin; then
|
||||
[ -n "$M2_HOME" ] &&
|
||||
M2_HOME=`cygpath --path --windows "$M2_HOME"`
|
||||
[ -n "$JAVA_HOME" ] &&
|
||||
JAVA_HOME=`cygpath --path --windows "$JAVA_HOME"`
|
||||
[ -n "$CLASSPATH" ] &&
|
||||
CLASSPATH=`cygpath --path --windows "$CLASSPATH"`
|
||||
[ -n "$MAVEN_PROJECTBASEDIR" ] &&
|
||||
MAVEN_PROJECTBASEDIR=`cygpath --path --windows "$MAVEN_PROJECTBASEDIR"`
|
||||
fi
|
||||
|
||||
WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
|
||||
|
||||
exec "$JAVACMD" \
|
||||
$MAVEN_OPTS \
|
||||
-classpath "$MAVEN_PROJECTBASEDIR/.mvn/wrapper/maven-wrapper.jar" \
|
||||
"-Dmaven.home=${M2_HOME}" "-Dmaven.multiModuleProjectDirectory=${MAVEN_PROJECTBASEDIR}" \
|
||||
${WRAPPER_LAUNCHER} $MAVEN_CONFIG "$@"
|
||||
161
mvnw.cmd
vendored
161
mvnw.cmd
vendored
@@ -1,161 +0,0 @@
|
||||
@REM ----------------------------------------------------------------------------
|
||||
@REM Licensed to the Apache Software Foundation (ASF) under one
|
||||
@REM or more contributor license agreements. See the NOTICE file
|
||||
@REM distributed with this work for additional information
|
||||
@REM regarding copyright ownership. The ASF licenses this file
|
||||
@REM to you under the Apache License, Version 2.0 (the
|
||||
@REM "License"); you may not use this file except in compliance
|
||||
@REM with the License. You may obtain a copy of the License at
|
||||
@REM
|
||||
@REM http://www.apache.org/licenses/LICENSE-2.0
|
||||
@REM
|
||||
@REM Unless required by applicable law or agreed to in writing,
|
||||
@REM software distributed under the License is distributed on an
|
||||
@REM "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
|
||||
@REM KIND, either express or implied. See the License for the
|
||||
@REM specific language governing permissions and limitations
|
||||
@REM under the License.
|
||||
@REM ----------------------------------------------------------------------------
|
||||
|
||||
@REM ----------------------------------------------------------------------------
|
||||
@REM Maven2 Start Up Batch script
|
||||
@REM
|
||||
@REM Required ENV vars:
|
||||
@REM JAVA_HOME - location of a JDK home dir
|
||||
@REM
|
||||
@REM Optional ENV vars
|
||||
@REM M2_HOME - location of maven2's installed home dir
|
||||
@REM MAVEN_BATCH_ECHO - set to 'on' to enable the echoing of the batch commands
|
||||
@REM MAVEN_BATCH_PAUSE - set to 'on' to wait for a key stroke before ending
|
||||
@REM MAVEN_OPTS - parameters passed to the Java VM when running Maven
|
||||
@REM e.g. to debug Maven itself, use
|
||||
@REM set MAVEN_OPTS=-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000
|
||||
@REM MAVEN_SKIP_RC - flag to disable loading of mavenrc files
|
||||
@REM ----------------------------------------------------------------------------
|
||||
|
||||
@REM Begin all REM lines with '@' in case MAVEN_BATCH_ECHO is 'on'
|
||||
@echo off
|
||||
@REM set title of command window
|
||||
title %0
|
||||
@REM enable echoing my setting MAVEN_BATCH_ECHO to 'on'
|
||||
@if "%MAVEN_BATCH_ECHO%" == "on" echo %MAVEN_BATCH_ECHO%
|
||||
|
||||
@REM set %HOME% to equivalent of $HOME
|
||||
if "%HOME%" == "" (set "HOME=%HOMEDRIVE%%HOMEPATH%")
|
||||
|
||||
@REM Execute a user defined script before this one
|
||||
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPre
|
||||
@REM check for pre script, once with legacy .bat ending and once with .cmd ending
|
||||
if exist "%HOME%\mavenrc_pre.bat" call "%HOME%\mavenrc_pre.bat"
|
||||
if exist "%HOME%\mavenrc_pre.cmd" call "%HOME%\mavenrc_pre.cmd"
|
||||
:skipRcPre
|
||||
|
||||
@setlocal
|
||||
|
||||
set ERROR_CODE=0
|
||||
|
||||
@REM To isolate internal variables from possible post scripts, we use another setlocal
|
||||
@setlocal
|
||||
|
||||
@REM ==== START VALIDATION ====
|
||||
if not "%JAVA_HOME%" == "" goto OkJHome
|
||||
|
||||
echo.
|
||||
echo Error: JAVA_HOME not found in your environment. >&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the >&2
|
||||
echo location of your Java installation. >&2
|
||||
echo.
|
||||
goto error
|
||||
|
||||
:OkJHome
|
||||
if exist "%JAVA_HOME%\bin\java.exe" goto init
|
||||
|
||||
echo.
|
||||
echo Error: JAVA_HOME is set to an invalid directory. >&2
|
||||
echo JAVA_HOME = "%JAVA_HOME%" >&2
|
||||
echo Please set the JAVA_HOME variable in your environment to match the >&2
|
||||
echo location of your Java installation. >&2
|
||||
echo.
|
||||
goto error
|
||||
|
||||
@REM ==== END VALIDATION ====
|
||||
|
||||
:init
|
||||
|
||||
@REM Find the project base dir, i.e. the directory that contains the folder ".mvn".
|
||||
@REM Fallback to current working directory if not found.
|
||||
|
||||
set MAVEN_PROJECTBASEDIR=%MAVEN_BASEDIR%
|
||||
IF NOT "%MAVEN_PROJECTBASEDIR%"=="" goto endDetectBaseDir
|
||||
|
||||
set EXEC_DIR=%CD%
|
||||
set WDIR=%EXEC_DIR%
|
||||
:findBaseDir
|
||||
IF EXIST "%WDIR%"\.mvn goto baseDirFound
|
||||
cd ..
|
||||
IF "%WDIR%"=="%CD%" goto baseDirNotFound
|
||||
set WDIR=%CD%
|
||||
goto findBaseDir
|
||||
|
||||
:baseDirFound
|
||||
set MAVEN_PROJECTBASEDIR=%WDIR%
|
||||
cd "%EXEC_DIR%"
|
||||
goto endDetectBaseDir
|
||||
|
||||
:baseDirNotFound
|
||||
set MAVEN_PROJECTBASEDIR=%EXEC_DIR%
|
||||
cd "%EXEC_DIR%"
|
||||
|
||||
:endDetectBaseDir
|
||||
|
||||
IF NOT EXIST "%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config" goto endReadAdditionalConfig
|
||||
|
||||
@setlocal EnableExtensions EnableDelayedExpansion
|
||||
for /F "usebackq delims=" %%a in ("%MAVEN_PROJECTBASEDIR%\.mvn\jvm.config") do set JVM_CONFIG_MAVEN_PROPS=!JVM_CONFIG_MAVEN_PROPS! %%a
|
||||
@endlocal & set JVM_CONFIG_MAVEN_PROPS=%JVM_CONFIG_MAVEN_PROPS%
|
||||
|
||||
:endReadAdditionalConfig
|
||||
|
||||
SET MAVEN_JAVA_EXE="%JAVA_HOME%\bin\java.exe"
|
||||
set WRAPPER_JAR="%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.jar"
|
||||
set WRAPPER_LAUNCHER=org.apache.maven.wrapper.MavenWrapperMain
|
||||
|
||||
set DOWNLOAD_URL="https://repo.maven.apache.org/maven2/io/takari/maven-wrapper/0.4.2/maven-wrapper-0.4.2.jar"
|
||||
FOR /F "tokens=1,2 delims==" %%A IN (%MAVEN_PROJECTBASEDIR%\.mvn\wrapper\maven-wrapper.properties) DO (
|
||||
IF "%%A"=="wrapperUrl" SET DOWNLOAD_URL=%%B
|
||||
)
|
||||
|
||||
@REM Extension to allow automatically downloading the maven-wrapper.jar from Maven-central
|
||||
@REM This allows using the maven wrapper in projects that prohibit checking in binary data.
|
||||
if exist %WRAPPER_JAR% (
|
||||
echo Found %WRAPPER_JAR%
|
||||
) else (
|
||||
echo Couldn't find %WRAPPER_JAR%, downloading it ...
|
||||
echo Downloading from: %DOWNLOAD_URL%
|
||||
powershell -Command "(New-Object Net.WebClient).DownloadFile('%DOWNLOAD_URL%', '%WRAPPER_JAR%')"
|
||||
echo Finished downloading %WRAPPER_JAR%
|
||||
)
|
||||
@REM End of extension
|
||||
|
||||
%MAVEN_JAVA_EXE% %JVM_CONFIG_MAVEN_PROPS% %MAVEN_OPTS% %MAVEN_DEBUG_OPTS% -classpath %WRAPPER_JAR% "-Dmaven.multiModuleProjectDirectory=%MAVEN_PROJECTBASEDIR%" %WRAPPER_LAUNCHER% %MAVEN_CONFIG% %*
|
||||
if ERRORLEVEL 1 goto error
|
||||
goto end
|
||||
|
||||
:error
|
||||
set ERROR_CODE=1
|
||||
|
||||
:end
|
||||
@endlocal & set ERROR_CODE=%ERROR_CODE%
|
||||
|
||||
if not "%MAVEN_SKIP_RC%" == "" goto skipRcPost
|
||||
@REM check for post script, once with legacy .bat ending and once with .cmd ending
|
||||
if exist "%HOME%\mavenrc_post.bat" call "%HOME%\mavenrc_post.bat"
|
||||
if exist "%HOME%\mavenrc_post.cmd" call "%HOME%\mavenrc_post.cmd"
|
||||
:skipRcPost
|
||||
|
||||
@REM pause the script if MAVEN_BATCH_PAUSE is set to 'on'
|
||||
if "%MAVEN_BATCH_PAUSE%" == "on" pause
|
||||
|
||||
if "%MAVEN_TERMINATE_CMD%" == "on" exit %ERROR_CODE%
|
||||
|
||||
exit /B %ERROR_CODE%
|
||||
493
pom.xml
493
pom.xml
@@ -1,493 +0,0 @@
|
||||
<?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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-r2dbc</artifactId>
|
||||
<version>3.0.0-SNAPSHOT</version>
|
||||
|
||||
<name>Spring Data R2DBC</name>
|
||||
<description>Spring Data module for R2DBC</description>
|
||||
<url>https://projects.spring.io/spring-data-r2dbc</url>
|
||||
|
||||
<parent>
|
||||
<groupId>org.springframework.data.build</groupId>
|
||||
<artifactId>spring-data-parent</artifactId>
|
||||
<version>3.0.0-SNAPSHOT</version>
|
||||
</parent>
|
||||
|
||||
<properties>
|
||||
|
||||
<dist.key>DATAR2DBC</dist.key>
|
||||
|
||||
<springdata.commons>3.0.0-SNAPSHOT</springdata.commons>
|
||||
<springdata.jdbc>3.0.0-SNAPSHOT</springdata.jdbc>
|
||||
<springdata.relational>${springdata.jdbc}</springdata.relational>
|
||||
<java-module-name>spring.data.r2dbc</java-module-name>
|
||||
<sonar.dynamicAnalysis>reuseReports</sonar.dynamicAnalysis>
|
||||
|
||||
<degraph-check.version>0.1.4</degraph-check.version>
|
||||
<postgresql.version>42.2.25</postgresql.version>
|
||||
<mysql.version>8.0.21</mysql.version>
|
||||
<r2dbc-spi-test.version>0.9.1.RELEASE</r2dbc-spi-test.version>
|
||||
<mssql-jdbc.version>7.1.2.jre8-preview</mssql-jdbc.version>
|
||||
<mariadb-jdbc.version>2.5.4</mariadb-jdbc.version>
|
||||
<r2dbc-releasetrain.version>Borca-RELEASE</r2dbc-releasetrain.version>
|
||||
<reactive-streams.version>1.0.3</reactive-streams.version>
|
||||
<netty>4.1.73.Final</netty>
|
||||
</properties>
|
||||
|
||||
<inceptionYear>2018</inceptionYear>
|
||||
|
||||
<developers>
|
||||
<developer>
|
||||
<id>mpaluch</id>
|
||||
<name>Mark Paluch</name>
|
||||
<email>mpaluch(at)pivotal.io</email>
|
||||
<organization>Pivotal Software, Inc.</organization>
|
||||
<organizationUrl>https://pivotal.io</organizationUrl>
|
||||
<roles>
|
||||
<role>Project Lead</role>
|
||||
</roles>
|
||||
<timezone>+1</timezone>
|
||||
</developer>
|
||||
<developer>
|
||||
<id>ogierke</id>
|
||||
<name>Oliver Gierke</name>
|
||||
<email>ogierke(at)pivotal.io</email>
|
||||
<organization>Pivotal Software, Inc.</organization>
|
||||
<organizationUrl>https://pivotal.io</organizationUrl>
|
||||
<roles>
|
||||
<role>Project Lead</role>
|
||||
</roles>
|
||||
<timezone>+1</timezone>
|
||||
</developer>
|
||||
</developers>
|
||||
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>io.r2dbc</groupId>
|
||||
<artifactId>r2dbc-bom</artifactId>
|
||||
<version>${r2dbc-releasetrain.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.testcontainers</groupId>
|
||||
<artifactId>testcontainers-bom</artifactId>
|
||||
<version>${testcontainers}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>io.netty</groupId>
|
||||
<artifactId>netty-bom</artifactId>
|
||||
<version>${netty}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
<dependencies>
|
||||
|
||||
<dependency>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<artifactId>spring-data-commons</artifactId>
|
||||
<version>${springdata.commons}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<artifactId>spring-data-relational</artifactId>
|
||||
<version>${springdata.relational}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-r2dbc</artifactId>
|
||||
</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>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework</groupId>
|
||||
<artifactId>spring-core</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.r2dbc</groupId>
|
||||
<artifactId>r2dbc-spi</artifactId>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.projectreactor</groupId>
|
||||
<artifactId>reactor-core</artifactId>
|
||||
</dependency>
|
||||
|
||||
<!-- Kotlin extension -->
|
||||
<dependency>
|
||||
<groupId>org.jetbrains.kotlin</groupId>
|
||||
<artifactId>kotlin-stdlib</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.jetbrains.kotlin</groupId>
|
||||
<artifactId>kotlin-reflect</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.jetbrains.kotlinx</groupId>
|
||||
<artifactId>kotlinx-coroutines-core</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.jetbrains.kotlinx</groupId>
|
||||
<artifactId>kotlinx-coroutines-reactor</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.assertj</groupId>
|
||||
<artifactId>assertj-core</artifactId>
|
||||
<version>${assertj}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.projectreactor</groupId>
|
||||
<artifactId>reactor-test</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- JDBC Drivers -->
|
||||
|
||||
<dependency>
|
||||
<groupId>org.postgresql</groupId>
|
||||
<artifactId>postgresql</artifactId>
|
||||
<version>${postgresql.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>mysql</groupId>
|
||||
<artifactId>mysql-connector-java</artifactId>
|
||||
<version>${mysql.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.mariadb.jdbc</groupId>
|
||||
<artifactId>mariadb-java-client</artifactId>
|
||||
<version>${mariadb-jdbc.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.microsoft.sqlserver</groupId>
|
||||
<artifactId>mssql-jdbc</artifactId>
|
||||
<version>${mssql-jdbc.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.oracle.database.jdbc</groupId>
|
||||
<artifactId>ojdbc11</artifactId>
|
||||
<version>21.4.0.0.1</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- R2DBC Drivers -->
|
||||
|
||||
<dependency>
|
||||
<groupId>org.postgresql</groupId>
|
||||
<artifactId>r2dbc-postgresql</artifactId>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.r2dbc</groupId>
|
||||
<artifactId>r2dbc-h2</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.r2dbc</groupId>
|
||||
<artifactId>r2dbc-mssql</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.mariadb</groupId>
|
||||
<artifactId>r2dbc-mariadb</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.r2dbc</groupId>
|
||||
<artifactId>r2dbc-spi-test</artifactId>
|
||||
<version>${r2dbc-spi-test.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<!-- Testcontainers -->
|
||||
|
||||
<dependency>
|
||||
<groupId>org.testcontainers</groupId>
|
||||
<artifactId>mysql</artifactId>
|
||||
<scope>test</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>jcl-over-slf4j</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.testcontainers</groupId>
|
||||
<artifactId>mariadb</artifactId>
|
||||
<scope>test</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>jcl-over-slf4j</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.testcontainers</groupId>
|
||||
<artifactId>oracle-xe</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.testcontainers</groupId>
|
||||
<artifactId>postgresql</artifactId>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>de.schauderhaft.degraph</groupId>
|
||||
<artifactId>degraph-check</artifactId>
|
||||
<version>${degraph-check.version}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.mockk</groupId>
|
||||
<artifactId>mockk</artifactId>
|
||||
<version>${mockk}</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>org.awaitility</groupId>
|
||||
<artifactId>awaitility</artifactId>
|
||||
<version>4.0.3</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
||||
<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-javadoc-plugin</artifactId>
|
||||
<configuration>
|
||||
<links>
|
||||
<link>https://docs.spring.io/spring/docs/${spring}/javadoc-api/
|
||||
</link>
|
||||
<link>
|
||||
https://docs.spring.io/spring-data/commons/docs/current/api/
|
||||
</link>
|
||||
<link>https://docs.oracle.com/javase/8/docs/api/</link>
|
||||
<link>https://r2dbc.io/spec/0.8.0.RELEASE/api/</link>
|
||||
</links>
|
||||
</configuration>
|
||||
</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.asciidoctor</groupId>
|
||||
<artifactId>asciidoctor-maven-plugin</artifactId>
|
||||
<configuration>
|
||||
<sourceDirectory>${project.root}/src/main/asciidoc</sourceDirectory>
|
||||
<sourceDocumentName>index.adoc</sourceDocumentName>
|
||||
<doctype>book</doctype>
|
||||
<attributes>
|
||||
<version>${project.version}</version>
|
||||
<projectName>${project.name}</projectName>
|
||||
<projectVersion>${project.version}</projectVersion>
|
||||
<aspectjVersion>${aspectj}</aspectjVersion>
|
||||
<querydslVersion>${querydsl}</querydslVersion>
|
||||
<springVersion>${spring}</springVersion>
|
||||
<r2dbcVersion>${r2dbc-releasetrain.version}</r2dbcVersion>
|
||||
<reactiveStreamsVersion>${reactive-streams.version}
|
||||
</reactiveStreamsVersion>
|
||||
<releasetrainVersion>${releasetrain}</releasetrainVersion>
|
||||
<allow-uri-read>true</allow-uri-read>
|
||||
<toclevels>3</toclevels>
|
||||
<numbered>true</numbered>
|
||||
</attributes>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.codehaus.mojo</groupId>
|
||||
<artifactId>flatten-maven-plugin</artifactId>
|
||||
<version>1.1.0</version>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>flatten</id>
|
||||
<phase>process-resources</phase>
|
||||
<goals>
|
||||
<goal>flatten</goal>
|
||||
</goals>
|
||||
<configuration>
|
||||
<updatePomFile>true</updatePomFile>
|
||||
<flattenMode>oss</flattenMode>
|
||||
<pomElements>
|
||||
<pluginManagement>keep</pluginManagement>
|
||||
<properties>keep</properties>
|
||||
<parent>expand</parent>
|
||||
<repositories>remove</repositories>
|
||||
</pomElements>
|
||||
</configuration>
|
||||
</execution>
|
||||
<execution>
|
||||
<id>flatten-clean</id>
|
||||
<phase>clean</phase>
|
||||
<goals>
|
||||
<goal>clean</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<profiles>
|
||||
<profile>
|
||||
<id>no-jacoco</id>
|
||||
<build>
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.jacoco</groupId>
|
||||
<artifactId>jacoco-maven-plugin</artifactId>
|
||||
<executions>
|
||||
<execution>
|
||||
<id>jacoco-initialize</id>
|
||||
<phase>none</phase>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
</profile>
|
||||
|
||||
<profile>
|
||||
<id>java11</id>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.oracle.database.r2dbc</groupId>
|
||||
<artifactId>oracle-r2dbc</artifactId>
|
||||
<version>0.1.0</version>
|
||||
<scope>test</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
</profile>
|
||||
</profiles>
|
||||
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>spring-libs-snapshot</id>
|
||||
<url>https://repo.spring.io/libs-snapshot</url>
|
||||
</repository>
|
||||
<repository>
|
||||
<id>oss-sonatype-snapshots</id>
|
||||
<url>https://oss.sonatype.org/content/repositories/snapshots/</url>
|
||||
<snapshots>
|
||||
<enabled>true</enabled>
|
||||
</snapshots>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<pluginRepositories>
|
||||
<pluginRepository>
|
||||
<id>spring-plugins-release</id>
|
||||
<url>https://repo.spring.io/plugins-release</url>
|
||||
</pluginRepository>
|
||||
</pluginRepositories>
|
||||
|
||||
</project>
|
||||
29
settings.xml
29
settings.xml
@@ -1,29 +0,0 @@
|
||||
<settings xmlns="http://maven.apache.org/SETTINGS/1.0.0"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/SETTINGS/1.0.0
|
||||
https://maven.apache.org/xsd/settings-1.0.0.xsd">
|
||||
|
||||
<servers>
|
||||
<server>
|
||||
<id>spring-plugins-release</id>
|
||||
<username>${env.ARTIFACTORY_USR}</username>
|
||||
<password>${env.ARTIFACTORY_PSW}</password>
|
||||
</server>
|
||||
<server>
|
||||
<id>spring-libs-snapshot</id>
|
||||
<username>${env.ARTIFACTORY_USR}</username>
|
||||
<password>${env.ARTIFACTORY_PSW}</password>
|
||||
</server>
|
||||
<server>
|
||||
<id>spring-libs-milestone</id>
|
||||
<username>${env.ARTIFACTORY_USR}</username>
|
||||
<password>${env.ARTIFACTORY_PSW}</password>
|
||||
</server>
|
||||
<server>
|
||||
<id>spring-libs-release</id>
|
||||
<username>${env.ARTIFACTORY_USR}</username>
|
||||
<password>${env.ARTIFACTORY_PSW}</password>
|
||||
</server>
|
||||
</servers>
|
||||
|
||||
</settings>
|
||||
@@ -1,53 +0,0 @@
|
||||
= Spring Data R2DBC - Reference Documentation
|
||||
Mark Paluch, Jay Bryant, Stephen Cohen
|
||||
:revnumber: {version}
|
||||
:revdate: {localdate}
|
||||
ifdef::backend-epub3[:front-cover-image: image:epub-cover.png[Front Cover,1050,1600]]
|
||||
:spring-data-commons-docs: ../../../../spring-data-commons/src/main/asciidoc
|
||||
:spring-data-r2dbc-javadoc: https://docs.spring.io/spring-data/r2dbc/docs/{version}/api
|
||||
:spring-framework-ref: https://docs.spring.io/spring/docs/{springVersion}/reference/html
|
||||
:reactiveStreamsJavadoc: https://www.reactive-streams.org/reactive-streams-{reactiveStreamsVersion}-javadoc
|
||||
:example-root: ../../../src/test/java/org/springframework/data/r2dbc/documentation
|
||||
:tabsize: 2
|
||||
|
||||
(C) 2018-2022 The original authors.
|
||||
|
||||
NOTE: Copies of this document may be made for your own use and for distribution to others, provided that you do not charge any fee for such copies and further provided that each copy contains this Copyright Notice, whether distributed in print or electronically.
|
||||
|
||||
toc::[]
|
||||
|
||||
// The blank line before each include prevents content from running together in a bad way
|
||||
// (because an included bit does not have its own blank lines).
|
||||
|
||||
include::preface.adoc[]
|
||||
|
||||
include::new-features.adoc[leveloffset=+1]
|
||||
|
||||
include::{spring-data-commons-docs}/dependencies.adoc[leveloffset=+1]
|
||||
|
||||
include::{spring-data-commons-docs}/repositories.adoc[leveloffset=+1]
|
||||
|
||||
[[reference]]
|
||||
= Reference Documentation
|
||||
|
||||
include::reference/introduction.adoc[leveloffset=+1]
|
||||
|
||||
include::reference/r2dbc.adoc[leveloffset=+1]
|
||||
|
||||
include::reference/r2dbc-repositories.adoc[leveloffset=+1]
|
||||
|
||||
include::{spring-data-commons-docs}/auditing.adoc[leveloffset=+1]
|
||||
|
||||
include::reference/r2dbc-auditing.adoc[leveloffset=+1]
|
||||
|
||||
include::reference/mapping.adoc[leveloffset=+1]
|
||||
|
||||
include::reference/kotlin.adoc[leveloffset=+1]
|
||||
|
||||
[[appendix]]
|
||||
= Appendix
|
||||
|
||||
:numbered!:
|
||||
include::{spring-data-commons-docs}/repository-query-keywords-reference.adoc[leveloffset=+1]
|
||||
include::{spring-data-commons-docs}/repository-query-return-types-reference.adoc[leveloffset=+1]
|
||||
include::reference/r2dbc-upgrading.adoc[leveloffset=+1]
|
||||
@@ -1,45 +0,0 @@
|
||||
[[new-features]]
|
||||
= New & Noteworthy
|
||||
|
||||
[[new-features.1-3-0]]
|
||||
== What's New in Spring Data R2DBC 1.3.0
|
||||
|
||||
* Introduce <<r2dbc.repositories.queries.query-by-example,Query by Example support>>.
|
||||
|
||||
[[new-features.1-2-0]]
|
||||
== What's New in Spring Data R2DBC 1.2.0
|
||||
|
||||
* Deprecate Spring Data R2DBC `DatabaseClient` and move off deprecated API in favor of Spring R2DBC.
|
||||
Consult the <<upgrading.1.1-1.2,Migration Guide>> for further details.
|
||||
* Support for <<entity-callbacks>>.
|
||||
* <<r2dbc.auditing,Auditing>> through `@EnableR2dbcAuditing`.
|
||||
* Support for `@Value` in persistence constructors.
|
||||
* Support for Oracle's R2DBC driver.
|
||||
|
||||
[[new-features.1-1-0]]
|
||||
== What's New in Spring Data R2DBC 1.1.0
|
||||
|
||||
* Introduction of `R2dbcEntityTemplate` for entity-oriented operations.
|
||||
* <<r2dbc.repositories.queries,Query derivation>>.
|
||||
* Support interface projections with `DatabaseClient.as(…)`.
|
||||
* <<r2dbc.datbaseclient.filter,Support for `ExecuteFunction` and `StatementFilterFunction` via `DatabaseClient.filter(…)`>>.
|
||||
|
||||
[[new-features.1-0-0]]
|
||||
== What's New in Spring Data R2DBC 1.0.0
|
||||
|
||||
* Upgrade to R2DBC 0.8.0.RELEASE.
|
||||
* `@Modifying` annotation for query methods to consume affected row count.
|
||||
* Repository `save(…)` with an associated ID completes with `TransientDataAccessException` if the row does not exist in the database.
|
||||
* Added `SingleConnectionConnectionFactory` for testing using connection singletons.
|
||||
* Support for {spring-framework-ref}/core.html#expressions[SpEL expressions] in `@Query`.
|
||||
* `ConnectionFactory` routing through `AbstractRoutingConnectionFactory`.
|
||||
* Utilities for schema initialization through `ResourceDatabasePopulator` and `ScriptUtils`.
|
||||
* Propagation and reset of Auto-Commit and Isolation Level control through `TransactionDefinition`.
|
||||
* Support for Entity-level converters.
|
||||
* Kotlin extensions for reified generics and <<kotlin.coroutines,Coroutines>>.
|
||||
* Add pluggable mechanism to register dialects.
|
||||
* Support for named parameters.
|
||||
* Initial R2DBC support through `DatabaseClient`.
|
||||
* Initial Transaction support through `TransactionalDatabaseClient`.
|
||||
* Initial R2DBC Repository Support through `R2dbcRepository`.
|
||||
* Initial Dialect support for Postgres and Microsoft SQL Server.
|
||||
@@ -1,122 +0,0 @@
|
||||
[[preface]]
|
||||
= Preface
|
||||
|
||||
The Spring Data R2DBC project applies core Spring concepts to the development of solutions that use the https://r2dbc.io[R2DBC] drivers for relational databases.
|
||||
We provide a `DatabaseClient` as a high-level abstraction for storing and querying rows.
|
||||
|
||||
This document is the reference guide for Spring Data - R2DBC Support.
|
||||
It explains R2DBC module concepts and semantics.
|
||||
|
||||
This section provides some basic introduction to Spring and databases.
|
||||
[[get-started:first-steps:spring]]
|
||||
== Learning Spring
|
||||
|
||||
Spring Data uses Spring framework's {spring-framework-ref}/core.html[core] functionality, including:
|
||||
|
||||
* {spring-framework-ref}/core.html#beans[IoC] container
|
||||
* {spring-framework-ref}/core.html#validation[type conversion system]
|
||||
* {spring-framework-ref}/core.html#expressions[expression language]
|
||||
* {spring-framework-ref}/integration.html#jmx[JMX integration]
|
||||
* {spring-framework-ref}/data-access.html#dao-exceptions[DAO exception hierarchy].
|
||||
|
||||
While you need not know the Spring APIs, understanding the concepts behind them is important.
|
||||
At a minimum, the idea behind Inversion of Control (IoC) should be familiar, and you should be familiar with whatever IoC container you choose to use.
|
||||
|
||||
You can use the core functionality of the R2DBC support directly, with no need to invoke the IoC services of the Spring Container.
|
||||
This is much like `JdbcTemplate`, which can be used "`standalone`" without any other services of the Spring container.
|
||||
To use all the features of Spring Data R2DBC, such as the repository support, you need to configure some parts of the library to use Spring.
|
||||
|
||||
To learn more about Spring, refer to the comprehensive documentation that explains the Spring Framework in detail.
|
||||
There are a lot of articles, blog entries, and books on the subject.
|
||||
See the Spring framework https://spring.io/docs[home page] for more information.
|
||||
|
||||
[[get-started:first-steps:what]]
|
||||
== What is R2DBC?
|
||||
|
||||
https://r2dbc.io[R2DBC] is the acronym for Reactive Relational Database Connectivity.
|
||||
R2DBC is an API specification initiative that declares a reactive API to be implemented by driver vendors to access their relational databases.
|
||||
|
||||
Part of the answer as to why R2DBC was created is the need for a non-blocking application stack to handle concurrency with a small number of threads and scale with fewer hardware resources.
|
||||
This need cannot be satisfied by reusing standardized relational database access APIs -- namely JDBC –- as JDBC is a fully blocking API.
|
||||
Attempts to compensate for blocking behavior with a `ThreadPool` are of limited use.
|
||||
|
||||
The other part of the answer is that most applications use a relational database to store their data.
|
||||
While several NoSQL database vendors provide reactive database clients for their databases, migration to NoSQL is not an option for most projects.
|
||||
This was the motivation for a new common API to serve as a foundation for any non-blocking database driver.
|
||||
While the open source ecosystem hosts various non-blocking relational database driver implementations, each client comes with a vendor-specific API, so a generic layer on top of these libraries is not possible.
|
||||
|
||||
[[get-started:first-steps:reactive]]
|
||||
== What is Reactive?
|
||||
|
||||
The term, "`reactive`", refers to programming models that are built around reacting to change, availability, and processability -— network components reacting to I/O events, UI controllers reacting to mouse events, resources being made available, and others.
|
||||
In that sense, non-blocking is reactive, because, instead of being blocked, we are now in the mode of reacting to notifications as operations complete or data becomes available.
|
||||
|
||||
There is also another important mechanism that we on the Spring team associate with reactive, and that is non-blocking back pressure.
|
||||
In synchronous, imperative code, blocking calls serve as a natural form of back pressure that forces the caller to wait.
|
||||
In non-blocking code, it becomes essential to control the rate of events so that a fast producer does not overwhelm its destination.
|
||||
|
||||
https://github.com/reactive-streams/reactive-streams-jvm/blob/v{reactiveStreamsVersion}/README.md#specification[Reactive Streams is a small spec] (also https://docs.oracle.com/javase/9/docs/api/java/util/concurrent/Flow.html[adopted in Java 9]) that defines the interaction between asynchronous components with back pressure.
|
||||
For example, a data repository (acting as a {reactiveStreamsJavadoc}/org/reactivestreams/Publisher.html[`Publisher`]) can produce data that an HTTP server (acting as a {reactiveStreamsJavadoc}/org/reactivestreams/Subscriber.html`[`Subscriber`]) can then write to the response.
|
||||
The main purpose of Reactive Streams is to let the subscriber control how quickly or how slowly the publisher produces data.
|
||||
|
||||
[[get-started:first-steps:reactive-api]]
|
||||
== Reactive API
|
||||
|
||||
Reactive Streams plays an important role for interoperability. It is of interest to libraries and infrastructure components but less useful as an application API, because it is too low-level.
|
||||
Applications need a higher-level and richer, functional API to compose async logic —- similar to the Java 8 Stream API but not only for tables.
|
||||
This is the role that reactive libraries play.
|
||||
|
||||
https://github.com/reactor/reactor[Project Reactor] is the reactive library of choice for Spring Data R2DBC.
|
||||
It provides the https://projectreactor.io/docs/core/release/api/reactor/core/publisher/Mono.html[`Mono`] and https://projectreactor.io/docs/core/release/api/reactor/core/publisher/Flux.html[`Flux`] API types to work on data sequences of `0..1` (`Mono`) and `0..N` (`Flux`) through a rich set of operators aligned with the ReactiveX vocabulary of operators.
|
||||
Reactor is a Reactive Streams library, and, therefore, all of its operators support non-blocking back pressure.
|
||||
Reactor has a strong focus on server-side Java. It is developed in close collaboration with Spring.
|
||||
|
||||
Spring Data R2DBC requires Project Reactor as a core dependency, but it is interoperable with other reactive libraries through the Reactive Streams specification.
|
||||
As a general rule, a Spring Data R2DBC repository accepts a plain `Publisher` as input, adapts it to a Reactor type internally, uses that, and returns either a `Mono` or a `Flux` as output.
|
||||
So, you can pass any `Publisher` as input and apply operations on the output, but you need to adapt the output for use with another reactive library.
|
||||
Whenever feasible, Spring Data adapts transparently to the use of RxJava or another reactive library.
|
||||
|
||||
[[requirements]]
|
||||
== Requirements
|
||||
|
||||
The Spring Data R2DBC 1.x binaries require:
|
||||
|
||||
* JDK level 8.0 and above
|
||||
* https://spring.io/docs[Spring Framework] {springVersion} and above
|
||||
* https://r2dbc.io[R2DBC] {r2dbcVersion} and above
|
||||
|
||||
[[get-started:help]]
|
||||
== Additional Help Resources
|
||||
|
||||
Learning a new framework is not always straightforward.
|
||||
In this section, we try to provide what we think is an easy-to-follow guide for starting with the Spring Data R2DBC module.
|
||||
However, if you encounter issues or you need advice, use one of the following links:
|
||||
|
||||
[[get-started:help:community]]
|
||||
Community Forum :: Spring Data on https://stackoverflow.com/questions/tagged/spring-data[Stack Overflow] is a tag for all Spring Data (not just R2DBC) users to share information and help each other.
|
||||
Note that registration is needed only for posting.
|
||||
|
||||
[[get-started:help:professional]]
|
||||
Professional Support :: Professional, from-the-source support, with guaranteed response time, is available from https://pivotal.io/[Pivotal Sofware, Inc.], the company behind Spring Data and Spring.
|
||||
|
||||
[[get-started:up-to-date]]
|
||||
== Following Development
|
||||
|
||||
* For information on the Spring Data R2DBC source code repository, nightly builds, and snapshot artifacts, see the Spring Data R2DBC https://projects.spring.io/spring-data-r2dbc/[home page].
|
||||
|
||||
* You can help make Spring Data best serve the needs of the Spring community by interacting with developers through the community on https://stackoverflow.com/questions/tagged/spring-data[Stack Overflow].
|
||||
|
||||
* If you encounter a bug or want to suggest an improvement, please create a ticket on the Spring Data R2DBC https://github.com/spring-projects/spring-data-r2dbc/issues[issue tracker].
|
||||
|
||||
* To stay up to date with the latest news and announcements in the Spring ecosystem, subscribe to the Spring Community https://spring.io[Portal].
|
||||
|
||||
* You can also follow the Spring https://spring.io/blog[blog] or the Spring Data project team on Twitter (https://twitter.com/SpringData[SpringData]).
|
||||
|
||||
[[project-metadata]]
|
||||
== Project Metadata
|
||||
|
||||
* Version control: https://github.com/spring-projects/spring-data-r2dbc
|
||||
* Bugtracker: https://github.com/spring-projects/spring-data-r2dbc/issues
|
||||
* Release repository: https://repo.spring.io/libs-release
|
||||
* Milestone repository: https://repo.spring.io/libs-milestone
|
||||
* Snapshot repository: https://repo.spring.io/libs-snapshot
|
||||
@@ -1,10 +0,0 @@
|
||||
[[introduction]]
|
||||
= Introduction
|
||||
|
||||
== Document Structure
|
||||
|
||||
This part of the reference documentation explains the core functionality offered by Spring Data R2DBC.
|
||||
|
||||
"`<<r2dbc.core>>`" introduces the R2DBC module feature set.
|
||||
|
||||
"`<<r2dbc.repositories>>`" introduces the repository support for R2DBC.
|
||||
@@ -1,28 +0,0 @@
|
||||
include::../{spring-data-commons-docs}/kotlin.adoc[]
|
||||
|
||||
include::../{spring-data-commons-docs}/kotlin-extensions.adoc[leveloffset=+1]
|
||||
|
||||
To retrieve a list of `SWCharacter` objects in Java, you would normally write the following:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
Flux<SWCharacter> characters = client.select().from(SWCharacter.class).fetch().all();
|
||||
----
|
||||
|
||||
With Kotlin and the Spring Data extensions, you can instead write the following:
|
||||
|
||||
[source,kotlin]
|
||||
----
|
||||
val characters = client.select().from<SWCharacter>().fetch().all()
|
||||
// or (both are equivalent)
|
||||
val characters : Flux<SWCharacter> = client.select().from().fetch().all()
|
||||
----
|
||||
|
||||
As in Java, `characters` in Kotlin is strongly typed, but Kotlin's clever type inference allows for shorter syntax.
|
||||
|
||||
Spring Data R2DBC provides the following extensions:
|
||||
|
||||
* Reified generics support for `DatabaseClient` and `Criteria`.
|
||||
* <<kotlin.coroutines>> extensions for `DatabaseClient`.
|
||||
|
||||
include::../{spring-data-commons-docs}/kotlin-coroutines.adoc[leveloffset=+1]
|
||||
@@ -1,324 +0,0 @@
|
||||
[[mapping]]
|
||||
= Mapping
|
||||
|
||||
Rich mapping support is provided by the `MappingR2dbcConverter`. `MappingR2dbcConverter` has a rich metadata model that allows mapping domain objects to a data row.
|
||||
The mapping metadata model is populated by using annotations on your domain objects.
|
||||
However, the infrastructure is not limited to using annotations as the only source of metadata information.
|
||||
The `MappingR2dbcConverter` also lets you map objects to rows without providing any additional metadata, by following a set of conventions.
|
||||
|
||||
This section describes the features of the `MappingR2dbcConverter`, including how to use conventions for mapping objects to rows and how to override those conventions with annotation-based mapping metadata.
|
||||
|
||||
include::../{spring-data-commons-docs}/object-mapping.adoc[leveloffset=+1]
|
||||
|
||||
[[mapping.conventions]]
|
||||
== Convention-based Mapping
|
||||
|
||||
`MappingR2dbcConverter` has a few conventions for mapping objects to rows when no additional mapping metadata is provided.
|
||||
The conventions are:
|
||||
|
||||
* The short Java class name is mapped to the table name in the following manner.
|
||||
The `com.bigbank.SavingsAccount` class maps to the `SAVINGS_ACCOUNT` table name.
|
||||
The same name mapping is applied for mapping fields to column names.
|
||||
For example, the `firstName` field maps to the `FIRST_NAME` column.
|
||||
You can control this mapping by providing a custom `NamingStrategy`. See <<mapping.configuration>> for more detail.
|
||||
Table and column names that are derived from property or class names are used in SQL statements without quotes by default.
|
||||
You can control this behavior by setting `R2dbcMappingContext.setForceQuote(true)`.
|
||||
|
||||
* Nested objects are not supported.
|
||||
|
||||
* The converter uses any Spring Converters registered with it to override the default mapping of object properties to row columns and values.
|
||||
|
||||
* The fields of an object are used to convert to and from columns in the row.
|
||||
Public `JavaBean` properties are not used.
|
||||
|
||||
* If you have a single non-zero-argument constructor whose constructor argument names match top-level column names of the row, that constructor is used.
|
||||
Otherwise, the zero-argument constructor is used.
|
||||
If there is more than one non-zero-argument constructor, an exception is thrown.
|
||||
|
||||
[[mapping.configuration]]
|
||||
== Mapping Configuration
|
||||
|
||||
By default (unless explicitly configured) an instance of `MappingR2dbcConverter` is created when you create a `DatabaseClient`.
|
||||
You can create your own instance of the `MappingR2dbcConverter`.
|
||||
By creating your own instance, you can register Spring converters to map specific classes to and from the database.
|
||||
|
||||
You can configure the `MappingR2dbcConverter` as well as `DatabaseClient` and `ConnectionFactory` by using Java-based metadata. The following example uses Spring's Java-based configuration:
|
||||
|
||||
If you set `setForceQuote` of the `R2dbcMappingContext to` true, table and column names derived from classes and properties are used with database specific quotes.
|
||||
This means that it is OK to use reserved SQL words (such as order) in these names.
|
||||
You can do so by overriding `r2dbcMappingContext(Optional<NamingStrategy>)` of `AbstractR2dbcConfiguration`.
|
||||
Spring Data converts the letter casing of such a name to that form which is also used by the configured database when no quoting is used.
|
||||
Therefore, you can use unquoted names when creating tables, as long as you do not use keywords or special characters in your names.
|
||||
For databases that adhere to the SQL standard, this means that names are converted to upper case.
|
||||
The quoting character and the way names get capitalized is controlled by the used `Dialect`.
|
||||
See <<r2dbc.drivers>> for how to configure custom dialects.
|
||||
|
||||
.@Configuration class to configure R2DBC mapping support
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
@Configuration
|
||||
public class MyAppConfig extends AbstractR2dbcConfiguration {
|
||||
|
||||
public ConnectionFactory connectionFactory() {
|
||||
return ConnectionFactories.get("r2dbc:…");
|
||||
}
|
||||
|
||||
// the following are optional
|
||||
|
||||
@Override
|
||||
protected List<Object> getCustomConverters() {
|
||||
|
||||
List<Converter<?, ?>> converterList = new ArrayList<Converter<?, ?>>();
|
||||
converterList.add(new org.springframework.data.r2dbc.test.PersonReadConverter());
|
||||
converterList.add(new org.springframework.data.r2dbc.test.PersonWriteConverter());
|
||||
return converterList;
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
`AbstractR2dbcConfiguration` requires you to implement a method that defines a `ConnectionFactory`.
|
||||
|
||||
You can add additional converters to the converter by overriding the `r2dbcCustomConversions` method.
|
||||
|
||||
You can configure a custom `NamingStrategy` by registering it as a bean.
|
||||
The `NamingStrategy` controls how the names of classes and properties get converted to the names of tables and columns.
|
||||
|
||||
NOTE: `AbstractR2dbcConfiguration` creates a `DatabaseClient` instance and registers it with the container under the name of `databaseClient`.
|
||||
|
||||
[[mapping.usage]]
|
||||
== Metadata-based Mapping
|
||||
|
||||
To take full advantage of the object mapping functionality inside the Spring Data R2DBC support, you should annotate your mapped objects with the `@Table` annotation.
|
||||
Although it is not necessary for the mapping framework to have this annotation (your POJOs are mapped correctly, even without any annotations), it lets the classpath scanner find and pre-process your domain objects to extract the necessary metadata.
|
||||
If you do not use this annotation, your application takes a slight performance hit the first time you store a domain object, because the mapping framework needs to build up its internal metadata model so that it knows about the properties of your domain object and how to persist them.
|
||||
The following example shows a domain object:
|
||||
|
||||
.Example domain object
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
package com.mycompany.domain;
|
||||
|
||||
@Table
|
||||
public class Person {
|
||||
|
||||
@Id
|
||||
private Long id;
|
||||
|
||||
private Integer ssn;
|
||||
|
||||
private String firstName;
|
||||
|
||||
private String lastName;
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
IMPORTANT: The `@Id` annotation tells the mapper which property you want to use as the primary key.
|
||||
|
||||
[[mapping.types]]
|
||||
=== Default Type Mapping
|
||||
|
||||
The following table explains how property types of an entity affect mapping:
|
||||
|
||||
|===
|
||||
|Source Type | Target Type | Remarks
|
||||
|
||||
|Primitive types and wrapper types
|
||||
|Passthru
|
||||
|Can be customized using <<mapping.explicit.converters, Explicit Converters>>.
|
||||
|
||||
|JSR-310 Date/Time types
|
||||
|Passthru
|
||||
|Can be customized using <<mapping.explicit.converters, Explicit Converters>>.
|
||||
|
||||
|
||||
|`String`, `BigInteger`, `BigDecimal`, and `UUID`
|
||||
|Passthru
|
||||
|Can be customized using <<mapping.explicit.converters, Explicit Converters>>.
|
||||
|
||||
|`Enum`
|
||||
|String
|
||||
|Can be customized by registering a <<mapping.explicit.converters, Explicit Converters>>.
|
||||
|
||||
|`Blob` and `Clob`
|
||||
|Passthru
|
||||
|Can be customized using <<mapping.explicit.converters, Explicit Converters>>.
|
||||
|
||||
|`byte[]`, `ByteBuffer`
|
||||
|Passthru
|
||||
|Considered a binary payload.
|
||||
|
||||
|`Collection<T>`
|
||||
|Array of `T`
|
||||
|Conversion to Array type if supported by the configured <<r2dbc.drivers, driver>>, not supported otherwise.
|
||||
|
||||
|Arrays of primitive types, wrapper types and `String`
|
||||
|Array of wrapper type (e.g. `int[]` -> `Integer[]`)
|
||||
|Conversion to Array type if supported by the configured <<r2dbc.drivers, driver>>, not supported otherwise.
|
||||
|
||||
|Driver-specific types
|
||||
|Passthru
|
||||
|Contributed as a simple type by the used `R2dbcDialect`.
|
||||
|
||||
|Complex objects
|
||||
|Target type depends on registered `Converter`.
|
||||
|Requires a <<mapping.explicit.converters, Explicit Converters>>, not supported otherwise.
|
||||
|
||||
|===
|
||||
|
||||
NOTE: The native data type for a column depends on the R2DBC driver type mapping.
|
||||
Drivers can contribute additional simple types such as Geometry types.
|
||||
|
||||
[[mapping.usage.annotations]]
|
||||
=== Mapping Annotation Overview
|
||||
|
||||
The `MappingR2dbcConverter` can use metadata to drive the mapping of objects to rows.
|
||||
The following annotations are available:
|
||||
|
||||
* `@Id`: Applied at the field level to mark the primary key.
|
||||
* `@Table`: Applied at the class level to indicate this class is a candidate for mapping to the database.
|
||||
You can specify the name of the table where the database is stored.
|
||||
* `@Transient`: By default, all fields are mapped to the row.
|
||||
This annotation excludes the field where it is applied from being stored in the database.
|
||||
Transient properties cannot be used within a persistence constructor as the converter cannot materialize a value for the constructor argument.
|
||||
* `@PersistenceConstructor`: Marks a given constructor -- even a package protected one -- to use when instantiating the object from the database.
|
||||
Constructor arguments are mapped by name to the values in the retrieved row.
|
||||
* `@Value`: This annotation is part of the Spring Framework.
|
||||
Within the mapping framework it can be applied to constructor arguments.
|
||||
This lets you use a Spring Expression Language statement to transform a key’s value retrieved in the database before it is used to construct a domain object.
|
||||
In order to reference a column of a given row one has to use expressions like: `@Value("#root.myProperty")` where root refers to the root of the given `Row`.
|
||||
* `@Column`: Applied at the field level to describe the name of the column as it is represented in the row, letting the name be different from the field name of the class.
|
||||
Names specified with a `@Column` annotation are always quoted when used in SQL statements.
|
||||
For most databases, this means that these names are case-sensitive.
|
||||
It also means that you can use special characters in these names.
|
||||
However, this is not recommended, since it may cause problems with other tools.
|
||||
* `@Version`: Applied at field level is used for optimistic locking and checked for modification on save operations.
|
||||
The value is `null` (`zero` for primitive types) is considered as marker for entities to be new.
|
||||
The initially stored value is `zero` (`one` for primitive types).
|
||||
The version gets incremented automatically on every update.
|
||||
See <<r2dbc.optimistic-locking>> for further reference.
|
||||
|
||||
The mapping metadata infrastructure is defined in the separate `spring-data-commons` project that is technology-agnostic.
|
||||
Specific subclasses are used in the R2DBC support to support annotation based metadata.
|
||||
Other strategies can also be put in place (if there is demand).
|
||||
|
||||
[[mapping.custom.object.construction]]
|
||||
=== Customized Object Construction
|
||||
|
||||
The mapping subsystem allows the customization of the object construction by annotating a constructor with the `@PersistenceConstructor` annotation.The values to be used for the constructor parameters are resolved in the following way:
|
||||
|
||||
* If a parameter is annotated with the `@Value` annotation, the given expression is evaluated, and the result is used as the parameter value.
|
||||
* If the Java type has a property whose name matches the given field of the input row, then its property information is used to select the appropriate constructor parameter to which to pass the input field value.
|
||||
This works only if the parameter name information is present in the Java `.class` files, which you can achieve by compiling the source with debug information or using the `-parameters` command-line switch for `javac` in Java 8.
|
||||
* Otherwise, a `MappingException` is thrown to indicate that the given constructor parameter could not be bound.
|
||||
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
class OrderItem {
|
||||
|
||||
private @Id final String id;
|
||||
private final int quantity;
|
||||
private final double unitPrice;
|
||||
|
||||
OrderItem(String id, int quantity, double unitPrice) {
|
||||
this.id = id;
|
||||
this.quantity = quantity;
|
||||
this.unitPrice = unitPrice;
|
||||
}
|
||||
|
||||
// getters/setters ommitted
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
[[mapping.explicit.converters]]
|
||||
=== Overriding Mapping with Explicit Converters
|
||||
|
||||
When storing and querying your objects, it is often convenient to have a `R2dbcConverter` instance to handle the mapping of all Java types to `OutboundRow` instances.
|
||||
However, you may sometimes want the `R2dbcConverter` instances to do most of the work but let you selectively handle the conversion for a particular type -- perhaps to optimize performance.
|
||||
|
||||
To selectively handle the conversion yourself, register one or more one or more `org.springframework.core.convert.converter.Converter` instances with the `R2dbcConverter`.
|
||||
|
||||
You can use the `r2dbcCustomConversions` method in `AbstractR2dbcConfiguration` to configure converters.
|
||||
The examples <<mapping.configuration, at the beginning of this chapter>> show how to perform the configuration with Java.
|
||||
|
||||
NOTE: Custom top-level entity conversion requires asymmetric types for conversion.
|
||||
Inbound data is extracted from R2DBC's `Row`.
|
||||
Outbound data (to be used with `INSERT`/`UPDATE` statements) is represented as `OutboundRow` and later assembled to a statement.
|
||||
|
||||
The following example of a Spring Converter implementation converts from a `Row` to a `Person` POJO:
|
||||
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
@ReadingConverter
|
||||
public class PersonReadConverter implements Converter<Row, Person> {
|
||||
|
||||
public Person convert(Row source) {
|
||||
Person p = new Person(source.get("id", String.class),source.get("name", String.class));
|
||||
p.setAge(source.get("age", Integer.class));
|
||||
return p;
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
Please note that converters get applied on singular properties.
|
||||
Collection properties (e.g. `Collection<Person>`) are iterated and converted element-wise.
|
||||
Collection converters (e.g. `Converter<List<Person>>, OutboundRow`) are not supported.
|
||||
|
||||
NOTE: R2DBC uses boxed primitives (`Integer.class` instead of `int.class`) to return primitive values.
|
||||
|
||||
The following example converts from a `Person` to a `OutboundRow`:
|
||||
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
@WritingConverter
|
||||
public class PersonWriteConverter implements Converter<Person, OutboundRow> {
|
||||
|
||||
public OutboundRow convert(Person source) {
|
||||
OutboundRow row = new OutboundRow();
|
||||
row.put("id", Parameter.from(source.getId()));
|
||||
row.put("name", Parameter.from(source.getFirstName()));
|
||||
row.put("age", Parameter.from(source.getAge()));
|
||||
return row;
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
[[mapping.explicit.enum.converters]]
|
||||
==== Overriding Enum Mapping with Explicit Converters
|
||||
|
||||
Some databases, such as https://github.com/pgjdbc/r2dbc-postgresql#postgres-enum-types[Postgres], can natively write enum values using their database-specific enumerated column type.
|
||||
Spring Data converts `Enum` values by default to `String` values for maximum portability.
|
||||
To retain the actual enum value, register a `@Writing` converter whose source and target types use the actual enum type to avoid using `Enum.name()` conversion.
|
||||
Additionally, you need to configure the enum type on the driver level so that the driver is aware how to represent the enum type.
|
||||
|
||||
The following example shows the involved components to read and write `Color` enum values natively:
|
||||
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
enum Color {
|
||||
Grey, Blue
|
||||
}
|
||||
|
||||
class ColorConverter extends EnumWriteSupport<Color> {
|
||||
|
||||
}
|
||||
|
||||
|
||||
class Product {
|
||||
@Id long id;
|
||||
Color color;
|
||||
|
||||
// …
|
||||
}
|
||||
----
|
||||
====
|
||||
@@ -1,23 +0,0 @@
|
||||
[[r2dbc.auditing]]
|
||||
== General Auditing Configuration for R2DBC
|
||||
|
||||
Since Spring Data R2DBC 1.2, auditing can be enabled by annotating a configuration class with the `@EnableR2dbcAuditing` annotation, as the following example shows:
|
||||
|
||||
.Activating auditing using JavaConfig
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
@Configuration
|
||||
@EnableR2dbcAuditing
|
||||
class Config {
|
||||
|
||||
@Bean
|
||||
public ReactiveAuditorAware<AuditableUser> myAuditorProvider() {
|
||||
return new AuditorAwareImpl();
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
If you expose a bean of type `ReactiveAuditorAware` to the `ApplicationContext`, the auditing infrastructure picks it up automatically and uses it to determine the current user to be set on domain types.
|
||||
If you have multiple implementations registered in the `ApplicationContext`, you can select the one to be used by explicitly setting the `auditorAwareRef` attribute of `@EnableR2dbcAuditing`.
|
||||
@@ -1,201 +0,0 @@
|
||||
R2DBC contains a wide range of features:
|
||||
|
||||
* Spring configuration support with Java-based `@Configuration` classes for an R2DBC driver instance.
|
||||
* `R2dbcEntityTemplate` as central class for entity-bound operations that increases productivity when performing common R2DBC operations with integrated object mapping between rows and POJOs.
|
||||
* Feature-rich object mapping integrated with Spring's Conversion Service.
|
||||
* Annotation-based mapping metadata that is extensible to support other metadata formats.
|
||||
* Automatic implementation of Repository interfaces, including support for custom query methods.
|
||||
|
||||
For most tasks, you should use `R2dbcEntityTemplate` or the repository support, which both use the rich mapping functionality.
|
||||
`R2dbcEntityTemplate` is the place to look for accessing functionality such as ad-hoc CRUD operations.
|
||||
|
||||
[[r2dbc.getting-started]]
|
||||
== Getting Started
|
||||
|
||||
An easy way to set up a working environment is to create a Spring-based project through https://start.spring.io[start.spring.io].
|
||||
To do so:
|
||||
|
||||
. Add the following to the pom.xml files `dependencies` element:
|
||||
+
|
||||
====
|
||||
[source,xml,subs="+attributes"]
|
||||
----
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>io.r2dbc</groupId>
|
||||
<artifactId>r2dbc-bom</artifactId>
|
||||
<version>${r2dbc-releasetrain.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
|
||||
<dependencies>
|
||||
|
||||
<!-- other dependency elements omitted -->
|
||||
|
||||
<dependency>
|
||||
<groupId>org.springframework.data</groupId>
|
||||
<artifactId>spring-data-r2dbc</artifactId>
|
||||
<version>{version}</version>
|
||||
</dependency>
|
||||
|
||||
<!-- a R2DBC driver -->
|
||||
<dependency>
|
||||
<groupId>io.r2dbc</groupId>
|
||||
<artifactId>r2dbc-h2</artifactId>
|
||||
<version>{r2dbcVersion}</version>
|
||||
</dependency>
|
||||
|
||||
</dependencies>
|
||||
----
|
||||
====
|
||||
|
||||
. Change the version of Spring in the pom.xml to be
|
||||
+
|
||||
====
|
||||
[source,xml,subs="+attributes"]
|
||||
----
|
||||
<spring-framework.version>{springVersion}</spring-framework.version>
|
||||
----
|
||||
====
|
||||
|
||||
. Add the following location of the Spring Milestone repository for Maven to your `pom.xml` such that it is at the same level as your `<dependencies/>` element:
|
||||
+
|
||||
====
|
||||
[source,xml]
|
||||
----
|
||||
<repositories>
|
||||
<repository>
|
||||
<id>spring-milestone</id>
|
||||
<name>Spring Maven MILESTONE Repository</name>
|
||||
<url>https://repo.spring.io/libs-milestone</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
----
|
||||
====
|
||||
|
||||
The repository is also https://repo.spring.io/milestone/org/springframework/data/[browseable here].
|
||||
|
||||
You may also want to set the logging level to `DEBUG` to see some additional information.
|
||||
To do so, edit the `application.properties` file to have the following content:
|
||||
|
||||
====
|
||||
[source]
|
||||
----
|
||||
logging.level.org.springframework.r2dbc=DEBUG
|
||||
----
|
||||
====
|
||||
|
||||
Then you can, for example, create a `Person` class to persist, as follows:
|
||||
|
||||
====
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../{example-root}/Person.java[tags=class]
|
||||
----
|
||||
====
|
||||
|
||||
Next, you need to create a table structure in your database, as follows:
|
||||
|
||||
====
|
||||
[source,sql]
|
||||
----
|
||||
CREATE TABLE person
|
||||
(id VARCHAR(255) PRIMARY KEY,
|
||||
name VARCHAR(255),
|
||||
age INT);
|
||||
----
|
||||
====
|
||||
|
||||
You also need a main application to run, as follows:
|
||||
|
||||
|
||||
====
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../{example-root}/R2dbcApp.java[tag=class]
|
||||
----
|
||||
====
|
||||
|
||||
When you run the main program, the preceding examples produce output similar to the following:
|
||||
|
||||
====
|
||||
[source]
|
||||
----
|
||||
2018-11-28 10:47:03,893 DEBUG amework.core.r2dbc.DefaultDatabaseClient: 310 - Executing SQL statement [CREATE TABLE person
|
||||
(id VARCHAR(255) PRIMARY KEY,
|
||||
name VARCHAR(255),
|
||||
age INT)]
|
||||
2018-11-28 10:47:04,074 DEBUG amework.core.r2dbc.DefaultDatabaseClient: 908 - Executing SQL statement [INSERT INTO person (id, name, age) VALUES($1, $2, $3)]
|
||||
2018-11-28 10:47:04,092 DEBUG amework.core.r2dbc.DefaultDatabaseClient: 575 - Executing SQL statement [SELECT id, name, age FROM person]
|
||||
2018-11-28 10:47:04,436 INFO org.spring.r2dbc.example.R2dbcApp: 43 - Person [id='joe', name='Joe', age=34]
|
||||
----
|
||||
====
|
||||
|
||||
Even in this simple example, there are few things to notice:
|
||||
|
||||
* You can create an instance of the central helper class in Spring Data R2DBC (`R2dbcEntityTemplate`) by using a standard `io.r2dbc.spi.ConnectionFactory` object.
|
||||
* The mapper works against standard POJO objects without the need for any additional metadata (though you can, optionally, provide that information -- see <<mapping,here>>.).
|
||||
* Mapping conventions can use field access.Notice that the `Person` class has only getters.
|
||||
* If the constructor argument names match the column names of the stored row, they are used to instantiate the object.
|
||||
|
||||
[[r2dbc.examples-repo]]
|
||||
== Examples Repository
|
||||
|
||||
There is a https://github.com/spring-projects/spring-data-examples[GitHub repository with several examples] that you can download and play around with to get a feel for how the library works.
|
||||
|
||||
[[r2dbc.connecting]]
|
||||
== Connecting to a Relational Database with Spring
|
||||
|
||||
One of the first tasks when using relational databases and Spring is to create a `io.r2dbc.spi.ConnectionFactory` object by using the IoC container.Make sure to use a <<r2dbc.drivers,supported database and driver>>.
|
||||
|
||||
[[r2dbc.connectionfactory]]
|
||||
=== Registering a `ConnectionFactory` Instance using Java-based Metadata
|
||||
|
||||
The following example shows an example of using Java-based bean metadata to register an instance of `io.r2dbc.spi.ConnectionFactory`:
|
||||
|
||||
.Registering a `io.r2dbc.spi.ConnectionFactory` object using Java-based bean metadata
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
@Configuration
|
||||
public class ApplicationConfiguration extends AbstractR2dbcConfiguration {
|
||||
|
||||
@Override
|
||||
@Bean
|
||||
public ConnectionFactory connectionFactory() {
|
||||
return …
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
This approach lets you use the standard `io.r2dbc.spi.ConnectionFactory` instance, with the container using Spring's `AbstractR2dbcConfiguration`.As compared to registering a `ConnectionFactory` instance directly, the configuration support has the added advantage of also providing the container with an `ExceptionTranslator` implementation that translates R2DBC exceptions to exceptions in Spring's portable `DataAccessException` hierarchy for data access classes annotated with the `@Repository` annotation.This hierarchy and the use of `@Repository` is described in {spring-framework-ref}/data-access.html[Spring's DAO support features].
|
||||
|
||||
`AbstractR2dbcConfiguration` also registers `DatabaseClient`, which is required for database interaction and for Repository implementation.
|
||||
|
||||
[[r2dbc.drivers]]
|
||||
=== R2DBC Drivers
|
||||
|
||||
Spring Data R2DBC supports drivers through R2DBC's pluggable SPI mechanism.
|
||||
You can use any driver that implements the R2DBC spec with Spring Data R2DBC.
|
||||
Since Spring Data R2DBC reacts to specific features of each database, it requires a `Dialect` implementation otherwise your application won't start up.
|
||||
Spring Data R2DBC ships with dialect implementations for the following drivers:
|
||||
|
||||
* https://github.com/r2dbc/r2dbc-h2[H2] (`io.r2dbc:r2dbc-h2`)
|
||||
* https://github.com/mariadb-corporation/mariadb-connector-r2dbc[MariaDB] (`org.mariadb:r2dbc-mariadb`)
|
||||
* https://github.com/r2dbc/r2dbc-mssql[Microsoft SQL Server] (`io.r2dbc:r2dbc-mssql`)
|
||||
* https://github.com/mirromutth/r2dbc-mysql[MySQL] (`dev.miku:r2dbc-mysql`)
|
||||
* https://github.com/jasync-sql/jasync-sql[jasync-sql MySQL] (`com.github.jasync-sql:jasync-r2dbc-mysql`)
|
||||
* https://github.com/r2dbc/r2dbc-postgresql[Postgres] (`io.r2dbc:r2dbc-postgresql`)
|
||||
* https://github.com/oracle/oracle-r2dbc[Oracle] (`com.oracle.database.r2dbc:oracle-r2dbc`)
|
||||
|
||||
Spring Data R2DBC reacts to database specifics by inspecting the `ConnectionFactory` and selects the appropriate database dialect accordingly.
|
||||
You need to configure your own {spring-data-r2dbc-javadoc}/api/org/springframework/data/r2dbc/dialect/R2dbcDialect.html[`R2dbcDialect`] if the driver you use is not yet known to Spring Data R2DBC.
|
||||
|
||||
TIP: Dialects are resolved by {spring-data-r2dbc-javadoc}/org/springframework/data/r2dbc/dialect/DialectResolver.html[`DialectResolver`] from a `ConnectionFactory`, typically by inspecting `ConnectionFactoryMetadata`.
|
||||
+ You can let Spring auto-discover your `R2dbcDialect` by registering a class that implements `org.springframework.data.r2dbc.dialect.DialectResolver$R2dbcDialectProvider` through `META-INF/spring.factories`.
|
||||
`DialectResolver` discovers dialect provider implementations from the class path using Spring's `SpringFactoriesLoader`.
|
||||
@@ -1,43 +0,0 @@
|
||||
[[r2dbc.entity-callbacks]]
|
||||
= Store specific EntityCallbacks
|
||||
|
||||
Spring Data R2DBC uses the `EntityCallback` API for its auditing support and reacts on the following callbacks.
|
||||
|
||||
.Supported Entity Callbacks
|
||||
[%header,cols="4"]
|
||||
|===
|
||||
| Callback
|
||||
| Method
|
||||
| Description
|
||||
| Order
|
||||
|
||||
| BeforeConvertCallback
|
||||
| `onBeforeConvert(T entity, SqlIdentifier table)`
|
||||
| Invoked before a domain object is converted to `OutboundRow`.
|
||||
| `Ordered.LOWEST_PRECEDENCE`
|
||||
|
||||
| AfterConvertCallback
|
||||
| `onAfterConvert(T entity, SqlIdentifier table)`
|
||||
| Invoked after a domain object is loaded. +
|
||||
Can modify the domain object after reading it from a row.
|
||||
| `Ordered.LOWEST_PRECEDENCE`
|
||||
|
||||
| AuditingEntityCallback
|
||||
| `onBeforeConvert(T entity, SqlIdentifier table)`
|
||||
| Marks an auditable entity _created_ or _modified_
|
||||
| 100
|
||||
|
||||
| BeforeSaveCallback
|
||||
| `onBeforeSave(T entity, OutboundRow row, SqlIdentifier table)`
|
||||
| Invoked before a domain object is saved. +
|
||||
Can modify the target, to be persisted, `OutboundRow` containing all mapped entity information.
|
||||
| `Ordered.LOWEST_PRECEDENCE`
|
||||
|
||||
| AfterSaveCallback
|
||||
| `onAfterSave(T entity, OutboundRow row, SqlIdentifier table)`
|
||||
| Invoked after a domain object is saved. +
|
||||
Can modify the domain object, to be returned after save, `OutboundRow` containing all mapped entity information.
|
||||
| `Ordered.LOWEST_PRECEDENCE`
|
||||
|
||||
|===
|
||||
|
||||
@@ -1,438 +0,0 @@
|
||||
[[r2dbc.repositories]]
|
||||
= R2DBC Repositories
|
||||
|
||||
[[r2dbc.repositories.intro]]
|
||||
This chapter points out the specialties for repository support for R2DBC.
|
||||
This chapter builds on the core repository support explained in <<repositories>>.
|
||||
Before reading this chapter, you should have a sound understanding of the basic concepts explained there.
|
||||
|
||||
[[r2dbc.repositories.usage]]
|
||||
== Usage
|
||||
|
||||
To access domain entities stored in a relational database, you can use our sophisticated repository support that eases implementation quite significantly.
|
||||
To do so, create an interface for your repository.
|
||||
Consider the following `Person` class:
|
||||
|
||||
.Sample Person entity
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
public class Person {
|
||||
|
||||
@Id
|
||||
private Long id;
|
||||
private String firstname;
|
||||
private String lastname;
|
||||
|
||||
// … getters and setters omitted
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
The following example shows a repository interface for the preceding `Person` class:
|
||||
|
||||
.Basic repository interface to persist Person entities
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
public interface PersonRepository extends ReactiveCrudRepository<Person, Long> {
|
||||
|
||||
// additional custom query methods go here
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
To configure R2DBC repositories, you can use the `@EnableR2dbcRepositories` annotation.
|
||||
If no base package is configured, the infrastructure scans the package of the annotated configuration class.
|
||||
The following example shows how to use Java configuration for a repository:
|
||||
|
||||
.Java configuration for repositories
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
@Configuration
|
||||
@EnableR2dbcRepositories
|
||||
class ApplicationConfig extends AbstractR2dbcConfiguration {
|
||||
|
||||
@Override
|
||||
public ConnectionFactory connectionFactory() {
|
||||
return …
|
||||
}
|
||||
}
|
||||
----
|
||||
====
|
||||
|
||||
Because our domain repository extends `ReactiveCrudRepository`, it provides you with reactive CRUD operations to access the entities.
|
||||
On top of `ReactiveCrudRepository`, there is also `ReactiveSortingRepository`, which adds additional sorting functionality similar to that of `PagingAndSortingRepository`.
|
||||
Working with the repository instance is merely a matter of dependency injecting it into a client.
|
||||
Consequently, you can retrieve all `Person` objects with the following code:
|
||||
|
||||
.Paging access to Person entities
|
||||
====
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../{example-root}/PersonRepositoryTests.java[tags=class]
|
||||
----
|
||||
====
|
||||
|
||||
The preceding example creates an application context with Spring's unit test support, which performs annotation-based dependency injection into test cases.
|
||||
Inside the test method, we use the repository to query the database.
|
||||
We use `StepVerifier` as a test aid to verify our expectations against the results.
|
||||
|
||||
[[r2dbc.repositories.queries]]
|
||||
== Query Methods
|
||||
|
||||
Most of the data access operations you usually trigger on a repository result in a query being run against the databases.
|
||||
Defining such a query is a matter of declaring a method on the repository interface, as the following example shows:
|
||||
|
||||
.PersonRepository with query methods
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
interface ReactivePersonRepository extends ReactiveSortingRepository<Person, Long> {
|
||||
|
||||
Flux<Person> findByFirstname(String firstname); <1>
|
||||
|
||||
Flux<Person> findByFirstname(Publisher<String> firstname); <2>
|
||||
|
||||
Flux<Person> findByFirstnameOrderByLastname(String firstname, Pageable pageable); <3>
|
||||
|
||||
Mono<Person> findByFirstnameAndLastname(String firstname, String lastname); <4>
|
||||
|
||||
Mono<Person> findFirstByLastname(String lastname); <5>
|
||||
|
||||
@Query("SELECT * FROM person WHERE lastname = :lastname")
|
||||
Flux<Person> findByLastname(String lastname); <6>
|
||||
|
||||
@Query("SELECT firstname, lastname FROM person WHERE lastname = $1")
|
||||
Mono<Person> findFirstByLastname(String lastname); <7>
|
||||
}
|
||||
----
|
||||
<1> The method shows a query for all people with the given `firstname`. The query is derived by parsing the method name for constraints that can be concatenated with `And` and `Or`. Thus, the method name results in a query expression of `SELECT … FROM person WHERE firstname = :firstname`.
|
||||
<2> The method shows a query for all people with the given `firstname` once the `firstname` is emitted by the given `Publisher`.
|
||||
<3> Use `Pageable` to pass offset and sorting parameters to the database.
|
||||
<4> Find a single entity for the given criteria. It completes with `IncorrectResultSizeDataAccessException` on non-unique results.
|
||||
<5> Unless <4>, the first entity is always emitted even if the query yields more result rows.
|
||||
<6> The `findByLastname` method shows a query for all people with the given last name.
|
||||
<7> A query for a single `Person` entity projecting only `firstname` and `lastname` columns.
|
||||
The annotated query uses native bind markers, which are Postgres bind markers in this example.
|
||||
====
|
||||
|
||||
Note that the columns of a select statement used in a `@Query` annotation must match the names generated by the `NamingStrategy` for the respective property.
|
||||
If a select statement does not include a matching column, that property is not set. If that property is required by the persistence constructor, either null or (for primitive types) the default value is provided.
|
||||
|
||||
The following table shows the keywords that are supported for query methods:
|
||||
|
||||
[cols="1,2,3", options="header", subs="quotes"]
|
||||
.Supported keywords for query methods
|
||||
|===
|
||||
| Keyword
|
||||
| Sample
|
||||
| Logical result
|
||||
|
||||
| `After`
|
||||
| `findByBirthdateAfter(Date date)`
|
||||
| `birthdate > date`
|
||||
|
||||
| `GreaterThan`
|
||||
| `findByAgeGreaterThan(int age)`
|
||||
| `age > age`
|
||||
|
||||
| `GreaterThanEqual`
|
||||
| `findByAgeGreaterThanEqual(int age)`
|
||||
| `age >= age`
|
||||
|
||||
| `Before`
|
||||
| `findByBirthdateBefore(Date date)`
|
||||
| `birthdate < date`
|
||||
|
||||
| `LessThan`
|
||||
| `findByAgeLessThan(int age)`
|
||||
| `age < age`
|
||||
|
||||
| `LessThanEqual`
|
||||
| `findByAgeLessThanEqual(int age)`
|
||||
| `age \<= age`
|
||||
|
||||
| `Between`
|
||||
| `findByAgeBetween(int from, int to)`
|
||||
| `age BETWEEN from AND to`
|
||||
|
||||
| `NotBetween`
|
||||
| `findByAgeNotBetween(int from, int to)`
|
||||
| `age NOT BETWEEN from AND to`
|
||||
|
||||
| `In`
|
||||
| `findByAgeIn(Collection<Integer> ages)`
|
||||
| `age IN (age1, age2, ageN)`
|
||||
|
||||
| `NotIn`
|
||||
| `findByAgeNotIn(Collection ages)`
|
||||
| `age NOT IN (age1, age2, ageN)`
|
||||
|
||||
| `IsNotNull`, `NotNull`
|
||||
| `findByFirstnameNotNull()`
|
||||
| `firstname IS NOT NULL`
|
||||
|
||||
| `IsNull`, `Null`
|
||||
| `findByFirstnameNull()`
|
||||
| `firstname IS NULL`
|
||||
|
||||
| `Like`, `StartingWith`, `EndingWith`
|
||||
| `findByFirstnameLike(String name)`
|
||||
| `firstname LIKE name`
|
||||
|
||||
| `NotLike`, `IsNotLike`
|
||||
| `findByFirstnameNotLike(String name)`
|
||||
| `firstname NOT LIKE name`
|
||||
|
||||
| `Containing` on String
|
||||
| `findByFirstnameContaining(String name)`
|
||||
| `firstname LIKE '%' + name +'%'`
|
||||
|
||||
| `NotContaining` on String
|
||||
| `findByFirstnameNotContaining(String name)`
|
||||
| `firstname NOT LIKE '%' + name +'%'`
|
||||
|
||||
| `(No keyword)`
|
||||
| `findByFirstname(String name)`
|
||||
| `firstname = name`
|
||||
|
||||
| `Not`
|
||||
| `findByFirstnameNot(String name)`
|
||||
| `firstname != name`
|
||||
|
||||
| `IsTrue`, `True`
|
||||
| `findByActiveIsTrue()`
|
||||
| `active IS TRUE`
|
||||
|
||||
| `IsFalse`, `False`
|
||||
| `findByActiveIsFalse()`
|
||||
| `active IS FALSE`
|
||||
|===
|
||||
|
||||
[[r2dbc.repositories.modifying]]
|
||||
=== Modifying Queries
|
||||
|
||||
The previous sections describe how to declare queries to access a given entity or collection of entities.
|
||||
Using keywords from the preceding table can be used in conjunction with `delete…By` or `remove…By` to create derived queries that delete matching rows.
|
||||
|
||||
.`Delete…By` Query
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
interface ReactivePersonRepository extends ReactiveSortingRepository<Person, String> {
|
||||
|
||||
Mono<Integer> deleteByLastname(String lastname); <1>
|
||||
|
||||
Mono<Void> deletePersonByLastname(String lastname); <2>
|
||||
|
||||
Mono<Boolean> deletePersonByLastname(String lastname); <3>
|
||||
}
|
||||
----
|
||||
<1> Using a return type of `Mono<Integer>` returns the number of affected rows.
|
||||
<2> Using `Void` just reports whether the rows were successfully deleted without emitting a result value.
|
||||
<3> Using `Boolean` reports whether at least one row was removed.
|
||||
====
|
||||
|
||||
As this approach is feasible for comprehensive custom functionality, you can modify queries that only need parameter binding by annotating the query method with `@Modifying`, as shown in the following example:
|
||||
|
||||
====
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../{example-root}/PersonRepository.java[tags=atModifying]
|
||||
----
|
||||
====
|
||||
|
||||
The result of a modifying query can be:
|
||||
|
||||
* `Void` (or Kotlin `Unit`) to discard update count and await completion.
|
||||
* `Integer` or another numeric type emitting the affected rows count.
|
||||
* `Boolean` to emit whether at least one row was updated.
|
||||
|
||||
The `@Modifying` annotation is only relevant in combination with the `@Query` annotation.
|
||||
Derived custom methods do not require this annotation.
|
||||
|
||||
Alternatively, you can add custom modifying behavior by using the facilities described in <<repositories.custom-implementations,Custom Implementations for Spring Data Repositories>>.
|
||||
|
||||
[[r2dbc.repositories.queries.spel]]
|
||||
=== Queries with SpEL Expressions
|
||||
|
||||
Query string definitions can be used together with SpEL expressions to create dynamic queries at runtime.
|
||||
SpEL expressions can provide predicate values which are evaluated right before running the query.
|
||||
|
||||
Expressions expose method arguments through an array that contains all the arguments.
|
||||
The following query uses `[0]`
|
||||
to declare the predicate value for `lastname` (which is equivalent to the `:lastname` parameter binding):
|
||||
|
||||
====
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../{example-root}/PersonRepository.java[tags=spel]
|
||||
----
|
||||
====
|
||||
|
||||
SpEL in query strings can be a powerful way to enhance queries.
|
||||
However, they can also accept a broad range of unwanted arguments.
|
||||
You should make sure to sanitize strings before passing them to the query to avoid unwanted changes to your query.
|
||||
|
||||
Expression support is extensible through the Query SPI: `org.springframework.data.spel.spi.EvaluationContextExtension`.
|
||||
The Query SPI can contribute properties and functions and can customize the root object.
|
||||
Extensions are retrieved from the application context at the time of SpEL evaluation when the query is built.
|
||||
|
||||
TIP: When using SpEL expressions in combination with plain parameters, use named parameter notation instead of native bind markers to ensure a proper binding order.
|
||||
|
||||
[[r2dbc.repositories.queries.query-by-example]]
|
||||
=== Query By Example
|
||||
|
||||
Spring Data R2DBC also lets you use Query By Example to fashion queries.
|
||||
This technique allows you to use a "probe" object.
|
||||
Essentially, any field that isn't empty or `null` will be used to match.
|
||||
|
||||
Here's an example:
|
||||
|
||||
====
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../{example-root}/QueryByExampleTests.java[tag=example]
|
||||
----
|
||||
<1> Create a domain object with the criteria (`null` fields will be ignored).
|
||||
<2> Using the domain object, create an `Example`.
|
||||
<3> Through the `R2dbcRepository`, execute query (use `findOne` for a `Mono`).
|
||||
====
|
||||
|
||||
This illustrates how to craft a simple probe using a domain object.
|
||||
In this case, it will query based on the `Employee` object's `name` field being equal to `Frodo`.
|
||||
`null` fields are ignored.
|
||||
|
||||
====
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../{example-root}/QueryByExampleTests.java[tag=example-2]
|
||||
----
|
||||
<1> Create a custom `ExampleMatcher` that matches on ALL fields (use `matchingAny()` to match on *ANY* fields)
|
||||
<2> For the `name` field, use a wildcard that matches against the end of the field
|
||||
<3> Match columns against `null` (don't forget that `NULL` doesn't equal `NULL` in relational databases).
|
||||
<4> Ignore the `role` field when forming the query.
|
||||
<5> Plug the custom `ExampleMatcher` into the probe.
|
||||
====
|
||||
|
||||
It's also possible to apply a `withTransform()` against any property, allowing you to transform a property before forming the query.
|
||||
For example, you can apply a `toUpperCase()` to a `String` -based property before the query is created.
|
||||
|
||||
Query By Example really shines when you you don't know all the fields needed in a query in advance.
|
||||
If you were building a filter on a web page where the user can pick the fields, Query By Example is a great way to flexibly capture that into an efficient query.
|
||||
|
||||
[[r2dbc.entity-persistence.state-detection-strategies]]
|
||||
include::../{spring-data-commons-docs}/is-new-state-detection.adoc[leveloffset=+2]
|
||||
|
||||
[[r2dbc.entity-persistence.id-generation]]
|
||||
=== ID Generation
|
||||
|
||||
Spring Data R2DBC uses the ID to identify entities.
|
||||
The ID of an entity must be annotated with Spring Data's https://docs.spring.io/spring-data/commons/docs/current/api/org/springframework/data/annotation/Id.html[`@Id`] annotation.
|
||||
|
||||
When your database has an auto-increment column for the ID column, the generated value gets set in the entity after inserting it into the database.
|
||||
|
||||
Spring Data R2DBC does not attempt to insert values of identifier columns when the entity is new and the identifier value defaults to its initial value.
|
||||
That is `0` for primitive types and `null` if the identifier property uses a numeric wrapper type such as `Long`.
|
||||
|
||||
One important constraint is that, after saving an entity, the entity must not be new anymore.
|
||||
Note that whether an entity is new is part of the entity's state.
|
||||
With auto-increment columns, this happens automatically, because the ID gets set by Spring Data with the value from the ID column.
|
||||
|
||||
[[r2dbc.optimistic-locking]]
|
||||
=== Optimistic Locking
|
||||
|
||||
The `@Version` annotation provides syntax similar to that of JPA in the context of R2DBC and makes sure updates are only applied to rows with a matching version.
|
||||
Therefore, the actual value of the version property is added to the update query in such a way that the update does not have any effect if another operation altered the row in the meantime.
|
||||
In that case, an `OptimisticLockingFailureException` is thrown.
|
||||
The following example shows these features:
|
||||
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
@Table
|
||||
class Person {
|
||||
|
||||
@Id Long id;
|
||||
String firstname;
|
||||
String lastname;
|
||||
@Version Long version;
|
||||
}
|
||||
|
||||
R2dbcEntityTemplate template = …;
|
||||
|
||||
Mono<Person> daenerys = template.insert(new Person("Daenerys")); <1>
|
||||
|
||||
Person other = template.select(Person.class)
|
||||
.matching(query(where("id").is(daenerys.getId())))
|
||||
.first().block(); <2>
|
||||
|
||||
daenerys.setLastname("Targaryen");
|
||||
template.update(daenerys); <3>
|
||||
|
||||
template.update(other).subscribe(); // emits OptimisticLockingFailureException <4>
|
||||
----
|
||||
<1> Initially insert row. `version` is set to `0`.
|
||||
<2> Load the just inserted row. `version` is still `0`.
|
||||
<3> Update the row with `version = 0`.Set the `lastname` and bump `version` to `1`.
|
||||
<4> Try to update the previously loaded row that still has `version = 0`.The operation fails with an `OptimisticLockingFailureException`, as the current `version` is `1`.
|
||||
====
|
||||
|
||||
:projection-collection: Flux
|
||||
include::../{spring-data-commons-docs}/repository-projections.adoc[leveloffset=+2]
|
||||
|
||||
[[projections.resultmapping]]
|
||||
==== Result Mapping
|
||||
|
||||
A query method returning an Interface- or DTO projection is backed by results produced by the actual query.
|
||||
Interface projections generally rely on mapping results onto the domain type first to consider potential `@Column` type mappings and the actual projection proxy uses a potentially partially materialized entity to expose projection data.
|
||||
|
||||
Result mapping for DTO projections depends on the actual query type.
|
||||
Derived queries use the domain type to map results, and Spring Data creates DTO instances solely from properties available on the domain type.
|
||||
Declaring properties in your DTO that are not available on the domain type is not supported.
|
||||
|
||||
String-based queries use a different approach since the actual query, specifically the field projection, and result type declaration are close together.
|
||||
DTO projections used with query methods annotated with `@Query` map query results directly into the DTO type.
|
||||
Field mappings on the domain type are not considered.
|
||||
Using the DTO type directly, your query method can benefit from a more dynamic projection that isn't restricted to the domain model.
|
||||
|
||||
include::../{spring-data-commons-docs}/entity-callbacks.adoc[leveloffset=+1]
|
||||
include::./r2dbc-entity-callbacks.adoc[leveloffset=+2]
|
||||
|
||||
[[r2dbc.multiple-databases]]
|
||||
== Working with multiple Databases
|
||||
|
||||
When working with multiple, potentially different databases, your application will require a different approach to configuration.
|
||||
The provided `AbstractR2dbcConfiguration` support class assumes a single `ConnectionFactory` from which the `Dialect` gets derived.
|
||||
That being said, you need to define a few beans yourself to configure Spring Data R2DBC to work with multiple databases.
|
||||
|
||||
R2DBC repositories require `R2dbcEntityOperations` to implement repositories.
|
||||
A simple configuration to scan for repositories without using `AbstractR2dbcConfiguration` looks like:
|
||||
|
||||
[source,java]
|
||||
----
|
||||
@Configuration
|
||||
@EnableR2dbcRepositories(basePackages = "com.acme.mysql", entityOperationsRef = "mysqlR2dbcEntityOperations")
|
||||
static class MySQLConfiguration {
|
||||
|
||||
@Bean
|
||||
@Qualifier("mysql")
|
||||
public ConnectionFactory mysqlConnectionFactory() {
|
||||
return …
|
||||
}
|
||||
|
||||
@Bean
|
||||
public R2dbcEntityOperations mysqlR2dbcEntityOperations(@Qualifier("mysql") ConnectionFactory connectionFactory) {
|
||||
|
||||
DatabaseClient databaseClient = DatabaseClient.create(connectionFactory);
|
||||
|
||||
return new R2dbcEntityTemplate(databaseClient, MySqlDialect.INSTANCE);
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Note that `@EnableR2dbcRepositories` allows configuration either through `databaseClientRef` or `entityOperationsRef`.
|
||||
Using various `DatabaseClient` beans is useful when connecting to multiple databases of the same type.
|
||||
When using different database systems that differ in their dialect, use `@EnableR2dbcRepositories`(entityOperationsRef = …)` instead.
|
||||
@@ -1,193 +0,0 @@
|
||||
[[r2dbc.datbaseclient.fluent-api]]
|
||||
[[r2dbc.entityoperations]]
|
||||
= R2dbcEntityOperations Data Access API
|
||||
|
||||
`R2dbcEntityTemplate` is the central entrypoint for Spring Data R2DBC.
|
||||
It provides direct entity-oriented methods and a more narrow, fluent interface for typical ad-hoc use-cases, such as querying, inserting, updating, and deleting data.
|
||||
|
||||
The entry points (`insert()`, `select()`, `update()`, and others) follow a natural naming schema based on the operation to be run.
|
||||
Moving on from the entry point, the API is designed to offer only context-dependent methods that lead to a terminating method that creates and runs a SQL statement.
|
||||
Spring Data R2DBC uses a `R2dbcDialect` abstraction to determine bind markers, pagination support and the data types natively supported by the underlying driver.
|
||||
|
||||
NOTE: All terminal methods return always a `Publisher` type that represents the desired operation.
|
||||
The actual statements are sent to the database upon subscription.
|
||||
|
||||
[[r2dbc.entityoperations.save-insert]]
|
||||
== Methods for Inserting and Updating Entities
|
||||
|
||||
There are several convenient methods on `R2dbcEntityTemplate` for saving and inserting your objects.
|
||||
To have more fine-grained control over the conversion process, you can register Spring converters with `R2dbcCustomConversions` -- for example `Converter<Person, OutboundRow>` and `Converter<Row, Person>`.
|
||||
|
||||
The simple case of using the save operation is to save a POJO. In this case, the table name is determined by name (not fully qualified) of the class.
|
||||
You may also call the save operation with a specific collection name.
|
||||
You can use mapping metadata to override the collection in which to store the object.
|
||||
|
||||
When inserting or saving, if the `Id` property is not set, the assumption is that its value will be auto-generated by the database.
|
||||
Consequently, for auto-generation the type of the `Id` property or field in your class must be a `Long`, or `Integer`.
|
||||
|
||||
The following example shows how to insert a row and retrieving its contents:
|
||||
|
||||
.Inserting and retrieving entities using the `R2dbcEntityTemplate`
|
||||
====
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../{example-root}/R2dbcEntityTemplateSnippets.java[tag=insertAndSelect]
|
||||
----
|
||||
====
|
||||
|
||||
The following insert and update operations are available:
|
||||
|
||||
A similar set of insert operations is also available:
|
||||
|
||||
* `Mono<T>` *insert* `(T objectToSave)`: Insert the object to the default table.
|
||||
* `Mono<T>` *update* `(T objectToSave)`: Insert the object to the default table.
|
||||
|
||||
Table names can be customized by using the fluent API.
|
||||
|
||||
[[r2dbc.entityoperations.selecting]]
|
||||
== Selecting Data
|
||||
|
||||
The `select(…)` and `selectOne(…)` methods on `R2dbcEntityTemplate` are used to select data from a table.
|
||||
Both methods take a <<r2dbc.datbaseclient.fluent-api.criteria,`Query`>> object that defines the field projection, the `WHERE` clause, the `ORDER BY` clause and limit/offset pagination.
|
||||
Limit/offset functionality is transparent to the application regardless of the underlying database.
|
||||
This functionality is supported by the <<r2dbc.drivers,`R2dbcDialect` abstraction>> to cater for differences between the individual SQL flavors.
|
||||
|
||||
.Selecting entities using the `R2dbcEntityTemplate`
|
||||
====
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../{example-root}/R2dbcEntityTemplateSnippets.java[tag=select]
|
||||
----
|
||||
====
|
||||
|
||||
[[r2dbc.entityoperations.fluent-api]]
|
||||
== Fluent API
|
||||
|
||||
This section explains the fluent API usage.
|
||||
Consider the following simple query:
|
||||
|
||||
====
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../{example-root}/R2dbcEntityTemplateSnippets.java[tag=simpleSelect]
|
||||
----
|
||||
<1> Using `Person` with the `select(…)` method maps tabular results on `Person` result objects.
|
||||
<2> Fetching `all()` rows returns a `Flux<Person>` without limiting results.
|
||||
====
|
||||
|
||||
The following example declares a more complex query that specifies the table name by name, a `WHERE` condition, and an `ORDER BY` clause:
|
||||
|
||||
====
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../{example-root}/R2dbcEntityTemplateSnippets.java[tag=fullSelect]
|
||||
----
|
||||
<1> Selecting from a table by name returns row results using the given domain type.
|
||||
<2> The issued query declares a `WHERE` condition on `firstname` and `lastname` columns to filter results.
|
||||
<3> Results can be ordered by individual column names, resulting in an `ORDER BY` clause.
|
||||
<4> Selecting the one result fetches only a single row.
|
||||
This way of consuming rows expects the query to return exactly a single result.
|
||||
`Mono` emits a `IncorrectResultSizeDataAccessException` if the query yields more than a single result.
|
||||
====
|
||||
|
||||
TIP: You can directly apply <<projections,Projections>> to results by providing the target type via `select(Class<?>)`.
|
||||
|
||||
You can switch between retrieving a single entity and retrieving multiple entities through the following terminating methods:
|
||||
|
||||
* `first()`: Consume only the first row, returning a `Mono`.
|
||||
The returned `Mono` completes without emitting an object if the query returns no results.
|
||||
* `one()`: Consume exactly one row, returning a `Mono`.
|
||||
The returned `Mono` completes without emitting an object if the query returns no results.
|
||||
If the query returns more than one row, `Mono` completes exceptionally emitting `IncorrectResultSizeDataAccessException`.
|
||||
* `all()`: Consume all returned rows returning a `Flux`.
|
||||
* `count()`: Apply a count projection returning `Mono<Long>`.
|
||||
* `exists()`: Return whether the query yields any rows by returning `Mono<Boolean>`.
|
||||
|
||||
You can use the `select()` entry point to express your `SELECT` queries.
|
||||
The resulting `SELECT` queries support the commonly used clauses (`WHERE` and `ORDER BY`) and support pagination.
|
||||
The fluent API style let you chain together multiple methods while having easy-to-understand code.
|
||||
To improve readability, you can use static imports that let you avoid using the 'new' keyword for creating `Criteria` instances.
|
||||
|
||||
[[r2dbc.datbaseclient.fluent-api.criteria]]
|
||||
=== Methods for the Criteria Class
|
||||
|
||||
The `Criteria` class provides the following methods, all of which correspond to SQL operators:
|
||||
|
||||
* `Criteria` *and* `(String column)`: Adds a chained `Criteria` with the specified `property` to the current `Criteria` and returns the newly created one.
|
||||
* `Criteria` *or* `(String column)`: Adds a chained `Criteria` with the specified `property` to the current `Criteria` and returns the newly created one.
|
||||
* `Criteria` *greaterThan* `(Object o)`: Creates a criterion by using the `>` operator.
|
||||
* `Criteria` *greaterThanOrEquals* `(Object o)`: Creates a criterion by using the `>=` operator.
|
||||
* `Criteria` *in* `(Object... o)`: Creates a criterion by using the `IN` operator for a varargs argument.
|
||||
* `Criteria` *in* `(Collection<?> collection)`: Creates a criterion by using the `IN` operator using a collection.
|
||||
* `Criteria` *is* `(Object o)`: Creates a criterion by using column matching (`property = value`).
|
||||
* `Criteria` *isNull* `()`: Creates a criterion by using the `IS NULL` operator.
|
||||
* `Criteria` *isNotNull* `()`: Creates a criterion by using the `IS NOT NULL` operator.
|
||||
* `Criteria` *lessThan* `(Object o)`: Creates a criterion by using the `<` operator.
|
||||
* `Criteria` *lessThanOrEquals* `(Object o)`: Creates a criterion by using the `<=` operator.
|
||||
* `Criteria` *like* `(Object o)`: Creates a criterion by using the `LIKE` operator without escape character processing.
|
||||
* `Criteria` *not* `(Object o)`: Creates a criterion by using the `!=` operator.
|
||||
* `Criteria` *notIn* `(Object... o)`: Creates a criterion by using the `NOT IN` operator for a varargs argument.
|
||||
* `Criteria` *notIn* `(Collection<?> collection)`: Creates a criterion by using the `NOT IN` operator using a collection.
|
||||
|
||||
You can use `Criteria` with `SELECT`, `UPDATE`, and `DELETE` queries.
|
||||
|
||||
[[r2dbc.entityoperations.fluent-api.insert]]
|
||||
== Inserting Data
|
||||
|
||||
You can use the `insert()` entry point to insert data.
|
||||
|
||||
Consider the following simple typed insert operation:
|
||||
|
||||
====
|
||||
[source,java,indent=0]
|
||||
----
|
||||
include::../{example-root}/R2dbcEntityTemplateSnippets.java[tag=insert]
|
||||
----
|
||||
<1> Using `Person` with the `into(…)` method sets the `INTO` table, based on mapping metadata.
|
||||
It also prepares the insert statement to accept `Person` objects for inserting.
|
||||
<2> Provide a scalar `Person` object.
|
||||
Alternatively, you can supply a `Publisher` to run a stream of `INSERT` statements.
|
||||
This method extracts all non-`null` values and inserts them.
|
||||
====
|
||||
|
||||
[[r2dbc.entityoperations.fluent-api.update]]
|
||||
== Updating Data
|
||||
|
||||
You can use the `update()` entry point to update rows.
|
||||
Updating data starts by specifying the table to update by accepting `Update` specifying assignments.
|
||||
It also accepts `Query` to create a `WHERE` clause.
|
||||
|
||||
Consider the following simple typed update operation:
|
||||
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
Person modified = …
|
||||
|
||||
include::../{example-root}/R2dbcEntityTemplateSnippets.java[tag=update]
|
||||
----
|
||||
<1> Update `Person` objects and apply mapping based on mapping metadata.
|
||||
<2> Set a different table name by calling the `inTable(…)` method.
|
||||
<3> Specify a query that translates into a `WHERE` clause.
|
||||
<4> Apply the `Update` object.
|
||||
Set in this case `age` to `42` and return the number of affected rows.
|
||||
====
|
||||
|
||||
[[r2dbc.entityoperations.fluent-api.delete]]
|
||||
== Deleting Data
|
||||
|
||||
You can use the `delete()` entry point to delete rows.
|
||||
Removing data starts with a specification of the table to delete from and, optionally, accepts a `Criteria` to create a `WHERE` clause.
|
||||
|
||||
Consider the following simple insert operation:
|
||||
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
include::../{example-root}/R2dbcEntityTemplateSnippets.java[tag=delete]
|
||||
----
|
||||
<1> Delete `Person` objects and apply mapping based on mapping metadata.
|
||||
<2> Set a different table name by calling the `from(…)` method.
|
||||
<3> Specify a query that translates into a `WHERE` clause.
|
||||
<4> Apply the delete operation and return the number of affected rows.
|
||||
====
|
||||
@@ -1,62 +0,0 @@
|
||||
[appendix]
|
||||
[[migration-guide]]
|
||||
= Migration Guide
|
||||
|
||||
The following sections explain how to migrate to a newer version of Spring Data R2DBC.
|
||||
|
||||
[[upgrading.1.1-1.2]]
|
||||
== Upgrading from 1.1.x to 1.2.x
|
||||
|
||||
Spring Data R2DBC was developed with the intent to evaluate how well R2DBC can integrate with Spring applications.
|
||||
One of the main aspects was to move core support into Spring Framework once R2DBC support has proven useful.
|
||||
Spring Framework 5.3 ships with a new module: Spring R2DBC (`spring-r2dbc`).
|
||||
|
||||
`spring-r2dbc` ships core R2DBC functionality (a slim variant of `DatabaseClient`, Transaction Manager, Connection Factory initialization, Exception translation) that was initially provided by Spring Data R2DBC.
|
||||
The 1.2.0 release aligns with what's provided in Spring R2DBC by making several changes outlined in the following sections.
|
||||
|
||||
Spring R2DBC's `DatabaseClient` is a more lightweight implementation that encapsulates a pure SQL-oriented interface.
|
||||
You will notice that the method to run SQL statements changed from `DatabaseClient.execute(…)` to `DatabaseClient.sql(…)`.
|
||||
The fluent API for CRUD operations has moved into `R2dbcEntityTemplate`.
|
||||
|
||||
If you use logging of SQL statements through the logger prefix `org.springframework.data.r2dbc`, make sure to update it to `org.springframework.r2dbc` (that is removing `.data`) to point to Spring R2DBC components.
|
||||
|
||||
[[upgrading.1.1-1.2.deprecation]]
|
||||
=== Deprecations
|
||||
|
||||
* Deprecation of `o.s.d.r2dbc.core.DatabaseClient` and its support classes `ConnectionAccessor`, `FetchSpec`, `SqlProvider` and a few more.
|
||||
Named parameter support classes such as `NamedParameterExpander` are encapsulated by Spring R2DBC's `DatabaseClient` implementation hence we're not providing replacements as this was internal API in the first place.
|
||||
Use `o.s.r2dbc.core.DatabaseClient` and their Spring R2DBC replacements available from `org.springframework.r2dbc.core`.
|
||||
Entity-based methods (`select`/`insert`/`update`/`delete`) methods are available through `R2dbcEntityTemplate` which was introduced with version 1.1.
|
||||
* Deprecation of `o.s.d.r2dbc.connectionfactory`, `o.s.d.r2dbc.connectionfactory.init`, and `o.s.d.r2dbc.connectionfactory.lookup` packages.
|
||||
Use Spring R2DBC's variant which you can find at `o.s.r2dbc.connection`.
|
||||
* Deprecation of `o.s.d.r2dbc.convert.ColumnMapRowMapper`.
|
||||
Use `o.s.r2dbc.core.ColumnMapRowMapper` instead.
|
||||
* Deprecation of binding support classes `o.s.d.r2dbc.dialect.Bindings`, `BindMarker`, `BindMarkers`, `BindMarkersFactory` and related types.
|
||||
Use replacements from `org.springframework.r2dbc.core.binding`.
|
||||
* Deprecation of `BadSqlGrammarException`, `UncategorizedR2dbcException` and exception translation at `o.s.d.r2dbc.support`.
|
||||
Spring R2DBC provides a slim exception translation variant without an SPI for now available through `o.s.r2dbc.connection.ConnectionFactoryUtils#convertR2dbcException`.
|
||||
|
||||
[[upgrading.1.1-1.2.replacements]]
|
||||
=== Usage of replacements provided by Spring R2DBC
|
||||
|
||||
To ease migration, several deprecated types are now subtypes of their replacements provided by Spring R2DBC.
|
||||
Spring Data R2DBC has changes several methods or introduced new methods accepting Spring R2DBC types.
|
||||
Specifically the following classes are changed:
|
||||
|
||||
* `R2dbcEntityTemplate`
|
||||
* `R2dbcDialect`
|
||||
* Types in `org.springframework.data.r2dbc.query`
|
||||
|
||||
We recommend that you review and update your imports if you work with these types directly.
|
||||
|
||||
=== Breaking Changes
|
||||
|
||||
* `OutboundRow` and statement mappers switched from using `SettableValue` to `Parameter`
|
||||
* Repository factory support requires `o.s.r2dbc.core.DatabaseClient` instead of `o.s.data.r2dbc.core.DatabaseClient`.
|
||||
|
||||
[[upgrading.1.1-1.2.dependencies]]
|
||||
=== Dependency Changes
|
||||
|
||||
To make use of Spring R2DBC, make sure to include the following dependency:
|
||||
|
||||
* `org.springframework:spring-r2dbc`
|
||||
@@ -1,6 +0,0 @@
|
||||
[[r2dbc.core]]
|
||||
= R2DBC support
|
||||
|
||||
include::r2dbc-core.adoc[]
|
||||
|
||||
include::r2dbc-template.adoc[leveloffset=+1]
|
||||
@@ -1,242 +0,0 @@
|
||||
/*
|
||||
* Copyright 2018-2022 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
|
||||
*
|
||||
* https://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.r2dbc.config;
|
||||
|
||||
import io.r2dbc.spi.ConnectionFactory;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.data.convert.CustomConversions;
|
||||
import org.springframework.data.convert.CustomConversions.StoreConversions;
|
||||
import org.springframework.data.r2dbc.convert.MappingR2dbcConverter;
|
||||
import org.springframework.data.r2dbc.convert.R2dbcConverter;
|
||||
import org.springframework.data.r2dbc.convert.R2dbcCustomConversions;
|
||||
import org.springframework.data.r2dbc.core.DefaultReactiveDataAccessStrategy;
|
||||
import org.springframework.data.r2dbc.core.R2dbcEntityTemplate;
|
||||
import org.springframework.data.r2dbc.core.ReactiveDataAccessStrategy;
|
||||
import org.springframework.data.r2dbc.dialect.DialectResolver;
|
||||
import org.springframework.data.r2dbc.dialect.R2dbcDialect;
|
||||
import org.springframework.data.r2dbc.mapping.R2dbcMappingContext;
|
||||
import org.springframework.data.relational.core.conversion.BasicRelationalConverter;
|
||||
import org.springframework.data.relational.core.mapping.NamingStrategy;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.r2dbc.core.DatabaseClient;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Base class for Spring Data R2DBC configuration containing bean declarations that must be registered for Spring Data
|
||||
* R2DBC to work.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @see ConnectionFactory
|
||||
* @see DatabaseClient
|
||||
* @see org.springframework.data.r2dbc.repository.config.EnableR2dbcRepositories
|
||||
*/
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
public abstract class AbstractR2dbcConfiguration implements ApplicationContextAware {
|
||||
|
||||
private static final String CONNECTION_FACTORY_BEAN_NAME = "connectionFactory";
|
||||
|
||||
private @Nullable ApplicationContext context;
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.context.ApplicationContextAware#setApplicationContext(org.springframework.context.ApplicationContext)
|
||||
*/
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
||||
this.context = applicationContext;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a R2DBC {@link ConnectionFactory}. Annotate with {@link Bean} in case you want to expose a
|
||||
* {@link ConnectionFactory} instance to the {@link org.springframework.context.ApplicationContext}.
|
||||
*
|
||||
* @return the configured {@link ConnectionFactory}.
|
||||
*/
|
||||
public abstract ConnectionFactory connectionFactory();
|
||||
|
||||
/**
|
||||
* Return a {@link R2dbcDialect} for the given {@link ConnectionFactory}. This method attempts to resolve a
|
||||
* {@link R2dbcDialect} from {@link io.r2dbc.spi.ConnectionFactoryMetadata}. Override this method to specify a dialect
|
||||
* instead of attempting to resolve one.
|
||||
*
|
||||
* @param connectionFactory the configured {@link ConnectionFactory}.
|
||||
* @return the resolved {@link R2dbcDialect}.
|
||||
* @throws org.springframework.data.r2dbc.dialect.DialectResolver.NoDialectException if the {@link R2dbcDialect} cannot be determined.
|
||||
*/
|
||||
public R2dbcDialect getDialect(ConnectionFactory connectionFactory) {
|
||||
return DialectResolver.getDialect(connectionFactory);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a {@link DatabaseClient} using {@link #connectionFactory()} and {@link ReactiveDataAccessStrategy}.
|
||||
*
|
||||
* @return must not be {@literal null}.
|
||||
* @throws IllegalArgumentException if any of the required args is {@literal null}.
|
||||
*/
|
||||
@Bean({ "r2dbcDatabaseClient", "databaseClient" })
|
||||
public DatabaseClient databaseClient() {
|
||||
|
||||
ConnectionFactory connectionFactory = lookupConnectionFactory();
|
||||
|
||||
return DatabaseClient.builder() //
|
||||
.connectionFactory(connectionFactory) //
|
||||
.bindMarkers(getDialect(connectionFactory).getBindMarkersFactory()) //
|
||||
.build();
|
||||
}
|
||||
|
||||
/**
|
||||
* Register {@link R2dbcEntityTemplate} using {@link #databaseClient()} and {@link #connectionFactory()}.
|
||||
*
|
||||
* @param databaseClient must not be {@literal null}.
|
||||
* @param dataAccessStrategy must not be {@literal null}.
|
||||
* @return
|
||||
* @since 1.2
|
||||
*/
|
||||
@Bean
|
||||
public R2dbcEntityTemplate r2dbcEntityTemplate(DatabaseClient databaseClient,
|
||||
ReactiveDataAccessStrategy dataAccessStrategy) {
|
||||
|
||||
Assert.notNull(databaseClient, "DatabaseClient must not be null!");
|
||||
Assert.notNull(dataAccessStrategy, "ReactiveDataAccessStrategy must not be null!");
|
||||
|
||||
return new R2dbcEntityTemplate(databaseClient, dataAccessStrategy);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a {@link R2dbcMappingContext} and apply an optional {@link NamingStrategy}.
|
||||
*
|
||||
* @param namingStrategy optional {@link NamingStrategy}. Use {@link NamingStrategy#INSTANCE} as fallback.
|
||||
* @param r2dbcCustomConversions customized R2DBC conversions.
|
||||
* @return must not be {@literal null}.
|
||||
* @throws IllegalArgumentException if any of the required args is {@literal null}.
|
||||
*/
|
||||
@Bean
|
||||
public R2dbcMappingContext r2dbcMappingContext(Optional<NamingStrategy> namingStrategy,
|
||||
R2dbcCustomConversions r2dbcCustomConversions) {
|
||||
|
||||
Assert.notNull(namingStrategy, "NamingStrategy must not be null!");
|
||||
|
||||
R2dbcMappingContext context = new R2dbcMappingContext(namingStrategy.orElse(NamingStrategy.INSTANCE));
|
||||
context.setSimpleTypeHolder(r2dbcCustomConversions.getSimpleTypeHolder());
|
||||
|
||||
return context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link ReactiveDataAccessStrategy} using the configured
|
||||
* {@link #r2dbcConverter(R2dbcMappingContext, R2dbcCustomConversions) R2dbcConverter}.
|
||||
*
|
||||
* @param converter the configured {@link R2dbcConverter}.
|
||||
* @return must not be {@literal null}.
|
||||
* @see #r2dbcConverter(R2dbcMappingContext, R2dbcCustomConversions)
|
||||
* @see #getDialect(ConnectionFactory)
|
||||
* @throws IllegalArgumentException if any of the {@literal mappingContext} is {@literal null}.
|
||||
*/
|
||||
@Bean
|
||||
public ReactiveDataAccessStrategy reactiveDataAccessStrategy(R2dbcConverter converter) {
|
||||
|
||||
Assert.notNull(converter, "MappingContext must not be null!");
|
||||
|
||||
return new DefaultReactiveDataAccessStrategy(getDialect(lookupConnectionFactory()), converter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link org.springframework.data.r2dbc.convert.R2dbcConverter} using the configured
|
||||
* {@link #r2dbcMappingContext(Optional, R2dbcCustomConversions)} R2dbcMappingContext}.
|
||||
*
|
||||
* @param mappingContext the configured {@link R2dbcMappingContext}.
|
||||
* @param r2dbcCustomConversions customized R2DBC conversions.
|
||||
* @return must not be {@literal null}.
|
||||
* @see #r2dbcMappingContext(Optional, R2dbcCustomConversions)
|
||||
* @see #getDialect(ConnectionFactory)
|
||||
* @throws IllegalArgumentException if any of the {@literal mappingContext} is {@literal null}.
|
||||
* @since 1.2
|
||||
*/
|
||||
@Bean
|
||||
public MappingR2dbcConverter r2dbcConverter(R2dbcMappingContext mappingContext,
|
||||
R2dbcCustomConversions r2dbcCustomConversions) {
|
||||
|
||||
Assert.notNull(mappingContext, "MappingContext must not be null!");
|
||||
|
||||
return new MappingR2dbcConverter(mappingContext, r2dbcCustomConversions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register custom {@link Converter}s in a {@link CustomConversions} object if required. These
|
||||
* {@link CustomConversions} will be registered with the {@link BasicRelationalConverter} and
|
||||
* {@link #r2dbcMappingContext(Optional, R2dbcCustomConversions)}. Returns an empty {@link R2dbcCustomConversions}
|
||||
* instance by default. Override {@link #getCustomConverters()} to supply custom converters.
|
||||
*
|
||||
* @return must not be {@literal null}.
|
||||
* @see #getCustomConverters()
|
||||
*/
|
||||
@Bean
|
||||
public R2dbcCustomConversions r2dbcCustomConversions() {
|
||||
return new R2dbcCustomConversions(getStoreConversions(), getCustomConverters());
|
||||
}
|
||||
|
||||
/**
|
||||
* Customization hook to return custom converters.
|
||||
*
|
||||
* @return return custom converters.
|
||||
*/
|
||||
protected List<Object> getCustomConverters() {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link R2dbcDialect}-specific {@link StoreConversions}.
|
||||
*
|
||||
* @return the {@link R2dbcDialect}-specific {@link StoreConversions}.
|
||||
*/
|
||||
protected StoreConversions getStoreConversions() {
|
||||
|
||||
R2dbcDialect dialect = getDialect(lookupConnectionFactory());
|
||||
|
||||
List<Object> converters = new ArrayList<>(dialect.getConverters());
|
||||
converters.addAll(R2dbcCustomConversions.STORE_CONVERTERS);
|
||||
|
||||
return StoreConversions.of(dialect.getSimpleTypeHolder(), converters);
|
||||
}
|
||||
|
||||
ConnectionFactory lookupConnectionFactory() {
|
||||
|
||||
ApplicationContext context = this.context;
|
||||
Assert.notNull(context, "ApplicationContext is not yet initialized");
|
||||
|
||||
String[] beanNamesForType = context.getBeanNamesForType(ConnectionFactory.class);
|
||||
|
||||
for (String beanName : beanNamesForType) {
|
||||
|
||||
if (beanName.equals(CONNECTION_FACTORY_BEAN_NAME)) {
|
||||
return context.getBean(CONNECTION_FACTORY_BEAN_NAME, ConnectionFactory.class);
|
||||
}
|
||||
}
|
||||
|
||||
return connectionFactory();
|
||||
}
|
||||
}
|
||||
@@ -1,70 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020-2022 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
|
||||
*
|
||||
* https://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.r2dbc.config;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Inherited;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.data.auditing.DateTimeProvider;
|
||||
import org.springframework.data.domain.ReactiveAuditorAware;
|
||||
|
||||
/**
|
||||
* Annotation to enable auditing in R2DBC via annotation configuration.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 1.2
|
||||
*/
|
||||
@Inherited
|
||||
@Documented
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Import(R2dbcAuditingRegistrar.class)
|
||||
public @interface EnableR2dbcAuditing {
|
||||
|
||||
/**
|
||||
* Configures the {@link ReactiveAuditorAware} bean to be used to lookup the current principal.
|
||||
*
|
||||
* @return empty {@link String} by default.
|
||||
*/
|
||||
String auditorAwareRef() default "";
|
||||
|
||||
/**
|
||||
* Configures whether the creation and modification dates are set. Defaults to {@literal true}.
|
||||
*
|
||||
* @return {@literal true} by default.
|
||||
*/
|
||||
boolean setDates() default true;
|
||||
|
||||
/**
|
||||
* Configures whether the entity shall be marked as modified on creation. Defaults to {@literal true}.
|
||||
*
|
||||
* @return {@literal true} by default.
|
||||
*/
|
||||
boolean modifyOnCreate() default true;
|
||||
|
||||
/**
|
||||
* Configures a {@link DateTimeProvider} bean name that allows customizing the timestamp to be used for setting
|
||||
* creation and modification dates.
|
||||
*
|
||||
* @return empty {@link String} by default.
|
||||
*/
|
||||
String dateTimeProviderRef() default "";
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020-2022 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
|
||||
*
|
||||
* https://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.r2dbc.config;
|
||||
|
||||
import org.springframework.beans.factory.FactoryBean;
|
||||
import org.springframework.data.mapping.context.PersistentEntities;
|
||||
import org.springframework.data.r2dbc.mapping.R2dbcMappingContext;
|
||||
|
||||
/**
|
||||
* Simple helper to be able to wire the {@link PersistentEntities} from a {@link R2dbcMappingContext} bean available in
|
||||
* the application context.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 1.2
|
||||
*/
|
||||
public class PersistentEntitiesFactoryBean implements FactoryBean<PersistentEntities> {
|
||||
|
||||
private final R2dbcMappingContext mappingContext;
|
||||
|
||||
/**
|
||||
* Creates a new {@link PersistentEntitiesFactoryBean} for the given {@link R2dbcMappingContext}.
|
||||
*
|
||||
* @param mappingContext must not be {@literal null}.
|
||||
*/
|
||||
public PersistentEntitiesFactoryBean(R2dbcMappingContext mappingContext) {
|
||||
this.mappingContext = mappingContext;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.beans.factory.FactoryBean#getObject()
|
||||
*/
|
||||
@Override
|
||||
public PersistentEntities getObject() {
|
||||
return PersistentEntities.of(mappingContext);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.beans.factory.FactoryBean#getObjectType()
|
||||
*/
|
||||
@Override
|
||||
public Class<?> getObjectType() {
|
||||
return PersistentEntities.class;
|
||||
}
|
||||
}
|
||||
@@ -1,96 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020-2022 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
|
||||
*
|
||||
* https://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.r2dbc.config;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
|
||||
import org.springframework.beans.factory.config.BeanDefinition;
|
||||
import org.springframework.beans.factory.support.AbstractBeanDefinition;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||
import org.springframework.beans.factory.support.BeanDefinitionRegistry;
|
||||
import org.springframework.context.annotation.ImportBeanDefinitionRegistrar;
|
||||
import org.springframework.data.auditing.ReactiveIsNewAwareAuditingHandler;
|
||||
import org.springframework.data.auditing.config.AuditingBeanDefinitionRegistrarSupport;
|
||||
import org.springframework.data.auditing.config.AuditingConfiguration;
|
||||
import org.springframework.data.config.ParsingUtils;
|
||||
import org.springframework.data.r2dbc.mapping.event.ReactiveAuditingEntityCallback;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* {@link ImportBeanDefinitionRegistrar} to enable {@link EnableR2dbcAuditing} annotation.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 1.2
|
||||
*/
|
||||
class R2dbcAuditingRegistrar extends AuditingBeanDefinitionRegistrarSupport {
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.auditing.config.AuditingBeanDefinitionRegistrarSupport#getAnnotation()
|
||||
*/
|
||||
@Override
|
||||
protected Class<? extends Annotation> getAnnotation() {
|
||||
return EnableR2dbcAuditing.class;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.auditing.config.AuditingBeanDefinitionRegistrarSupport#getAuditingHandlerBeanName()
|
||||
*/
|
||||
@Override
|
||||
protected String getAuditingHandlerBeanName() {
|
||||
return "r2dbcAuditingHandler";
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.auditing.config.AuditingBeanDefinitionRegistrarSupport#getAuditHandlerBeanDefinitionBuilder(org.springframework.data.auditing.config.AuditingConfiguration)
|
||||
*/
|
||||
@Override
|
||||
protected BeanDefinitionBuilder getAuditHandlerBeanDefinitionBuilder(AuditingConfiguration configuration) {
|
||||
|
||||
Assert.notNull(configuration, "AuditingConfiguration must not be null!");
|
||||
|
||||
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(ReactiveIsNewAwareAuditingHandler.class);
|
||||
|
||||
BeanDefinitionBuilder definition = BeanDefinitionBuilder.genericBeanDefinition(PersistentEntitiesFactoryBean.class);
|
||||
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_CONSTRUCTOR);
|
||||
|
||||
builder.addConstructorArgValue(definition.getBeanDefinition());
|
||||
return configureDefaultAuditHandlerAttributes(configuration, builder);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.auditing.config.AuditingBeanDefinitionRegistrarSupport#registerAuditListener(org.springframework.beans.factory.config.BeanDefinition, org.springframework.beans.factory.support.BeanDefinitionRegistry)
|
||||
*/
|
||||
@Override
|
||||
protected void registerAuditListenerBeanDefinition(BeanDefinition auditingHandlerDefinition,
|
||||
BeanDefinitionRegistry registry) {
|
||||
|
||||
Assert.notNull(auditingHandlerDefinition, "BeanDefinition must not be null!");
|
||||
Assert.notNull(registry, "BeanDefinitionRegistry must not be null!");
|
||||
|
||||
BeanDefinitionBuilder builder = BeanDefinitionBuilder.rootBeanDefinition(ReactiveAuditingEntityCallback.class);
|
||||
|
||||
builder.addConstructorArgValue(ParsingUtils.getObjectFactoryBeanDefinition(getAuditingHandlerBeanName(), registry));
|
||||
builder.getRawBeanDefinition().setSource(auditingHandlerDefinition.getSource());
|
||||
|
||||
registerInfrastructureBeanWithId(builder.getBeanDefinition(), ReactiveAuditingEntityCallback.class.getName(),
|
||||
registry);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
/**
|
||||
* Configuration classes for Spring Data R2DBC.
|
||||
*/
|
||||
@org.springframework.lang.NonNullApi
|
||||
@org.springframework.lang.NonNullFields
|
||||
package org.springframework.data.r2dbc.config;
|
||||
@@ -1,48 +0,0 @@
|
||||
/*
|
||||
* Copyright 2018-2022 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
|
||||
*
|
||||
* https://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.r2dbc.convert;
|
||||
|
||||
import io.r2dbc.spi.Row;
|
||||
import io.r2dbc.spi.RowMetadata;
|
||||
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
/**
|
||||
* Maps a {@link io.r2dbc.spi.Row} to an entity of type {@code T}, including entities referenced.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @author Ryland Degnan
|
||||
*/
|
||||
public class EntityRowMapper<T> implements BiFunction<Row, RowMetadata, T> {
|
||||
|
||||
private final Class<T> typeRoRead;
|
||||
private final R2dbcConverter converter;
|
||||
|
||||
public EntityRowMapper(Class<T> typeRoRead, R2dbcConverter converter) {
|
||||
|
||||
this.typeRoRead = typeRoRead;
|
||||
this.converter = converter;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.util.function.BiFunction#apply(java.lang.Object, java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public T apply(Row row, RowMetadata metadata) {
|
||||
return converter.read(typeRoRead, row, metadata);
|
||||
}
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020-2022 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
|
||||
*
|
||||
* https://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.r2dbc.convert;
|
||||
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.data.convert.WritingConverter;
|
||||
|
||||
/**
|
||||
* Support class to natively write {@link Enum} values to the database.
|
||||
* <p>
|
||||
* By default, Spring Data converts enum values by to {@link Enum#name() String} for maximum portability. Registering a
|
||||
* {@link WritingConverter} allows retaining the enum type so that actual enum values get passed thru to the driver.
|
||||
* <p>
|
||||
* Enum types that should be written using their actual enum value to the database should require a converter for type
|
||||
* pinning. Extend this class as the {@link org.springframework.data.convert.CustomConversions} support inspects
|
||||
* {@link Converter} generics to identify conversion rules.
|
||||
* <p>
|
||||
* For example:
|
||||
*
|
||||
* <pre class="code">
|
||||
* enum Color {
|
||||
* Grey, Blue
|
||||
* }
|
||||
*
|
||||
* class ColorConverter extends EnumWriteSupport<Color> {
|
||||
*
|
||||
* }
|
||||
* </pre>
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @param <E> the enum type that should be written using the actual value.
|
||||
* @since 1.2
|
||||
*/
|
||||
@WritingConverter
|
||||
public abstract class EnumWriteSupport<E extends Enum<E>> implements Converter<E, E> {
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.core.convert.converter.Converter#convert(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public E convert(E enumInstance) {
|
||||
return enumInstance;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,751 +0,0 @@
|
||||
/*
|
||||
* Copyright 2018-2022 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
|
||||
*
|
||||
* https://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.r2dbc.convert;
|
||||
|
||||
import io.r2dbc.spi.ColumnMetadata;
|
||||
import io.r2dbc.spi.Row;
|
||||
import io.r2dbc.spi.RowMetadata;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
import org.springframework.core.CollectionFactory;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.dao.InvalidDataAccessApiUsageException;
|
||||
import org.springframework.data.convert.CustomConversions;
|
||||
import org.springframework.data.mapping.IdentifierAccessor;
|
||||
import org.springframework.data.mapping.MappingException;
|
||||
import org.springframework.data.mapping.PersistentProperty;
|
||||
import org.springframework.data.mapping.PersistentPropertyAccessor;
|
||||
import org.springframework.data.mapping.PreferredConstructor;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.mapping.model.ConvertingPropertyAccessor;
|
||||
import org.springframework.data.mapping.model.DefaultSpELExpressionEvaluator;
|
||||
import org.springframework.data.mapping.model.ParameterValueProvider;
|
||||
import org.springframework.data.mapping.model.SpELContext;
|
||||
import org.springframework.data.mapping.model.SpELExpressionEvaluator;
|
||||
import org.springframework.data.mapping.model.SpELExpressionParameterValueProvider;
|
||||
import org.springframework.data.r2dbc.mapping.OutboundRow;
|
||||
import org.springframework.data.r2dbc.support.ArrayUtils;
|
||||
import org.springframework.data.relational.core.conversion.BasicRelationalConverter;
|
||||
import org.springframework.data.relational.core.conversion.RelationalConverter;
|
||||
import org.springframework.data.relational.core.dialect.ArrayColumns;
|
||||
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
|
||||
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
|
||||
import org.springframework.data.util.ClassTypeInformation;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.r2dbc.core.Parameter;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
/**
|
||||
* Converter for R2DBC.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @author Oliver Drotbohm
|
||||
*/
|
||||
public class MappingR2dbcConverter extends BasicRelationalConverter implements R2dbcConverter {
|
||||
|
||||
/**
|
||||
* Creates a new {@link MappingR2dbcConverter} given {@link MappingContext}.
|
||||
*
|
||||
* @param context must not be {@literal null}.
|
||||
*/
|
||||
public MappingR2dbcConverter(
|
||||
MappingContext<? extends RelationalPersistentEntity<?>, ? extends RelationalPersistentProperty> context) {
|
||||
super(context, new R2dbcCustomConversions(R2dbcCustomConversions.STORE_CONVERSIONS, Collections.emptyList()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link MappingR2dbcConverter} given {@link MappingContext} and {@link CustomConversions}.
|
||||
*
|
||||
* @param context must not be {@literal null}.
|
||||
*/
|
||||
public MappingR2dbcConverter(
|
||||
MappingContext<? extends RelationalPersistentEntity<?>, ? extends RelationalPersistentProperty> context,
|
||||
CustomConversions conversions) {
|
||||
super(context, conversions);
|
||||
}
|
||||
|
||||
// ----------------------------------
|
||||
// Entity reading
|
||||
// ----------------------------------
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.convert.EntityReader#read(java.lang.Class, S)
|
||||
*/
|
||||
@Override
|
||||
public <R> R read(Class<R> type, Row row) {
|
||||
return read(type, row, null);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.r2dbc.convert.R2dbcConverter#read(java.lang.Class, io.r2dbc.spi.Row, io.r2dbc.spi.RowMetadata)
|
||||
*/
|
||||
@Override
|
||||
public <R> R read(Class<R> type, Row row, @Nullable RowMetadata metadata) {
|
||||
|
||||
TypeInformation<? extends R> typeInfo = ClassTypeInformation.from(type);
|
||||
Class<? extends R> rawType = typeInfo.getType();
|
||||
|
||||
if (Row.class.isAssignableFrom(rawType)) {
|
||||
return type.cast(row);
|
||||
}
|
||||
|
||||
if (getConversions().hasCustomReadTarget(Row.class, rawType)
|
||||
&& getConversionService().canConvert(Row.class, rawType)) {
|
||||
return getConversionService().convert(row, rawType);
|
||||
}
|
||||
|
||||
return read(getRequiredPersistentEntity(type), row, metadata);
|
||||
}
|
||||
|
||||
private <R> R read(RelationalPersistentEntity<R> entity, Row row, @Nullable RowMetadata metadata) {
|
||||
|
||||
R result = createInstance(row, metadata, "", entity);
|
||||
|
||||
if (entity.requiresPropertyPopulation()) {
|
||||
ConvertingPropertyAccessor<R> propertyAccessor = new ConvertingPropertyAccessor<>(
|
||||
entity.getPropertyAccessor(result), getConversionService());
|
||||
|
||||
for (RelationalPersistentProperty property : entity) {
|
||||
|
||||
if (entity.isConstructorArgument(property)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Object value = readFrom(row, metadata, property, "");
|
||||
|
||||
if (value != null) {
|
||||
propertyAccessor.setProperty(property, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Read a single value or a complete Entity from the {@link Row} passed as an argument.
|
||||
*
|
||||
* @param row the {@link Row} to extract the value from. Must not be {@literal null}.
|
||||
* @param metadata the {@link RowMetadata}. Can be {@literal null}.
|
||||
* @param property the {@link RelationalPersistentProperty} for which the value is intended. Must not be
|
||||
* {@literal null}.
|
||||
* @param prefix to be used for all column names accessed by this method. Must not be {@literal null}.
|
||||
* @return the value read from the {@link Row}. May be {@literal null}.
|
||||
*/
|
||||
@Nullable
|
||||
private Object readFrom(Row row, @Nullable RowMetadata metadata, RelationalPersistentProperty property,
|
||||
String prefix) {
|
||||
|
||||
String identifier = prefix + property.getColumnName().getReference();
|
||||
|
||||
try {
|
||||
|
||||
Object value = null;
|
||||
if (metadata == null || RowMetadataUtils.containsColumn(metadata, identifier)) {
|
||||
value = row.get(identifier);
|
||||
}
|
||||
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (getConversions().hasCustomReadTarget(value.getClass(), property.getType())) {
|
||||
return readValue(value, property.getTypeInformation());
|
||||
}
|
||||
|
||||
if (property.isEntity()) {
|
||||
return readEntityFrom(row, metadata, property);
|
||||
}
|
||||
|
||||
return readValue(value, property.getTypeInformation());
|
||||
|
||||
} catch (Exception o_O) {
|
||||
throw new MappingException(String.format("Could not read property %s from column %s!", property, identifier),
|
||||
o_O);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public Object readValue(@Nullable Object value, TypeInformation<?> type) {
|
||||
|
||||
if (null == value) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (getConversions().hasCustomReadTarget(value.getClass(), type.getType())) {
|
||||
return getConversionService().convert(value, type.getType());
|
||||
} else if (value instanceof Collection || value.getClass().isArray()) {
|
||||
return readCollectionOrArray(asCollection(value), type);
|
||||
} else {
|
||||
return getPotentiallyConvertedSimpleRead(value, type.getType());
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads the given value into a collection of the given {@link TypeInformation}.
|
||||
*
|
||||
* @param source must not be {@literal null}.
|
||||
* @param targetType must not be {@literal null}.
|
||||
* @return the converted {@link Collection} or array, will never be {@literal null}.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private Object readCollectionOrArray(Collection<?> source, TypeInformation<?> targetType) {
|
||||
|
||||
Assert.notNull(targetType, "Target type must not be null!");
|
||||
|
||||
Class<?> collectionType = targetType.isSubTypeOf(Collection.class) //
|
||||
? targetType.getType() //
|
||||
: List.class;
|
||||
|
||||
TypeInformation<?> componentType = targetType.getComponentType() != null //
|
||||
? targetType.getComponentType() //
|
||||
: ClassTypeInformation.OBJECT;
|
||||
Class<?> rawComponentType = componentType.getType();
|
||||
|
||||
Collection<Object> items = targetType.getType().isArray() //
|
||||
? new ArrayList<>(source.size()) //
|
||||
: CollectionFactory.createCollection(collectionType, rawComponentType, source.size());
|
||||
|
||||
if (source.isEmpty()) {
|
||||
return getPotentiallyConvertedSimpleRead(items, targetType.getType());
|
||||
}
|
||||
|
||||
for (Object element : source) {
|
||||
|
||||
if (!Object.class.equals(rawComponentType) && element instanceof Collection) {
|
||||
if (!rawComponentType.isArray() && !ClassUtils.isAssignable(Iterable.class, rawComponentType)) {
|
||||
throw new MappingException(String.format(
|
||||
"Cannot convert %1$s of type %2$s into an instance of %3$s! Implement a custom Converter<%2$s, %3$s> and register it with the CustomConversions",
|
||||
element, element.getClass(), rawComponentType));
|
||||
}
|
||||
}
|
||||
if (element instanceof List) {
|
||||
items.add(readCollectionOrArray((Collection<Object>) element, componentType));
|
||||
} else {
|
||||
items.add(getPotentiallyConvertedSimpleRead(element, rawComponentType));
|
||||
}
|
||||
}
|
||||
|
||||
return getPotentiallyConvertedSimpleRead(items, targetType.getType());
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether we have a custom conversion for the given simple object. Converts the given value if so, applies
|
||||
* {@link Enum} handling or returns the value as is.
|
||||
*
|
||||
* @param value
|
||||
* @param target must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
@Nullable
|
||||
@SuppressWarnings({ "rawtypes", "unchecked" })
|
||||
private Object getPotentiallyConvertedSimpleRead(@Nullable Object value, @Nullable Class<?> target) {
|
||||
|
||||
if (value == null || target == null || ClassUtils.isAssignableValue(target, value)) {
|
||||
return value;
|
||||
}
|
||||
|
||||
if (getConversions().hasCustomReadTarget(value.getClass(), target)) {
|
||||
return getConversionService().convert(value, target);
|
||||
}
|
||||
|
||||
if (Enum.class.isAssignableFrom(target)) {
|
||||
return Enum.valueOf((Class<Enum>) target, value.toString());
|
||||
}
|
||||
|
||||
return getConversionService().convert(value, target);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <S> S readEntityFrom(Row row, @Nullable RowMetadata metadata, PersistentProperty<?> property) {
|
||||
|
||||
String prefix = property.getName() + "_";
|
||||
|
||||
RelationalPersistentEntity<?> entity = getMappingContext().getRequiredPersistentEntity(property.getActualType());
|
||||
|
||||
if (entity.hasIdProperty()) {
|
||||
if (readFrom(row, metadata, entity.getRequiredIdProperty(), prefix) == null) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
Object instance = createInstance(row, metadata, prefix, entity);
|
||||
|
||||
if (entity.requiresPropertyPopulation()) {
|
||||
PersistentPropertyAccessor<?> accessor = entity.getPropertyAccessor(instance);
|
||||
ConvertingPropertyAccessor<?> propertyAccessor = new ConvertingPropertyAccessor<>(accessor,
|
||||
getConversionService());
|
||||
|
||||
for (RelationalPersistentProperty p : entity) {
|
||||
if (!entity.isConstructorArgument(property)) {
|
||||
propertyAccessor.setProperty(p, readFrom(row, metadata, p, prefix));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (S) instance;
|
||||
}
|
||||
|
||||
private <S> S createInstance(Row row, @Nullable RowMetadata rowMetadata, String prefix,
|
||||
RelationalPersistentEntity<S> entity) {
|
||||
|
||||
PreferredConstructor<S, RelationalPersistentProperty> persistenceConstructor = entity.getPersistenceConstructor();
|
||||
ParameterValueProvider<RelationalPersistentProperty> provider;
|
||||
|
||||
if (persistenceConstructor != null && persistenceConstructor.hasParameters()) {
|
||||
|
||||
SpELContext spELContext = new SpELContext(new RowPropertyAccessor(rowMetadata));
|
||||
SpELExpressionEvaluator expressionEvaluator = new DefaultSpELExpressionEvaluator(row, spELContext);
|
||||
provider = new SpELExpressionParameterValueProvider<>(expressionEvaluator, getConversionService(),
|
||||
new RowParameterValueProvider(row, rowMetadata, entity, this, prefix));
|
||||
} else {
|
||||
provider = NoOpParameterValueProvider.INSTANCE;
|
||||
}
|
||||
|
||||
return createInstance(entity, provider::getParameterValue);
|
||||
}
|
||||
|
||||
// ----------------------------------
|
||||
// Entity writing
|
||||
// ----------------------------------
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.convert.EntityWriter#write(java.lang.Object, java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public void write(Object source, OutboundRow sink) {
|
||||
|
||||
Class<?> userClass = ClassUtils.getUserClass(source);
|
||||
|
||||
Optional<Class<?>> customTarget = getConversions().getCustomWriteTarget(userClass, OutboundRow.class);
|
||||
if (customTarget.isPresent()) {
|
||||
|
||||
OutboundRow result = getConversionService().convert(source, OutboundRow.class);
|
||||
sink.putAll(result);
|
||||
return;
|
||||
}
|
||||
|
||||
writeInternal(source, sink, userClass);
|
||||
}
|
||||
|
||||
private void writeInternal(Object source, OutboundRow sink, Class<?> userClass) {
|
||||
|
||||
RelationalPersistentEntity<?> entity = getRequiredPersistentEntity(userClass);
|
||||
PersistentPropertyAccessor<?> propertyAccessor = entity.getPropertyAccessor(source);
|
||||
|
||||
writeProperties(sink, entity, propertyAccessor, entity.isNew(source));
|
||||
}
|
||||
|
||||
private void writeProperties(OutboundRow sink, RelationalPersistentEntity<?> entity,
|
||||
PersistentPropertyAccessor<?> accessor, boolean isNew) {
|
||||
|
||||
for (RelationalPersistentProperty property : entity) {
|
||||
|
||||
if (!property.isWritable()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Object value;
|
||||
|
||||
if (property.isIdProperty()) {
|
||||
IdentifierAccessor identifierAccessor = entity.getIdentifierAccessor(accessor.getBean());
|
||||
value = identifierAccessor.getIdentifier();
|
||||
} else {
|
||||
value = accessor.getProperty(property);
|
||||
}
|
||||
|
||||
if (value == null) {
|
||||
writeNullInternal(sink, property);
|
||||
continue;
|
||||
}
|
||||
|
||||
if (getConversions().isSimpleType(value.getClass())) {
|
||||
writeSimpleInternal(sink, value, isNew, property);
|
||||
} else {
|
||||
writePropertyInternal(sink, value, isNew, property);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void writeSimpleInternal(OutboundRow sink, Object value, boolean isNew,
|
||||
RelationalPersistentProperty property) {
|
||||
|
||||
Object result = getPotentiallyConvertedSimpleWrite(value);
|
||||
|
||||
sink.put(property.getColumnName(),
|
||||
Parameter.fromOrEmpty(result, getPotentiallyConvertedSimpleNullType(property.getType())));
|
||||
}
|
||||
|
||||
private void writePropertyInternal(OutboundRow sink, Object value, boolean isNew,
|
||||
RelationalPersistentProperty property) {
|
||||
|
||||
TypeInformation<?> valueType = ClassTypeInformation.from(value.getClass());
|
||||
|
||||
if (valueType.isCollectionLike()) {
|
||||
|
||||
if (valueType.getActualType() != null && valueType.getRequiredActualType().isCollectionLike()) {
|
||||
|
||||
// pass-thru nested collections
|
||||
writeSimpleInternal(sink, value, isNew, property);
|
||||
return;
|
||||
}
|
||||
|
||||
List<Object> collectionInternal = createCollection(asCollection(value), property);
|
||||
sink.put(property.getColumnName(), Parameter.from(collectionInternal));
|
||||
return;
|
||||
}
|
||||
|
||||
throw new InvalidDataAccessApiUsageException("Nested entities are not supported");
|
||||
}
|
||||
|
||||
/**
|
||||
* Writes the given {@link Collection} using the given {@link RelationalPersistentProperty} information.
|
||||
*
|
||||
* @param collection must not be {@literal null}.
|
||||
* @param property must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
protected List<Object> createCollection(Collection<?> collection, RelationalPersistentProperty property) {
|
||||
return writeCollectionInternal(collection, property.getTypeInformation(), new ArrayList<>());
|
||||
}
|
||||
|
||||
/**
|
||||
* Populates the given {@link Collection sink} with converted values from the given {@link Collection source}.
|
||||
*
|
||||
* @param source the collection to create a {@link Collection} for, must not be {@literal null}.
|
||||
* @param type the {@link TypeInformation} to consider or {@literal null} if unknown.
|
||||
* @param sink the {@link Collection} to write to.
|
||||
* @return
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
private List<Object> writeCollectionInternal(Collection<?> source, @Nullable TypeInformation<?> type,
|
||||
Collection<?> sink) {
|
||||
|
||||
TypeInformation<?> componentType = null;
|
||||
|
||||
List<Object> collection = sink instanceof List ? (List<Object>) sink : new ArrayList<>(sink);
|
||||
|
||||
if (type != null) {
|
||||
componentType = type.getComponentType();
|
||||
}
|
||||
|
||||
for (Object element : source) {
|
||||
|
||||
Class<?> elementType = element == null ? null : element.getClass();
|
||||
|
||||
if (elementType == null || getConversions().isSimpleType(elementType)) {
|
||||
collection.add(getPotentiallyConvertedSimpleWrite(element,
|
||||
componentType != null ? componentType.getType() : Object.class));
|
||||
} else if (element instanceof Collection || elementType.isArray()) {
|
||||
collection.add(writeCollectionInternal(asCollection(element), componentType, new ArrayList<>()));
|
||||
} else {
|
||||
throw new InvalidDataAccessApiUsageException("Nested entities are not supported");
|
||||
}
|
||||
}
|
||||
|
||||
return collection;
|
||||
}
|
||||
|
||||
private void writeNullInternal(OutboundRow sink, RelationalPersistentProperty property) {
|
||||
|
||||
sink.put(property.getColumnName(), Parameter.empty(getPotentiallyConvertedSimpleNullType(property.getType())));
|
||||
}
|
||||
|
||||
private Class<?> getPotentiallyConvertedSimpleNullType(Class<?> type) {
|
||||
|
||||
Optional<Class<?>> customTarget = getConversions().getCustomWriteTarget(type);
|
||||
|
||||
if (customTarget.isPresent()) {
|
||||
return customTarget.get();
|
||||
|
||||
}
|
||||
|
||||
if (type.isEnum()) {
|
||||
return String.class;
|
||||
}
|
||||
|
||||
return type;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether we have a custom conversion registered for the given value into an arbitrary simple type. Returns
|
||||
* the converted value if so. If not, we perform special enum handling or simply return the value as is.
|
||||
*
|
||||
* @param value
|
||||
* @return
|
||||
*/
|
||||
@Nullable
|
||||
private Object getPotentiallyConvertedSimpleWrite(@Nullable Object value) {
|
||||
return getPotentiallyConvertedSimpleWrite(value, Object.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether we have a custom conversion registered for the given value into an arbitrary simple type. Returns
|
||||
* the converted value if so. If not, we perform special enum handling or simply return the value as is.
|
||||
*
|
||||
* @param value
|
||||
* @return
|
||||
*/
|
||||
@Nullable
|
||||
private Object getPotentiallyConvertedSimpleWrite(@Nullable Object value, Class<?> typeHint) {
|
||||
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (Object.class != typeHint) {
|
||||
|
||||
if (getConversionService().canConvert(value.getClass(), typeHint)) {
|
||||
value = getConversionService().convert(value, typeHint);
|
||||
}
|
||||
}
|
||||
|
||||
Optional<Class<?>> customTarget = getConversions().getCustomWriteTarget(value.getClass());
|
||||
|
||||
if (customTarget.isPresent()) {
|
||||
return getConversionService().convert(value, customTarget.get());
|
||||
}
|
||||
|
||||
return Enum.class.isAssignableFrom(value.getClass()) ? ((Enum<?>) value).name() : value;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.r2dbc.convert.R2dbcConverter#getArrayValue(org.springframework.data.r2dbc.dialect.ArrayColumns, org.springframework.data.relational.core.mapping.RelationalPersistentProperty, java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public Object getArrayValue(ArrayColumns arrayColumns, RelationalPersistentProperty property, Object value) {
|
||||
|
||||
Class<?> actualType = null;
|
||||
if (value instanceof Collection) {
|
||||
actualType = CollectionUtils.findCommonElementType((Collection<?>) value);
|
||||
} else if (value.getClass().isArray()) {
|
||||
actualType = value.getClass().getComponentType();
|
||||
}
|
||||
|
||||
if (actualType == null) {
|
||||
actualType = property.getActualType();
|
||||
}
|
||||
|
||||
Class<?> targetType = arrayColumns.getArrayType(actualType);
|
||||
|
||||
if (!property.isArray() || !targetType.isAssignableFrom(value.getClass())) {
|
||||
|
||||
int depth = value.getClass().isArray() ? ArrayUtils.getDimensionDepth(value.getClass()) : 1;
|
||||
Class<?> targetArrayType = ArrayUtils.getArrayClass(targetType, depth);
|
||||
return getConversionService().convert(value, targetArrayType);
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.r2dbc.convert.R2dbcConverter#getTargetType(Class)
|
||||
*/
|
||||
@Override
|
||||
public Class<?> getTargetType(Class<?> valueType) {
|
||||
|
||||
Optional<Class<?>> writeTarget = getConversions().getCustomWriteTarget(valueType);
|
||||
|
||||
return writeTarget.orElseGet(() -> {
|
||||
return Enum.class.isAssignableFrom(valueType) ? String.class : valueType;
|
||||
});
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.r2dbc.convert.R2dbcConverter#isSimpleType(Class)
|
||||
*/
|
||||
@Override
|
||||
public boolean isSimpleType(Class<?> type) {
|
||||
return getConversions().isSimpleType(type);
|
||||
}
|
||||
|
||||
// ----------------------------------
|
||||
// Id handling
|
||||
// ----------------------------------
|
||||
|
||||
/**
|
||||
* Returns a {@link java.util.function.Function} that populates the id property of the {@code object} from a
|
||||
* {@link Row}.
|
||||
*
|
||||
* @param object must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> BiFunction<Row, RowMetadata, T> populateIdIfNecessary(T object) {
|
||||
|
||||
Assert.notNull(object, "Entity object must not be null!");
|
||||
|
||||
Class<?> userClass = ClassUtils.getUserClass(object);
|
||||
RelationalPersistentEntity<?> entity = getMappingContext().getRequiredPersistentEntity(userClass);
|
||||
|
||||
if (!entity.hasIdProperty()) {
|
||||
return (row, rowMetadata) -> object;
|
||||
}
|
||||
|
||||
return (row, metadata) -> {
|
||||
|
||||
PersistentPropertyAccessor<?> propertyAccessor = entity.getPropertyAccessor(object);
|
||||
RelationalPersistentProperty idProperty = entity.getRequiredIdProperty();
|
||||
|
||||
boolean idPropertyUpdateNeeded = false;
|
||||
|
||||
Object id = propertyAccessor.getProperty(idProperty);
|
||||
if (idProperty.getType().isPrimitive()) {
|
||||
idPropertyUpdateNeeded = id instanceof Number && ((Number) id).longValue() == 0;
|
||||
} else {
|
||||
idPropertyUpdateNeeded = id == null;
|
||||
}
|
||||
|
||||
if (idPropertyUpdateNeeded) {
|
||||
return potentiallySetId(row, metadata, propertyAccessor, idProperty) //
|
||||
? (T) propertyAccessor.getBean() //
|
||||
: object;
|
||||
}
|
||||
|
||||
return object;
|
||||
};
|
||||
}
|
||||
|
||||
private boolean potentiallySetId(Row row, RowMetadata metadata, PersistentPropertyAccessor<?> propertyAccessor,
|
||||
RelationalPersistentProperty idProperty) {
|
||||
|
||||
String idColumnName = idProperty.getColumnName().getReference();
|
||||
Object generatedIdValue = extractGeneratedIdentifier(row, metadata, idColumnName);
|
||||
|
||||
if (generatedIdValue == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
ConversionService conversionService = getConversionService();
|
||||
propertyAccessor.setProperty(idProperty, conversionService.convert(generatedIdValue, idProperty.getType()));
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Object extractGeneratedIdentifier(Row row, RowMetadata metadata, String idColumnName) {
|
||||
|
||||
if (RowMetadataUtils.containsColumn(metadata, idColumnName)) {
|
||||
return row.get(idColumnName);
|
||||
}
|
||||
|
||||
Iterable<? extends ColumnMetadata> columns = RowMetadataUtils.getColumnMetadata(metadata);
|
||||
Iterator<? extends ColumnMetadata> it = columns.iterator();
|
||||
|
||||
if (it.hasNext()) {
|
||||
ColumnMetadata column = it.next();
|
||||
return row.get(column.getName());
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private <R> RelationalPersistentEntity<R> getRequiredPersistentEntity(Class<R> type) {
|
||||
return (RelationalPersistentEntity<R>) getMappingContext().getRequiredPersistentEntity(type);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns given object as {@link Collection}. Will return the {@link Collection} as is if the source is a
|
||||
* {@link Collection} already, will convert an array into a {@link Collection} or simply create a single element
|
||||
* collection for everything else.
|
||||
*
|
||||
* @param source
|
||||
* @return
|
||||
*/
|
||||
private static Collection<?> asCollection(Object source) {
|
||||
|
||||
if (source instanceof Collection) {
|
||||
return (Collection<?>) source;
|
||||
}
|
||||
|
||||
return source.getClass().isArray() ? CollectionUtils.arrayToList(source) : Collections.singleton(source);
|
||||
}
|
||||
|
||||
enum NoOpParameterValueProvider implements ParameterValueProvider<RelationalPersistentProperty> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public <T> T getParameterValue(
|
||||
org.springframework.data.mapping.Parameter<T, RelationalPersistentProperty> parameter) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private class RowParameterValueProvider implements ParameterValueProvider<RelationalPersistentProperty> {
|
||||
|
||||
private final Row resultSet;
|
||||
private final RowMetadata metadata;
|
||||
private final RelationalPersistentEntity<?> entity;
|
||||
private final RelationalConverter converter;
|
||||
private final String prefix;
|
||||
|
||||
public RowParameterValueProvider(Row resultSet, RowMetadata metadata, RelationalPersistentEntity<?> entity,
|
||||
RelationalConverter converter, String prefix) {
|
||||
this.resultSet = resultSet;
|
||||
this.metadata = metadata;
|
||||
this.entity = entity;
|
||||
this.converter = converter;
|
||||
this.prefix = prefix;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mapping.model.ParameterValueProvider#getParameterValue(org.springframework.data.mapping.PreferredConstructor.Parameter)
|
||||
*/
|
||||
@Override
|
||||
@Nullable
|
||||
public <T> T getParameterValue(
|
||||
org.springframework.data.mapping.Parameter<T, RelationalPersistentProperty> parameter) {
|
||||
|
||||
RelationalPersistentProperty property = this.entity.getRequiredPersistentProperty(parameter.getName());
|
||||
Object value = readFrom(this.resultSet, this.metadata, property, this.prefix);
|
||||
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Class<T> type = parameter.getType().getType();
|
||||
|
||||
if (type.isInstance(value)) {
|
||||
return type.cast(value);
|
||||
}
|
||||
|
||||
try {
|
||||
return this.converter.getConversionService().convert(value, type);
|
||||
} catch (Exception o_O) {
|
||||
throw new MappingException(String.format("Couldn't read parameter %s.", parameter.getName()), o_O);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,105 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019-2022 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
|
||||
*
|
||||
* https://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.r2dbc.convert;
|
||||
|
||||
import io.r2dbc.spi.Row;
|
||||
import io.r2dbc.spi.RowMetadata;
|
||||
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.data.convert.EntityReader;
|
||||
import org.springframework.data.convert.EntityWriter;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.r2dbc.mapping.OutboundRow;
|
||||
import org.springframework.data.relational.core.conversion.RelationalConverter;
|
||||
import org.springframework.data.relational.core.dialect.ArrayColumns;
|
||||
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
|
||||
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
|
||||
|
||||
/**
|
||||
* Central R2DBC specific converter interface.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @see EntityReader
|
||||
*/
|
||||
public interface R2dbcConverter
|
||||
extends EntityReader<Object, Row>, EntityWriter<Object, OutboundRow>, RelationalConverter {
|
||||
|
||||
/**
|
||||
* Returns the underlying {@link MappingContext} used by the converter.
|
||||
*
|
||||
* @return never {@literal null}
|
||||
*/
|
||||
MappingContext<? extends RelationalPersistentEntity<?>, ? extends RelationalPersistentProperty> getMappingContext();
|
||||
|
||||
/**
|
||||
* Returns the underlying {@link ConversionService} used by the converter.
|
||||
*
|
||||
* @return never {@literal null}.
|
||||
*/
|
||||
ConversionService getConversionService();
|
||||
|
||||
/**
|
||||
* Convert a {@code value} into an array representation according to {@link ArrayColumns}.
|
||||
*
|
||||
* @param arrayColumns dialect-specific array handling configuration.
|
||||
* @param property
|
||||
* @param value
|
||||
* @return
|
||||
*/
|
||||
Object getArrayValue(ArrayColumns arrayColumns, RelationalPersistentProperty property, Object value);
|
||||
|
||||
/**
|
||||
* Return the target type for a value considering registered converters.
|
||||
*
|
||||
* @param valueType must not be {@literal null}.
|
||||
* @return
|
||||
* @since 1.1
|
||||
*/
|
||||
Class<?> getTargetType(Class<?> valueType);
|
||||
|
||||
/**
|
||||
* Return whether the {@code type} is a simple type. Simple types are database primitives or types with a custom
|
||||
* mapping strategy.
|
||||
*
|
||||
* @param type the type to inspect, must not be {@literal null}.
|
||||
* @return {@literal true} if the type is a simple one.
|
||||
* @see org.springframework.data.mapping.model.SimpleTypeHolder
|
||||
* @since 1.2
|
||||
*/
|
||||
boolean isSimpleType(Class<?> type);
|
||||
|
||||
/**
|
||||
* Returns a {@link java.util.function.Function} that populates the id property of the {@code object} from a
|
||||
* {@link Row}.
|
||||
*
|
||||
* @param object must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
<T> BiFunction<Row, RowMetadata, T> populateIdIfNecessary(T object);
|
||||
|
||||
/**
|
||||
* Reads the given source into the given type.
|
||||
*
|
||||
* @param type they type to convert the given source to.
|
||||
* @param source the source to create an object of the given type from.
|
||||
* @param metadata the {@link RowMetadata}.
|
||||
* @return
|
||||
*/
|
||||
<R> R read(Class<R> type, Row source, RowMetadata metadata);
|
||||
|
||||
}
|
||||
@@ -1,303 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019-2022 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
|
||||
*
|
||||
* https://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.r2dbc.convert;
|
||||
|
||||
import io.r2dbc.spi.Row;
|
||||
|
||||
import java.time.LocalDate;
|
||||
import java.time.LocalDateTime;
|
||||
import java.time.LocalTime;
|
||||
import java.time.OffsetDateTime;
|
||||
import java.time.ZonedDateTime;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.core.convert.converter.ConverterFactory;
|
||||
import org.springframework.data.convert.CustomConversions;
|
||||
import org.springframework.data.convert.Jsr310Converters;
|
||||
import org.springframework.data.convert.WritingConverter;
|
||||
import org.springframework.data.r2dbc.convert.R2dbcConverters.RowToNumberConverterFactory.LocalDateConverterOverride;
|
||||
import org.springframework.data.r2dbc.convert.R2dbcConverters.RowToNumberConverterFactory.LocalDateTimeConverterOverride;
|
||||
import org.springframework.data.r2dbc.convert.R2dbcConverters.RowToNumberConverterFactory.LocalTimeConverterOverride;
|
||||
import org.springframework.data.r2dbc.convert.R2dbcConverters.RowToNumberConverterFactory.RowToOffsetDateTimeConverter;
|
||||
import org.springframework.data.r2dbc.convert.R2dbcConverters.RowToNumberConverterFactory.RowToStringConverter;
|
||||
import org.springframework.data.r2dbc.convert.R2dbcConverters.RowToNumberConverterFactory.RowToUuidConverter;
|
||||
import org.springframework.data.r2dbc.convert.R2dbcConverters.RowToNumberConverterFactory.RowToZonedDateTimeConverter;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.NumberUtils;
|
||||
|
||||
/**
|
||||
* Wrapper class to contain useful converters for the usage with R2DBC.
|
||||
*
|
||||
* @author Hebert Coelho
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
abstract class R2dbcConverters {
|
||||
|
||||
private R2dbcConverters() {}
|
||||
|
||||
/**
|
||||
* @return A list of the registered converters
|
||||
*/
|
||||
public static Collection<Object> getConvertersToRegister() {
|
||||
|
||||
List<Object> converters = new ArrayList<>();
|
||||
|
||||
converters.add(RowToBooleanConverter.INSTANCE);
|
||||
converters.add(RowToNumberConverterFactory.INSTANCE);
|
||||
converters.add(RowToLocalDateConverter.INSTANCE);
|
||||
converters.add(RowToLocalDateTimeConverter.INSTANCE);
|
||||
converters.add(RowToLocalTimeConverter.INSTANCE);
|
||||
converters.add(RowToOffsetDateTimeConverter.INSTANCE);
|
||||
converters.add(RowToStringConverter.INSTANCE);
|
||||
converters.add(RowToUuidConverter.INSTANCE);
|
||||
converters.add(RowToZonedDateTimeConverter.INSTANCE);
|
||||
|
||||
return converters;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return A list of the registered converters to enforce JSR-310 type usage.
|
||||
* @see CustomConversions#DEFAULT_CONVERTERS
|
||||
* @see Jsr310Converters
|
||||
*/
|
||||
public static Collection<Object> getOverrideConvertersToRegister() {
|
||||
|
||||
List<Object> converters = new ArrayList<>();
|
||||
|
||||
converters.add(LocalDateConverterOverride.INSTANCE);
|
||||
converters.add(LocalDateTimeConverterOverride.INSTANCE);
|
||||
converters.add(LocalTimeConverterOverride.INSTANCE);
|
||||
|
||||
return converters;
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple singleton to convert {@link Row}s to their {@link Boolean} representation.
|
||||
*
|
||||
* @author Hebert Coelho
|
||||
*/
|
||||
public enum RowToBooleanConverter implements Converter<Row, Boolean> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public Boolean convert(Row row) {
|
||||
return row.get(0, Boolean.class);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple singleton to convert {@link Row}s to their {@link LocalDate} representation.
|
||||
*
|
||||
* @author Hebert Coelho
|
||||
*/
|
||||
public enum RowToLocalDateConverter implements Converter<Row, LocalDate> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public LocalDate convert(Row row) {
|
||||
return row.get(0, LocalDate.class);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple singleton to convert {@link Row}s to their {@link LocalDateTime} representation.
|
||||
*
|
||||
* @author Hebert Coelho
|
||||
*/
|
||||
public enum RowToLocalDateTimeConverter implements Converter<Row, LocalDateTime> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public LocalDateTime convert(Row row) {
|
||||
return row.get(0, LocalDateTime.class);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple singleton to convert {@link Row}s to their {@link LocalTime} representation.
|
||||
*
|
||||
* @author Hebert Coelho
|
||||
*/
|
||||
public enum RowToLocalTimeConverter implements Converter<Row, LocalTime> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public LocalTime convert(Row row) {
|
||||
return row.get(0, LocalTime.class);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Singleton converter factory to convert the first column of a {@link Row} to a {@link Number}.
|
||||
* <p>
|
||||
* Support Number classes including Byte, Short, Integer, Float, Double, Long, BigInteger, BigDecimal. This class
|
||||
* delegates to {@link NumberUtils#convertNumberToTargetClass(Number, Class)} to perform the conversion.
|
||||
*
|
||||
* @see Byte
|
||||
* @see Short
|
||||
* @see Integer
|
||||
* @see Long
|
||||
* @see java.math.BigInteger
|
||||
* @see Float
|
||||
* @see Double
|
||||
* @see java.math.BigDecimal
|
||||
* @author Hebert Coelho
|
||||
*/
|
||||
public enum RowToNumberConverterFactory implements ConverterFactory<Row, Number> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public <T extends Number> Converter<Row, T> getConverter(Class<T> targetType) {
|
||||
Assert.notNull(targetType, "Target type must not be null");
|
||||
return new RowToNumber<>(targetType);
|
||||
}
|
||||
|
||||
static class RowToNumber<T extends Number> implements Converter<Row, T> {
|
||||
|
||||
private final Class<T> targetType;
|
||||
|
||||
RowToNumber(Class<T> targetType) {
|
||||
this.targetType = targetType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public T convert(Row source) {
|
||||
|
||||
Object object = source.get(0);
|
||||
|
||||
return (object != null ? NumberUtils.convertNumberToTargetClass((Number) object, this.targetType) : null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple singleton to convert {@link Row}s to their {@link OffsetDateTime} representation.
|
||||
*
|
||||
* @author Hebert Coelho
|
||||
*/
|
||||
public enum RowToOffsetDateTimeConverter implements Converter<Row, OffsetDateTime> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public OffsetDateTime convert(Row row) {
|
||||
return row.get(0, OffsetDateTime.class);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple singleton to convert {@link Row}s to their {@link String} representation.
|
||||
*
|
||||
* @author Hebert Coelho
|
||||
*/
|
||||
public enum RowToStringConverter implements Converter<Row, String> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public String convert(Row row) {
|
||||
return row.get(0, String.class);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple singleton to convert {@link Row}s to their {@link UUID} representation.
|
||||
*
|
||||
* @author Hebert Coelho
|
||||
*/
|
||||
public enum RowToUuidConverter implements Converter<Row, UUID> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public UUID convert(Row row) {
|
||||
return row.get(0, UUID.class);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple singleton to convert {@link Row}s to their {@link ZonedDateTime} representation.
|
||||
*
|
||||
* @author Hebert Coelho
|
||||
*/
|
||||
public enum RowToZonedDateTimeConverter implements Converter<Row, ZonedDateTime> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public ZonedDateTime convert(Row row) {
|
||||
return row.get(0, ZonedDateTime.class);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link Converter} override that forces {@link LocalDate} to stay on {@link LocalDate}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@WritingConverter
|
||||
public enum LocalDateConverterOverride implements Converter<LocalDate, LocalDate> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public LocalDate convert(LocalDate value) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link Converter} override that forces {@link LocalDateTime} to stay on {@link LocalDateTime}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@WritingConverter
|
||||
public enum LocalDateTimeConverterOverride implements Converter<LocalDateTime, LocalDateTime> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public LocalDateTime convert(LocalDateTime value) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link Converter} override that forces {@link LocalTime} to stay on {@link LocalTime}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@WritingConverter
|
||||
public enum LocalTimeConverterOverride implements Converter<LocalTime, LocalTime> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public LocalTime convert(LocalTime value) {
|
||||
return value;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,109 +0,0 @@
|
||||
package org.springframework.data.r2dbc.convert;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.data.convert.CustomConversions;
|
||||
import org.springframework.data.r2dbc.dialect.R2dbcDialect;
|
||||
import org.springframework.data.r2dbc.mapping.R2dbcSimpleTypeHolder;
|
||||
|
||||
/**
|
||||
* Value object to capture custom conversion. {@link R2dbcCustomConversions} also act as factory for
|
||||
* {@link org.springframework.data.mapping.model.SimpleTypeHolder}
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @see CustomConversions
|
||||
* @see org.springframework.data.mapping.model.SimpleTypeHolder
|
||||
*/
|
||||
public class R2dbcCustomConversions extends CustomConversions {
|
||||
|
||||
public static final List<Object> STORE_CONVERTERS;
|
||||
|
||||
public static final StoreConversions STORE_CONVERSIONS;
|
||||
|
||||
static {
|
||||
|
||||
List<Object> converters = new ArrayList<>(R2dbcConverters.getConvertersToRegister());
|
||||
|
||||
STORE_CONVERTERS = Collections.unmodifiableList(converters);
|
||||
STORE_CONVERSIONS = StoreConversions.of(R2dbcSimpleTypeHolder.HOLDER, STORE_CONVERTERS);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link R2dbcCustomConversions} instance registering the given converters.
|
||||
*
|
||||
* @param converters must not be {@literal null}.
|
||||
* @deprecated since 1.3, use {@link #of(R2dbcDialect, Object...)} or
|
||||
* {@link #R2dbcCustomConversions(StoreConversions, Collection)} directly to consider dialect-native
|
||||
* simple types. Use {@link CustomConversions.StoreConversions#NONE} to omit store-specific converters.
|
||||
*/
|
||||
@Deprecated
|
||||
public R2dbcCustomConversions(Collection<?> converters) {
|
||||
super(new R2dbcCustomConversionsConfiguration(STORE_CONVERSIONS, appendOverrides(converters)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link R2dbcCustomConversions} instance registering the given converters.
|
||||
*
|
||||
* @param storeConversions must not be {@literal null}.
|
||||
* @param converters must not be {@literal null}.
|
||||
*/
|
||||
public R2dbcCustomConversions(StoreConversions storeConversions, Collection<?> converters) {
|
||||
super(new R2dbcCustomConversionsConfiguration(storeConversions, appendOverrides(converters)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link R2dbcCustomConversions} from the given {@link R2dbcDialect} and {@code converters}.
|
||||
*
|
||||
* @param dialect must not be {@literal null}.
|
||||
* @param converters must not be {@literal null}.
|
||||
* @return the custom conversions object.
|
||||
* @since 1.2
|
||||
*/
|
||||
public static R2dbcCustomConversions of(R2dbcDialect dialect, Object... converters) {
|
||||
return of(dialect, Arrays.asList(converters));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link R2dbcCustomConversions} from the given {@link R2dbcDialect} and {@code converters}.
|
||||
*
|
||||
* @param dialect must not be {@literal null}.
|
||||
* @param converters must not be {@literal null}.
|
||||
* @return the custom conversions object.
|
||||
* @since 1.2
|
||||
*/
|
||||
public static R2dbcCustomConversions of(R2dbcDialect dialect, Collection<?> converters) {
|
||||
|
||||
List<Object> storeConverters = new ArrayList<>(dialect.getConverters());
|
||||
storeConverters.addAll(R2dbcCustomConversions.STORE_CONVERTERS);
|
||||
|
||||
return new R2dbcCustomConversions(StoreConversions.of(dialect.getSimpleTypeHolder(), storeConverters), converters);
|
||||
}
|
||||
|
||||
private static List<?> appendOverrides(Collection<?> converters) {
|
||||
|
||||
List<Object> objects = new ArrayList<>(converters);
|
||||
objects.addAll(R2dbcConverters.getOverrideConvertersToRegister());
|
||||
|
||||
return objects;
|
||||
}
|
||||
|
||||
static class R2dbcCustomConversionsConfiguration extends ConverterConfiguration {
|
||||
|
||||
public R2dbcCustomConversionsConfiguration(StoreConversions storeConversions, List<?> userConverters) {
|
||||
super(storeConversions, userConverters, convertiblePair -> {
|
||||
|
||||
if (convertiblePair.getSourceType().getName().startsWith("java.time.")
|
||||
&& convertiblePair.getTargetType().equals(Date.class)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,74 +0,0 @@
|
||||
/*
|
||||
* Copyright 2021-2022 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
|
||||
*
|
||||
* https://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.r2dbc.convert;
|
||||
|
||||
import io.r2dbc.spi.ColumnMetadata;
|
||||
import io.r2dbc.spi.RowMetadata;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.ReflectionUtils;
|
||||
|
||||
/**
|
||||
* Utility methods for {@link io.r2dbc.spi.RowMetadata}
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 1.3.7
|
||||
*/
|
||||
class RowMetadataUtils {
|
||||
|
||||
private static final @Nullable Method getColumnMetadatas = ReflectionUtils.findMethod(RowMetadata.class,
|
||||
"getColumnMetadatas");
|
||||
|
||||
/**
|
||||
* Check whether the column {@code name} is contained in {@link RowMetadata}. The check happens case-insensitive.
|
||||
*
|
||||
* @param metadata the metadata object to inspect.
|
||||
* @param name column name.
|
||||
* @return {@code true} if the metadata contains the column {@code name}.
|
||||
*/
|
||||
public static boolean containsColumn(RowMetadata metadata, String name) {
|
||||
|
||||
Iterable<? extends ColumnMetadata> columns = getColumnMetadata(metadata);
|
||||
|
||||
for (ColumnMetadata columnMetadata : columns) {
|
||||
if (name.equalsIgnoreCase(columnMetadata.getName())) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the {@link Iterable} of {@link ColumnMetadata} from {@link RowMetadata}.
|
||||
*
|
||||
* @param metadata the metadata object to inspect.
|
||||
* @return
|
||||
* @since 1.4.1
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public static Iterable<? extends ColumnMetadata> getColumnMetadata(RowMetadata metadata) {
|
||||
|
||||
if (getColumnMetadatas != null) {
|
||||
// Return type of RowMetadata.getColumnMetadatas was updated with R2DBC 0.9.
|
||||
return (Iterable<? extends ColumnMetadata>) ReflectionUtils.invokeMethod(getColumnMetadatas, metadata);
|
||||
}
|
||||
|
||||
return metadata.getColumnMetadatas();
|
||||
}
|
||||
}
|
||||
@@ -1,75 +0,0 @@
|
||||
/*
|
||||
* Copyright 2013-2022 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
|
||||
*
|
||||
* https://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.r2dbc.convert;
|
||||
|
||||
import io.r2dbc.spi.Row;
|
||||
import io.r2dbc.spi.RowMetadata;
|
||||
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.expression.PropertyAccessor;
|
||||
import org.springframework.expression.TypedValue;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* {@link PropertyAccessor} to read values from a {@link Row}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 1.2
|
||||
*/
|
||||
class RowPropertyAccessor implements PropertyAccessor {
|
||||
|
||||
private final @Nullable RowMetadata rowMetadata;
|
||||
|
||||
RowPropertyAccessor(@Nullable RowMetadata rowMetadata) {
|
||||
this.rowMetadata = rowMetadata;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?>[] getSpecificTargetClasses() {
|
||||
return new Class<?>[] { Row.class };
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canRead(EvaluationContext context, @Nullable Object target, String name) {
|
||||
return rowMetadata != null && target != null && RowMetadataUtils.containsColumn(rowMetadata, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypedValue read(EvaluationContext context, @Nullable Object target, String name) {
|
||||
|
||||
if (target == null) {
|
||||
return TypedValue.NULL;
|
||||
}
|
||||
|
||||
Object value = ((Row) target).get(name);
|
||||
|
||||
if (value == null) {
|
||||
return TypedValue.NULL;
|
||||
}
|
||||
|
||||
return new TypedValue(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean canWrite(EvaluationContext context, @Nullable Object target, String name) {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void write(EvaluationContext context, @Nullable Object target, String name, @Nullable Object newValue) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
/**
|
||||
* R2DBC-specific conversion and converter implementations.
|
||||
*/
|
||||
@org.springframework.lang.NonNullApi
|
||||
@org.springframework.lang.NonNullFields
|
||||
package org.springframework.data.r2dbc.convert;
|
||||
@@ -1,71 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019-2022 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
|
||||
*
|
||||
* https://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.r2dbc.core;
|
||||
|
||||
import org.springframework.data.util.Streamable;
|
||||
import org.springframework.lang.Nullable;
|
||||
|
||||
/**
|
||||
* Interface that defines common functionality for objects that can offer parameter values for named bind parameters,
|
||||
* serving as argument for {@link NamedParameterExpander} operations.
|
||||
* <p>
|
||||
* This interface allows for the specification of the type in addition to parameter values. All parameter values and
|
||||
* types are identified by specifying the name of the parameter.
|
||||
* <p>
|
||||
* Intended to wrap various implementations like a {@link java.util.Map} with a consistent interface.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @see MapBindParameterSource
|
||||
* @deprecated since 1.2, without replacement.
|
||||
*/
|
||||
@Deprecated
|
||||
interface BindParameterSource {
|
||||
|
||||
/**
|
||||
* Determine whether there is a value for the specified named parameter.
|
||||
*
|
||||
* @param paramName the name of the parameter.
|
||||
* @return {@literal true} if there is a value defined; {@literal false} otherwise.
|
||||
*/
|
||||
boolean hasValue(String paramName);
|
||||
|
||||
/**
|
||||
* Return the parameter value for the requested named parameter.
|
||||
*
|
||||
* @param paramName the name of the parameter.
|
||||
* @return the value of the specified parameter, can be {@literal null}.
|
||||
* @throws IllegalArgumentException if there is no value for the requested parameter.
|
||||
*/
|
||||
@Nullable
|
||||
Object getValue(String paramName) throws IllegalArgumentException;
|
||||
|
||||
/**
|
||||
* Determine the type for the specified named parameter.
|
||||
*
|
||||
* @param paramName the name of the parameter.
|
||||
* @return the type of the specified parameter, or {@link Object#getClass()} if not known.
|
||||
*/
|
||||
default Class<?> getType(String paramName) {
|
||||
return Object.class;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns parameter names of the underlying parameter source.
|
||||
*
|
||||
* @return parameter names of the underlying parameter source.
|
||||
*/
|
||||
Streamable<String> getParameterNames();
|
||||
}
|
||||
@@ -1,354 +0,0 @@
|
||||
/*
|
||||
* Copyright 2018-2022 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
|
||||
*
|
||||
* https://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.r2dbc.core;
|
||||
|
||||
import io.r2dbc.spi.Row;
|
||||
import io.r2dbc.spi.RowMetadata;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
import org.springframework.dao.InvalidDataAccessApiUsageException;
|
||||
import org.springframework.dao.InvalidDataAccessResourceUsageException;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.r2dbc.convert.EntityRowMapper;
|
||||
import org.springframework.data.r2dbc.convert.MappingR2dbcConverter;
|
||||
import org.springframework.data.r2dbc.convert.R2dbcConverter;
|
||||
import org.springframework.data.r2dbc.convert.R2dbcCustomConversions;
|
||||
import org.springframework.data.r2dbc.dialect.R2dbcDialect;
|
||||
import org.springframework.data.r2dbc.mapping.OutboundRow;
|
||||
import org.springframework.data.r2dbc.mapping.R2dbcMappingContext;
|
||||
import org.springframework.data.r2dbc.query.UpdateMapper;
|
||||
import org.springframework.data.r2dbc.support.ArrayUtils;
|
||||
import org.springframework.data.relational.core.dialect.ArrayColumns;
|
||||
import org.springframework.data.relational.core.dialect.RenderContextFactory;
|
||||
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
|
||||
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
|
||||
import org.springframework.data.relational.core.sql.SqlIdentifier;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.r2dbc.core.Parameter;
|
||||
import org.springframework.r2dbc.core.PreparedOperation;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
||||
/**
|
||||
* Default {@link ReactiveDataAccessStrategy} implementation.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @author Louis Morgan
|
||||
* @author Jens Schauder
|
||||
*/
|
||||
public class DefaultReactiveDataAccessStrategy implements ReactiveDataAccessStrategy {
|
||||
|
||||
private final R2dbcDialect dialect;
|
||||
private final R2dbcConverter converter;
|
||||
private final UpdateMapper updateMapper;
|
||||
private final MappingContext<RelationalPersistentEntity<?>, ? extends RelationalPersistentProperty> mappingContext;
|
||||
private final StatementMapper statementMapper;
|
||||
private final NamedParameterExpander expander = new NamedParameterExpander();
|
||||
|
||||
/**
|
||||
* Creates a new {@link DefaultReactiveDataAccessStrategy} given {@link R2dbcDialect} and optional
|
||||
* {@link org.springframework.core.convert.converter.Converter}s.
|
||||
*
|
||||
* @param dialect the {@link R2dbcDialect} to use.
|
||||
*/
|
||||
public DefaultReactiveDataAccessStrategy(R2dbcDialect dialect) {
|
||||
this(dialect, Collections.emptyList());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link DefaultReactiveDataAccessStrategy} given {@link R2dbcDialect} and optional
|
||||
* {@link org.springframework.core.convert.converter.Converter}s.
|
||||
*
|
||||
* @param dialect the {@link R2dbcDialect} to use.
|
||||
* @param converters custom converters to register, must not be {@literal null}.
|
||||
* @see R2dbcCustomConversions
|
||||
* @see org.springframework.core.convert.converter.Converter
|
||||
*/
|
||||
public DefaultReactiveDataAccessStrategy(R2dbcDialect dialect, Collection<?> converters) {
|
||||
this(dialect, createConverter(dialect, converters));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link R2dbcConverter} given {@link R2dbcDialect} and custom {@code converters}.
|
||||
*
|
||||
* @param dialect must not be {@literal null}.
|
||||
* @param converters must not be {@literal null}.
|
||||
* @return the {@link R2dbcConverter}.
|
||||
*/
|
||||
public static R2dbcConverter createConverter(R2dbcDialect dialect, Collection<?> converters) {
|
||||
|
||||
Assert.notNull(dialect, "Dialect must not be null");
|
||||
Assert.notNull(converters, "Converters must not be null");
|
||||
|
||||
R2dbcCustomConversions customConversions = R2dbcCustomConversions.of(dialect, converters);
|
||||
|
||||
R2dbcMappingContext context = new R2dbcMappingContext();
|
||||
context.setSimpleTypeHolder(customConversions.getSimpleTypeHolder());
|
||||
|
||||
return new MappingR2dbcConverter(context, customConversions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link DefaultReactiveDataAccessStrategy} given {@link R2dbcDialect} and {@link R2dbcConverter}.
|
||||
*
|
||||
* @param dialect the {@link R2dbcDialect} to use.
|
||||
* @param converter must not be {@literal null}.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public DefaultReactiveDataAccessStrategy(R2dbcDialect dialect, R2dbcConverter converter) {
|
||||
|
||||
Assert.notNull(dialect, "Dialect must not be null");
|
||||
Assert.notNull(converter, "RelationalConverter must not be null");
|
||||
|
||||
this.converter = converter;
|
||||
this.updateMapper = new UpdateMapper(dialect, converter);
|
||||
this.mappingContext = (MappingContext<RelationalPersistentEntity<?>, ? extends RelationalPersistentProperty>) this.converter
|
||||
.getMappingContext();
|
||||
this.dialect = dialect;
|
||||
|
||||
RenderContextFactory factory = new RenderContextFactory(dialect);
|
||||
this.statementMapper = new DefaultStatementMapper(dialect, factory.createRenderContext(), this.updateMapper,
|
||||
this.mappingContext);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.r2dbc.function.ReactiveDataAccessStrategy#getAllColumns(java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public List<SqlIdentifier> getAllColumns(Class<?> entityType) {
|
||||
|
||||
RelationalPersistentEntity<?> persistentEntity = getPersistentEntity(entityType);
|
||||
|
||||
if (persistentEntity == null) {
|
||||
return Collections.singletonList(SqlIdentifier.unquoted("*"));
|
||||
}
|
||||
|
||||
List<SqlIdentifier> columnNames = new ArrayList<>();
|
||||
for (RelationalPersistentProperty property : persistentEntity) {
|
||||
columnNames.add(property.getColumnName());
|
||||
}
|
||||
|
||||
return columnNames;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.r2dbc.function.ReactiveDataAccessStrategy#getIdentifierColumns(java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public List<SqlIdentifier> getIdentifierColumns(Class<?> entityType) {
|
||||
|
||||
RelationalPersistentEntity<?> persistentEntity = getRequiredPersistentEntity(entityType);
|
||||
|
||||
List<SqlIdentifier> columnNames = new ArrayList<>();
|
||||
for (RelationalPersistentProperty property : persistentEntity) {
|
||||
|
||||
if (property.isIdProperty()) {
|
||||
columnNames.add(property.getColumnName());
|
||||
}
|
||||
}
|
||||
|
||||
return columnNames;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.r2dbc.function.ReactiveDataAccessStrategy#getOutboundRow(java.lang.Object)
|
||||
*/
|
||||
public OutboundRow getOutboundRow(Object object) {
|
||||
|
||||
Assert.notNull(object, "Entity object must not be null!");
|
||||
|
||||
OutboundRow row = new OutboundRow();
|
||||
|
||||
this.converter.write(object, row);
|
||||
|
||||
RelationalPersistentEntity<?> entity = getRequiredPersistentEntity(ClassUtils.getUserClass(object));
|
||||
|
||||
for (RelationalPersistentProperty property : entity) {
|
||||
|
||||
Parameter value = row.get(property.getColumnName());
|
||||
if (value != null && shouldConvertArrayValue(property, value)) {
|
||||
|
||||
Parameter writeValue = getArrayValue(value, property);
|
||||
row.put(property.getColumnName(), writeValue);
|
||||
}
|
||||
}
|
||||
|
||||
return row;
|
||||
}
|
||||
|
||||
private boolean shouldConvertArrayValue(RelationalPersistentProperty property, Parameter value) {
|
||||
|
||||
if (!property.isCollectionLike()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (value.hasValue() && (value.getValue() instanceof Collection || value.getValue().getClass().isArray())) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (Collection.class.isAssignableFrom(value.getType()) || value.getType().isArray()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private Parameter getArrayValue(Parameter value, RelationalPersistentProperty property) {
|
||||
|
||||
if (value.getType().equals(byte[].class)) {
|
||||
return value;
|
||||
}
|
||||
|
||||
ArrayColumns arrayColumns = this.dialect.getArraySupport();
|
||||
|
||||
if (!arrayColumns.isSupported()) {
|
||||
|
||||
throw new InvalidDataAccessResourceUsageException(
|
||||
"Dialect " + this.dialect.getClass().getName() + " does not support array columns");
|
||||
}
|
||||
|
||||
Class<?> actualType = null;
|
||||
if (value.getValue() instanceof Collection) {
|
||||
actualType = CollectionUtils.findCommonElementType((Collection<?>) value.getValue());
|
||||
} else if (!value.isEmpty() && value.getValue().getClass().isArray()) {
|
||||
actualType = value.getValue().getClass().getComponentType();
|
||||
}
|
||||
|
||||
if (actualType == null) {
|
||||
actualType = property.getActualType();
|
||||
}
|
||||
|
||||
actualType = converter.getTargetType(actualType);
|
||||
|
||||
if (value.isEmpty()) {
|
||||
|
||||
Class<?> targetType = arrayColumns.getArrayType(actualType);
|
||||
int depth = actualType.isArray() ? ArrayUtils.getDimensionDepth(actualType) : 1;
|
||||
Class<?> targetArrayType = ArrayUtils.getArrayClass(targetType, depth);
|
||||
return Parameter.empty(targetArrayType);
|
||||
}
|
||||
|
||||
return Parameter.fromOrEmpty(this.converter.getArrayValue(arrayColumns, property, value.getValue()),
|
||||
actualType);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.r2dbc.function.ReactiveDataAccessStrategy#getBindValue(Parameter)
|
||||
*/
|
||||
@Override
|
||||
public Parameter getBindValue(Parameter value) {
|
||||
return this.updateMapper.getBindValue(value);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.r2dbc.function.ReactiveDataAccessStrategy#getRowMapper(java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public <T> BiFunction<Row, RowMetadata, T> getRowMapper(Class<T> typeToRead) {
|
||||
return new EntityRowMapper<>(typeToRead, this.converter);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.r2dbc.core.ReactiveDataAccessStrategy#processNamedParameters(java.lang.String, org.springframework.data.r2dbc.core.ReactiveDataAccessStrategy.NamedParameterProvider)
|
||||
*/
|
||||
@Override
|
||||
public PreparedOperation<?> processNamedParameters(String query, NamedParameterProvider parameterProvider) {
|
||||
|
||||
List<String> parameterNames = this.expander.getParameterNames(query);
|
||||
|
||||
Map<String, Parameter> namedBindings = new LinkedHashMap<>(parameterNames.size());
|
||||
for (String parameterName : parameterNames) {
|
||||
|
||||
Parameter value = parameterProvider.getParameter(parameterNames.indexOf(parameterName), parameterName);
|
||||
|
||||
if (value == null) {
|
||||
throw new InvalidDataAccessApiUsageException(
|
||||
String.format("No parameter specified for [%s] in query [%s]", parameterName, query));
|
||||
}
|
||||
|
||||
namedBindings.put(parameterName, value);
|
||||
}
|
||||
|
||||
return this.expander.expand(query, this.dialect.getBindMarkersFactory(), new MapBindParameterSource(namedBindings));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.r2dbc.function.ReactiveDataAccessStrategy#getTableName(java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public SqlIdentifier getTableName(Class<?> type) {
|
||||
return getRequiredPersistentEntity(type).getTableName();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.r2dbc.function.ReactiveDataAccessStrategy#toSql(SqlIdentifier)
|
||||
*/
|
||||
@Override
|
||||
public String toSql(SqlIdentifier identifier) {
|
||||
return this.updateMapper.toSql(identifier);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.r2dbc.function.ReactiveDataAccessStrategy#getStatementMapper()
|
||||
*/
|
||||
@Override
|
||||
public StatementMapper getStatementMapper() {
|
||||
return this.statementMapper;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.r2dbc.function.ReactiveDataAccessStrategy#getConverter()
|
||||
*/
|
||||
public R2dbcConverter getConverter() {
|
||||
return this.converter;
|
||||
}
|
||||
|
||||
public MappingContext<RelationalPersistentEntity<?>, ? extends RelationalPersistentProperty> getMappingContext() {
|
||||
return this.mappingContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String renderForGeneratedValues(SqlIdentifier identifier) {
|
||||
return dialect.renderForGeneratedValues(identifier);
|
||||
}
|
||||
|
||||
private RelationalPersistentEntity<?> getRequiredPersistentEntity(Class<?> typeToRead) {
|
||||
return this.mappingContext.getRequiredPersistentEntity(typeToRead);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private RelationalPersistentEntity<?> getPersistentEntity(Class<?> typeToRead) {
|
||||
return this.mappingContext.getPersistentEntity(typeToRead);
|
||||
}
|
||||
}
|
||||
@@ -1,417 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019-2022 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
|
||||
*
|
||||
* https://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.r2dbc.core;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.r2dbc.convert.R2dbcConverter;
|
||||
import org.springframework.data.r2dbc.dialect.R2dbcDialect;
|
||||
import org.springframework.data.r2dbc.query.BoundAssignments;
|
||||
import org.springframework.data.r2dbc.query.BoundCondition;
|
||||
import org.springframework.data.r2dbc.query.UpdateMapper;
|
||||
import org.springframework.data.relational.core.dialect.RenderContextFactory;
|
||||
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
|
||||
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
|
||||
import org.springframework.data.relational.core.query.CriteriaDefinition;
|
||||
import org.springframework.data.relational.core.sql.*;
|
||||
import org.springframework.data.relational.core.sql.InsertBuilder.InsertValuesWithBuild;
|
||||
import org.springframework.data.relational.core.sql.render.RenderContext;
|
||||
import org.springframework.data.relational.core.sql.render.SqlRenderer;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.r2dbc.core.PreparedOperation;
|
||||
import org.springframework.r2dbc.core.binding.BindMarkers;
|
||||
import org.springframework.r2dbc.core.binding.BindTarget;
|
||||
import org.springframework.r2dbc.core.binding.Bindings;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Default {@link StatementMapper} implementation.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @author Roman Chigvintsev
|
||||
* @author Mingyuan Wu
|
||||
*/
|
||||
class DefaultStatementMapper implements StatementMapper {
|
||||
|
||||
private final R2dbcDialect dialect;
|
||||
private final RenderContext renderContext;
|
||||
private final UpdateMapper updateMapper;
|
||||
private final MappingContext<? extends RelationalPersistentEntity<?>, ? extends RelationalPersistentProperty> mappingContext;
|
||||
|
||||
DefaultStatementMapper(R2dbcDialect dialect, R2dbcConverter converter) {
|
||||
|
||||
RenderContextFactory factory = new RenderContextFactory(dialect);
|
||||
|
||||
this.dialect = dialect;
|
||||
this.renderContext = factory.createRenderContext();
|
||||
this.updateMapper = new UpdateMapper(dialect, converter);
|
||||
this.mappingContext = converter.getMappingContext();
|
||||
}
|
||||
|
||||
DefaultStatementMapper(R2dbcDialect dialect, RenderContext renderContext, UpdateMapper updateMapper,
|
||||
MappingContext<RelationalPersistentEntity<?>, ? extends RelationalPersistentProperty> mappingContext) {
|
||||
this.dialect = dialect;
|
||||
this.renderContext = renderContext;
|
||||
this.updateMapper = updateMapper;
|
||||
this.mappingContext = mappingContext;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.r2dbc.function.StatementMapper#forType(java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> TypedStatementMapper<T> forType(Class<T> type) {
|
||||
|
||||
Assert.notNull(type, "Type must not be null!");
|
||||
|
||||
return new DefaultTypedStatementMapper<>(
|
||||
(RelationalPersistentEntity<T>) this.mappingContext.getRequiredPersistentEntity(type));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.r2dbc.function.StatementMapper#getMappedObject(org.springframework.data.r2dbc.function.StatementMapper.SelectSpec)
|
||||
*/
|
||||
@Override
|
||||
public PreparedOperation<?> getMappedObject(SelectSpec selectSpec) {
|
||||
return getMappedObject(selectSpec, null);
|
||||
}
|
||||
|
||||
private PreparedOperation<Select> getMappedObject(SelectSpec selectSpec,
|
||||
@Nullable RelationalPersistentEntity<?> entity) {
|
||||
|
||||
Table table = selectSpec.getTable();
|
||||
SelectBuilder.SelectAndFrom selectAndFrom = StatementBuilder.select(getSelectList(selectSpec, entity));
|
||||
|
||||
if (selectSpec.isDistinct()) {
|
||||
selectAndFrom = selectAndFrom.distinct();
|
||||
}
|
||||
|
||||
SelectBuilder.SelectFromAndJoin selectBuilder = selectAndFrom.from(table);
|
||||
|
||||
BindMarkers bindMarkers = this.dialect.getBindMarkersFactory().create();
|
||||
Bindings bindings = Bindings.empty();
|
||||
CriteriaDefinition criteria = selectSpec.getCriteria();
|
||||
|
||||
if (criteria != null && !criteria.isEmpty()) {
|
||||
|
||||
BoundCondition mappedObject = this.updateMapper.getMappedObject(bindMarkers, criteria, table, entity);
|
||||
|
||||
bindings = mappedObject.getBindings();
|
||||
selectBuilder.where(mappedObject.getCondition());
|
||||
}
|
||||
|
||||
if (selectSpec.getSort().isSorted()) {
|
||||
|
||||
List<OrderByField> sort = this.updateMapper.getMappedSort(table, selectSpec.getSort(), entity);
|
||||
selectBuilder.orderBy(sort);
|
||||
}
|
||||
|
||||
if (selectSpec.getLimit() > 0) {
|
||||
selectBuilder.limit(selectSpec.getLimit());
|
||||
}
|
||||
|
||||
if (selectSpec.getOffset() > 0) {
|
||||
selectBuilder.offset(selectSpec.getOffset());
|
||||
}
|
||||
|
||||
Select select = selectBuilder.build();
|
||||
return new DefaultPreparedOperation<>(select, this.renderContext, bindings);
|
||||
}
|
||||
|
||||
protected List<Expression> getSelectList(SelectSpec selectSpec, @Nullable RelationalPersistentEntity<?> entity) {
|
||||
|
||||
if (entity == null) {
|
||||
return selectSpec.getSelectList();
|
||||
}
|
||||
|
||||
List<Expression> selectList = selectSpec.getSelectList();
|
||||
List<Expression> mapped = new ArrayList<>(selectList.size());
|
||||
|
||||
for (Expression expression : selectList) {
|
||||
mapped.add(updateMapper.getMappedObject(expression, entity));
|
||||
}
|
||||
|
||||
return mapped;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.r2dbc.function.StatementMapper#getMappedObject(org.springframework.data.r2dbc.function.StatementMapper.InsertSpec)
|
||||
*/
|
||||
@Override
|
||||
public PreparedOperation<Insert> getMappedObject(InsertSpec insertSpec) {
|
||||
return getMappedObject(insertSpec, null);
|
||||
}
|
||||
|
||||
private PreparedOperation<Insert> getMappedObject(InsertSpec insertSpec,
|
||||
@Nullable RelationalPersistentEntity<?> entity) {
|
||||
|
||||
BindMarkers bindMarkers = this.dialect.getBindMarkersFactory().create();
|
||||
Table table = Table.create(toSql(insertSpec.getTable()));
|
||||
|
||||
BoundAssignments boundAssignments = this.updateMapper.getMappedObject(bindMarkers, insertSpec.getAssignments(),
|
||||
table, entity);
|
||||
|
||||
Bindings bindings;
|
||||
|
||||
bindings = boundAssignments.getBindings();
|
||||
|
||||
InsertBuilder.InsertIntoColumnsAndValues insertBuilder = StatementBuilder.insert(table);
|
||||
InsertValuesWithBuild withBuild = (InsertValuesWithBuild) insertBuilder;
|
||||
|
||||
for (Assignment assignment : boundAssignments.getAssignments()) {
|
||||
|
||||
if (assignment instanceof AssignValue) {
|
||||
AssignValue assignValue = (AssignValue) assignment;
|
||||
|
||||
insertBuilder.column(assignValue.getColumn());
|
||||
withBuild = insertBuilder.value(assignValue.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
return new DefaultPreparedOperation<>(withBuild.build(), this.renderContext, bindings);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.r2dbc.function.StatementMapper#getMappedObject(org.springframework.data.r2dbc.function.StatementMapper.UpdateSpec)
|
||||
*/
|
||||
@Override
|
||||
public PreparedOperation<Update> getMappedObject(UpdateSpec updateSpec) {
|
||||
return getMappedObject(updateSpec, null);
|
||||
}
|
||||
|
||||
private PreparedOperation<Update> getMappedObject(UpdateSpec updateSpec,
|
||||
@Nullable RelationalPersistentEntity<?> entity) {
|
||||
|
||||
BindMarkers bindMarkers = this.dialect.getBindMarkersFactory().create();
|
||||
Table table = Table.create(toSql(updateSpec.getTable()));
|
||||
|
||||
if (updateSpec.getUpdate() == null || updateSpec.getUpdate().getAssignments().isEmpty()) {
|
||||
throw new IllegalArgumentException("UPDATE contains no assignments");
|
||||
}
|
||||
|
||||
BoundAssignments boundAssignments = this.updateMapper.getMappedObject(bindMarkers,
|
||||
updateSpec.getUpdate().getAssignments(), table, entity);
|
||||
|
||||
Bindings bindings;
|
||||
|
||||
bindings = boundAssignments.getBindings();
|
||||
|
||||
UpdateBuilder.UpdateWhere updateBuilder = StatementBuilder.update(table).set(boundAssignments.getAssignments());
|
||||
|
||||
Update update;
|
||||
|
||||
CriteriaDefinition criteria = updateSpec.getCriteria();
|
||||
if (criteria != null && !criteria.isEmpty()) {
|
||||
|
||||
BoundCondition boundCondition = this.updateMapper.getMappedObject(bindMarkers, criteria, table, entity);
|
||||
|
||||
bindings = bindings.and(boundCondition.getBindings());
|
||||
update = updateBuilder.where(boundCondition.getCondition()).build();
|
||||
} else {
|
||||
update = updateBuilder.build();
|
||||
}
|
||||
|
||||
return new DefaultPreparedOperation<>(update, this.renderContext, bindings);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.r2dbc.function.StatementMapper#getMappedObject(org.springframework.data.r2dbc.function.StatementMapper.DeleteSpec)
|
||||
*/
|
||||
@Override
|
||||
public PreparedOperation<Delete> getMappedObject(DeleteSpec deleteSpec) {
|
||||
return getMappedObject(deleteSpec, null);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.r2dbc.function.StatementMapper#getRenderContext()
|
||||
*/
|
||||
@Override
|
||||
public RenderContext getRenderContext() {
|
||||
return renderContext;
|
||||
}
|
||||
|
||||
private PreparedOperation<Delete> getMappedObject(DeleteSpec deleteSpec,
|
||||
@Nullable RelationalPersistentEntity<?> entity) {
|
||||
|
||||
BindMarkers bindMarkers = this.dialect.getBindMarkersFactory().create();
|
||||
Table table = Table.create(toSql(deleteSpec.getTable()));
|
||||
|
||||
DeleteBuilder.DeleteWhere deleteBuilder = StatementBuilder.delete(table);
|
||||
|
||||
Bindings bindings = Bindings.empty();
|
||||
|
||||
Delete delete;
|
||||
CriteriaDefinition criteria = deleteSpec.getCriteria();
|
||||
|
||||
if (criteria != null && !criteria.isEmpty()) {
|
||||
|
||||
BoundCondition boundCondition = this.updateMapper.getMappedObject(bindMarkers, deleteSpec.getCriteria(), table,
|
||||
entity);
|
||||
|
||||
bindings = boundCondition.getBindings();
|
||||
delete = deleteBuilder.where(boundCondition.getCondition()).build();
|
||||
} else {
|
||||
delete = deleteBuilder.build();
|
||||
}
|
||||
|
||||
return new DefaultPreparedOperation<>(delete, this.renderContext, bindings);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.r2dbc.function.StatementMapper#toSql(SqlIdentifier)
|
||||
*/
|
||||
private String toSql(SqlIdentifier identifier) {
|
||||
|
||||
Assert.notNull(identifier, "SqlIdentifier must not be null");
|
||||
|
||||
return identifier.toSql(this.dialect.getIdentifierProcessing());
|
||||
}
|
||||
|
||||
/**
|
||||
* Default implementation of {@link PreparedOperation}.
|
||||
*
|
||||
* @param <T>
|
||||
*/
|
||||
static class DefaultPreparedOperation<T> implements PreparedOperation<T> {
|
||||
|
||||
private final T source;
|
||||
private final RenderContext renderContext;
|
||||
private final Bindings bindings;
|
||||
|
||||
DefaultPreparedOperation(T source, RenderContext renderContext, Bindings bindings) {
|
||||
|
||||
this.source = source;
|
||||
this.renderContext = renderContext;
|
||||
this.bindings = bindings;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.r2dbc.function.PreparedOperation#getSource()
|
||||
*/
|
||||
@Override
|
||||
public T getSource() {
|
||||
return this.source;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.r2dbc.function.QueryOperation#toQuery()
|
||||
*/
|
||||
@Override
|
||||
public String toQuery() {
|
||||
|
||||
SqlRenderer sqlRenderer = SqlRenderer.create(this.renderContext);
|
||||
|
||||
if (this.source instanceof Select) {
|
||||
return sqlRenderer.render((Select) this.source);
|
||||
}
|
||||
|
||||
if (this.source instanceof Insert) {
|
||||
return sqlRenderer.render((Insert) this.source);
|
||||
}
|
||||
|
||||
if (this.source instanceof Update) {
|
||||
return sqlRenderer.render((Update) this.source);
|
||||
}
|
||||
|
||||
if (this.source instanceof Delete) {
|
||||
return sqlRenderer.render((Delete) this.source);
|
||||
}
|
||||
|
||||
throw new IllegalStateException("Cannot render " + this.getSource());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void bindTo(BindTarget to) {
|
||||
this.bindings.apply(to);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class DefaultTypedStatementMapper<T> implements TypedStatementMapper<T> {
|
||||
|
||||
final RelationalPersistentEntity<T> entity;
|
||||
|
||||
DefaultTypedStatementMapper(RelationalPersistentEntity<T> entity) {
|
||||
this.entity = entity;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.r2dbc.function.StatementMapper#forType(java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public <TC> TypedStatementMapper<TC> forType(Class<TC> type) {
|
||||
return DefaultStatementMapper.this.forType(type);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.r2dbc.function.StatementMapper#getMappedObject(org.springframework.data.r2dbc.function.StatementMapper.SelectSpec)
|
||||
*/
|
||||
@Override
|
||||
public PreparedOperation<?> getMappedObject(SelectSpec selectSpec) {
|
||||
return DefaultStatementMapper.this.getMappedObject(selectSpec, this.entity);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.r2dbc.function.StatementMapper#getMappedObject(org.springframework.data.r2dbc.function.StatementMapper.InsertSpec)
|
||||
*/
|
||||
@Override
|
||||
public PreparedOperation<?> getMappedObject(InsertSpec insertSpec) {
|
||||
return DefaultStatementMapper.this.getMappedObject(insertSpec, this.entity);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.r2dbc.function.StatementMapper#getMappedObject(org.springframework.data.r2dbc.function.StatementMapper.UpdateSpec)
|
||||
*/
|
||||
@Override
|
||||
public PreparedOperation<?> getMappedObject(UpdateSpec updateSpec) {
|
||||
return DefaultStatementMapper.this.getMappedObject(updateSpec, this.entity);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.r2dbc.function.StatementMapper#getMappedObject(org.springframework.data.r2dbc.function.StatementMapper.DeleteSpec)
|
||||
*/
|
||||
@Override
|
||||
public PreparedOperation<?> getMappedObject(DeleteSpec deleteSpec) {
|
||||
return DefaultStatementMapper.this.getMappedObject(deleteSpec, this.entity);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.r2dbc.function.StatementMapper#getRenderContext()
|
||||
*/
|
||||
@Override
|
||||
public RenderContext getRenderContext() {
|
||||
return DefaultStatementMapper.this.getRenderContext();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,26 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020-2022 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
|
||||
*
|
||||
* https://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.r2dbc.core;
|
||||
|
||||
/**
|
||||
* Stripped down interface providing access to a fluent API that specifies a basic set of reactive R2DBC operations.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 1.1
|
||||
* @see R2dbcEntityOperations
|
||||
*/
|
||||
public interface FluentR2dbcOperations
|
||||
extends ReactiveSelectOperation, ReactiveInsertOperation, ReactiveUpdateOperation, ReactiveDeleteOperation {}
|
||||
@@ -1,125 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019-2022 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
|
||||
*
|
||||
* https://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.r2dbc.core;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.data.util.Streamable;
|
||||
import org.springframework.r2dbc.core.Parameter;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* {@link BindParameterSource} implementation that holds a given {@link Map} of parameters encapsulated as
|
||||
* {@link Parameter}.
|
||||
* <p>
|
||||
* This class is intended for passing in a simple Map of parameter values to the methods of the
|
||||
* {@link NamedParameterExpander} class.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @deprecated since 1.2, use Spring's org.springframework.r2dbc.core.MapBindParameterSource support instead.
|
||||
*/
|
||||
class MapBindParameterSource implements BindParameterSource {
|
||||
|
||||
private final Map<String, Parameter> values;
|
||||
|
||||
/**
|
||||
* Creates a new empty {@link MapBindParameterSource}.
|
||||
*/
|
||||
MapBindParameterSource() {
|
||||
this(new LinkedHashMap<>());
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link MapBindParameterSource} given {@link Map} of {@link Parameter}.
|
||||
*
|
||||
* @param values the parameter mapping.
|
||||
*/
|
||||
MapBindParameterSource(Map<String, Parameter> values) {
|
||||
|
||||
Assert.notNull(values, "Values must not be null");
|
||||
|
||||
this.values = values;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a key-value pair to the {@link MapBindParameterSource}. The value must not be {@literal null}.
|
||||
*
|
||||
* @param paramName must not be {@literal null}.
|
||||
* @param value must not be {@literal null}.
|
||||
* @return {@code this} {@link MapBindParameterSource}
|
||||
*/
|
||||
MapBindParameterSource addValue(String paramName, Object value) {
|
||||
|
||||
Assert.notNull(paramName, "Parameter name must not be null!");
|
||||
Assert.notNull(value, "Value must not be null!");
|
||||
|
||||
this.values.put(paramName, Parameter.fromOrEmpty(value, value.getClass()));
|
||||
return this;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.r2dbc.function.SqlParameterSource#hasValue(java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
public boolean hasValue(String paramName) {
|
||||
|
||||
Assert.notNull(paramName, "Parameter name must not be null!");
|
||||
|
||||
return values.containsKey(paramName);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.r2dbc.function.SqlParameterSource#getType(java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
public Class<?> getType(String paramName) {
|
||||
|
||||
Assert.notNull(paramName, "Parameter name must not be null!");
|
||||
|
||||
Parameter settableValue = this.values.get(paramName);
|
||||
if (settableValue != null) {
|
||||
return settableValue.getType();
|
||||
}
|
||||
|
||||
return Object.class;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.r2dbc.function.SqlParameterSource#getValue(java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
public Object getValue(String paramName) throws IllegalArgumentException {
|
||||
|
||||
if (!hasValue(paramName)) {
|
||||
throw new IllegalArgumentException("No value registered for key '" + paramName + "'");
|
||||
}
|
||||
|
||||
return this.values.get(paramName).getValue();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.r2dbc.function.SqlParameterSource#getParameterNames()
|
||||
*/
|
||||
@Override
|
||||
public Streamable<String> getParameterNames() {
|
||||
return Streamable.of(this.values.keySet());
|
||||
}
|
||||
}
|
||||
@@ -1,152 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019-2022 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
|
||||
*
|
||||
* https://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.r2dbc.core;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.apache.commons.logging.Log;
|
||||
import org.apache.commons.logging.LogFactory;
|
||||
|
||||
import org.springframework.r2dbc.core.PreparedOperation;
|
||||
import org.springframework.r2dbc.core.binding.BindMarkersFactory;
|
||||
|
||||
/**
|
||||
* SQL translation support allowing the use of named parameters rather than native placeholders.
|
||||
* <p>
|
||||
* This class expands SQL from named parameters to native style placeholders at execution time. It also allows for
|
||||
* expanding a {@link java.util.List} of values to the appropriate number of placeholders.
|
||||
* <p>
|
||||
* References to the same parameter name are substituted with the same bind marker placeholder if a
|
||||
* {@link BindMarkersFactory} uses {@link BindMarkersFactory#identifiablePlaceholders() identifiable} placeholders.
|
||||
* <p>
|
||||
* <b>NOTE: An instance of this class is thread-safe once configured.</b>
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @deprecated since 1.2, without replacement.
|
||||
*/
|
||||
@Deprecated
|
||||
class NamedParameterExpander {
|
||||
|
||||
/**
|
||||
* Default maximum number of entries for the SQL cache: 256.
|
||||
*/
|
||||
public static final int DEFAULT_CACHE_LIMIT = 256;
|
||||
|
||||
private volatile int cacheLimit = DEFAULT_CACHE_LIMIT;
|
||||
|
||||
private final Log logger = LogFactory.getLog(getClass());
|
||||
|
||||
/**
|
||||
* Cache of original SQL String to ParsedSql representation.
|
||||
*/
|
||||
@SuppressWarnings("serial") private final Map<String, ParsedSql> parsedSqlCache = new LinkedHashMap<String, ParsedSql>(
|
||||
DEFAULT_CACHE_LIMIT, 0.75f, true) {
|
||||
@Override
|
||||
protected boolean removeEldestEntry(Map.Entry<String, ParsedSql> eldest) {
|
||||
return size() > getCacheLimit();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Create a new enabled instance of {@link NamedParameterExpander}.
|
||||
*/
|
||||
public NamedParameterExpander() {}
|
||||
|
||||
/**
|
||||
* Specify the maximum number of entries for the SQL cache. Default is 256.
|
||||
*/
|
||||
public void setCacheLimit(int cacheLimit) {
|
||||
this.cacheLimit = cacheLimit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the maximum number of entries for the SQL cache.
|
||||
*/
|
||||
public int getCacheLimit() {
|
||||
return this.cacheLimit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Obtain a parsed representation of the given SQL statement.
|
||||
* <p>
|
||||
* The default implementation uses an LRU cache with an upper limit of 256 entries.
|
||||
*
|
||||
* @param sql the original SQL statement
|
||||
* @return a representation of the parsed SQL statement
|
||||
*/
|
||||
private ParsedSql getParsedSql(String sql) {
|
||||
|
||||
if (getCacheLimit() <= 0) {
|
||||
return NamedParameterUtils.parseSqlStatement(sql);
|
||||
}
|
||||
|
||||
synchronized (this.parsedSqlCache) {
|
||||
|
||||
ParsedSql parsedSql = this.parsedSqlCache.get(sql);
|
||||
if (parsedSql == null) {
|
||||
|
||||
parsedSql = NamedParameterUtils.parseSqlStatement(sql);
|
||||
this.parsedSqlCache.put(sql, parsedSql);
|
||||
}
|
||||
return parsedSql;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the SQL statement and locate any placeholders or named parameters. Named parameters are substituted for a
|
||||
* native placeholder, and any select list is expanded to the required number of placeholders. Select lists may
|
||||
* contain an array of objects, and in that case the placeholders will be grouped and enclosed with parentheses. This
|
||||
* allows for the use of "expression lists" in the SQL statement like: <br />
|
||||
* <br />
|
||||
* {@code select id, name, state from table where (name, age) in (('John', 35), ('Ann', 50))}
|
||||
* <p>
|
||||
* The parameter values passed in are used to determine the number of placeholders to be used for a select list.
|
||||
* Select lists should be limited to 100 or fewer elements. A larger number of elements is not guaranteed to be
|
||||
* supported by the database and is strictly vendor-dependent.
|
||||
*
|
||||
* @param sql sql the original SQL statement
|
||||
* @param bindMarkersFactory the bind marker factory.
|
||||
* @param paramSource the source for named parameters.
|
||||
* @return the expanded sql that accepts bind parameters and allows for execution without further translation wrapped
|
||||
* as {@link PreparedOperation}.
|
||||
*/
|
||||
public PreparedOperation<String> expand(String sql, BindMarkersFactory bindMarkersFactory,
|
||||
BindParameterSource paramSource) {
|
||||
|
||||
ParsedSql parsedSql = getParsedSql(sql);
|
||||
|
||||
PreparedOperation<String> expanded = NamedParameterUtils.substituteNamedParameters(parsedSql, bindMarkersFactory,
|
||||
paramSource);
|
||||
|
||||
if (this.logger.isDebugEnabled()) {
|
||||
this.logger.debug(String.format("Expanding SQL statement [%s] to [%s]", sql, expanded.toQuery()));
|
||||
}
|
||||
|
||||
return expanded;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the SQL statement and locate any placeholders or named parameters. Named parameters are returned as result of
|
||||
* this method invocation.
|
||||
*
|
||||
* @return the parameter names.
|
||||
*/
|
||||
public List<String> getParameterNames(String sql) {
|
||||
return getParsedSql(sql).getParameterNames();
|
||||
}
|
||||
}
|
||||
@@ -1,610 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2022 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
|
||||
*
|
||||
* https://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.r2dbc.core;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
import java.util.TreeMap;
|
||||
|
||||
import org.springframework.dao.InvalidDataAccessApiUsageException;
|
||||
import org.springframework.r2dbc.core.PreparedOperation;
|
||||
import org.springframework.r2dbc.core.binding.BindMarker;
|
||||
import org.springframework.r2dbc.core.binding.BindMarkers;
|
||||
import org.springframework.r2dbc.core.binding.BindMarkersFactory;
|
||||
import org.springframework.r2dbc.core.binding.BindTarget;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Helper methods for named parameter parsing.
|
||||
* <p>
|
||||
* Only intended for internal use within Spring's Data's R2DBC framework. Partially extracted from Spring's JDBC named
|
||||
* parameter support.
|
||||
* <p>
|
||||
* References to the same parameter name are substituted with the same bind marker placeholder if a
|
||||
* {@link BindMarkersFactory} uses {@link BindMarkersFactory#identifiablePlaceholders() identifiable} placeholders.
|
||||
* <p>
|
||||
* This is a subset of Spring Frameworks's {@code org.springframework.r2dbc.namedparam.NamedParameterUtils}.
|
||||
*
|
||||
* @author Thomas Risberg
|
||||
* @author Juergen Hoeller
|
||||
* @author Mark Paluch
|
||||
* @deprecated since 1.2, use Spring's org.springframework.r2dbc.core.NamedParameterUtils support instead.
|
||||
*/
|
||||
@Deprecated
|
||||
abstract class NamedParameterUtils {
|
||||
|
||||
/**
|
||||
* Set of characters that qualify as comment or quotes starting characters.
|
||||
*/
|
||||
private static final String[] START_SKIP = new String[] { "'", "\"", "--", "/*" };
|
||||
|
||||
/**
|
||||
* Set of characters that at are the corresponding comment or quotes ending characters.
|
||||
*/
|
||||
private static final String[] STOP_SKIP = new String[] { "'", "\"", "\n", "*/" };
|
||||
|
||||
/**
|
||||
* Set of characters that qualify as parameter separators, indicating that a parameter name in a SQL String has ended.
|
||||
*/
|
||||
private static final String PARAMETER_SEPARATORS = "\"':&,;()|=+-*%/\\<>^";
|
||||
|
||||
/**
|
||||
* An index with separator flags per character code. Technically only needed between 34 and 124 at this point.
|
||||
*/
|
||||
private static final boolean[] separatorIndex = new boolean[128];
|
||||
|
||||
static {
|
||||
for (char c : PARAMETER_SEPARATORS.toCharArray()) {
|
||||
separatorIndex[c] = true;
|
||||
}
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Core methods used by NamedParameterSupport.
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Parse the SQL statement and locate any placeholders or named parameters. Named parameters are substituted for a
|
||||
* placeholder.
|
||||
*
|
||||
* @param sql the SQL statement
|
||||
* @return the parsed statement, represented as {@link ParsedSql} instance.
|
||||
*/
|
||||
public static ParsedSql parseSqlStatement(String sql) {
|
||||
|
||||
Assert.notNull(sql, "SQL must not be null");
|
||||
|
||||
Set<String> namedParameters = new HashSet<>();
|
||||
String sqlToUse = sql;
|
||||
List<ParameterHolder> parameterList = new ArrayList<>();
|
||||
|
||||
char[] statement = sql.toCharArray();
|
||||
int namedParameterCount = 0;
|
||||
int unnamedParameterCount = 0;
|
||||
int totalParameterCount = 0;
|
||||
|
||||
int escapes = 0;
|
||||
int i = 0;
|
||||
while (i < statement.length) {
|
||||
int skipToPosition = i;
|
||||
while (i < statement.length) {
|
||||
skipToPosition = skipCommentsAndQuotes(statement, i);
|
||||
if (i == skipToPosition) {
|
||||
break;
|
||||
} else {
|
||||
i = skipToPosition;
|
||||
}
|
||||
}
|
||||
if (i >= statement.length) {
|
||||
break;
|
||||
}
|
||||
char c = statement[i];
|
||||
if (c == ':' || c == '&') {
|
||||
int j = i + 1;
|
||||
if (c == ':' && j < statement.length && statement[j] == ':') {
|
||||
// Postgres-style "::" casting operator should be skipped
|
||||
i = i + 2;
|
||||
continue;
|
||||
}
|
||||
String parameter = null;
|
||||
if (c == ':' && j < statement.length && statement[j] == '{') {
|
||||
// :{x} style parameter
|
||||
while (statement[j] != '}') {
|
||||
j++;
|
||||
if (j >= statement.length) {
|
||||
throw new InvalidDataAccessApiUsageException(
|
||||
"Non-terminated named parameter declaration " + "at position " + i + " in statement: " + sql);
|
||||
}
|
||||
if (statement[j] == ':' || statement[j] == '{') {
|
||||
throw new InvalidDataAccessApiUsageException("Parameter name contains invalid character '" + statement[j]
|
||||
+ "' at position " + i + " in statement: " + sql);
|
||||
}
|
||||
}
|
||||
if (j - i > 2) {
|
||||
parameter = sql.substring(i + 2, j);
|
||||
namedParameterCount = addNewNamedParameter(namedParameters, namedParameterCount, parameter);
|
||||
totalParameterCount = addNamedParameter(parameterList, totalParameterCount, escapes, i, j + 1, parameter);
|
||||
}
|
||||
j++;
|
||||
} else {
|
||||
while (j < statement.length && !isParameterSeparator(statement[j])) {
|
||||
j++;
|
||||
}
|
||||
if (j - i > 1) {
|
||||
parameter = sql.substring(i + 1, j);
|
||||
namedParameterCount = addNewNamedParameter(namedParameters, namedParameterCount, parameter);
|
||||
totalParameterCount = addNamedParameter(parameterList, totalParameterCount, escapes, i, j, parameter);
|
||||
}
|
||||
}
|
||||
i = j - 1;
|
||||
} else {
|
||||
if (c == '\\') {
|
||||
int j = i + 1;
|
||||
if (j < statement.length && statement[j] == ':') {
|
||||
// escaped ":" should be skipped
|
||||
sqlToUse = sqlToUse.substring(0, i - escapes) + sqlToUse.substring(i - escapes + 1);
|
||||
escapes++;
|
||||
i = i + 2;
|
||||
continue;
|
||||
}
|
||||
}
|
||||
}
|
||||
i++;
|
||||
}
|
||||
ParsedSql parsedSql = new ParsedSql(sqlToUse);
|
||||
for (ParameterHolder ph : parameterList) {
|
||||
parsedSql.addNamedParameter(ph.getParameterName(), ph.getStartIndex(), ph.getEndIndex());
|
||||
}
|
||||
parsedSql.setNamedParameterCount(namedParameterCount);
|
||||
parsedSql.setUnnamedParameterCount(unnamedParameterCount);
|
||||
parsedSql.setTotalParameterCount(totalParameterCount);
|
||||
return parsedSql;
|
||||
}
|
||||
|
||||
private static int addNamedParameter(List<ParameterHolder> parameterList, int totalParameterCount, int escapes, int i,
|
||||
int j, String parameter) {
|
||||
|
||||
parameterList.add(new ParameterHolder(parameter, i - escapes, j - escapes));
|
||||
totalParameterCount++;
|
||||
return totalParameterCount;
|
||||
}
|
||||
|
||||
private static int addNewNamedParameter(Set<String> namedParameters, int namedParameterCount, String parameter) {
|
||||
if (!namedParameters.contains(parameter)) {
|
||||
namedParameters.add(parameter);
|
||||
namedParameterCount++;
|
||||
}
|
||||
return namedParameterCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Skip over comments and quoted names present in an SQL statement.
|
||||
*
|
||||
* @param statement character array containing SQL statement.
|
||||
* @param position current position of statement.
|
||||
* @return next position to process after any comments or quotes are skipped.
|
||||
*/
|
||||
private static int skipCommentsAndQuotes(char[] statement, int position) {
|
||||
|
||||
for (int i = 0; i < START_SKIP.length; i++) {
|
||||
if (statement[position] == START_SKIP[i].charAt(0)) {
|
||||
boolean match = true;
|
||||
for (int j = 1; j < START_SKIP[i].length(); j++) {
|
||||
if (statement[position + j] != START_SKIP[i].charAt(j)) {
|
||||
match = false;
|
||||
break;
|
||||
}
|
||||
}
|
||||
if (match) {
|
||||
int offset = START_SKIP[i].length();
|
||||
for (int m = position + offset; m < statement.length; m++) {
|
||||
if (statement[m] == STOP_SKIP[i].charAt(0)) {
|
||||
boolean endMatch = true;
|
||||
int endPos = m;
|
||||
for (int n = 1; n < STOP_SKIP[i].length(); n++) {
|
||||
if (m + n >= statement.length) {
|
||||
// last comment not closed properly
|
||||
return statement.length;
|
||||
}
|
||||
if (statement[m + n] != STOP_SKIP[i].charAt(n)) {
|
||||
endMatch = false;
|
||||
break;
|
||||
}
|
||||
endPos = m + n;
|
||||
}
|
||||
if (endMatch) {
|
||||
// found character sequence ending comment or quote
|
||||
return endPos + 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
// character sequence ending comment or quote not found
|
||||
return statement.length;
|
||||
}
|
||||
}
|
||||
}
|
||||
return position;
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse the SQL statement and locate any placeholders or named parameters. Named parameters are substituted for a
|
||||
* native placeholder, and any select list is expanded to the required number of placeholders. Select lists may
|
||||
* contain an array of objects, and in that case the placeholders will be grouped and enclosed with parentheses. This
|
||||
* allows for the use of "expression lists" in the SQL statement like: <br />
|
||||
* <br />
|
||||
* {@code select id, name, state from table where (name, age) in (('John', 35), ('Ann', 50))}
|
||||
* <p>
|
||||
* The parameter values passed in are used to determine the number of placeholders to be used for a select list.
|
||||
* Select lists should be limited to 100 or fewer elements. A larger number of elements is not guaranteed to be
|
||||
* supported by the database and is strictly vendor-dependent.
|
||||
*
|
||||
* @param parsedSql the parsed representation of the SQL statement.
|
||||
* @param bindMarkersFactory the bind marker factory.
|
||||
* @param paramSource the source for named parameters.
|
||||
* @return the expanded query that accepts bind parameters and allows for execution without further translation.
|
||||
* @see #parseSqlStatement
|
||||
*/
|
||||
public static PreparedOperation<String> substituteNamedParameters(ParsedSql parsedSql,
|
||||
BindMarkersFactory bindMarkersFactory, BindParameterSource paramSource) {
|
||||
|
||||
NamedParameters markerHolder = new NamedParameters(bindMarkersFactory);
|
||||
|
||||
String originalSql = parsedSql.getOriginalSql();
|
||||
List<String> paramNames = parsedSql.getParameterNames();
|
||||
if (paramNames.isEmpty()) {
|
||||
return new ExpandedQuery(originalSql, markerHolder, paramSource);
|
||||
}
|
||||
|
||||
StringBuilder actualSql = new StringBuilder(originalSql.length());
|
||||
int lastIndex = 0;
|
||||
for (int i = 0; i < paramNames.size(); i++) {
|
||||
String paramName = paramNames.get(i);
|
||||
int[] indexes = parsedSql.getParameterIndexes(i);
|
||||
int startIndex = indexes[0];
|
||||
int endIndex = indexes[1];
|
||||
actualSql.append(originalSql, lastIndex, startIndex);
|
||||
NamedParameters.NamedParameter marker = markerHolder.getOrCreate(paramName);
|
||||
if (paramSource.hasValue(paramName)) {
|
||||
Object value = paramSource.getValue(paramName);
|
||||
if (value instanceof Collection) {
|
||||
|
||||
Iterator<?> entryIter = ((Collection<?>) value).iterator();
|
||||
int k = 0;
|
||||
int counter = 0;
|
||||
while (entryIter.hasNext()) {
|
||||
if (k > 0) {
|
||||
actualSql.append(", ");
|
||||
}
|
||||
k++;
|
||||
Object entryItem = entryIter.next();
|
||||
if (entryItem instanceof Object[]) {
|
||||
Object[] expressionList = (Object[]) entryItem;
|
||||
actualSql.append('(');
|
||||
for (int m = 0; m < expressionList.length; m++) {
|
||||
if (m > 0) {
|
||||
actualSql.append(", ");
|
||||
}
|
||||
actualSql.append(marker.getPlaceholder(counter));
|
||||
counter++;
|
||||
}
|
||||
actualSql.append(')');
|
||||
} else {
|
||||
actualSql.append(marker.getPlaceholder(counter));
|
||||
counter++;
|
||||
}
|
||||
|
||||
}
|
||||
} else {
|
||||
actualSql.append(marker.getPlaceholder());
|
||||
}
|
||||
} else {
|
||||
actualSql.append(marker.getPlaceholder());
|
||||
}
|
||||
lastIndex = endIndex;
|
||||
}
|
||||
actualSql.append(originalSql, lastIndex, originalSql.length());
|
||||
|
||||
return new ExpandedQuery(actualSql.toString(), markerHolder, paramSource);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determine whether a parameter name ends at the current position, that is, whether the given character qualifies as
|
||||
* a separator.
|
||||
*/
|
||||
private static boolean isParameterSeparator(char c) {
|
||||
return (c < 128 && separatorIndex[c]) || Character.isWhitespace(c);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Convenience methods operating on a plain SQL String
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Parse the SQL statement and locate any placeholders or named parameters. Named parameters are substituted for a
|
||||
* native placeholder and any select list is expanded to the required number of placeholders.
|
||||
* <p>
|
||||
*
|
||||
* @param sql the SQL statement.
|
||||
* @param bindMarkersFactory the bind marker factory.
|
||||
* @param paramSource the source for named parameters.
|
||||
* @return the expanded query that accepts bind parameters and allows for execution without further translation.
|
||||
*/
|
||||
public static PreparedOperation<String> substituteNamedParameters(String sql, BindMarkersFactory bindMarkersFactory,
|
||||
BindParameterSource paramSource) {
|
||||
ParsedSql parsedSql = parseSqlStatement(sql);
|
||||
return substituteNamedParameters(parsedSql, bindMarkersFactory, paramSource);
|
||||
}
|
||||
|
||||
private static final class ParameterHolder {
|
||||
|
||||
private final String parameterName;
|
||||
|
||||
private final int startIndex;
|
||||
|
||||
private final int endIndex;
|
||||
|
||||
ParameterHolder(String parameterName, int startIndex, int endIndex) {
|
||||
this.parameterName = parameterName;
|
||||
this.startIndex = startIndex;
|
||||
this.endIndex = endIndex;
|
||||
}
|
||||
|
||||
String getParameterName() {
|
||||
return this.parameterName;
|
||||
}
|
||||
|
||||
int getStartIndex() {
|
||||
return this.startIndex;
|
||||
}
|
||||
|
||||
int getEndIndex() {
|
||||
return this.endIndex;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o)
|
||||
return true;
|
||||
if (!(o instanceof ParameterHolder))
|
||||
return false;
|
||||
ParameterHolder that = (ParameterHolder) o;
|
||||
return this.startIndex == that.startIndex && this.endIndex == that.endIndex
|
||||
&& Objects.equals(this.parameterName, that.parameterName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(this.parameterName, this.startIndex, this.endIndex);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Holder for bind markers progress.
|
||||
*/
|
||||
static class NamedParameters {
|
||||
|
||||
private final BindMarkers bindMarkers;
|
||||
private final boolean identifiable;
|
||||
private final Map<String, List<NamedParameter>> references = new TreeMap<>();
|
||||
|
||||
NamedParameters(BindMarkersFactory factory) {
|
||||
this.bindMarkers = factory.create();
|
||||
this.identifiable = factory.identifiablePlaceholders();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the {@link NamedParameter} identified by {@code namedParameter}.
|
||||
*
|
||||
* @param namedParameter
|
||||
* @return
|
||||
*/
|
||||
NamedParameter getOrCreate(String namedParameter) {
|
||||
|
||||
List<NamedParameter> reference = this.references.computeIfAbsent(namedParameter, ignore -> new ArrayList<>());
|
||||
|
||||
if (reference.isEmpty()) {
|
||||
NamedParameter param = new NamedParameter(namedParameter);
|
||||
reference.add(param);
|
||||
return param;
|
||||
}
|
||||
|
||||
if (this.identifiable) {
|
||||
return reference.get(0);
|
||||
}
|
||||
|
||||
NamedParameter param = new NamedParameter(namedParameter);
|
||||
reference.add(param);
|
||||
return param;
|
||||
}
|
||||
|
||||
List<NamedParameter> getMarker(String name) {
|
||||
return this.references.get(name);
|
||||
}
|
||||
|
||||
class NamedParameter {
|
||||
|
||||
private final String namedParameter;
|
||||
private final List<BindMarker> placeholders = new ArrayList<>();
|
||||
|
||||
NamedParameter(String namedParameter) {
|
||||
this.namedParameter = namedParameter;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a placeholder to translate a single value into a bindable parameter.
|
||||
* <p>
|
||||
* Can be called multiple times to create placeholders for array/collections.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
String addPlaceholder() {
|
||||
|
||||
BindMarker bindMarker = NamedParameters.this.bindMarkers.next(this.namedParameter);
|
||||
this.placeholders.add(bindMarker);
|
||||
return bindMarker.getPlaceholder();
|
||||
}
|
||||
|
||||
String getPlaceholder() {
|
||||
return getPlaceholder(0);
|
||||
}
|
||||
|
||||
String getPlaceholder(int counter) {
|
||||
|
||||
while (counter + 1 > this.placeholders.size()) {
|
||||
addPlaceholder();
|
||||
}
|
||||
|
||||
return this.placeholders.get(counter).getPlaceholder();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Expanded query that allows binding of parameters using parameter names that were used to expand the query. Binding
|
||||
* unrolls {@link Collection}s and nested arrays.
|
||||
*/
|
||||
private static class ExpandedQuery implements PreparedOperation<String> {
|
||||
|
||||
private final String expandedSql;
|
||||
|
||||
private final NamedParameters parameters;
|
||||
|
||||
private final BindParameterSource parameterSource;
|
||||
|
||||
ExpandedQuery(String expandedSql, NamedParameters parameters, BindParameterSource parameterSource) {
|
||||
this.expandedSql = expandedSql;
|
||||
this.parameters = parameters;
|
||||
this.parameterSource = parameterSource;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public void bind(org.springframework.r2dbc.core.binding.BindTarget target, String identifier, Object value) {
|
||||
|
||||
List<BindMarker> bindMarkers = getBindMarkers(identifier);
|
||||
|
||||
if (bindMarkers == null) {
|
||||
|
||||
target.bind(identifier, value);
|
||||
return;
|
||||
}
|
||||
|
||||
if (value instanceof Collection) {
|
||||
Collection<Object> collection = (Collection<Object>) value;
|
||||
|
||||
Iterator<Object> iterator = collection.iterator();
|
||||
Iterator<BindMarker> markers = bindMarkers.iterator();
|
||||
|
||||
while (iterator.hasNext()) {
|
||||
|
||||
Object valueToBind = iterator.next();
|
||||
|
||||
if (valueToBind instanceof Object[]) {
|
||||
Object[] objects = (Object[]) valueToBind;
|
||||
for (Object object : objects) {
|
||||
bind(target, markers, object);
|
||||
}
|
||||
} else {
|
||||
bind(target, markers, valueToBind);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (BindMarker bindMarker : bindMarkers) {
|
||||
bindMarker.bind(target, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void bind(org.springframework.r2dbc.core.binding.BindTarget target, Iterator<BindMarker> markers,
|
||||
Object valueToBind) {
|
||||
|
||||
Assert.isTrue(markers.hasNext(),
|
||||
() -> String.format(
|
||||
"No bind marker for value [%s] in SQL [%s]. Check that the query was expanded using the same arguments.",
|
||||
valueToBind, toQuery()));
|
||||
|
||||
markers.next().bind(target, valueToBind);
|
||||
}
|
||||
|
||||
public void bindNull(org.springframework.r2dbc.core.binding.BindTarget target, String identifier,
|
||||
Class<?> valueType) {
|
||||
|
||||
List<BindMarker> bindMarkers = getBindMarkers(identifier);
|
||||
|
||||
if (bindMarkers == null) {
|
||||
|
||||
target.bindNull(identifier, valueType);
|
||||
return;
|
||||
}
|
||||
|
||||
for (BindMarker bindMarker : bindMarkers) {
|
||||
bindMarker.bindNull(target, valueType);
|
||||
}
|
||||
}
|
||||
|
||||
List<BindMarker> getBindMarkers(String identifier) {
|
||||
|
||||
List<NamedParameters.NamedParameter> parameters = this.parameters.getMarker(identifier);
|
||||
|
||||
if (parameters == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
List<BindMarker> markers = new ArrayList<>();
|
||||
|
||||
for (NamedParameters.NamedParameter parameter : parameters) {
|
||||
markers.addAll(parameter.placeholders);
|
||||
}
|
||||
|
||||
return markers;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getSource() {
|
||||
return this.expandedSql;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void bindTo(BindTarget target) {
|
||||
|
||||
for (String namedParameter : this.parameterSource.getParameterNames()) {
|
||||
|
||||
Object value = this.parameterSource.getValue(namedParameter);
|
||||
|
||||
if (value == null) {
|
||||
bindNull(target, namedParameter, this.parameterSource.getType(namedParameter));
|
||||
} else {
|
||||
bind(target, namedParameter, value);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.r2dbc.function.QueryOperation#toQuery()
|
||||
*/
|
||||
@Override
|
||||
public String toQuery() {
|
||||
return this.expandedSql;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,145 +0,0 @@
|
||||
/*
|
||||
* Copyright 2002-2022 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
|
||||
*
|
||||
* https://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.r2dbc.core;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Holds information about a parsed SQL statement.
|
||||
* <p>
|
||||
* This is a copy of Spring Frameworks's {@code org.springframework.r2dbc.namedparam.ParsedSql}.
|
||||
*
|
||||
* @author Thomas Risberg
|
||||
* @author Juergen Hoeller
|
||||
* @deprecated since 1.2, use Spring's org.springframework.r2dbc.core.ParsedSql support instead.
|
||||
*/
|
||||
@Deprecated
|
||||
class ParsedSql {
|
||||
|
||||
private String originalSql;
|
||||
|
||||
private List<String> parameterNames = new ArrayList<>();
|
||||
|
||||
private List<int[]> parameterIndexes = new ArrayList<>();
|
||||
|
||||
private int namedParameterCount;
|
||||
|
||||
private int unnamedParameterCount;
|
||||
|
||||
private int totalParameterCount;
|
||||
|
||||
/**
|
||||
* Create a new instance of the {@link ParsedSql} class.
|
||||
*
|
||||
* @param originalSql the SQL statement that is being (or is to be) parsed
|
||||
*/
|
||||
ParsedSql(String originalSql) {
|
||||
this.originalSql = originalSql;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the SQL statement that is being parsed.
|
||||
*/
|
||||
String getOriginalSql() {
|
||||
return this.originalSql;
|
||||
}
|
||||
|
||||
/**
|
||||
* Add a named parameter parsed from this SQL statement.
|
||||
*
|
||||
* @param parameterName the name of the parameter
|
||||
* @param startIndex the start index in the original SQL String
|
||||
* @param endIndex the end index in the original SQL String
|
||||
*/
|
||||
void addNamedParameter(String parameterName, int startIndex, int endIndex) {
|
||||
this.parameterNames.add(parameterName);
|
||||
this.parameterIndexes.add(new int[] { startIndex, endIndex });
|
||||
}
|
||||
|
||||
/**
|
||||
* Return all of the parameters (bind variables) in the parsed SQL statement. Repeated occurrences of the same
|
||||
* parameter name are included here.
|
||||
*/
|
||||
List<String> getParameterNames() {
|
||||
return this.parameterNames;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the parameter indexes for the specified parameter.
|
||||
*
|
||||
* @param parameterPosition the position of the parameter (as index in the parameter names List)
|
||||
* @return the start index and end index, combined into a int array of length 2
|
||||
*/
|
||||
int[] getParameterIndexes(int parameterPosition) {
|
||||
return this.parameterIndexes.get(parameterPosition);
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the count of named parameters in the SQL statement. Each parameter name counts once; repeated occurrences do
|
||||
* not count here.
|
||||
*/
|
||||
void setNamedParameterCount(int namedParameterCount) {
|
||||
this.namedParameterCount = namedParameterCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the count of named parameters in the SQL statement. Each parameter name counts once; repeated occurrences do
|
||||
* not count here.
|
||||
*/
|
||||
int getNamedParameterCount() {
|
||||
return this.namedParameterCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the count of all of the unnamed parameters in the SQL statement.
|
||||
*/
|
||||
void setUnnamedParameterCount(int unnamedParameterCount) {
|
||||
this.unnamedParameterCount = unnamedParameterCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the count of all of the unnamed parameters in the SQL statement.
|
||||
*/
|
||||
int getUnnamedParameterCount() {
|
||||
return this.unnamedParameterCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the total count of all of the parameters in the SQL statement. Repeated occurrences of the same parameter name
|
||||
* do count here.
|
||||
*/
|
||||
void setTotalParameterCount(int totalParameterCount) {
|
||||
this.totalParameterCount = totalParameterCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the total count of all of the parameters in the SQL statement. Repeated occurrences of the same parameter
|
||||
* name do count here.
|
||||
*/
|
||||
int getTotalParameterCount() {
|
||||
return this.totalParameterCount;
|
||||
}
|
||||
|
||||
/**
|
||||
* Exposes the original SQL String.
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return this.originalSql;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,268 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020-2022 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
|
||||
*
|
||||
* https://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.r2dbc.core;
|
||||
|
||||
import io.r2dbc.spi.Row;
|
||||
import io.r2dbc.spi.RowMetadata;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
|
||||
import org.springframework.dao.DataAccessException;
|
||||
import org.springframework.dao.TransientDataAccessResourceException;
|
||||
import org.springframework.data.r2dbc.convert.R2dbcConverter;
|
||||
import org.springframework.data.relational.core.query.Query;
|
||||
import org.springframework.data.relational.core.query.Update;
|
||||
import org.springframework.r2dbc.core.DatabaseClient;
|
||||
import org.springframework.r2dbc.core.PreparedOperation;
|
||||
import org.springframework.r2dbc.core.RowsFetchSpec;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Interface specifying a basic set of reactive R2DBC operations using entities. Implemented by
|
||||
* {@link R2dbcEntityTemplate}. Not often used directly, but a useful option to enhance testability, as it can easily be
|
||||
* mocked or stubbed.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 1.1
|
||||
* @see DatabaseClient
|
||||
*/
|
||||
public interface R2dbcEntityOperations extends FluentR2dbcOperations {
|
||||
|
||||
/**
|
||||
* Expose the underlying {@link DatabaseClient} to allow SQL operations.
|
||||
*
|
||||
* @return the underlying {@link DatabaseClient}.
|
||||
* @see DatabaseClient
|
||||
*/
|
||||
DatabaseClient getDatabaseClient();
|
||||
|
||||
/**
|
||||
* Expose the underlying {@link ReactiveDataAccessStrategy} encapsulating dialect specifics.
|
||||
*
|
||||
* @return the underlying {@link ReactiveDataAccessStrategy}.
|
||||
* @see ReactiveDataAccessStrategy
|
||||
* @since 1.1.3
|
||||
* @deprecated use {@link #getConverter()} instead as {@link ReactiveDataAccessStrategy} will be removed in a future
|
||||
* release.
|
||||
*/
|
||||
@Deprecated
|
||||
ReactiveDataAccessStrategy getDataAccessStrategy();
|
||||
|
||||
/**
|
||||
* Return the underlying {@link R2dbcConverter}.
|
||||
*
|
||||
* @return the underlying {@link R2dbcConverter}.
|
||||
* @since 1.2
|
||||
*/
|
||||
R2dbcConverter getConverter();
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Methods dealing with org.springframework.data.r2dbc.query.Query
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Returns the number of rows for the given entity class applying {@link Query}. This overridden method allows users
|
||||
* to further refine the selection Query using a {@link Query} predicate to determine how many entities of the given
|
||||
* {@link Class type} match the Query.
|
||||
*
|
||||
* @param query user-defined count {@link Query} to execute; must not be {@literal null}.
|
||||
* @param entityClass {@link Class type} of the entity; must not be {@literal null}.
|
||||
* @return the number of existing entities.
|
||||
* @throws DataAccessException if any problem occurs while executing the query.
|
||||
*/
|
||||
Mono<Long> count(Query query, Class<?> entityClass) throws DataAccessException;
|
||||
|
||||
/**
|
||||
* Determine whether the result for {@code entityClass} {@link Query} yields at least one row.
|
||||
*
|
||||
* @param query user-defined exists {@link Query} to execute; must not be {@literal null}.
|
||||
* @param entityClass {@link Class type} of the entity; must not be {@literal null}.
|
||||
* @return {@literal true} if the object exists.
|
||||
* @throws DataAccessException if any problem occurs while executing the query.
|
||||
* @since 2.1
|
||||
*/
|
||||
Mono<Boolean> exists(Query query, Class<?> entityClass) throws DataAccessException;
|
||||
|
||||
/**
|
||||
* Execute a {@code SELECT} query and convert the resulting items to a stream of entities.
|
||||
*
|
||||
* @param query must not be {@literal null}.
|
||||
* @param entityClass the entity type must not be {@literal null}.
|
||||
* @return the result objects returned by the action.
|
||||
* @throws DataAccessException if there is any problem issuing the execution.
|
||||
*/
|
||||
<T> Flux<T> select(Query query, Class<T> entityClass) throws DataAccessException;
|
||||
|
||||
/**
|
||||
* Execute a {@code SELECT} query and convert the resulting item to an entity ensuring exactly one result.
|
||||
*
|
||||
* @param query must not be {@literal null}.
|
||||
* @param entityClass the entity type must not be {@literal null}.
|
||||
* @return exactly one result or {@link Mono#empty()} if no match found.
|
||||
* @throws org.springframework.dao.IncorrectResultSizeDataAccessException if more than one match found.
|
||||
* @throws DataAccessException if there is any problem issuing the execution.
|
||||
*/
|
||||
<T> Mono<T> selectOne(Query query, Class<T> entityClass) throws DataAccessException;
|
||||
|
||||
/**
|
||||
* Update the queried entities and return {@literal true} if the update was applied.
|
||||
*
|
||||
* @param query must not be {@literal null}.
|
||||
* @param update must not be {@literal null}.
|
||||
* @param entityClass the entity type must not be {@literal null}.
|
||||
* @return the number of affected rows.
|
||||
* @throws DataAccessException if there is any problem executing the query.
|
||||
*/
|
||||
Mono<Integer> update(Query query, Update update, Class<?> entityClass) throws DataAccessException;
|
||||
|
||||
/**
|
||||
* Remove entities (rows)/columns from the table by {@link Query}.
|
||||
*
|
||||
* @param query must not be {@literal null}.
|
||||
* @param entityClass the entity type must not be {@literal null}.
|
||||
* @return the number of affected rows.
|
||||
* @throws DataAccessException if there is any problem issuing the execution.
|
||||
*/
|
||||
Mono<Integer> delete(Query query, Class<?> entityClass) throws DataAccessException;
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Methods dealing with org.springframework.r2dbc.core.PreparedOperation
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Execute a query for a {@link RowsFetchSpec}, given {@link PreparedOperation}. Any provided bindings within
|
||||
* {@link PreparedOperation} are applied to the underlying {@link DatabaseClient}. The query is issued as-is without
|
||||
* additional pre-processing such as named parameter expansion. Results of the query are mapped onto
|
||||
* {@code entityClass}.
|
||||
*
|
||||
* @param operation the prepared operation wrapping a SQL query and bind parameters.
|
||||
* @param entityClass the entity type must not be {@literal null}.
|
||||
* @return a {@link RowsFetchSpec} ready to materialize.
|
||||
* @since 1.4
|
||||
* @throws DataAccessException if there is any problem issuing the execution.
|
||||
*/
|
||||
<T> RowsFetchSpec<T> query(PreparedOperation<?> operation, Class<T> entityClass) throws DataAccessException;
|
||||
|
||||
/**
|
||||
* Execute a query for a {@link RowsFetchSpec}, given {@link PreparedOperation}. Any provided bindings within
|
||||
* {@link PreparedOperation} are applied to the underlying {@link DatabaseClient}. The query is issued as-is without
|
||||
* additional pre-processing such as named parameter expansion. Results of the query are mapped using {@link Function
|
||||
* rowMapper}.
|
||||
*
|
||||
* @param operation the prepared operation wrapping a SQL query and bind parameters.
|
||||
* @param rowMapper the row mapper must not be {@literal null}.
|
||||
* @return a {@link RowsFetchSpec} with {@link Function rowMapper} applied ready to materialize.
|
||||
* @throws DataAccessException if there is any problem issuing the execution.
|
||||
* @since 1.4
|
||||
* @see #query(PreparedOperation, BiFunction)
|
||||
*/
|
||||
default <T> RowsFetchSpec<T> query(PreparedOperation<?> operation, Function<Row, T> rowMapper)
|
||||
throws DataAccessException {
|
||||
|
||||
Assert.notNull(rowMapper, "Row mapper must not be null");
|
||||
|
||||
return query(operation, ((row, rowMetadata) -> rowMapper.apply(row)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a query for a {@link RowsFetchSpec}, given {@link PreparedOperation}. Any provided bindings within
|
||||
* {@link PreparedOperation} are applied to the underlying {@link DatabaseClient}. The query is issued as-is without
|
||||
* additional pre-processing such as named parameter expansion. Results of the query are mapped using
|
||||
* {@link BiFunction rowMapper}.
|
||||
*
|
||||
* @param operation the prepared operation wrapping a SQL query and bind parameters.
|
||||
* @param rowMapper the row mapper must not be {@literal null}.
|
||||
* @return a {@link RowsFetchSpec} with {@link Function rowMapper} applied ready to materialize.
|
||||
* @since 1.4
|
||||
* @throws DataAccessException if there is any problem issuing the execution.
|
||||
*/
|
||||
<T> RowsFetchSpec<T> query(PreparedOperation<?> operation, BiFunction<Row, RowMetadata, T> rowMapper)
|
||||
throws DataAccessException;
|
||||
|
||||
/**
|
||||
* Execute a query for a {@link RowsFetchSpec} in the context of {@code entityClass}, given {@link PreparedOperation}.
|
||||
* Any provided bindings within {@link PreparedOperation} are applied to the underlying {@link DatabaseClient}. The
|
||||
* query is issued as-is without additional pre-processing such as named parameter expansion. Results of the query are
|
||||
* mapped using {@link Function rowMapper}.
|
||||
*
|
||||
* @param operation the prepared operation wrapping a SQL query and bind parameters.
|
||||
* @param entityClass the entity type must not be {@literal null}.
|
||||
* @param rowMapper the row mapper must not be {@literal null}.
|
||||
* @return a {@link RowsFetchSpec} with {@link Function rowMapper} applied ready to materialize.
|
||||
* @throws DataAccessException if there is any problem issuing the execution.
|
||||
* @since 1.4
|
||||
* @see #query(PreparedOperation, Class, BiFunction)
|
||||
*/
|
||||
default <T> RowsFetchSpec<T> query(PreparedOperation<?> operation, Class<?> entityClass, Function<Row, T> rowMapper)
|
||||
throws DataAccessException {
|
||||
|
||||
Assert.notNull(rowMapper, "Row mapper must not be null");
|
||||
|
||||
return query(operation, entityClass, ((row, rowMetadata) -> rowMapper.apply(row)));
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute a query for a {@link RowsFetchSpec} in the context of {@code entityClass}, given {@link PreparedOperation}.
|
||||
* Any provided bindings within {@link PreparedOperation} are applied to the underlying {@link DatabaseClient}. The
|
||||
* query is issued as-is without additional pre-processing such as named parameter expansion. Results of the query are
|
||||
* mapped using {@link BiFunction rowMapper}.
|
||||
*
|
||||
* @param operation the prepared operation wrapping a SQL query and bind parameters.
|
||||
* @param entityClass the entity type must not be {@literal null}.
|
||||
* @param rowMapper the row mapper must not be {@literal null}.
|
||||
* @return a {@link RowsFetchSpec} with {@link Function rowMapper} applied ready to materialize.
|
||||
* @throws DataAccessException if there is any problem issuing the execution.
|
||||
* @since 1.4
|
||||
* @see #query(PreparedOperation, Class, BiFunction)
|
||||
*/
|
||||
<T> RowsFetchSpec<T> query(PreparedOperation<?> operation, Class<?> entityClass,
|
||||
BiFunction<Row, RowMetadata, T> rowMapper) throws DataAccessException;
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Methods dealing with entities
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Insert the given entity and emit the entity if the insert was applied.
|
||||
*
|
||||
* @param entity the entity to insert, must not be {@literal null}.
|
||||
* @return the inserted entity.
|
||||
* @throws DataAccessException if there is any problem issuing the execution.
|
||||
*/
|
||||
<T> Mono<T> insert(T entity) throws DataAccessException;
|
||||
|
||||
/**
|
||||
* Update the given entity and emit the entity if the update was applied.
|
||||
*
|
||||
* @param entity the entity to update, must not be {@literal null}.
|
||||
* @return the updated entity.
|
||||
* @throws DataAccessException if there is any problem issuing the execution.
|
||||
* @throws TransientDataAccessResourceException if the update did not affect any rows.
|
||||
*/
|
||||
<T> Mono<T> update(T entity) throws DataAccessException;
|
||||
|
||||
/**
|
||||
* Delete the given entity and emit the entity if the delete was applied.
|
||||
*
|
||||
* @param entity must not be {@literal null}.
|
||||
* @return the deleted entity.
|
||||
* @throws DataAccessException if there is any problem issuing the execution.
|
||||
*/
|
||||
<T> Mono<T> delete(T entity) throws DataAccessException;
|
||||
}
|
||||
@@ -1,948 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020-2022 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
|
||||
*
|
||||
* https://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.r2dbc.core;
|
||||
|
||||
import io.r2dbc.spi.ConnectionFactory;
|
||||
import io.r2dbc.spi.Row;
|
||||
import io.r2dbc.spi.RowMetadata;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import java.beans.FeatureDescriptor;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.reactivestreams.Publisher;
|
||||
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.beans.factory.BeanFactory;
|
||||
import org.springframework.beans.factory.BeanFactoryAware;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.core.convert.ConversionService;
|
||||
import org.springframework.dao.DataAccessException;
|
||||
import org.springframework.dao.OptimisticLockingFailureException;
|
||||
import org.springframework.dao.TransientDataAccessResourceException;
|
||||
import org.springframework.data.mapping.IdentifierAccessor;
|
||||
import org.springframework.data.mapping.MappingException;
|
||||
import org.springframework.data.mapping.PersistentPropertyAccessor;
|
||||
import org.springframework.data.mapping.callback.ReactiveEntityCallbacks;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.projection.ProjectionInformation;
|
||||
import org.springframework.data.projection.SpelAwareProxyProjectionFactory;
|
||||
import org.springframework.data.r2dbc.convert.R2dbcConverter;
|
||||
import org.springframework.data.r2dbc.dialect.DialectResolver;
|
||||
import org.springframework.data.r2dbc.dialect.R2dbcDialect;
|
||||
import org.springframework.data.r2dbc.mapping.OutboundRow;
|
||||
import org.springframework.data.r2dbc.mapping.event.AfterConvertCallback;
|
||||
import org.springframework.data.r2dbc.mapping.event.AfterSaveCallback;
|
||||
import org.springframework.data.r2dbc.mapping.event.BeforeConvertCallback;
|
||||
import org.springframework.data.r2dbc.mapping.event.BeforeSaveCallback;
|
||||
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
|
||||
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
|
||||
import org.springframework.data.relational.core.query.Criteria;
|
||||
import org.springframework.data.relational.core.query.CriteriaDefinition;
|
||||
import org.springframework.data.relational.core.query.Query;
|
||||
import org.springframework.data.relational.core.query.Update;
|
||||
import org.springframework.data.relational.core.sql.Expression;
|
||||
import org.springframework.data.relational.core.sql.Expressions;
|
||||
import org.springframework.data.relational.core.sql.Functions;
|
||||
import org.springframework.data.relational.core.sql.SqlIdentifier;
|
||||
import org.springframework.data.relational.core.sql.Table;
|
||||
import org.springframework.data.util.ProxyUtils;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.r2dbc.core.DatabaseClient;
|
||||
import org.springframework.r2dbc.core.Parameter;
|
||||
import org.springframework.r2dbc.core.PreparedOperation;
|
||||
import org.springframework.r2dbc.core.RowsFetchSpec;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Implementation of {@link R2dbcEntityOperations}. It simplifies the use of Reactive R2DBC usage through entities and
|
||||
* helps to avoid common errors. This class uses {@link DatabaseClient} to execute SQL queries or updates, initiating
|
||||
* iteration over {@link io.r2dbc.spi.Result}.
|
||||
* <p>
|
||||
* Can be used within a service implementation via direct instantiation with a {@link DatabaseClient} reference, or get
|
||||
* prepared in an application context and given to services as bean reference.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @author Bogdan Ilchyshyn
|
||||
* @author Jens Schauder
|
||||
* @author Jose Luis Leon
|
||||
* @since 1.1
|
||||
*/
|
||||
public class R2dbcEntityTemplate implements R2dbcEntityOperations, BeanFactoryAware, ApplicationContextAware {
|
||||
|
||||
private final DatabaseClient databaseClient;
|
||||
|
||||
private final ReactiveDataAccessStrategy dataAccessStrategy;
|
||||
|
||||
private final MappingContext<? extends RelationalPersistentEntity<?>, ? extends RelationalPersistentProperty> mappingContext;
|
||||
|
||||
private final SpelAwareProxyProjectionFactory projectionFactory;
|
||||
|
||||
private @Nullable ReactiveEntityCallbacks entityCallbacks;
|
||||
|
||||
/**
|
||||
* Create a new {@link R2dbcEntityTemplate} given {@link ConnectionFactory}.
|
||||
*
|
||||
* @param connectionFactory must not be {@literal null}.
|
||||
* @since 1.2
|
||||
*/
|
||||
public R2dbcEntityTemplate(ConnectionFactory connectionFactory) {
|
||||
|
||||
Assert.notNull(connectionFactory, "ConnectionFactory must not be null");
|
||||
|
||||
R2dbcDialect dialect = DialectResolver.getDialect(connectionFactory);
|
||||
|
||||
this.databaseClient = DatabaseClient.builder().connectionFactory(connectionFactory)
|
||||
.bindMarkers(dialect.getBindMarkersFactory()).build();
|
||||
this.dataAccessStrategy = new DefaultReactiveDataAccessStrategy(dialect);
|
||||
this.mappingContext = dataAccessStrategy.getConverter().getMappingContext();
|
||||
this.projectionFactory = new SpelAwareProxyProjectionFactory();
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link R2dbcEntityTemplate} given {@link DatabaseClient}.
|
||||
*
|
||||
* @param databaseClient must not be {@literal null}.
|
||||
* @param dialect the dialect to use, must not be {@literal null}.
|
||||
* @since 1.2
|
||||
*/
|
||||
public R2dbcEntityTemplate(DatabaseClient databaseClient, R2dbcDialect dialect) {
|
||||
this(databaseClient, new DefaultReactiveDataAccessStrategy(dialect));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link R2dbcEntityTemplate} given {@link DatabaseClient}, {@link R2dbcDialect} and
|
||||
* {@link R2dbcConverter}.
|
||||
*
|
||||
* @param databaseClient must not be {@literal null}.
|
||||
* @param dialect the dialect to use, must not be {@literal null}.
|
||||
* @param converter the dialect to use, must not be {@literal null}.
|
||||
* @since 1.2
|
||||
*/
|
||||
public R2dbcEntityTemplate(DatabaseClient databaseClient, R2dbcDialect dialect, R2dbcConverter converter) {
|
||||
this(databaseClient, new DefaultReactiveDataAccessStrategy(dialect, converter));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link R2dbcEntityTemplate} given {@link DatabaseClient} and {@link ReactiveDataAccessStrategy}.
|
||||
*
|
||||
* @param databaseClient must not be {@literal null}.
|
||||
* @since 1.2
|
||||
*/
|
||||
public R2dbcEntityTemplate(DatabaseClient databaseClient,
|
||||
ReactiveDataAccessStrategy strategy) {
|
||||
|
||||
Assert.notNull(databaseClient, "DatabaseClient must not be null");
|
||||
Assert.notNull(strategy, "ReactiveDataAccessStrategy must not be null");
|
||||
|
||||
this.databaseClient = databaseClient;
|
||||
this.dataAccessStrategy = strategy;
|
||||
this.mappingContext = strategy.getConverter().getMappingContext();
|
||||
this.projectionFactory = new SpelAwareProxyProjectionFactory();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.r2dbc.core.R2dbcEntityOperations#getDatabaseClient()
|
||||
*/
|
||||
@Override
|
||||
public DatabaseClient getDatabaseClient() {
|
||||
return this.databaseClient;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.r2dbc.core.R2dbcEntityOperations#getDataAccessStrategy()
|
||||
*/
|
||||
@Override
|
||||
public ReactiveDataAccessStrategy getDataAccessStrategy() {
|
||||
return this.dataAccessStrategy;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.r2dbc.core.R2dbcEntityOperations#getConverter()
|
||||
*/
|
||||
@Override
|
||||
public R2dbcConverter getConverter() {
|
||||
return this.dataAccessStrategy.getConverter();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.beans.factory.BeanFactoryAware#setBeanFactory(org.springframework.beans.factory.BeanFactory)
|
||||
* @deprecated since 1.2 in favor of #setApplicationContext.
|
||||
*/
|
||||
@Override
|
||||
@Deprecated
|
||||
public void setBeanFactory(BeanFactory beanFactory) throws BeansException {}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.context.ApplicationContextAware#setApplicationContext(org.springframework.context.ApplicationContext)
|
||||
*/
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
||||
|
||||
if (entityCallbacks == null) {
|
||||
setEntityCallbacks(ReactiveEntityCallbacks.create(applicationContext));
|
||||
}
|
||||
|
||||
projectionFactory.setBeanFactory(applicationContext);
|
||||
projectionFactory.setBeanClassLoader(applicationContext.getClassLoader());
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the {@link ReactiveEntityCallbacks} instance to use when invoking
|
||||
* {@link org.springframework.data.mapping.callback.ReactiveEntityCallbacks callbacks} like the
|
||||
* {@link BeforeSaveCallback}.
|
||||
* <p>
|
||||
* Overrides potentially existing {@link ReactiveEntityCallbacks}.
|
||||
*
|
||||
* @param entityCallbacks must not be {@literal null}.
|
||||
* @throws IllegalArgumentException if the given instance is {@literal null}.
|
||||
* @since 1.2
|
||||
*/
|
||||
public void setEntityCallbacks(ReactiveEntityCallbacks entityCallbacks) {
|
||||
|
||||
Assert.notNull(entityCallbacks, "EntityCallbacks must not be null!");
|
||||
this.entityCallbacks = entityCallbacks;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Methods dealing with org.springframework.data.r2dbc.core.FluentR2dbcOperations
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.r2dbc.core.ReactiveSelectOperation#select(java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public <T> ReactiveSelect<T> select(Class<T> domainType) {
|
||||
return new ReactiveSelectOperationSupport(this).select(domainType);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.r2dbc.core.ReactiveInsertOperation#insert(java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public <T> ReactiveInsert<T> insert(Class<T> domainType) {
|
||||
return new ReactiveInsertOperationSupport(this).insert(domainType);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.r2dbc.core.ReactiveUpdateOperation#update(java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public ReactiveUpdate update(Class<?> domainType) {
|
||||
return new ReactiveUpdateOperationSupport(this).update(domainType);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.r2dbc.core.ReactiveDeleteOperation#delete(java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public ReactiveDelete delete(Class<?> domainType) {
|
||||
return new ReactiveDeleteOperationSupport(this).delete(domainType);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Methods dealing with org.springframework.data.r2dbc.query.Query
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.r2dbc.core.R2dbcEntityOperations#count(org.springframework.data.r2dbc.query.Query, java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public Mono<Long> count(Query query, Class<?> entityClass) throws DataAccessException {
|
||||
|
||||
Assert.notNull(query, "Query must not be null");
|
||||
Assert.notNull(entityClass, "Entity class must not be null");
|
||||
|
||||
return doCount(query, entityClass, getTableName(entityClass));
|
||||
}
|
||||
|
||||
Mono<Long> doCount(Query query, Class<?> entityClass, SqlIdentifier tableName) {
|
||||
|
||||
RelationalPersistentEntity<?> entity = getRequiredEntity(entityClass);
|
||||
StatementMapper statementMapper = dataAccessStrategy.getStatementMapper().forType(entityClass);
|
||||
|
||||
StatementMapper.SelectSpec selectSpec = statementMapper //
|
||||
.createSelect(tableName) //
|
||||
.doWithTable((table, spec) -> {
|
||||
|
||||
Expression countExpression = entity.hasIdProperty()
|
||||
? table.column(entity.getRequiredIdProperty().getColumnName())
|
||||
: Expressions.asterisk();
|
||||
return spec.withProjection(Functions.count(countExpression));
|
||||
});
|
||||
|
||||
Optional<CriteriaDefinition> criteria = query.getCriteria();
|
||||
if (criteria.isPresent()) {
|
||||
selectSpec = criteria.map(selectSpec::withCriteria).orElse(selectSpec);
|
||||
}
|
||||
|
||||
PreparedOperation<?> operation = statementMapper.getMappedObject(selectSpec);
|
||||
|
||||
return this.databaseClient.sql(operation) //
|
||||
.map((r, md) -> r.get(0, Long.class)) //
|
||||
.first() //
|
||||
.defaultIfEmpty(0L);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.r2dbc.core.R2dbcEntityOperations#exists(org.springframework.data.r2dbc.query.Query, java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public Mono<Boolean> exists(Query query, Class<?> entityClass) throws DataAccessException {
|
||||
|
||||
Assert.notNull(query, "Query must not be null");
|
||||
Assert.notNull(entityClass, "Entity class must not be null");
|
||||
|
||||
return doExists(query, entityClass, getTableName(entityClass));
|
||||
}
|
||||
|
||||
Mono<Boolean> doExists(Query query, Class<?> entityClass, SqlIdentifier tableName) {
|
||||
|
||||
RelationalPersistentEntity<?> entity = getRequiredEntity(entityClass);
|
||||
StatementMapper statementMapper = dataAccessStrategy.getStatementMapper().forType(entityClass);
|
||||
|
||||
SqlIdentifier columnName = entity.hasIdProperty() ? entity.getRequiredIdProperty().getColumnName()
|
||||
: SqlIdentifier.unquoted("*");
|
||||
|
||||
StatementMapper.SelectSpec selectSpec = statementMapper //
|
||||
.createSelect(tableName) //
|
||||
.withProjection(columnName) //
|
||||
.limit(1);
|
||||
|
||||
Optional<CriteriaDefinition> criteria = query.getCriteria();
|
||||
if (criteria.isPresent()) {
|
||||
selectSpec = criteria.map(selectSpec::withCriteria).orElse(selectSpec);
|
||||
}
|
||||
|
||||
PreparedOperation<?> operation = statementMapper.getMappedObject(selectSpec);
|
||||
|
||||
return this.databaseClient.sql(operation) //
|
||||
.map((r, md) -> r) //
|
||||
.first() //
|
||||
.hasElement();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.r2dbc.core.R2dbcEntityOperations#select(org.springframework.data.r2dbc.query.Query, java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public <T> Flux<T> select(Query query, Class<T> entityClass) throws DataAccessException {
|
||||
|
||||
Assert.notNull(query, "Query must not be null");
|
||||
Assert.notNull(entityClass, "Entity class must not be null");
|
||||
|
||||
SqlIdentifier tableName = getTableName(entityClass);
|
||||
return doSelect(query, entityClass, tableName, entityClass, RowsFetchSpec::all);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
<T, P extends Publisher<T>> P doSelect(Query query, Class<?> entityClass, SqlIdentifier tableName,
|
||||
Class<T> returnType, Function<RowsFetchSpec<T>, P> resultHandler) {
|
||||
|
||||
RowsFetchSpec<T> fetchSpec = doSelect(query, entityClass, tableName, returnType);
|
||||
|
||||
P result = resultHandler.apply(fetchSpec);
|
||||
|
||||
if (result instanceof Mono) {
|
||||
return (P) ((Mono<?>) result).flatMap(it -> maybeCallAfterConvert(it, tableName));
|
||||
}
|
||||
|
||||
return (P) ((Flux<?>) result).flatMap(it -> maybeCallAfterConvert(it, tableName));
|
||||
}
|
||||
|
||||
private <T> RowsFetchSpec<T> doSelect(Query query, Class<?> entityClass, SqlIdentifier tableName,
|
||||
Class<T> returnType) {
|
||||
|
||||
StatementMapper statementMapper = dataAccessStrategy.getStatementMapper().forType(entityClass);
|
||||
|
||||
StatementMapper.SelectSpec selectSpec = statementMapper //
|
||||
.createSelect(tableName) //
|
||||
.doWithTable((table, spec) -> spec.withProjection(getSelectProjection(table, query, returnType)));
|
||||
|
||||
if (query.getLimit() > 0) {
|
||||
selectSpec = selectSpec.limit(query.getLimit());
|
||||
}
|
||||
|
||||
if (query.getOffset() > 0) {
|
||||
selectSpec = selectSpec.offset(query.getOffset());
|
||||
}
|
||||
|
||||
if (query.isSorted()) {
|
||||
selectSpec = selectSpec.withSort(query.getSort());
|
||||
}
|
||||
|
||||
Optional<CriteriaDefinition> criteria = query.getCriteria();
|
||||
if (criteria.isPresent()) {
|
||||
selectSpec = criteria.map(selectSpec::withCriteria).orElse(selectSpec);
|
||||
}
|
||||
|
||||
PreparedOperation<?> operation = statementMapper.getMappedObject(selectSpec);
|
||||
|
||||
return getRowsFetchSpec(databaseClient.sql(operation), entityClass, returnType);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.r2dbc.core.R2dbcEntityOperations#selectOne(org.springframework.data.r2dbc.query.Query, java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public <T> Mono<T> selectOne(Query query, Class<T> entityClass) throws DataAccessException {
|
||||
return doSelect(query.limit(2), entityClass, getTableName(entityClass), entityClass, RowsFetchSpec::one);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.r2dbc.core.R2dbcEntityOperations#update(org.springframework.data.r2dbc.query.Query, org.springframework.data.r2dbc.query.Update, java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public Mono<Integer> update(Query query, Update update, Class<?> entityClass) throws DataAccessException {
|
||||
|
||||
Assert.notNull(query, "Query must not be null");
|
||||
Assert.notNull(update, "Update must not be null");
|
||||
Assert.notNull(entityClass, "Entity class must not be null");
|
||||
|
||||
return doUpdate(query, update, entityClass, getTableName(entityClass));
|
||||
}
|
||||
|
||||
Mono<Integer> doUpdate(Query query, Update update, Class<?> entityClass, SqlIdentifier tableName) {
|
||||
|
||||
StatementMapper statementMapper = dataAccessStrategy.getStatementMapper().forType(entityClass);
|
||||
|
||||
StatementMapper.UpdateSpec selectSpec = statementMapper //
|
||||
.createUpdate(tableName, update);
|
||||
|
||||
Optional<CriteriaDefinition> criteria = query.getCriteria();
|
||||
if (criteria.isPresent()) {
|
||||
selectSpec = criteria.map(selectSpec::withCriteria).orElse(selectSpec);
|
||||
}
|
||||
|
||||
PreparedOperation<?> operation = statementMapper.getMappedObject(selectSpec);
|
||||
return this.databaseClient.sql(operation).fetch().rowsUpdated();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.r2dbc.core.R2dbcEntityOperations#delete(org.springframework.data.r2dbc.query.Query, java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public Mono<Integer> delete(Query query, Class<?> entityClass) throws DataAccessException {
|
||||
|
||||
Assert.notNull(query, "Query must not be null");
|
||||
Assert.notNull(entityClass, "Entity class must not be null");
|
||||
|
||||
return doDelete(query, entityClass, getTableName(entityClass));
|
||||
}
|
||||
|
||||
Mono<Integer> doDelete(Query query, Class<?> entityClass, SqlIdentifier tableName) {
|
||||
|
||||
StatementMapper statementMapper = dataAccessStrategy.getStatementMapper().forType(entityClass);
|
||||
|
||||
StatementMapper.DeleteSpec deleteSpec = statementMapper //
|
||||
.createDelete(tableName);
|
||||
|
||||
Optional<CriteriaDefinition> criteria = query.getCriteria();
|
||||
if (criteria.isPresent()) {
|
||||
deleteSpec = criteria.map(deleteSpec::withCriteria).orElse(deleteSpec);
|
||||
}
|
||||
|
||||
PreparedOperation<?> operation = statementMapper.getMappedObject(deleteSpec);
|
||||
return this.databaseClient.sql(operation).fetch().rowsUpdated().defaultIfEmpty(0);
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Methods dealing with org.springframework.r2dbc.core.PreparedOperation
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.r2dbc.core.R2dbcEntityOperations#query(org.springframework.r2dbc.core.PreparedOperation, java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public <T> RowsFetchSpec<T> query(PreparedOperation<?> operation, Class<T> entityClass) {
|
||||
|
||||
Assert.notNull(operation, "PreparedOperation must not be null");
|
||||
Assert.notNull(entityClass, "Entity class must not be null");
|
||||
|
||||
return new EntityCallbackAdapter<>(getRowsFetchSpec(databaseClient.sql(operation), entityClass, entityClass),
|
||||
getTableNameOrEmpty(entityClass));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.r2dbc.core.R2dbcEntityOperations#query(org.springframework.r2dbc.core.PreparedOperation, java.util.function.BiFunction)
|
||||
*/
|
||||
@Override
|
||||
public <T> RowsFetchSpec<T> query(PreparedOperation<?> operation, BiFunction<Row, RowMetadata, T> rowMapper) {
|
||||
|
||||
Assert.notNull(operation, "PreparedOperation must not be null");
|
||||
Assert.notNull(rowMapper, "Row mapper must not be null");
|
||||
|
||||
return new EntityCallbackAdapter<>(databaseClient.sql(operation).map(rowMapper), SqlIdentifier.EMPTY);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.r2dbc.core.R2dbcEntityOperations#query(org.springframework.r2dbc.core.PreparedOperation, java.lang.Class, java.util.function.BiFunction)
|
||||
*/
|
||||
@Override
|
||||
public <T> RowsFetchSpec<T> query(PreparedOperation<?> operation, Class<?> entityClass,
|
||||
BiFunction<Row, RowMetadata, T> rowMapper) {
|
||||
|
||||
Assert.notNull(operation, "PreparedOperation must not be null");
|
||||
Assert.notNull(entityClass, "Entity class must not be null");
|
||||
Assert.notNull(rowMapper, "Row mapper must not be null");
|
||||
|
||||
return new EntityCallbackAdapter<>(databaseClient.sql(operation).map(rowMapper), getTableNameOrEmpty(entityClass));
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------
|
||||
// Methods dealing with entities
|
||||
// -------------------------------------------------------------------------
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.r2dbc.core.R2dbcEntityOperations#insert(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public <T> Mono<T> insert(T entity) throws DataAccessException {
|
||||
|
||||
Assert.notNull(entity, "Entity must not be null");
|
||||
|
||||
return doInsert(entity, getRequiredEntity(entity).getTableName());
|
||||
}
|
||||
|
||||
<T> Mono<T> doInsert(T entity, SqlIdentifier tableName) {
|
||||
|
||||
RelationalPersistentEntity<T> persistentEntity = getRequiredEntity(entity);
|
||||
|
||||
return maybeCallBeforeConvert(entity, tableName).flatMap(onBeforeConvert -> {
|
||||
|
||||
T initializedEntity = setVersionIfNecessary(persistentEntity, onBeforeConvert);
|
||||
|
||||
OutboundRow outboundRow = dataAccessStrategy.getOutboundRow(initializedEntity);
|
||||
|
||||
potentiallyRemoveId(persistentEntity, outboundRow);
|
||||
|
||||
return maybeCallBeforeSave(initializedEntity, outboundRow, tableName) //
|
||||
.flatMap(entityToSave -> doInsert(entityToSave, tableName, outboundRow));
|
||||
});
|
||||
}
|
||||
|
||||
private void potentiallyRemoveId(RelationalPersistentEntity<?> persistentEntity, OutboundRow outboundRow) {
|
||||
|
||||
RelationalPersistentProperty idProperty = persistentEntity.getIdProperty();
|
||||
if (idProperty == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
SqlIdentifier columnName = idProperty.getColumnName();
|
||||
Parameter parameter = outboundRow.get(columnName);
|
||||
|
||||
if (shouldSkipIdValue(parameter, idProperty)) {
|
||||
outboundRow.remove(columnName);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean shouldSkipIdValue(@Nullable Parameter value, RelationalPersistentProperty property) {
|
||||
|
||||
if (value == null || value.getValue() == null) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (value.getValue() instanceof Number) {
|
||||
return ((Number) value.getValue()).longValue() == 0L;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private <T> Mono<T> doInsert(T entity, SqlIdentifier tableName, OutboundRow outboundRow) {
|
||||
|
||||
StatementMapper mapper = dataAccessStrategy.getStatementMapper();
|
||||
StatementMapper.InsertSpec insert = mapper.createInsert(tableName);
|
||||
|
||||
for (SqlIdentifier column : outboundRow.keySet()) {
|
||||
Parameter settableValue = outboundRow.get(column);
|
||||
if (settableValue.hasValue()) {
|
||||
insert = insert.withColumn(column, settableValue);
|
||||
}
|
||||
}
|
||||
|
||||
PreparedOperation<?> operation = mapper.getMappedObject(insert);
|
||||
|
||||
List<SqlIdentifier> identifierColumns = dataAccessStrategy.getIdentifierColumns(entity.getClass());
|
||||
|
||||
return this.databaseClient.sql(operation) //
|
||||
.filter(statement -> {
|
||||
|
||||
if (identifierColumns.isEmpty()) {
|
||||
return statement.returnGeneratedValues();
|
||||
}
|
||||
|
||||
return statement.returnGeneratedValues(dataAccessStrategy.renderForGeneratedValues(identifierColumns.get(0)));
|
||||
}).map(this.dataAccessStrategy.getConverter().populateIdIfNecessary(entity)) //
|
||||
.all() //
|
||||
.last(entity).flatMap(saved -> maybeCallAfterSave(saved, outboundRow, tableName));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> T setVersionIfNecessary(RelationalPersistentEntity<T> persistentEntity, T entity) {
|
||||
|
||||
RelationalPersistentProperty versionProperty = persistentEntity.getVersionProperty();
|
||||
if (versionProperty == null) {
|
||||
return entity;
|
||||
}
|
||||
|
||||
Class<?> versionPropertyType = versionProperty.getType();
|
||||
Long version = versionPropertyType.isPrimitive() ? 1L : 0L;
|
||||
ConversionService conversionService = this.dataAccessStrategy.getConverter().getConversionService();
|
||||
PersistentPropertyAccessor<?> propertyAccessor = persistentEntity.getPropertyAccessor(entity);
|
||||
propertyAccessor.setProperty(versionProperty, conversionService.convert(version, versionPropertyType));
|
||||
|
||||
return (T) propertyAccessor.getBean();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.r2dbc.core.R2dbcEntityOperations#update(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public <T> Mono<T> update(T entity) throws DataAccessException {
|
||||
|
||||
Assert.notNull(entity, "Entity must not be null");
|
||||
|
||||
return doUpdate(entity, getRequiredEntity(entity).getTableName());
|
||||
}
|
||||
|
||||
private <T> Mono<T> doUpdate(T entity, SqlIdentifier tableName) {
|
||||
|
||||
RelationalPersistentEntity<T> persistentEntity = getRequiredEntity(entity);
|
||||
|
||||
return maybeCallBeforeConvert(entity, tableName).flatMap(onBeforeConvert -> {
|
||||
|
||||
T entityToUse;
|
||||
Criteria matchingVersionCriteria;
|
||||
|
||||
if (persistentEntity.hasVersionProperty()) {
|
||||
|
||||
matchingVersionCriteria = createMatchingVersionCriteria(onBeforeConvert, persistentEntity);
|
||||
entityToUse = incrementVersion(persistentEntity, onBeforeConvert);
|
||||
} else {
|
||||
|
||||
entityToUse = onBeforeConvert;
|
||||
matchingVersionCriteria = null;
|
||||
}
|
||||
|
||||
OutboundRow outboundRow = dataAccessStrategy.getOutboundRow(entityToUse);
|
||||
|
||||
return maybeCallBeforeSave(entityToUse, outboundRow, tableName) //
|
||||
.flatMap(onBeforeSave -> {
|
||||
|
||||
SqlIdentifier idColumn = persistentEntity.getRequiredIdProperty().getColumnName();
|
||||
Parameter id = outboundRow.remove(idColumn);
|
||||
Criteria criteria = Criteria.where(dataAccessStrategy.toSql(idColumn)).is(id);
|
||||
|
||||
if (matchingVersionCriteria != null) {
|
||||
criteria = criteria.and(matchingVersionCriteria);
|
||||
}
|
||||
|
||||
return doUpdate(onBeforeSave, tableName, persistentEntity, criteria, outboundRow);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
private <T> Mono<T> doUpdate(T entity, SqlIdentifier tableName, RelationalPersistentEntity<T> persistentEntity,
|
||||
Criteria criteria, OutboundRow outboundRow) {
|
||||
|
||||
Update update = Update.from((Map) outboundRow);
|
||||
|
||||
StatementMapper mapper = dataAccessStrategy.getStatementMapper();
|
||||
StatementMapper.UpdateSpec updateSpec = mapper.createUpdate(tableName, update).withCriteria(criteria);
|
||||
|
||||
PreparedOperation<?> operation = mapper.getMappedObject(updateSpec);
|
||||
|
||||
return this.databaseClient.sql(operation) //
|
||||
.fetch() //
|
||||
.rowsUpdated() //
|
||||
.handle((rowsUpdated, sink) -> {
|
||||
|
||||
if (rowsUpdated != 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (persistentEntity.hasVersionProperty()) {
|
||||
sink.error(new OptimisticLockingFailureException(
|
||||
formatOptimisticLockingExceptionMessage(entity, persistentEntity)));
|
||||
} else {
|
||||
sink.error(new TransientDataAccessResourceException(
|
||||
formatTransientEntityExceptionMessage(entity, persistentEntity)));
|
||||
}
|
||||
}).then(maybeCallAfterSave(entity, outboundRow, tableName));
|
||||
}
|
||||
|
||||
private <T> String formatOptimisticLockingExceptionMessage(T entity, RelationalPersistentEntity<T> persistentEntity) {
|
||||
|
||||
return String.format("Failed to update table [%s]. Version does not match for row with Id [%s].",
|
||||
persistentEntity.getTableName(), persistentEntity.getIdentifierAccessor(entity).getIdentifier());
|
||||
}
|
||||
|
||||
private <T> String formatTransientEntityExceptionMessage(T entity, RelationalPersistentEntity<T> persistentEntity) {
|
||||
|
||||
return String.format("Failed to update table [%s]. Row with Id [%s] does not exist.",
|
||||
persistentEntity.getTableName(), persistentEntity.getIdentifierAccessor(entity).getIdentifier());
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private <T> T incrementVersion(RelationalPersistentEntity<T> persistentEntity, T entity) {
|
||||
|
||||
PersistentPropertyAccessor<?> propertyAccessor = persistentEntity.getPropertyAccessor(entity);
|
||||
RelationalPersistentProperty versionProperty = persistentEntity.getVersionProperty();
|
||||
|
||||
ConversionService conversionService = this.dataAccessStrategy.getConverter().getConversionService();
|
||||
Object currentVersionValue = propertyAccessor.getProperty(versionProperty);
|
||||
long newVersionValue = 1L;
|
||||
if (currentVersionValue != null) {
|
||||
newVersionValue = conversionService.convert(currentVersionValue, Long.class) + 1;
|
||||
}
|
||||
Class<?> versionPropertyType = versionProperty.getType();
|
||||
propertyAccessor.setProperty(versionProperty, conversionService.convert(newVersionValue, versionPropertyType));
|
||||
|
||||
return (T) propertyAccessor.getBean();
|
||||
}
|
||||
|
||||
private <T> Criteria createMatchingVersionCriteria(T entity, RelationalPersistentEntity<T> persistentEntity) {
|
||||
|
||||
PersistentPropertyAccessor<?> propertyAccessor = persistentEntity.getPropertyAccessor(entity);
|
||||
RelationalPersistentProperty versionProperty = persistentEntity.getVersionProperty();
|
||||
|
||||
Object version = propertyAccessor.getProperty(versionProperty);
|
||||
Criteria.CriteriaStep versionColumn = Criteria.where(dataAccessStrategy.toSql(versionProperty.getColumnName()));
|
||||
if (version == null) {
|
||||
return versionColumn.isNull();
|
||||
} else {
|
||||
return versionColumn.is(version);
|
||||
}
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.r2dbc.core.R2dbcEntityOperations#delete(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public <T> Mono<T> delete(T entity) throws DataAccessException {
|
||||
|
||||
Assert.notNull(entity, "Entity must not be null");
|
||||
|
||||
RelationalPersistentEntity<?> persistentEntity = getRequiredEntity(entity);
|
||||
|
||||
return delete(getByIdQuery(entity, persistentEntity), persistentEntity.getType()).thenReturn(entity);
|
||||
}
|
||||
|
||||
protected <T> Mono<T> maybeCallBeforeConvert(T object, SqlIdentifier table) {
|
||||
|
||||
if (entityCallbacks != null) {
|
||||
return entityCallbacks.callback(BeforeConvertCallback.class, object, table);
|
||||
}
|
||||
|
||||
return Mono.just(object);
|
||||
}
|
||||
|
||||
protected <T> Mono<T> maybeCallBeforeSave(T object, OutboundRow row, SqlIdentifier table) {
|
||||
|
||||
if (entityCallbacks != null) {
|
||||
return entityCallbacks.callback(BeforeSaveCallback.class, object, row, table);
|
||||
}
|
||||
|
||||
return Mono.just(object);
|
||||
}
|
||||
|
||||
protected <T> Mono<T> maybeCallAfterSave(T object, OutboundRow row, SqlIdentifier table) {
|
||||
|
||||
if (entityCallbacks != null) {
|
||||
return entityCallbacks.callback(AfterSaveCallback.class, object, row, table);
|
||||
}
|
||||
|
||||
return Mono.just(object);
|
||||
}
|
||||
|
||||
protected <T> Mono<T> maybeCallAfterConvert(T object, SqlIdentifier table) {
|
||||
|
||||
if (entityCallbacks != null) {
|
||||
return entityCallbacks.callback(AfterConvertCallback.class, object, table);
|
||||
}
|
||||
|
||||
return Mono.just(object);
|
||||
}
|
||||
|
||||
private <T> Query getByIdQuery(T entity, RelationalPersistentEntity<?> persistentEntity) {
|
||||
|
||||
if (!persistentEntity.hasIdProperty()) {
|
||||
throw new MappingException("No id property found for object of type " + persistentEntity.getType() + "!");
|
||||
}
|
||||
|
||||
IdentifierAccessor identifierAccessor = persistentEntity.getIdentifierAccessor(entity);
|
||||
Object id = identifierAccessor.getRequiredIdentifier();
|
||||
|
||||
return Query.query(Criteria.where(persistentEntity.getRequiredIdProperty().getName()).is(id));
|
||||
}
|
||||
|
||||
SqlIdentifier getTableName(Class<?> entityClass) {
|
||||
return getRequiredEntity(entityClass).getTableName();
|
||||
}
|
||||
|
||||
SqlIdentifier getTableNameOrEmpty(Class<?> entityClass) {
|
||||
|
||||
RelationalPersistentEntity<?> entity = this.mappingContext.getPersistentEntity(entityClass);
|
||||
|
||||
return entity != null ? entity.getTableName() : SqlIdentifier.EMPTY;
|
||||
}
|
||||
|
||||
private RelationalPersistentEntity<?> getRequiredEntity(Class<?> entityClass) {
|
||||
return this.mappingContext.getRequiredPersistentEntity(entityClass);
|
||||
}
|
||||
|
||||
private <T> RelationalPersistentEntity<T> getRequiredEntity(T entity) {
|
||||
Class<?> entityType = ProxyUtils.getUserClass(entity);
|
||||
return (RelationalPersistentEntity) getRequiredEntity(entityType);
|
||||
}
|
||||
|
||||
private <T> List<Expression> getSelectProjection(Table table, Query query, Class<T> returnType) {
|
||||
|
||||
if (query.getColumns().isEmpty()) {
|
||||
|
||||
if (returnType.isInterface()) {
|
||||
|
||||
ProjectionInformation projectionInformation = projectionFactory.getProjectionInformation(returnType);
|
||||
|
||||
if (projectionInformation.isClosed()) {
|
||||
return projectionInformation.getInputProperties().stream().map(FeatureDescriptor::getName).map(table::column)
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
|
||||
return Collections.singletonList(table.asterisk());
|
||||
}
|
||||
|
||||
return query.getColumns().stream().map(table::column).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
private <T> RowsFetchSpec<T> getRowsFetchSpec(DatabaseClient.GenericExecuteSpec executeSpec, Class<?> entityClass,
|
||||
Class<T> returnType) {
|
||||
|
||||
boolean simpleType;
|
||||
|
||||
BiFunction<Row, RowMetadata, T> rowMapper;
|
||||
if (returnType.isInterface()) {
|
||||
simpleType = getConverter().isSimpleType(entityClass);
|
||||
rowMapper = dataAccessStrategy.getRowMapper(entityClass)
|
||||
.andThen(o -> projectionFactory.createProjection(returnType, o));
|
||||
} else {
|
||||
simpleType = getConverter().isSimpleType(returnType);
|
||||
rowMapper = dataAccessStrategy.getRowMapper(returnType);
|
||||
}
|
||||
|
||||
// avoid top-level null values if the read type is a simple one (e.g. SELECT MAX(age) via Integer.class)
|
||||
if (simpleType) {
|
||||
return new UnwrapOptionalFetchSpecAdapter<>(
|
||||
executeSpec.map((row, metadata) -> Optional.ofNullable(rowMapper.apply(row, metadata))));
|
||||
}
|
||||
|
||||
return executeSpec.map(rowMapper);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link RowsFetchSpec} adapter emitting values from {@link Optional} if they exist.
|
||||
*
|
||||
* @param <T>
|
||||
*/
|
||||
private static class UnwrapOptionalFetchSpecAdapter<T> implements RowsFetchSpec<T> {
|
||||
|
||||
private final RowsFetchSpec<Optional<T>> delegate;
|
||||
|
||||
private UnwrapOptionalFetchSpecAdapter(RowsFetchSpec<Optional<T>> delegate) {
|
||||
this.delegate = delegate;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<T> one() {
|
||||
return delegate.one().handle((optional, sink) -> optional.ifPresent(sink::next));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<T> first() {
|
||||
return delegate.first().handle((optional, sink) -> optional.ifPresent(sink::next));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<T> all() {
|
||||
return delegate.all().handle((optional, sink) -> optional.ifPresent(sink::next));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link RowsFetchSpec} adapter applying {@link #maybeCallAfterConvert(Object, SqlIdentifier)} to each emitted
|
||||
* object.
|
||||
*
|
||||
* @param <T>
|
||||
*/
|
||||
private class EntityCallbackAdapter<T> implements RowsFetchSpec<T> {
|
||||
|
||||
private final RowsFetchSpec<T> delegate;
|
||||
private final SqlIdentifier tableName;
|
||||
|
||||
private EntityCallbackAdapter(RowsFetchSpec<T> delegate, SqlIdentifier tableName) {
|
||||
this.delegate = delegate;
|
||||
this.tableName = tableName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<T> one() {
|
||||
return delegate.one().flatMap(it -> maybeCallAfterConvert(it, tableName));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<T> first() {
|
||||
return delegate.first().flatMap(it -> maybeCallAfterConvert(it, tableName));
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<T> all() {
|
||||
return delegate.all().flatMap(it -> maybeCallAfterConvert(it, tableName));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,161 +0,0 @@
|
||||
/*
|
||||
* Copyright 2018-2022 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
|
||||
*
|
||||
* https://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.r2dbc.core;
|
||||
|
||||
import io.r2dbc.spi.Row;
|
||||
import io.r2dbc.spi.RowMetadata;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.function.BiFunction;
|
||||
|
||||
import org.springframework.data.r2dbc.convert.R2dbcConverter;
|
||||
import org.springframework.data.r2dbc.mapping.OutboundRow;
|
||||
import org.springframework.data.relational.core.sql.IdentifierProcessing;
|
||||
import org.springframework.data.relational.core.sql.SqlIdentifier;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.r2dbc.core.Parameter;
|
||||
import org.springframework.r2dbc.core.PreparedOperation;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Data access strategy that generalizes convenience operations using mapped entities. Typically used internally by
|
||||
* {@link R2dbcEntityOperations} and repository support. SQL creation is limited to single-table operations and
|
||||
* single-column primary keys.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @author Jens Schauder
|
||||
* @see org.springframework.r2dbc.core.PreparedOperation
|
||||
* @deprecated since 1.2 in favor of using direct usage of {@link StatementMapper},
|
||||
* {@link org.springframework.data.r2dbc.query.UpdateMapper} and {@link R2dbcConverter}.
|
||||
*/
|
||||
@Deprecated
|
||||
public interface ReactiveDataAccessStrategy {
|
||||
|
||||
/**
|
||||
* @param entityType
|
||||
* @return all column names for a specific type.
|
||||
*/
|
||||
List<SqlIdentifier> getAllColumns(Class<?> entityType);
|
||||
|
||||
/**
|
||||
* @param entityType
|
||||
* @return all Id column names for a specific type.
|
||||
*/
|
||||
List<SqlIdentifier> getIdentifierColumns(Class<?> entityType);
|
||||
|
||||
/**
|
||||
* Returns a {@link OutboundRow} that maps column names to a {@link Parameter} value.
|
||||
*
|
||||
* @param object must not be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
OutboundRow getOutboundRow(Object object);
|
||||
|
||||
/**
|
||||
* Return a potentially converted {@link Parameter} for strategies that support type conversion.
|
||||
*
|
||||
* @param value must not be {@literal null}.
|
||||
* @return
|
||||
* @since 1.2
|
||||
*/
|
||||
Parameter getBindValue(Parameter value);
|
||||
|
||||
/**
|
||||
* Returns a {@link BiFunction row mapping function} to map {@link Row rows} to {@code T}.
|
||||
*
|
||||
* @param typeToRead
|
||||
* @param <T>
|
||||
* @return
|
||||
*/
|
||||
<T> BiFunction<Row, RowMetadata, T> getRowMapper(Class<T> typeToRead);
|
||||
|
||||
/**
|
||||
* @param type
|
||||
* @return the table name for the {@link Class entity type}.
|
||||
*/
|
||||
SqlIdentifier getTableName(Class<?> type);
|
||||
|
||||
/**
|
||||
* Expand named parameters and return a {@link PreparedOperation} wrapping the given bindings.
|
||||
*
|
||||
* @param query the query to expand.
|
||||
* @param parameterProvider indexed parameter bindings.
|
||||
* @return the {@link PreparedOperation} encapsulating expanded SQL and namedBindings.
|
||||
* @throws org.springframework.dao.InvalidDataAccessApiUsageException if a named parameter value cannot be resolved.
|
||||
* @deprecated since 1.2. {@link org.springframework.r2dbc.core.DatabaseClient} encapsulates named parameter handling
|
||||
* entirely.
|
||||
*/
|
||||
@Deprecated
|
||||
PreparedOperation<?> processNamedParameters(String query, NamedParameterProvider parameterProvider);
|
||||
|
||||
/**
|
||||
* Returns the {@link org.springframework.data.r2dbc.dialect.R2dbcDialect}-specific {@link StatementMapper}.
|
||||
*
|
||||
* @return the {@link org.springframework.data.r2dbc.dialect.R2dbcDialect}-specific {@link StatementMapper}.
|
||||
*/
|
||||
StatementMapper getStatementMapper();
|
||||
|
||||
/**
|
||||
* Returns the {@link R2dbcConverter}.
|
||||
*
|
||||
* @return the {@link R2dbcConverter}.
|
||||
*/
|
||||
R2dbcConverter getConverter();
|
||||
|
||||
/**
|
||||
* Render a {@link SqlIdentifier} for SQL usage.
|
||||
*
|
||||
* @param identifier the identifier to be rendered.
|
||||
* @return the SQL representation of the identifier with applied, potentially dialect-specific, processing rules.
|
||||
* @since 1.1
|
||||
* @see SqlIdentifier#toSql(IdentifierProcessing)
|
||||
*/
|
||||
String toSql(SqlIdentifier identifier);
|
||||
|
||||
/**
|
||||
* Render a {@link SqlIdentifier} in a way suitable for registering it as a generated key with a statement through
|
||||
* {@code Statement#returnGeneratedValues}.
|
||||
*
|
||||
* @param identifier to render. Must not be {@literal null}.
|
||||
* @return rendered identifier. Guaranteed to be not {@literal null}.
|
||||
* @since 1.3.2
|
||||
*/
|
||||
default String renderForGeneratedValues(SqlIdentifier identifier) {
|
||||
|
||||
Assert.notNull(identifier, "SqlIdentifier must not be null.");
|
||||
|
||||
return identifier.toSql(IdentifierProcessing.NONE);
|
||||
}
|
||||
|
||||
/**
|
||||
* Interface to retrieve parameters for named parameter processing.
|
||||
*/
|
||||
@FunctionalInterface
|
||||
interface NamedParameterProvider {
|
||||
|
||||
/**
|
||||
* Returns the {@link Parameter value} for a parameter identified either by name or by index.
|
||||
*
|
||||
* @param index parameter index according the parameter discovery order.
|
||||
* @param name name of the parameter.
|
||||
* @return the bindable value. Returning a {@literal null} value raises
|
||||
* {@link org.springframework.dao.InvalidDataAccessApiUsageException} in named parameter processing.
|
||||
*/
|
||||
@Nullable
|
||||
Parameter getParameter(int index, String name);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,124 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020-2022 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
|
||||
*
|
||||
* https://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.r2dbc.core;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.data.relational.core.query.Query;
|
||||
import org.springframework.data.relational.core.sql.SqlIdentifier;
|
||||
|
||||
/**
|
||||
* The {@link ReactiveDeleteOperation} interface allows creation and execution of {@code DELETE} operations in a fluent
|
||||
* API style.
|
||||
* <p>
|
||||
* The starting {@literal domainType} is used for mapping the {@link Query} provided via {@code matching}. By default,
|
||||
* the table to operate on is derived from the initial {@literal domainType} and can be defined there via
|
||||
* {@link org.springframework.data.relational.core.mapping.Table} annotation. Using {@code inTable} allows to override
|
||||
* the table name for the execution.
|
||||
*
|
||||
* <pre>
|
||||
* <code>
|
||||
* delete(Jedi.class)
|
||||
* .from("star_wars")
|
||||
* .matching(query(where("firstname").is("luke")))
|
||||
* .all();
|
||||
* </code>
|
||||
* </pre>
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 1.1
|
||||
*/
|
||||
public interface ReactiveDeleteOperation {
|
||||
|
||||
/**
|
||||
* Begin creating a {@code DELETE} operation for the given {@link Class domainType}.
|
||||
*
|
||||
* @param domainType {@link Class type} of domain object to delete; must not be {@literal null}.
|
||||
* @return new instance of {@link ReactiveDelete}.
|
||||
* @throws IllegalArgumentException if {@link Class domainType} is {@literal null}.
|
||||
* @see ReactiveDelete
|
||||
*/
|
||||
ReactiveDelete delete(Class<?> domainType);
|
||||
|
||||
/**
|
||||
* Table override (optional).
|
||||
*/
|
||||
interface DeleteWithTable extends TerminatingDelete {
|
||||
|
||||
/**
|
||||
* Explicitly set the {@link String name} of the table on which to perform the delete.
|
||||
* <p>
|
||||
* Skip this step to use the default table derived from the {@link Class domain type}.
|
||||
*
|
||||
* @param table {@link String name} of the table; must not be {@literal null} or empty.
|
||||
* @return new instance of {@link DeleteWithQuery}.
|
||||
* @throws IllegalArgumentException if {@link String table} is {@literal null} or empty.
|
||||
* @see DeleteWithQuery
|
||||
*/
|
||||
default DeleteWithQuery from(String table) {
|
||||
return from(SqlIdentifier.unquoted(table));
|
||||
}
|
||||
|
||||
/**
|
||||
* Explicitly set the {@link SqlIdentifier name} of the table on which to perform the delete.
|
||||
* <p>
|
||||
* Skip this step to use the default table derived from the {@link Class domain type}.
|
||||
*
|
||||
* @param table {@link SqlIdentifier name} of the table; must not be {@literal null}.
|
||||
* @return new instance of {@link DeleteWithQuery}.
|
||||
* @throws IllegalArgumentException if {@link SqlIdentifier table} is {@literal null}.
|
||||
* @see DeleteWithQuery
|
||||
*/
|
||||
DeleteWithQuery from(SqlIdentifier table);
|
||||
}
|
||||
|
||||
/**
|
||||
* Required {@link Query filter}.
|
||||
*/
|
||||
interface DeleteWithQuery extends TerminatingDelete {
|
||||
|
||||
/**
|
||||
* Define the {@link Query} used to filter elements in the delete.
|
||||
*
|
||||
* @param query {@link Query} used as the filter in the delete; must not be {@literal null}.
|
||||
* @return new instance of {@link TerminatingDelete}.
|
||||
* @throws IllegalArgumentException if {@link Query} is {@literal null}.
|
||||
* @see TerminatingDelete
|
||||
* @see Query
|
||||
*/
|
||||
TerminatingDelete matching(Query query);
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger {@code DELETE} operation by calling one of the terminating methods.
|
||||
*/
|
||||
interface TerminatingDelete {
|
||||
|
||||
/**
|
||||
* Remove all matching rows.
|
||||
*
|
||||
* @return the number of affected rows; never {@literal null}.
|
||||
* @see Mono
|
||||
*/
|
||||
Mono<Integer> all();
|
||||
}
|
||||
|
||||
/**
|
||||
* The {@link ReactiveDelete} interface provides methods for constructing {@code DELETE} operations in a fluent way.
|
||||
*/
|
||||
interface ReactiveDelete extends DeleteWithTable, DeleteWithQuery {}
|
||||
|
||||
}
|
||||
@@ -1,103 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020-2022 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
|
||||
*
|
||||
* https://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.r2dbc.core;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.data.relational.core.query.Query;
|
||||
import org.springframework.data.relational.core.sql.SqlIdentifier;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Implementation of {@link ReactiveDeleteOperation}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 1.1
|
||||
*/
|
||||
class ReactiveDeleteOperationSupport implements ReactiveDeleteOperation {
|
||||
|
||||
private final R2dbcEntityTemplate template;
|
||||
|
||||
ReactiveDeleteOperationSupport(R2dbcEntityTemplate template) {
|
||||
this.template = template;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.r2dbc.core.ReactiveDeleteOperation#delete(java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public ReactiveDelete delete(Class<?> domainType) {
|
||||
|
||||
Assert.notNull(domainType, "DomainType must not be null");
|
||||
|
||||
return new ReactiveDeleteSupport(template, domainType, Query.empty(), null);
|
||||
}
|
||||
|
||||
static class ReactiveDeleteSupport implements ReactiveDelete, TerminatingDelete {
|
||||
|
||||
private final R2dbcEntityTemplate template;
|
||||
private final Class<?> domainType;
|
||||
private final Query query;
|
||||
private final @Nullable SqlIdentifier tableName;
|
||||
|
||||
ReactiveDeleteSupport(R2dbcEntityTemplate template, Class<?> domainType, Query query,
|
||||
@Nullable SqlIdentifier tableName) {
|
||||
|
||||
this.template = template;
|
||||
this.domainType = domainType;
|
||||
this.query = query;
|
||||
this.tableName = tableName;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.r2dbc.core.ReactiveDeleteOperation.DeleteWithTable#from(SqlIdentifier)
|
||||
*/
|
||||
@Override
|
||||
public DeleteWithQuery from(SqlIdentifier tableName) {
|
||||
|
||||
Assert.notNull(tableName, "Table name must not be null");
|
||||
|
||||
return new ReactiveDeleteSupport(template, domainType, query, tableName);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.r2dbc.core.ReactiveDeleteOperation.DeleteWithQuery#matching(org.springframework.data.r2dbc.query.Query)
|
||||
*/
|
||||
@Override
|
||||
public TerminatingDelete matching(Query query) {
|
||||
|
||||
Assert.notNull(query, "Query must not be null");
|
||||
|
||||
return new ReactiveDeleteSupport(template, domainType, query, tableName);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.r2dbc.core.ReactiveDeleteOperation.TerminatingDelete#all()
|
||||
*/
|
||||
public Mono<Integer> all() {
|
||||
return template.doDelete(query, domainType, getTableName());
|
||||
}
|
||||
|
||||
private SqlIdentifier getTableName() {
|
||||
return tableName != null ? tableName : template.getTableName(domainType);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,105 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020-2022 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
|
||||
*
|
||||
* https://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.r2dbc.core;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.data.relational.core.sql.SqlIdentifier;
|
||||
|
||||
/**
|
||||
* The {@link ReactiveInsertOperation} interface allows creation and execution of {@code INSERT} operations in a fluent
|
||||
* API style.
|
||||
* <p>
|
||||
* By default,the table to operate on is derived from the initial {@link Class domainType} and can be defined there via
|
||||
* {@link org.springframework.data.relational.core.mapping.Table} annotation. Using {@code inTable} allows to override
|
||||
* the table name for the execution.
|
||||
*
|
||||
* <pre>
|
||||
* <code>
|
||||
* insert(Jedi.class)
|
||||
* .into("star_wars")
|
||||
* .using(luke);
|
||||
* </code>
|
||||
* </pre>
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 1.1
|
||||
*/
|
||||
public interface ReactiveInsertOperation {
|
||||
|
||||
/**
|
||||
* Begin creating an {@code INSERT} operation for given {@link Class domainType}.
|
||||
*
|
||||
* @param <T> {@link Class type} of the application domain object.
|
||||
* @param domainType {@link Class type} of the domain object to insert; must not be {@literal null}.
|
||||
* @return new instance of {@link ReactiveInsert}.
|
||||
* @throws IllegalArgumentException if {@link Class domainType} is {@literal null}.
|
||||
* @see ReactiveInsert
|
||||
*/
|
||||
<T> ReactiveInsert<T> insert(Class<T> domainType);
|
||||
|
||||
/**
|
||||
* Table override (optional).
|
||||
*/
|
||||
interface InsertWithTable<T> extends TerminatingInsert<T> {
|
||||
|
||||
/**
|
||||
* Explicitly set the {@link String name} of the table.
|
||||
* <p>
|
||||
* Skip this step to use the default table derived from the {@link Class domain type}.
|
||||
*
|
||||
* @param table {@link String name} of the table; must not be {@literal null} or empty.
|
||||
* @return new instance of {@link TerminatingInsert}.
|
||||
* @throws IllegalArgumentException if {@link String table} is {@literal null} or empty.
|
||||
*/
|
||||
default TerminatingInsert<T> into(String table) {
|
||||
return into(SqlIdentifier.unquoted(table));
|
||||
}
|
||||
|
||||
/**
|
||||
* Explicitly set the {@link SqlIdentifier name} of the table.
|
||||
* <p>
|
||||
* Skip this step to use the default table derived from the {@link Class domain type}.
|
||||
*
|
||||
* @param table {@link SqlIdentifier name} of the table; must not be {@literal null}.
|
||||
* @return new instance of {@link TerminatingInsert}.
|
||||
* @throws IllegalArgumentException if {@link SqlIdentifier table} is {@literal null}.
|
||||
*/
|
||||
TerminatingInsert<T> into(SqlIdentifier table);
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger {@code INSERT} execution by calling one of the terminating methods.
|
||||
*/
|
||||
interface TerminatingInsert<T> {
|
||||
|
||||
/**
|
||||
* Insert exactly one {@link Object}.
|
||||
*
|
||||
* @param object {@link Object} to insert; must not be {@literal null}.
|
||||
* @return the write result for this operation.
|
||||
* @throws IllegalArgumentException if {@link Object} is {@literal null}.
|
||||
* @see Mono
|
||||
*/
|
||||
Mono<T> using(T object);
|
||||
}
|
||||
|
||||
/**
|
||||
* The {@link ReactiveInsert} interface provides methods for constructing {@code INSERT} operations in a fluent way.
|
||||
*/
|
||||
interface ReactiveInsert<T> extends InsertWithTable<T> {}
|
||||
|
||||
}
|
||||
@@ -1,91 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020-2022 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
|
||||
*
|
||||
* https://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.r2dbc.core;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.data.relational.core.sql.SqlIdentifier;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Implementation of {@link ReactiveInsertOperation}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 1.1
|
||||
*/
|
||||
class ReactiveInsertOperationSupport implements ReactiveInsertOperation {
|
||||
|
||||
private final R2dbcEntityTemplate template;
|
||||
|
||||
ReactiveInsertOperationSupport(R2dbcEntityTemplate template) {
|
||||
this.template = template;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.r2dbc.core.ReactiveInsertOperation#insert(java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public <T> ReactiveInsert<T> insert(Class<T> domainType) {
|
||||
|
||||
Assert.notNull(domainType, "DomainType must not be null");
|
||||
|
||||
return new ReactiveInsertSupport<>(template, domainType, null);
|
||||
}
|
||||
|
||||
static class ReactiveInsertSupport<T> implements ReactiveInsert<T> {
|
||||
|
||||
private final R2dbcEntityTemplate template;
|
||||
private final Class<T> domainType;
|
||||
private final @Nullable SqlIdentifier tableName;
|
||||
|
||||
ReactiveInsertSupport(R2dbcEntityTemplate template, Class<T> domainType, @Nullable SqlIdentifier tableName) {
|
||||
|
||||
this.template = template;
|
||||
this.domainType = domainType;
|
||||
this.tableName = tableName;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.r2dbc.core.ReactiveInsertOperation.InsertWithTable#into(SqlIdentifier)
|
||||
*/
|
||||
@Override
|
||||
public TerminatingInsert<T> into(SqlIdentifier tableName) {
|
||||
|
||||
Assert.notNull(tableName, "Table name must not be null");
|
||||
|
||||
return new ReactiveInsertSupport<>(template, domainType, tableName);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.r2dbc.core.ReactiveInsertOperation.TerminatingInsert#one(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public Mono<T> using(T object) {
|
||||
|
||||
Assert.notNull(object, "Object to insert must not be null");
|
||||
|
||||
return template.doInsert(object, getTableName());
|
||||
}
|
||||
|
||||
private SqlIdentifier getTableName() {
|
||||
return tableName != null ? tableName : template.getTableName(domainType);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,182 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020-2022 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
|
||||
*
|
||||
* https://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.r2dbc.core;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.data.relational.core.query.Query;
|
||||
import org.springframework.data.relational.core.sql.SqlIdentifier;
|
||||
|
||||
/**
|
||||
* The {@link ReactiveSelectOperation} interface allows creation and execution of {@code SELECT} operations in a fluent
|
||||
* API style.
|
||||
* <p>
|
||||
* The starting {@literal domainType} is used for mapping the {@link Query} provided via {@code matching}. By default,
|
||||
* the originating {@literal domainType} is also used for mapping back the result from the {@link io.r2dbc.spi.Row}.
|
||||
* However, it is possible to define an different {@literal returnType} via {@code as} to mapping the result.
|
||||
* <p>
|
||||
* By default, the table to operate on is derived from the initial {@literal domainType} and can be defined there via
|
||||
* the {@link org.springframework.data.relational.core.mapping.Table} annotation. Using {@code inTable} allows to
|
||||
* override the table name for the execution.
|
||||
*
|
||||
* <pre>
|
||||
* <code>
|
||||
* select(Human.class)
|
||||
* .from("star_wars")
|
||||
* .as(Jedi.class)
|
||||
* .matching(query(where("firstname").is("luke")))
|
||||
* .all();
|
||||
* </code>
|
||||
* </pre>
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 1.1
|
||||
*/
|
||||
public interface ReactiveSelectOperation {
|
||||
|
||||
/**
|
||||
* Begin creating a {@code SELECT} operation for the given {@link Class domainType}.
|
||||
*
|
||||
* @param <T> {@link Class type} of the application domain object.
|
||||
* @param domainType {@link Class type} of the domain object to query; must not be {@literal null}.
|
||||
* @return new instance of {@link ReactiveSelect}.
|
||||
* @throws IllegalArgumentException if {@link Class domainType} is {@literal null}.
|
||||
* @see ReactiveSelect
|
||||
*/
|
||||
<T> ReactiveSelect<T> select(Class<T> domainType);
|
||||
|
||||
/**
|
||||
* Table override (optional).
|
||||
*/
|
||||
interface SelectWithTable<T> extends SelectWithQuery<T> {
|
||||
|
||||
/**
|
||||
* Explicitly set the {@link String name} of the table on which to perform the query.
|
||||
* <p>
|
||||
* Skip this step to use the default table derived from the {@link Class domain type}.
|
||||
*
|
||||
* @param table {@link String name} of the table; must not be {@literal null} or empty.
|
||||
* @return new instance of {@link SelectWithProjection}.
|
||||
* @throws IllegalArgumentException if {@link String table} is {@literal null} or empty.
|
||||
* @see SelectWithProjection
|
||||
*/
|
||||
default SelectWithProjection<T> from(String table) {
|
||||
return from(SqlIdentifier.unquoted(table));
|
||||
}
|
||||
|
||||
/**
|
||||
* Explicitly set the {@link SqlIdentifier name} of the table on which to perform the query.
|
||||
* <p>
|
||||
* Skip this step to use the default table derived from the {@link Class domain type}.
|
||||
*
|
||||
* @param table {@link SqlIdentifier name} of the table; must not be {@literal null}.
|
||||
* @return new instance of {@link SelectWithProjection}.
|
||||
* @throws IllegalArgumentException if {@link SqlIdentifier table} is {@literal null}.
|
||||
* @see SelectWithProjection
|
||||
*/
|
||||
SelectWithProjection<T> from(SqlIdentifier table);
|
||||
}
|
||||
|
||||
/**
|
||||
* Result type override (optional).
|
||||
*/
|
||||
interface SelectWithProjection<T> extends SelectWithQuery<T> {
|
||||
|
||||
/**
|
||||
* Define the {@link Class result target type} that the fields should be mapped to.
|
||||
* <p>
|
||||
* Skip this step if you are only interested in the original {@link Class domain type}.
|
||||
*
|
||||
* @param <R> {@link Class type} of the result.
|
||||
* @param resultType desired {@link Class type} of the result; must not be {@literal null}.
|
||||
* @return new instance of {@link SelectWithQuery}.
|
||||
* @throws IllegalArgumentException if {@link Class resultType} is {@literal null}.
|
||||
* @see SelectWithQuery
|
||||
*/
|
||||
<R> SelectWithQuery<R> as(Class<R> resultType);
|
||||
}
|
||||
|
||||
/**
|
||||
* Define a {@link Query} used as the filter for the {@code SELECT}.
|
||||
*/
|
||||
interface SelectWithQuery<T> extends TerminatingSelect<T> {
|
||||
|
||||
/**
|
||||
* Set the {@link Query} used as a filter in the {@code SELECT} statement.
|
||||
*
|
||||
* @param query {@link Query} used as a filter; must not be {@literal null}.
|
||||
* @return new instance of {@link TerminatingSelect}.
|
||||
* @throws IllegalArgumentException if {@link Query} is {@literal null}.
|
||||
* @see Query
|
||||
* @see TerminatingSelect
|
||||
*/
|
||||
TerminatingSelect<T> matching(Query query);
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger {@code SELECT} execution by calling one of the terminating methods.
|
||||
*/
|
||||
interface TerminatingSelect<T> {
|
||||
|
||||
/**
|
||||
* Get the number of matching elements.
|
||||
*
|
||||
* @return a {@link Mono} emitting the total number of matching elements; never {@literal null}.
|
||||
* @see Mono
|
||||
*/
|
||||
Mono<Long> count();
|
||||
|
||||
/**
|
||||
* Check for the presence of matching elements.
|
||||
*
|
||||
* @return a {@link Mono} emitting {@literal true} if at least one matching element exists; never {@literal null}.
|
||||
* @see Mono
|
||||
*/
|
||||
Mono<Boolean> exists();
|
||||
|
||||
/**
|
||||
* Get the first result or no result.
|
||||
*
|
||||
* @return the first result or {@link Mono#empty()} if no match found; never {@literal null}.
|
||||
* @see Mono
|
||||
*/
|
||||
Mono<T> first();
|
||||
|
||||
/**
|
||||
* Get exactly zero or one result.
|
||||
*
|
||||
* @return exactly one result or {@link Mono#empty()} if no match found; never {@literal null}.
|
||||
* @throws org.springframework.dao.IncorrectResultSizeDataAccessException if more than one match found.
|
||||
* @see Mono
|
||||
*/
|
||||
Mono<T> one();
|
||||
|
||||
/**
|
||||
* Get all matching elements.
|
||||
*
|
||||
* @return all matching elements; never {@literal null}.
|
||||
* @see Flux
|
||||
*/
|
||||
Flux<T> all();
|
||||
}
|
||||
|
||||
/**
|
||||
* The {@link ReactiveSelect} interface provides methods for constructing {@code SELECT} operations in a fluent way.
|
||||
*/
|
||||
interface ReactiveSelect<T> extends SelectWithTable<T>, SelectWithProjection<T> {}
|
||||
|
||||
}
|
||||
@@ -1,156 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020-2022 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
|
||||
*
|
||||
* https://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.r2dbc.core;
|
||||
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.data.relational.core.query.Query;
|
||||
import org.springframework.data.relational.core.sql.SqlIdentifier;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.r2dbc.core.RowsFetchSpec;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Implementation of {@link ReactiveSelectOperation}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 1.1
|
||||
*/
|
||||
class ReactiveSelectOperationSupport implements ReactiveSelectOperation {
|
||||
|
||||
private final R2dbcEntityTemplate template;
|
||||
|
||||
ReactiveSelectOperationSupport(R2dbcEntityTemplate template) {
|
||||
this.template = template;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.r2dbc.core.ReactiveSelectOperation#select(java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public <T> ReactiveSelect<T> select(Class<T> domainType) {
|
||||
|
||||
Assert.notNull(domainType, "DomainType must not be null");
|
||||
|
||||
return new ReactiveSelectSupport<>(this.template, domainType, domainType, Query.empty(), null);
|
||||
}
|
||||
|
||||
static class ReactiveSelectSupport<T> implements ReactiveSelect<T> {
|
||||
|
||||
private final R2dbcEntityTemplate template;
|
||||
private final Class<?> domainType;
|
||||
private final Class<T> returnType;
|
||||
private final Query query;
|
||||
private final @Nullable SqlIdentifier tableName;
|
||||
|
||||
ReactiveSelectSupport(R2dbcEntityTemplate template, Class<?> domainType, Class<T> returnType, Query query,
|
||||
@Nullable SqlIdentifier tableName) {
|
||||
|
||||
this.template = template;
|
||||
this.domainType = domainType;
|
||||
this.returnType = returnType;
|
||||
this.query = query;
|
||||
this.tableName = tableName;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.r2dbc.core.ReactiveSelectOperation.SelectWithTable#from(java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
public SelectWithProjection<T> from(SqlIdentifier tableName) {
|
||||
|
||||
Assert.notNull(tableName, "Table name must not be null");
|
||||
|
||||
return new ReactiveSelectSupport<>(template, domainType, returnType, query, tableName);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.r2dbc.core.ReactiveSelectOperation.SelectWithProjection#as(java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public <R> SelectWithQuery<R> as(Class<R> returnType) {
|
||||
|
||||
Assert.notNull(returnType, "ReturnType must not be null");
|
||||
|
||||
return new ReactiveSelectSupport<>(template, domainType, returnType, query, tableName);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.r2dbc.core.ReactiveSelectOperation.SelectWithQuery#matching(org.springframework.data.r2dbc.query.Query)
|
||||
*/
|
||||
@Override
|
||||
public TerminatingSelect<T> matching(Query query) {
|
||||
|
||||
Assert.notNull(query, "Query must not be null");
|
||||
|
||||
return new ReactiveSelectSupport<>(template, domainType, returnType, query, tableName);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.r2dbc.core.ReactiveSelectOperation.TerminatingSelect#count()
|
||||
*/
|
||||
@Override
|
||||
public Mono<Long> count() {
|
||||
return template.doCount(query, domainType, getTableName());
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.r2dbc.core.ReactiveSelectOperation.TerminatingSelect#exists()
|
||||
*/
|
||||
@Override
|
||||
public Mono<Boolean> exists() {
|
||||
return template.doExists(query, domainType, getTableName());
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.r2dbc.core.ReactiveSelectOperation.TerminatingSelect#first()
|
||||
*/
|
||||
@Override
|
||||
public Mono<T> first() {
|
||||
return template.doSelect(query.limit(1), domainType, getTableName(), returnType, RowsFetchSpec::first);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.r2dbc.core.ReactiveSelectOperation.TerminatingSelect#one()
|
||||
*/
|
||||
@Override
|
||||
public Mono<T> one() {
|
||||
return template.doSelect(query.limit(2), domainType, getTableName(), returnType, RowsFetchSpec::one);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.r2dbc.core.ReactiveSelectOperation.TerminatingSelect#all()
|
||||
*/
|
||||
@Override
|
||||
public Flux<T> all() {
|
||||
return template.doSelect(query, domainType, getTableName(), returnType, RowsFetchSpec::all);
|
||||
}
|
||||
|
||||
private SqlIdentifier getTableName() {
|
||||
return tableName != null ? tableName : template.getTableName(domainType);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,128 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020-2022 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
|
||||
*
|
||||
* https://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.r2dbc.core;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.data.relational.core.query.Query;
|
||||
import org.springframework.data.relational.core.query.Update;
|
||||
import org.springframework.data.relational.core.sql.SqlIdentifier;
|
||||
|
||||
/**
|
||||
* The {@link ReactiveUpdateOperation} interface allows creation and execution of {@code UPDATE} operations in a fluent
|
||||
* API style.
|
||||
* <p>
|
||||
* The starting {@literal domainType} is used for mapping the {@link Query} provided via {@code matching}, as well as
|
||||
* the {@link Update} via {@code apply}.
|
||||
* <p>
|
||||
* By default, the table to operate on is derived from the initial {@literal domainType} and can be defined there via
|
||||
* the {@link org.springframework.data.relational.core.mapping.Table} annotation. Using {@code inTable} allows a
|
||||
* developer to override the table name for the execution.
|
||||
*
|
||||
* <pre>
|
||||
* <code>
|
||||
* update(Jedi.class)
|
||||
* .table("star_wars")
|
||||
* .matching(query(where("firstname").is("luke")))
|
||||
* .apply(update("lastname", "skywalker"))
|
||||
* .all();
|
||||
* </code>
|
||||
* </pre>
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 1.1
|
||||
*/
|
||||
public interface ReactiveUpdateOperation {
|
||||
|
||||
/**
|
||||
* Begin creating an {@code UPDATE} operation for the given {@link Class domainType}.
|
||||
*
|
||||
* @param domainType {@link Class type} of domain object to update; must not be {@literal null}.
|
||||
* @return new instance of {@link ReactiveUpdate}.
|
||||
* @throws IllegalArgumentException if {@link Class domainType} is {@literal null}.
|
||||
* @see ReactiveUpdate
|
||||
*/
|
||||
ReactiveUpdate update(Class<?> domainType);
|
||||
|
||||
/**
|
||||
* Table override (optional).
|
||||
*/
|
||||
interface UpdateWithTable extends TerminatingUpdate {
|
||||
|
||||
/**
|
||||
* Explicitly set the {@link String name} of the table on which to perform the update.
|
||||
* <p>
|
||||
* Skip this step to use the default table derived from the {@link Class domain type}.
|
||||
*
|
||||
* @param table {@link String name} of the table; must not be {@literal null} or empty.
|
||||
* @return new instance of {@link UpdateWithQuery}.
|
||||
* @throws IllegalArgumentException if {@link String table} is {@literal null} or empty.
|
||||
* @see UpdateWithQuery
|
||||
*/
|
||||
default UpdateWithQuery inTable(String table) {
|
||||
return inTable(SqlIdentifier.unquoted(table));
|
||||
}
|
||||
|
||||
/**
|
||||
* Explicitly set the {@link SqlIdentifier name} of the table on which to perform the update.
|
||||
* <p>
|
||||
* Skip this step to use the default table derived from the {@link Class domain type}.
|
||||
*
|
||||
* @param table {@link SqlIdentifier name} of the table; must not be {@literal null}.
|
||||
* @return new instance of {@link UpdateWithQuery}.
|
||||
* @throws IllegalArgumentException if {@link SqlIdentifier table} is {@literal null}.
|
||||
* @see UpdateWithQuery
|
||||
*/
|
||||
UpdateWithQuery inTable(SqlIdentifier table);
|
||||
}
|
||||
|
||||
/**
|
||||
* Define a {@link Query} used as the filter for the {@link Update}.
|
||||
*/
|
||||
interface UpdateWithQuery extends TerminatingUpdate {
|
||||
|
||||
/**
|
||||
* Filter rows to update by the given {@link Query}.
|
||||
*
|
||||
* @param query {@link Query} used as a filter in the update; must not be {@literal null}.
|
||||
* @return new instance of {@link TerminatingUpdate}.
|
||||
* @throws IllegalArgumentException if {@link Query} is {@literal null}.
|
||||
* @see Query
|
||||
* @see TerminatingUpdate
|
||||
*/
|
||||
TerminatingUpdate matching(Query query);
|
||||
}
|
||||
|
||||
/**
|
||||
* Trigger {@code UPDATE} execution by calling one of the terminating methods.
|
||||
*/
|
||||
interface TerminatingUpdate {
|
||||
|
||||
/**
|
||||
* Update all matching rows in the table.
|
||||
*
|
||||
* @return the number of affected rows by the update; never {@literal null}.
|
||||
* @see Mono
|
||||
*/
|
||||
Mono<Integer> apply(Update update);
|
||||
}
|
||||
|
||||
/**
|
||||
* The {@link ReactiveUpdate} interface provides methods for constructing {@code UPDATE} operations in a fluent way.
|
||||
*/
|
||||
interface ReactiveUpdate extends UpdateWithTable, UpdateWithQuery {}
|
||||
|
||||
}
|
||||
@@ -1,108 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020-2022 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
|
||||
*
|
||||
* https://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.r2dbc.core;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.springframework.data.relational.core.query.Query;
|
||||
import org.springframework.data.relational.core.query.Update;
|
||||
import org.springframework.data.relational.core.sql.SqlIdentifier;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Implementation of {@link ReactiveUpdateOperation}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 1.1
|
||||
*/
|
||||
class ReactiveUpdateOperationSupport implements ReactiveUpdateOperation {
|
||||
|
||||
private final R2dbcEntityTemplate template;
|
||||
|
||||
ReactiveUpdateOperationSupport(R2dbcEntityTemplate template) {
|
||||
this.template = template;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.r2dbc.core.ReactiveUpdateOperation#update(java.lang.Class)
|
||||
*/
|
||||
@Override
|
||||
public ReactiveUpdate update(Class<?> domainType) {
|
||||
|
||||
Assert.notNull(domainType, "DomainType must not be null");
|
||||
|
||||
return new ReactiveUpdateSupport(template, domainType, Query.empty(), null);
|
||||
}
|
||||
|
||||
static class ReactiveUpdateSupport implements ReactiveUpdate, TerminatingUpdate {
|
||||
|
||||
private final R2dbcEntityTemplate template;
|
||||
private final Class<?> domainType;
|
||||
private final Query query;
|
||||
private final @Nullable SqlIdentifier tableName;
|
||||
|
||||
ReactiveUpdateSupport(R2dbcEntityTemplate template, Class<?> domainType, Query query,
|
||||
@Nullable SqlIdentifier tableName) {
|
||||
|
||||
this.template = template;
|
||||
this.domainType = domainType;
|
||||
this.query = query;
|
||||
this.tableName = tableName;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.r2dbc.core.ReactiveUpdateOperation.UpdateWithTable#inTable(SqlIdentifier)
|
||||
*/
|
||||
@Override
|
||||
public UpdateWithQuery inTable(SqlIdentifier tableName) {
|
||||
|
||||
Assert.notNull(tableName, "Table name must not be null");
|
||||
|
||||
return new ReactiveUpdateSupport(template, domainType, query, tableName);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.r2dbc.core.ReactiveUpdateOperation.UpdateWithQuery#matching(org.springframework.data.r2dbc.query.Query)
|
||||
*/
|
||||
@Override
|
||||
public TerminatingUpdate matching(Query query) {
|
||||
|
||||
Assert.notNull(query, "Query must not be null");
|
||||
|
||||
return new ReactiveUpdateSupport(template, domainType, query, tableName);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.r2dbc.core.ReactiveUpdateOperation.TerminatingUpdate#apply(org.springframework.data.r2dbc.query.Update)
|
||||
*/
|
||||
@Override
|
||||
public Mono<Integer> apply(Update update) {
|
||||
|
||||
Assert.notNull(update, "Update must not be null");
|
||||
|
||||
return template.doUpdate(query, update, domainType, getTableName());
|
||||
}
|
||||
|
||||
private SqlIdentifier getTableName() {
|
||||
return tableName != null ? tableName : template.getTableName(this.domainType);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,632 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019-2022 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
|
||||
*
|
||||
* https://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.r2dbc.core;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.function.BiFunction;
|
||||
import java.util.function.Supplier;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.springframework.data.domain.Pageable;
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.r2dbc.convert.R2dbcConverter;
|
||||
import org.springframework.data.r2dbc.dialect.R2dbcDialect;
|
||||
import org.springframework.data.relational.core.query.Criteria;
|
||||
import org.springframework.data.relational.core.query.CriteriaDefinition;
|
||||
import org.springframework.data.relational.core.sql.Expression;
|
||||
import org.springframework.data.relational.core.sql.SqlIdentifier;
|
||||
import org.springframework.data.relational.core.sql.Table;
|
||||
import org.springframework.data.relational.core.sql.render.RenderContext;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.r2dbc.core.Parameter;
|
||||
import org.springframework.r2dbc.core.PreparedOperation;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Mapper for statement specifications to {@link PreparedOperation}. Statement mapping applies a
|
||||
* {@link org.springframework.data.r2dbc.dialect.R2dbcDialect}-specific transformation considering
|
||||
* {@link org.springframework.r2dbc.core.binding.BindMarkers} and vendor-specific SQL differences.
|
||||
* <p>
|
||||
* {@link PreparedOperation Mapped statements} can be used directly with
|
||||
* {@link org.springframework.r2dbc.core.DatabaseClient#sql(Supplier)} without specifying further SQL or bindings as the
|
||||
* prepared operation encapsulates the specified SQL operation.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @author Roman Chigvintsev
|
||||
* @author Mingyuan Wu
|
||||
*/
|
||||
public interface StatementMapper {
|
||||
|
||||
/**
|
||||
* Create a new {@link StatementMapper} given {@link R2dbcDialect} and {@link R2dbcConverter}.
|
||||
*
|
||||
* @param dialect must not be {@literal null}.
|
||||
* @param converter must not be {@literal null}.
|
||||
* @return the new {@link StatementMapper}.
|
||||
* @since 1.2
|
||||
*/
|
||||
static StatementMapper create(R2dbcDialect dialect, R2dbcConverter converter) {
|
||||
|
||||
Assert.notNull(dialect, "R2dbcDialect must not be null");
|
||||
Assert.notNull(converter, "R2dbcConverter must not be null");
|
||||
|
||||
return new DefaultStatementMapper(dialect, converter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a typed {@link StatementMapper} that considers type-specific mapping metadata.
|
||||
*
|
||||
* @param type must not be {@literal null}.
|
||||
* @param <T>
|
||||
* @return the typed {@link StatementMapper}.
|
||||
*/
|
||||
<T> TypedStatementMapper<T> forType(Class<T> type);
|
||||
|
||||
/**
|
||||
* Map a select specification to a {@link PreparedOperation}.
|
||||
*
|
||||
* @param selectSpec the insert operation definition, must not be {@literal null}.
|
||||
* @return the {@link PreparedOperation} for {@link SelectSpec}.
|
||||
*/
|
||||
PreparedOperation<?> getMappedObject(SelectSpec selectSpec);
|
||||
|
||||
/**
|
||||
* Map a insert specification to a {@link PreparedOperation}.
|
||||
*
|
||||
* @param insertSpec the insert operation definition, must not be {@literal null}.
|
||||
* @return the {@link PreparedOperation} for {@link InsertSpec}.
|
||||
*/
|
||||
PreparedOperation<?> getMappedObject(InsertSpec insertSpec);
|
||||
|
||||
/**
|
||||
* Map a update specification to a {@link PreparedOperation}.
|
||||
*
|
||||
* @param updateSpec the update operation definition, must not be {@literal null}.
|
||||
* @return the {@link PreparedOperation} for {@link UpdateSpec}.
|
||||
*/
|
||||
PreparedOperation<?> getMappedObject(UpdateSpec updateSpec);
|
||||
|
||||
/**
|
||||
* Map a delete specification to a {@link PreparedOperation}.
|
||||
*
|
||||
* @param deleteSpec the update operation definition, must not be {@literal null}.
|
||||
* @return the {@link PreparedOperation} for {@link DeleteSpec}.
|
||||
*/
|
||||
PreparedOperation<?> getMappedObject(DeleteSpec deleteSpec);
|
||||
|
||||
/**
|
||||
* Extension to {@link StatementMapper} that is associated with a type.
|
||||
*
|
||||
* @param <T>
|
||||
*/
|
||||
interface TypedStatementMapper<T> extends StatementMapper {}
|
||||
|
||||
/**
|
||||
* Create a {@code SELECT} specification for {@code table}.
|
||||
*
|
||||
* @param table
|
||||
* @return the {@link SelectSpec}.
|
||||
*/
|
||||
default SelectSpec createSelect(String table) {
|
||||
return SelectSpec.create(table);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@code SELECT} specification for {@code table}.
|
||||
*
|
||||
* @param table
|
||||
* @return the {@link SelectSpec}.
|
||||
* @since 1.1
|
||||
*/
|
||||
default SelectSpec createSelect(SqlIdentifier table) {
|
||||
return SelectSpec.create(table);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an {@code INSERT} specification for {@code table}.
|
||||
*
|
||||
* @param table
|
||||
* @return the {@link InsertSpec}.
|
||||
*/
|
||||
default InsertSpec createInsert(String table) {
|
||||
return InsertSpec.create(table);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an {@code INSERT} specification for {@code table}.
|
||||
*
|
||||
* @param table
|
||||
* @return the {@link InsertSpec}.
|
||||
* @since 1.1
|
||||
*/
|
||||
default InsertSpec createInsert(SqlIdentifier table) {
|
||||
return InsertSpec.create(table);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an {@code UPDATE} specification for {@code table}.
|
||||
*
|
||||
* @param table
|
||||
* @return the {@link UpdateSpec}.
|
||||
*/
|
||||
default UpdateSpec createUpdate(String table, org.springframework.data.relational.core.query.Update update) {
|
||||
return UpdateSpec.create(table, update);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an {@code UPDATE} specification for {@code table}.
|
||||
*
|
||||
* @param table
|
||||
* @return the {@link UpdateSpec}.
|
||||
* @since 1.1
|
||||
*/
|
||||
default UpdateSpec createUpdate(SqlIdentifier table, org.springframework.data.relational.core.query.Update update) {
|
||||
return UpdateSpec.create(table, update);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@code DELETE} specification for {@code table}.
|
||||
*
|
||||
* @param table
|
||||
* @return the {@link DeleteSpec}.
|
||||
*/
|
||||
default DeleteSpec createDelete(String table) {
|
||||
return DeleteSpec.create(table);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@code DELETE} specification for {@code table}.
|
||||
*
|
||||
* @param table
|
||||
* @return the {@link DeleteSpec}.
|
||||
* @since 1.1
|
||||
*/
|
||||
default DeleteSpec createDelete(SqlIdentifier table) {
|
||||
return DeleteSpec.create(table);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns {@link RenderContext}.
|
||||
*
|
||||
* @return {@link RenderContext} instance or {@literal null} if {@link RenderContext} is not available
|
||||
*/
|
||||
@Nullable
|
||||
default RenderContext getRenderContext() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* {@code SELECT} specification.
|
||||
*/
|
||||
class SelectSpec {
|
||||
|
||||
private final Table table;
|
||||
private final List<String> projectedFields;
|
||||
private final List<Expression> selectList;
|
||||
private final @Nullable CriteriaDefinition criteria;
|
||||
private final Sort sort;
|
||||
private final long offset;
|
||||
private final int limit;
|
||||
private final boolean distinct;
|
||||
|
||||
protected SelectSpec(Table table, List<String> projectedFields, List<Expression> selectList,
|
||||
@Nullable CriteriaDefinition criteria, Sort sort, int limit, long offset, boolean distinct) {
|
||||
this.table = table;
|
||||
this.projectedFields = projectedFields;
|
||||
this.selectList = selectList;
|
||||
this.criteria = criteria;
|
||||
this.sort = sort;
|
||||
this.offset = offset;
|
||||
this.limit = limit;
|
||||
this.distinct = distinct;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an {@code SELECT} specification for {@code table}.
|
||||
*
|
||||
* @param table
|
||||
* @return the {@link SelectSpec}.
|
||||
*/
|
||||
public static SelectSpec create(String table) {
|
||||
return create(SqlIdentifier.unquoted(table));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an {@code SELECT} specification for {@code table}.
|
||||
*
|
||||
* @param table
|
||||
* @return the {@link SelectSpec}.
|
||||
* @since 1.1
|
||||
*/
|
||||
public static SelectSpec create(SqlIdentifier table) {
|
||||
|
||||
List<String> projectedFields = Collections.emptyList();
|
||||
List<Expression> selectList = Collections.emptyList();
|
||||
return new SelectSpec(Table.create(table), projectedFields, selectList, Criteria.empty(), Sort.unsorted(), -1, -1,
|
||||
false);
|
||||
}
|
||||
|
||||
public SelectSpec doWithTable(BiFunction<Table, SelectSpec, SelectSpec> function) {
|
||||
return function.apply(getTable(), this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Associate {@code projectedFields} with the select and create a new {@link SelectSpec}.
|
||||
*
|
||||
* @param projectedFields
|
||||
* @return the {@link SelectSpec}.
|
||||
* @since 1.1
|
||||
*/
|
||||
public SelectSpec withProjection(String... projectedFields) {
|
||||
return withProjection(Arrays.stream(projectedFields).map(table::column).collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Associate {@code projectedFields} with the select and create a new {@link SelectSpec}.
|
||||
*
|
||||
* @param projectedFields
|
||||
* @return the {@link SelectSpec}.
|
||||
* @since 1.1
|
||||
*/
|
||||
public SelectSpec withProjection(SqlIdentifier... projectedFields) {
|
||||
return withProjection(Arrays.stream(projectedFields).map(table::column).collect(Collectors.toList()));
|
||||
}
|
||||
|
||||
/**
|
||||
* Associate {@code expressions} with the select list and create a new {@link SelectSpec}.
|
||||
*
|
||||
* @param expressions
|
||||
* @return the {@link SelectSpec}.
|
||||
* @since 1.1
|
||||
*/
|
||||
public SelectSpec withProjection(Expression... expressions) {
|
||||
|
||||
List<Expression> selectList = new ArrayList<>(this.selectList);
|
||||
selectList.addAll(Arrays.asList(expressions));
|
||||
|
||||
return new SelectSpec(this.table, projectedFields, selectList, this.criteria, this.sort, this.limit, this.offset,
|
||||
this.distinct);
|
||||
}
|
||||
|
||||
/**
|
||||
* Associate {@code projectedFields} with the select and create a new {@link SelectSpec}.
|
||||
*
|
||||
* @param projectedFields
|
||||
* @return the {@link SelectSpec}.
|
||||
* @since 1.1
|
||||
*/
|
||||
public SelectSpec withProjection(Collection<Expression> projectedFields) {
|
||||
|
||||
List<Expression> selectList = new ArrayList<>(this.selectList);
|
||||
selectList.addAll(projectedFields);
|
||||
|
||||
return new SelectSpec(this.table, this.projectedFields, selectList, this.criteria, this.sort, this.limit,
|
||||
this.offset, this.distinct);
|
||||
}
|
||||
|
||||
/**
|
||||
* Associate a {@link Criteria} with the select and return a new {@link SelectSpec}.
|
||||
*
|
||||
* @param criteria
|
||||
* @return the {@link SelectSpec}.
|
||||
*/
|
||||
public SelectSpec withCriteria(CriteriaDefinition criteria) {
|
||||
return new SelectSpec(this.table, this.projectedFields, this.selectList, criteria, this.sort, this.limit,
|
||||
this.offset, this.distinct);
|
||||
}
|
||||
|
||||
/**
|
||||
* Associate {@link Sort} with the select and create a new {@link SelectSpec}.
|
||||
*
|
||||
* @param sort
|
||||
* @return the {@link SelectSpec}.
|
||||
*/
|
||||
public SelectSpec withSort(Sort sort) {
|
||||
|
||||
if (sort.isSorted()) {
|
||||
return new SelectSpec(this.table, this.projectedFields, this.selectList, this.criteria, sort, this.limit,
|
||||
this.offset, this.distinct);
|
||||
}
|
||||
|
||||
return new SelectSpec(this.table, this.projectedFields, this.selectList, this.criteria, this.sort, this.limit,
|
||||
this.offset, this.distinct);
|
||||
}
|
||||
|
||||
/**
|
||||
* Associate a {@link Pageable} with the select and create a new {@link SelectSpec}.
|
||||
*
|
||||
* @param page
|
||||
* @return the {@link SelectSpec}.
|
||||
*/
|
||||
public SelectSpec withPage(Pageable page) {
|
||||
|
||||
if (page.isPaged()) {
|
||||
|
||||
Sort sort = page.getSort();
|
||||
|
||||
return new SelectSpec(this.table, this.projectedFields, this.selectList, this.criteria,
|
||||
sort.isSorted() ? sort : this.sort, page.getPageSize(), page.getOffset(), this.distinct);
|
||||
}
|
||||
|
||||
return new SelectSpec(this.table, this.projectedFields, this.selectList, this.criteria, this.sort, this.limit,
|
||||
this.offset, this.distinct);
|
||||
}
|
||||
|
||||
/**
|
||||
* Associate a result offset with the select and create a new {@link SelectSpec}.
|
||||
*
|
||||
* @param offset
|
||||
* @return the {@link SelectSpec}.
|
||||
*/
|
||||
public SelectSpec offset(long offset) {
|
||||
return new SelectSpec(this.table, this.projectedFields, this.selectList, this.criteria, this.sort, this.limit,
|
||||
offset, this.distinct);
|
||||
}
|
||||
|
||||
/**
|
||||
* Associate a result limit with the select and create a new {@link SelectSpec}.
|
||||
*
|
||||
* @param limit
|
||||
* @return the {@link SelectSpec}.
|
||||
*/
|
||||
public SelectSpec limit(int limit) {
|
||||
return new SelectSpec(this.table, this.projectedFields, this.selectList, this.criteria, this.sort, limit,
|
||||
this.offset, this.distinct);
|
||||
}
|
||||
|
||||
/**
|
||||
* Associate a result statement distinct with the select and create a new {@link SelectSpec}.
|
||||
*
|
||||
* @return the {@link SelectSpec}.
|
||||
*/
|
||||
public SelectSpec distinct() {
|
||||
return new SelectSpec(this.table, this.projectedFields, this.selectList, this.criteria, this.sort, limit,
|
||||
this.offset, true);
|
||||
}
|
||||
|
||||
public Table getTable() {
|
||||
return this.table;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return
|
||||
* @deprecated since 1.1, use {@link #getSelectList()} instead.
|
||||
*/
|
||||
@Deprecated
|
||||
public List<String> getProjectedFields() {
|
||||
return Collections.unmodifiableList(this.projectedFields);
|
||||
}
|
||||
|
||||
public List<Expression> getSelectList() {
|
||||
return Collections.unmodifiableList(selectList);
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public CriteriaDefinition getCriteria() {
|
||||
return this.criteria;
|
||||
}
|
||||
|
||||
public Sort getSort() {
|
||||
return this.sort;
|
||||
}
|
||||
|
||||
public long getOffset() {
|
||||
return this.offset;
|
||||
}
|
||||
|
||||
public int getLimit() {
|
||||
return this.limit;
|
||||
}
|
||||
|
||||
public boolean isDistinct() {
|
||||
return this.distinct;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@code INSERT} specification.
|
||||
*/
|
||||
class InsertSpec {
|
||||
|
||||
private final SqlIdentifier table;
|
||||
private final Map<SqlIdentifier, Parameter> assignments;
|
||||
|
||||
protected InsertSpec(SqlIdentifier table, Map<SqlIdentifier, Parameter> assignments) {
|
||||
this.table = table;
|
||||
this.assignments = assignments;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an {@code INSERT} specification for {@code table}.
|
||||
*
|
||||
* @param table
|
||||
* @return the {@link InsertSpec}.
|
||||
*/
|
||||
public static InsertSpec create(String table) {
|
||||
return create(SqlIdentifier.unquoted(table));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an {@code INSERT} specification for {@code table}.
|
||||
*
|
||||
* @param table
|
||||
* @return the {@link InsertSpec}.
|
||||
* @since 1.1
|
||||
*/
|
||||
public static InsertSpec create(SqlIdentifier table) {
|
||||
return new InsertSpec(table, Collections.emptyMap());
|
||||
}
|
||||
|
||||
/**
|
||||
* Associate a column with a {@link Parameter} and create a new {@link InsertSpec}.
|
||||
*
|
||||
* @param column
|
||||
* @param value
|
||||
* @return the {@link InsertSpec}.
|
||||
* @since 1.2
|
||||
*/
|
||||
public InsertSpec withColumn(String column, Parameter value) {
|
||||
return withColumn(SqlIdentifier.unquoted(column), value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Associate a column with a {@link Parameter} and create a new {@link InsertSpec}.
|
||||
*
|
||||
* @param column
|
||||
* @param value
|
||||
* @return the {@link InsertSpec}.
|
||||
* @since 1.2
|
||||
*/
|
||||
public InsertSpec withColumn(SqlIdentifier column, Parameter value) {
|
||||
|
||||
Map<SqlIdentifier, Parameter> values = new LinkedHashMap<>(this.assignments);
|
||||
values.put(column, value);
|
||||
|
||||
return new InsertSpec(this.table, values);
|
||||
}
|
||||
|
||||
public SqlIdentifier getTable() {
|
||||
return this.table;
|
||||
}
|
||||
|
||||
public Map<SqlIdentifier, Parameter> getAssignments() {
|
||||
return Collections.unmodifiableMap(this.assignments);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@code UPDATE} specification.
|
||||
*/
|
||||
class UpdateSpec {
|
||||
|
||||
private final SqlIdentifier table;
|
||||
private final @Nullable org.springframework.data.relational.core.query.Update update;
|
||||
private final @Nullable CriteriaDefinition criteria;
|
||||
|
||||
protected UpdateSpec(SqlIdentifier table, @Nullable org.springframework.data.relational.core.query.Update update,
|
||||
@Nullable CriteriaDefinition criteria) {
|
||||
|
||||
this.table = table;
|
||||
this.update = update;
|
||||
this.criteria = criteria;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an {@code INSERT} specification for {@code table}.
|
||||
*
|
||||
* @param table
|
||||
* @return the {@link InsertSpec}.
|
||||
*/
|
||||
public static UpdateSpec create(String table, org.springframework.data.relational.core.query.Update update) {
|
||||
return create(SqlIdentifier.unquoted(table), update);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an {@code INSERT} specification for {@code table}.
|
||||
*
|
||||
* @param table
|
||||
* @return the {@link InsertSpec}.
|
||||
* @since 1.1
|
||||
*/
|
||||
public static UpdateSpec create(SqlIdentifier table, org.springframework.data.relational.core.query.Update update) {
|
||||
return new UpdateSpec(table, update, Criteria.empty());
|
||||
}
|
||||
|
||||
/**
|
||||
* Associate a {@link Criteria} with the update and return a new {@link UpdateSpec}.
|
||||
*
|
||||
* @param criteria
|
||||
* @return the {@link UpdateSpec}.
|
||||
*/
|
||||
public UpdateSpec withCriteria(CriteriaDefinition criteria) {
|
||||
return new UpdateSpec(this.table, this.update, criteria);
|
||||
}
|
||||
|
||||
public SqlIdentifier getTable() {
|
||||
return this.table;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public org.springframework.data.relational.core.query.Update getUpdate() {
|
||||
return this.update;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public CriteriaDefinition getCriteria() {
|
||||
return this.criteria;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* {@code DELETE} specification.
|
||||
*/
|
||||
class DeleteSpec {
|
||||
|
||||
private final SqlIdentifier table;
|
||||
private final @Nullable CriteriaDefinition criteria;
|
||||
|
||||
protected DeleteSpec(SqlIdentifier table, CriteriaDefinition criteria) {
|
||||
this.table = table;
|
||||
this.criteria = criteria;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an {@code DELETE} specification for {@code table}.
|
||||
*
|
||||
* @param table
|
||||
* @return the {@link DeleteSpec}.
|
||||
*/
|
||||
public static DeleteSpec create(String table) {
|
||||
return create(SqlIdentifier.unquoted(table));
|
||||
}
|
||||
|
||||
/**
|
||||
* Create an {@code DELETE} specification for {@code table}.
|
||||
*
|
||||
* @param table
|
||||
* @return the {@link DeleteSpec}.
|
||||
* @since 1.1
|
||||
*/
|
||||
public static DeleteSpec create(SqlIdentifier table) {
|
||||
return new DeleteSpec(table, Criteria.empty());
|
||||
}
|
||||
|
||||
/**
|
||||
* Associate a {@link Criteria} with the delete and return a new {@link DeleteSpec}.
|
||||
*
|
||||
* @param criteria
|
||||
* @return the {@link DeleteSpec}.
|
||||
*/
|
||||
public DeleteSpec withCriteria(CriteriaDefinition criteria) {
|
||||
return new DeleteSpec(this.table, criteria);
|
||||
}
|
||||
|
||||
public SqlIdentifier getTable() {
|
||||
return this.table;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public CriteriaDefinition getCriteria() {
|
||||
return this.criteria;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
/**
|
||||
* Core domain types around DatabaseClient.
|
||||
*/
|
||||
@org.springframework.lang.NonNullApi
|
||||
@org.springframework.lang.NonNullFields
|
||||
package org.springframework.data.r2dbc.core;
|
||||
@@ -1,64 +0,0 @@
|
||||
/*
|
||||
* Copyright 2021-2022 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
|
||||
*
|
||||
* https://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.r2dbc.dialect;
|
||||
|
||||
import org.springframework.r2dbc.core.Parameter;
|
||||
import org.springframework.r2dbc.core.binding.BindTarget;
|
||||
|
||||
/**
|
||||
* Utility to bind {@link Parameter} to a {@link BindTarget}. Mainly used within the framework.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 1.4.1
|
||||
*/
|
||||
public final class BindTargetBinder {
|
||||
|
||||
private final BindTarget target;
|
||||
|
||||
public BindTargetBinder(BindTarget target) {
|
||||
this.target = target;
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind a {@link Parameter} by name.
|
||||
*
|
||||
* @param name must not be {@literal null}.
|
||||
* @param parameter must not be {@literal null}.
|
||||
*/
|
||||
public void bind(String name, Parameter parameter) {
|
||||
Object value = parameter.getValue();
|
||||
if (value == null) {
|
||||
target.bindNull(name, parameter.getType());
|
||||
} else {
|
||||
target.bind(name, value);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Bind a {@link Parameter} by index.
|
||||
*
|
||||
* @param index must not be {@literal null}.
|
||||
* @param parameter must not be {@literal null}.
|
||||
*/
|
||||
public void bind(int index, Parameter parameter) {
|
||||
Object value = parameter.getValue();
|
||||
if (value == null) {
|
||||
target.bindNull(index, parameter.getType());
|
||||
} else {
|
||||
target.bind(index, parameter.getValue());
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,138 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019-2022 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
|
||||
*
|
||||
* https://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.r2dbc.dialect;
|
||||
|
||||
import io.r2dbc.spi.ConnectionFactory;
|
||||
import io.r2dbc.spi.ConnectionFactoryMetadata;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.springframework.core.io.support.SpringFactoriesLoader;
|
||||
import org.springframework.dao.NonTransientDataAccessException;
|
||||
import org.springframework.data.util.Optionals;
|
||||
import org.springframework.util.LinkedCaseInsensitiveMap;
|
||||
|
||||
/**
|
||||
* Resolves a {@link R2dbcDialect} from a {@link ConnectionFactory} using {@link R2dbcDialectProvider}. Dialect
|
||||
* resolution uses Spring's {@link SpringFactoriesLoader spring.factories} to determine available extensions.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @see R2dbcDialect
|
||||
* @see SpringFactoriesLoader
|
||||
*/
|
||||
public class DialectResolver {
|
||||
|
||||
private static final List<R2dbcDialectProvider> DETECTORS = SpringFactoriesLoader
|
||||
.loadFactories(R2dbcDialectProvider.class, DialectResolver.class.getClassLoader());
|
||||
|
||||
// utility constructor.
|
||||
private DialectResolver() {}
|
||||
|
||||
/**
|
||||
* Retrieve a {@link R2dbcDialect} by inspecting {@link ConnectionFactory} and its metadata.
|
||||
*
|
||||
* @param connectionFactory must not be {@literal null}.
|
||||
* @return the resolved {@link R2dbcDialect} {@link NoDialectException} if the database type cannot be determined from
|
||||
* {@link ConnectionFactory}.
|
||||
* @throws NoDialectException if no {@link R2dbcDialect} can be found.
|
||||
*/
|
||||
public static R2dbcDialect getDialect(ConnectionFactory connectionFactory) {
|
||||
|
||||
return DETECTORS.stream() //
|
||||
.map(it -> it.getDialect(connectionFactory)) //
|
||||
.flatMap(Optionals::toStream) //
|
||||
.findFirst() //
|
||||
.orElseThrow(() -> {
|
||||
return new NoDialectException(
|
||||
String.format("Cannot determine a dialect for %s using %s. Please provide a Dialect.",
|
||||
connectionFactory.getMetadata().getName(), connectionFactory));
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* SPI to extend Spring's default R2DBC Dialect discovery mechanism. Implementations of this interface are discovered
|
||||
* through Spring's {@link SpringFactoriesLoader} mechanism.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @see org.springframework.core.io.support.SpringFactoriesLoader
|
||||
*/
|
||||
public interface R2dbcDialectProvider {
|
||||
|
||||
/**
|
||||
* Returns a {@link R2dbcDialect} for a {@link ConnectionFactory}.
|
||||
*
|
||||
* @param connectionFactory the connection factory to be used with the {@link R2dbcDialect}.
|
||||
* @return {@link Optional} containing the {@link R2dbcDialect} if the {@link R2dbcDialectProvider} can provide a
|
||||
* dialect object, otherwise {@link Optional#empty()}.
|
||||
*/
|
||||
Optional<R2dbcDialect> getDialect(ConnectionFactory connectionFactory);
|
||||
}
|
||||
|
||||
/**
|
||||
* Exception thrown when {@link DialectResolver} cannot resolve a {@link R2dbcDialect}.
|
||||
*/
|
||||
public static class NoDialectException extends NonTransientDataAccessException {
|
||||
|
||||
/**
|
||||
* Constructor for NoDialectFoundException.
|
||||
*
|
||||
* @param msg the detail message
|
||||
*/
|
||||
public NoDialectException(String msg) {
|
||||
super(msg);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Built-in dialects. Used typically as last {@link R2dbcDialectProvider} when other providers register with a higher
|
||||
* precedence.
|
||||
*
|
||||
* @see org.springframework.core.Ordered
|
||||
* @see org.springframework.core.annotation.AnnotationAwareOrderComparator
|
||||
*/
|
||||
static class BuiltInDialectProvider implements R2dbcDialectProvider {
|
||||
|
||||
private static final Map<String, R2dbcDialect> BUILTIN = new LinkedCaseInsensitiveMap<>(Locale.ENGLISH);
|
||||
|
||||
static {
|
||||
BUILTIN.put("H2", H2Dialect.INSTANCE);
|
||||
BUILTIN.put("Microsoft SQL Server", SqlServerDialect.INSTANCE);
|
||||
BUILTIN.put("MySQL", MySqlDialect.INSTANCE);
|
||||
BUILTIN.put("MariaDB", MySqlDialect.INSTANCE);
|
||||
BUILTIN.put("Oracle", OracleDialect.INSTANCE);
|
||||
BUILTIN.put("PostgreSQL", PostgresDialect.INSTANCE);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Optional<R2dbcDialect> getDialect(ConnectionFactory connectionFactory) {
|
||||
|
||||
ConnectionFactoryMetadata metadata = connectionFactory.getMetadata();
|
||||
R2dbcDialect r2dbcDialect = BUILTIN.get(metadata.getName());
|
||||
|
||||
if (r2dbcDialect != null) {
|
||||
return Optional.of(r2dbcDialect);
|
||||
}
|
||||
|
||||
return BUILTIN.keySet().stream() //
|
||||
.filter(it -> metadata.getName().contains(it)) //
|
||||
.map(BUILTIN::get) //
|
||||
.findFirst();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,22 +0,0 @@
|
||||
package org.springframework.data.r2dbc.dialect;
|
||||
|
||||
import org.springframework.data.relational.core.sql.SqlIdentifier;
|
||||
|
||||
/**
|
||||
* An SQL dialect for H2 in Postgres Compatibility mode.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @author Jens Schauder
|
||||
*/
|
||||
public class H2Dialect extends PostgresDialect {
|
||||
|
||||
/**
|
||||
* Singleton instance.
|
||||
*/
|
||||
public static final H2Dialect INSTANCE = new H2Dialect();
|
||||
|
||||
@Override
|
||||
public String renderForGeneratedValues(SqlIdentifier identifier) {
|
||||
return identifier.getReference(getIdentifierProcessing());
|
||||
}
|
||||
}
|
||||
@@ -1,129 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019-2022 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
|
||||
*
|
||||
* https://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.r2dbc.dialect;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.URI;
|
||||
import java.net.URL;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.data.convert.ReadingConverter;
|
||||
import org.springframework.data.convert.WritingConverter;
|
||||
import org.springframework.data.relational.core.sql.SqlIdentifier;
|
||||
import org.springframework.r2dbc.core.binding.BindMarkersFactory;
|
||||
|
||||
/**
|
||||
* An SQL dialect for MySQL.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @author Jens Schauder
|
||||
*/
|
||||
public class MySqlDialect extends org.springframework.data.relational.core.dialect.MySqlDialect
|
||||
implements R2dbcDialect {
|
||||
|
||||
private static final Set<Class<?>> SIMPLE_TYPES = new HashSet<>(
|
||||
Arrays.asList(UUID.class, URL.class, URI.class, InetAddress.class));
|
||||
|
||||
/**
|
||||
* Singleton instance.
|
||||
*/
|
||||
public static final MySqlDialect INSTANCE = new MySqlDialect();
|
||||
|
||||
private static final BindMarkersFactory ANONYMOUS = BindMarkersFactory.anonymous("?");
|
||||
|
||||
/**
|
||||
* MySQL specific converters.
|
||||
*/
|
||||
private static final List<Object> CONVERTERS = Arrays.asList(ByteToBooleanConverter.INSTANCE,
|
||||
BooleanToByteConverter.INSTANCE);
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.r2dbc.dialect.Dialect#getBindMarkersFactory()
|
||||
*/
|
||||
@Override
|
||||
public BindMarkersFactory getBindMarkersFactory() {
|
||||
return ANONYMOUS;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.r2dbc.dialect.Dialect#getSimpleTypesKeys()
|
||||
*/
|
||||
@Override
|
||||
public Collection<? extends Class<?>> getSimpleTypes() {
|
||||
return SIMPLE_TYPES;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.r2dbc.dialect.R2dbcDialect#getConverters()
|
||||
*/
|
||||
@Override
|
||||
public Collection<Object> getConverters() {
|
||||
return CONVERTERS;
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple singleton to convert {@link Byte}s to their {@link Boolean} representation. MySQL does not have a built-in
|
||||
* boolean type by default, so relies on using a byte instead. Non-zero values represent {@literal true}.
|
||||
*
|
||||
* @author Michael Berry
|
||||
*/
|
||||
@ReadingConverter
|
||||
public enum ByteToBooleanConverter implements Converter<Byte, Boolean> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public Boolean convert(Byte s) {
|
||||
|
||||
if (s == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return s != 0;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String renderForGeneratedValues(SqlIdentifier identifier) {
|
||||
return identifier.getReference(getIdentifierProcessing());
|
||||
}
|
||||
|
||||
/**
|
||||
* Simple singleton to convert {@link Boolean}s to their {@link Byte} representation. MySQL does not have a built-in
|
||||
* boolean type by default, so relies on using a byte instead. {@literal true} maps to {@code 1}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@WritingConverter
|
||||
public enum BooleanToByteConverter implements Converter<Boolean, Byte> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public Byte convert(Boolean s) {
|
||||
return (byte) (s.booleanValue() ? 1 : 0);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
/*
|
||||
* Copyright 2021-2022 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
|
||||
*
|
||||
* https://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.r2dbc.dialect;
|
||||
|
||||
import org.springframework.r2dbc.core.binding.BindMarkersFactory;
|
||||
|
||||
/**
|
||||
* An SQL dialect for Oracle.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 1.2.6
|
||||
*/
|
||||
public class OracleDialect extends org.springframework.data.relational.core.dialect.OracleDialect
|
||||
implements R2dbcDialect {
|
||||
|
||||
/**
|
||||
* Singleton instance.
|
||||
*/
|
||||
public static final OracleDialect INSTANCE = new OracleDialect();
|
||||
|
||||
private static final BindMarkersFactory NAMED = BindMarkersFactory.named(":", "P", 32,
|
||||
OracleDialect::filterBindMarker);
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.r2dbc.dialect.Dialect#getBindMarkersFactory()
|
||||
*/
|
||||
@Override
|
||||
public BindMarkersFactory getBindMarkersFactory() {
|
||||
return NAMED;
|
||||
}
|
||||
|
||||
private static String filterBindMarker(CharSequence input) {
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
for (int i = 0; i < input.length(); i++) {
|
||||
|
||||
char ch = input.charAt(i);
|
||||
|
||||
// ascii letter or digit
|
||||
if (Character.isLetterOrDigit(ch) && ch < 127) {
|
||||
builder.append(ch);
|
||||
}
|
||||
}
|
||||
|
||||
if (builder.length() == 0) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return "_" + builder;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,316 +0,0 @@
|
||||
package org.springframework.data.r2dbc.dialect;
|
||||
|
||||
import io.r2dbc.postgresql.codec.Json;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.net.URI;
|
||||
import java.net.URL;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import org.springframework.core.convert.converter.Converter;
|
||||
import org.springframework.data.convert.ReadingConverter;
|
||||
import org.springframework.data.convert.WritingConverter;
|
||||
import org.springframework.data.geo.Box;
|
||||
import org.springframework.data.geo.Circle;
|
||||
import org.springframework.data.geo.Point;
|
||||
import org.springframework.data.geo.Polygon;
|
||||
import org.springframework.data.mapping.model.SimpleTypeHolder;
|
||||
import org.springframework.data.relational.core.dialect.ArrayColumns;
|
||||
import org.springframework.data.util.Lazy;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.r2dbc.core.binding.BindMarkersFactory;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* An SQL dialect for Postgres.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @author Jose Luis Leon
|
||||
*/
|
||||
public class PostgresDialect extends org.springframework.data.relational.core.dialect.PostgresDialect
|
||||
implements R2dbcDialect {
|
||||
|
||||
private static final Set<Class<?>> SIMPLE_TYPES;
|
||||
|
||||
private static final boolean JSON_PRESENT = ClassUtils.isPresent("io.r2dbc.postgresql.codec.Json",
|
||||
PostgresDialect.class.getClassLoader());
|
||||
|
||||
private static final boolean GEO_TYPES_PRESENT = ClassUtils.isPresent("io.r2dbc.postgresql.codec.Polygon",
|
||||
PostgresDialect.class.getClassLoader());
|
||||
|
||||
static {
|
||||
|
||||
Set<Class<?>> simpleTypes = new HashSet<>(
|
||||
Arrays.asList(UUID.class, URL.class, URI.class, InetAddress.class, Map.class));
|
||||
|
||||
// conditional Postgres Geo support.
|
||||
Stream.of("io.r2dbc.postgresql.codec.Box", //
|
||||
"io.r2dbc.postgresql.codec.Circle", //
|
||||
"io.r2dbc.postgresql.codec.Line", //
|
||||
"io.r2dbc.postgresql.codec.Lseg", //
|
||||
"io.r2dbc.postgresql.codec.Point", //
|
||||
"io.r2dbc.postgresql.codec.Path", //
|
||||
"io.r2dbc.postgresql.codec.Polygon") //
|
||||
.forEach(s -> ifClassPresent(s, simpleTypes::add));
|
||||
|
||||
// conditional Postgres JSON support.
|
||||
ifClassPresent("io.r2dbc.postgresql.codec.Json", simpleTypes::add);
|
||||
|
||||
// conditional Postgres Interval support
|
||||
ifClassPresent("io.r2dbc.postgresql.codec.Interval", simpleTypes::add);
|
||||
|
||||
SIMPLE_TYPES = simpleTypes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Singleton instance.
|
||||
*/
|
||||
public static final PostgresDialect INSTANCE = new PostgresDialect();
|
||||
|
||||
private static final BindMarkersFactory INDEXED = BindMarkersFactory.indexed("$", 1);
|
||||
|
||||
private final Lazy<ArrayColumns> arrayColumns = Lazy.of(() -> new R2dbcArrayColumns(
|
||||
org.springframework.data.relational.core.dialect.PostgresDialect.INSTANCE.getArraySupport(),
|
||||
getSimpleTypeHolder()));
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.r2dbc.dialect.Dialect#getBindMarkersFactory()
|
||||
*/
|
||||
@Override
|
||||
public BindMarkersFactory getBindMarkersFactory() {
|
||||
return INDEXED;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.r2dbc.dialect.Dialect#getSimpleTypesKeys()
|
||||
*/
|
||||
@Override
|
||||
public Collection<? extends Class<?>> getSimpleTypes() {
|
||||
return SIMPLE_TYPES;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.r2dbc.dialect.Dialect#getArraySupport()
|
||||
*/
|
||||
@Override
|
||||
public ArrayColumns getArraySupport() {
|
||||
return this.arrayColumns.get();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.r2dbc.dialect.Dialect#getConverters()
|
||||
*/
|
||||
@Override
|
||||
public Collection<Object> getConverters() {
|
||||
|
||||
if (!GEO_TYPES_PRESENT && !JSON_PRESENT) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
List<Object> converters = new ArrayList<>();
|
||||
|
||||
if (GEO_TYPES_PRESENT) {
|
||||
converters.addAll(Arrays.asList(FromPostgresPointConverter.INSTANCE, ToPostgresPointConverter.INSTANCE, //
|
||||
FromPostgresCircleConverter.INSTANCE, ToPostgresCircleConverter.INSTANCE, //
|
||||
FromPostgresBoxConverter.INSTANCE, ToPostgresBoxConverter.INSTANCE, //
|
||||
FromPostgresPolygonConverter.INSTANCE, ToPostgresPolygonConverter.INSTANCE));
|
||||
}
|
||||
|
||||
if (JSON_PRESENT) {
|
||||
converters.addAll(Arrays.asList(JsonToByteArrayConverter.INSTANCE, JsonToStringConverter.INSTANCE));
|
||||
}
|
||||
|
||||
return converters;
|
||||
}
|
||||
|
||||
private static class R2dbcArrayColumns implements ArrayColumns {
|
||||
|
||||
private final ArrayColumns delegate;
|
||||
private final SimpleTypeHolder simpleTypeHolder;
|
||||
|
||||
R2dbcArrayColumns(ArrayColumns delegate, SimpleTypeHolder simpleTypeHolder) {
|
||||
this.delegate = delegate;
|
||||
this.simpleTypeHolder = simpleTypeHolder;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSupported() {
|
||||
return this.delegate.isSupported();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Class<?> getArrayType(Class<?> userType) {
|
||||
|
||||
Class<?> typeToUse = userType;
|
||||
while (typeToUse.getComponentType() != null) {
|
||||
typeToUse = typeToUse.getComponentType();
|
||||
}
|
||||
|
||||
if (!this.simpleTypeHolder.isSimpleType(typeToUse)) {
|
||||
throw new IllegalArgumentException("Unsupported array type: " + ClassUtils.getQualifiedName(typeToUse));
|
||||
}
|
||||
|
||||
return this.delegate.getArrayType(typeToUse);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* If the class is present on the class path, invoke the specified consumer {@code action} with the class object,
|
||||
* otherwise do nothing.
|
||||
*
|
||||
* @param action block to be executed if a value is present.
|
||||
*/
|
||||
private static void ifClassPresent(String className, Consumer<Class<?>> action) {
|
||||
|
||||
if (ClassUtils.isPresent(className, PostgresDialect.class.getClassLoader())) {
|
||||
action.accept(ClassUtils.resolveClassName(className, PostgresDialect.class.getClassLoader()));
|
||||
}
|
||||
}
|
||||
|
||||
@ReadingConverter
|
||||
private enum FromPostgresBoxConverter implements Converter<io.r2dbc.postgresql.codec.Box, Box> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public Box convert(io.r2dbc.postgresql.codec.Box source) {
|
||||
return new Box(FromPostgresPointConverter.INSTANCE.convert(source.getA()),
|
||||
FromPostgresPointConverter.INSTANCE.convert(source.getB()));
|
||||
}
|
||||
}
|
||||
|
||||
@WritingConverter
|
||||
private enum ToPostgresBoxConverter implements Converter<Box, io.r2dbc.postgresql.codec.Box> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public io.r2dbc.postgresql.codec.Box convert(Box source) {
|
||||
return io.r2dbc.postgresql.codec.Box.of(ToPostgresPointConverter.INSTANCE.convert(source.getFirst()),
|
||||
ToPostgresPointConverter.INSTANCE.convert(source.getSecond()));
|
||||
}
|
||||
}
|
||||
|
||||
@ReadingConverter
|
||||
private enum FromPostgresCircleConverter implements Converter<io.r2dbc.postgresql.codec.Circle, Circle> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public Circle convert(io.r2dbc.postgresql.codec.Circle source) {
|
||||
return new Circle(source.getCenter().getX(), source.getCenter().getY(), source.getRadius());
|
||||
}
|
||||
}
|
||||
|
||||
@WritingConverter
|
||||
private enum ToPostgresCircleConverter implements Converter<Circle, io.r2dbc.postgresql.codec.Circle> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public io.r2dbc.postgresql.codec.Circle convert(Circle source) {
|
||||
return io.r2dbc.postgresql.codec.Circle.of(source.getCenter().getX(), source.getCenter().getY(),
|
||||
source.getRadius().getValue());
|
||||
}
|
||||
}
|
||||
|
||||
@ReadingConverter
|
||||
private enum FromPostgresPolygonConverter implements Converter<io.r2dbc.postgresql.codec.Polygon, Polygon> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public Polygon convert(io.r2dbc.postgresql.codec.Polygon source) {
|
||||
|
||||
List<io.r2dbc.postgresql.codec.Point> sourcePoints = source.getPoints();
|
||||
List<Point> targetPoints = new ArrayList<>(sourcePoints.size());
|
||||
|
||||
for (io.r2dbc.postgresql.codec.Point sourcePoint : sourcePoints) {
|
||||
targetPoints.add(FromPostgresPointConverter.INSTANCE.convert(sourcePoint));
|
||||
}
|
||||
|
||||
return new Polygon(targetPoints);
|
||||
}
|
||||
}
|
||||
|
||||
@WritingConverter
|
||||
private enum ToPostgresPolygonConverter implements Converter<Polygon, io.r2dbc.postgresql.codec.Polygon> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public io.r2dbc.postgresql.codec.Polygon convert(Polygon source) {
|
||||
|
||||
List<Point> sourcePoints = source.getPoints();
|
||||
List<io.r2dbc.postgresql.codec.Point> targetPoints = new ArrayList<>(sourcePoints.size());
|
||||
|
||||
for (Point sourcePoint : sourcePoints) {
|
||||
targetPoints.add(ToPostgresPointConverter.INSTANCE.convert(sourcePoint));
|
||||
}
|
||||
|
||||
return io.r2dbc.postgresql.codec.Polygon.of(targetPoints);
|
||||
}
|
||||
}
|
||||
|
||||
@ReadingConverter
|
||||
private enum FromPostgresPointConverter implements Converter<io.r2dbc.postgresql.codec.Point, Point> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public Point convert(io.r2dbc.postgresql.codec.Point source) {
|
||||
return new Point(source.getX(), source.getY());
|
||||
}
|
||||
}
|
||||
|
||||
@WritingConverter
|
||||
private enum ToPostgresPointConverter implements Converter<Point, io.r2dbc.postgresql.codec.Point> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public io.r2dbc.postgresql.codec.Point convert(Point source) {
|
||||
return io.r2dbc.postgresql.codec.Point.of(source.getX(), source.getY());
|
||||
}
|
||||
}
|
||||
|
||||
@ReadingConverter
|
||||
private enum JsonToStringConverter implements Converter<Json, String> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public String convert(Json source) {
|
||||
return source.asString();
|
||||
}
|
||||
}
|
||||
|
||||
@ReadingConverter
|
||||
private enum JsonToByteArrayConverter implements Converter<Json, byte[]> {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public byte[] convert(Json source) {
|
||||
return source.asArray();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,76 +0,0 @@
|
||||
package org.springframework.data.r2dbc.dialect;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.springframework.data.mapping.model.SimpleTypeHolder;
|
||||
import org.springframework.data.r2dbc.mapping.R2dbcSimpleTypeHolder;
|
||||
import org.springframework.data.relational.core.dialect.Dialect;
|
||||
import org.springframework.data.relational.core.sql.SqlIdentifier;
|
||||
import org.springframework.r2dbc.core.binding.BindMarkersFactory;
|
||||
|
||||
/**
|
||||
* R2DBC-specific extension to {@link Dialect}. Represents a dialect that is implemented by a particular database.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @author Jens Schauder
|
||||
* @author Michael Berry
|
||||
*/
|
||||
public interface R2dbcDialect extends Dialect {
|
||||
|
||||
/**
|
||||
* Returns the {@link BindMarkersFactory} used by this dialect.
|
||||
*
|
||||
* @return the {@link BindMarkersFactory} used by this dialect.
|
||||
*/
|
||||
BindMarkersFactory getBindMarkersFactory();
|
||||
|
||||
/**
|
||||
* Return a collection of types that are natively supported by this database/driver. Defaults to
|
||||
* {@link Collections#emptySet()}.
|
||||
*
|
||||
* @return a collection of types that are natively supported by this database/driver. Defaults to
|
||||
* {@link Collections#emptySet()}.
|
||||
*/
|
||||
default Collection<? extends Class<?>> getSimpleTypes() {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the {@link SimpleTypeHolder} for this dialect.
|
||||
*
|
||||
* @return the {@link SimpleTypeHolder} for this dialect.
|
||||
* @see #getSimpleTypes()
|
||||
*/
|
||||
default SimpleTypeHolder getSimpleTypeHolder() {
|
||||
|
||||
Set<Class<?>> simpleTypes = new HashSet<>(getSimpleTypes());
|
||||
simpleTypes.addAll(R2dbcSimpleTypeHolder.R2DBC_SIMPLE_TYPES);
|
||||
|
||||
return new SimpleTypeHolder(simpleTypes, true);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a collection of converters for this dialect.
|
||||
*
|
||||
* @return a collection of converters for this dialect.
|
||||
*/
|
||||
default Collection<Object> getConverters() {
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a {@link SqlIdentifier} in a way suitable for registering it as a generated key with a statement through
|
||||
* {@code Statement#returnGeneratedValues}. The default implementation renders it as it would render a SQL
|
||||
* representation of the identifier, i.e. with quotes where applicable.
|
||||
*
|
||||
* @param identifier to render. Must not be {@literal null}.
|
||||
* @return rendered identifier. Guaranteed to be not {@literal null}.
|
||||
* @since 1.3.2
|
||||
*/
|
||||
default String renderForGeneratedValues(SqlIdentifier identifier) {
|
||||
return identifier.toSql(getIdentifierProcessing());
|
||||
}
|
||||
}
|
||||
@@ -1,67 +0,0 @@
|
||||
package org.springframework.data.r2dbc.dialect;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.springframework.r2dbc.core.binding.BindMarkersFactory;
|
||||
|
||||
/**
|
||||
* An SQL dialect for Microsoft SQL Server.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public class SqlServerDialect extends org.springframework.data.relational.core.dialect.SqlServerDialect
|
||||
implements R2dbcDialect {
|
||||
|
||||
private static final Set<Class<?>> SIMPLE_TYPES = new HashSet<>(Collections.singletonList(UUID.class));
|
||||
|
||||
/**
|
||||
* Singleton instance.
|
||||
*/
|
||||
public static final SqlServerDialect INSTANCE = new SqlServerDialect();
|
||||
|
||||
private static final BindMarkersFactory NAMED = BindMarkersFactory.named("@", "P", 32,
|
||||
SqlServerDialect::filterBindMarker);
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.r2dbc.dialect.Dialect#getBindMarkersFactory()
|
||||
*/
|
||||
@Override
|
||||
public BindMarkersFactory getBindMarkersFactory() {
|
||||
return NAMED;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.r2dbc.dialect.Dialect#getSimpleTypesKeys()
|
||||
*/
|
||||
@Override
|
||||
public Collection<? extends Class<?>> getSimpleTypes() {
|
||||
return SIMPLE_TYPES;
|
||||
}
|
||||
|
||||
private static String filterBindMarker(CharSequence input) {
|
||||
|
||||
StringBuilder builder = new StringBuilder();
|
||||
|
||||
for (int i = 0; i < input.length(); i++) {
|
||||
|
||||
char ch = input.charAt(i);
|
||||
|
||||
// ascii letter or digit
|
||||
if (Character.isLetterOrDigit(ch) && ch < 127) {
|
||||
builder.append(ch);
|
||||
}
|
||||
}
|
||||
|
||||
if (builder.length() == 0) {
|
||||
return "";
|
||||
}
|
||||
|
||||
return "_" + builder.toString();
|
||||
}
|
||||
}
|
||||
@@ -1,7 +0,0 @@
|
||||
/**
|
||||
* Dialects abstract the SQL dialect of the underlying database.
|
||||
*/
|
||||
@NonNullApi
|
||||
package org.springframework.data.r2dbc.dialect;
|
||||
|
||||
import org.springframework.lang.NonNullApi;
|
||||
@@ -1,299 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019-2022 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
|
||||
*
|
||||
* https://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.r2dbc.mapping;
|
||||
|
||||
import io.r2dbc.spi.Row;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.BiConsumer;
|
||||
|
||||
import org.springframework.data.relational.core.sql.SqlIdentifier;
|
||||
import org.springframework.r2dbc.core.Parameter;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Representation of a {@link Row} to be written through a {@code INSERT} or {@code UPDATE} statement. Row keys are
|
||||
* represented as {@link SqlIdentifier}. {@link String} key names are translated to
|
||||
* {@link SqlIdentifier#unquoted(String) unquoted identifiers} when adding or querying for entries.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @see SqlIdentifier
|
||||
* @see Parameter
|
||||
*/
|
||||
public class OutboundRow implements Map<SqlIdentifier, Parameter>, Cloneable {
|
||||
|
||||
private final Map<SqlIdentifier, Parameter> rowAsMap;
|
||||
|
||||
/**
|
||||
* Creates an empty {@link OutboundRow} instance.
|
||||
*/
|
||||
public OutboundRow() {
|
||||
this.rowAsMap = new LinkedHashMap<>();
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link OutboundRow} from a {@link Map}.
|
||||
*
|
||||
* @param map the map used to initialize the {@link OutboundRow}.
|
||||
*/
|
||||
public OutboundRow(Map<String, Parameter> map) {
|
||||
|
||||
Assert.notNull(map, "Map must not be null");
|
||||
|
||||
this.rowAsMap = new LinkedHashMap<>(map.size());
|
||||
|
||||
map.forEach((s, Parameter) -> this.rowAsMap.put(SqlIdentifier.unquoted(s), Parameter));
|
||||
}
|
||||
|
||||
private OutboundRow(OutboundRow map) {
|
||||
|
||||
this.rowAsMap = new LinkedHashMap<>(map.size());
|
||||
this.rowAsMap.putAll(map);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link OutboundRow} instance initialized with the given key/value pair.
|
||||
*
|
||||
* @param key key.
|
||||
* @param value value.
|
||||
* @see SqlIdentifier#unquoted(String)
|
||||
*/
|
||||
public OutboundRow(String key, Parameter value) {
|
||||
this(SqlIdentifier.unquoted(key), value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a {@link OutboundRow} instance initialized with the given key/value pair.
|
||||
*
|
||||
* @param key key.
|
||||
* @param value value.
|
||||
* @since 1.1
|
||||
*/
|
||||
public OutboundRow(SqlIdentifier key, Parameter value) {
|
||||
this.rowAsMap = new LinkedHashMap<>();
|
||||
this.rowAsMap.put(key, value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Put the given key/value pair into this {@link OutboundRow} and return this. Useful for chaining puts in a single
|
||||
* expression:
|
||||
*
|
||||
* <pre class="code">
|
||||
* row.append("a", 1).append("b", 2)}
|
||||
* </pre>
|
||||
*
|
||||
* @param key key.
|
||||
* @param value value.
|
||||
* @return this
|
||||
* @see SqlIdentifier#unquoted(String)
|
||||
*/
|
||||
public OutboundRow append(String key, Parameter value) {
|
||||
return append(SqlIdentifier.unquoted(key), value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Put the given key/value pair into this {@link OutboundRow} and return this. Useful for chaining puts in a single
|
||||
* expression:
|
||||
*
|
||||
* <pre class="code">
|
||||
* row.append("a", 1).append("b", 2)}
|
||||
* </pre>
|
||||
*
|
||||
* @param key key.
|
||||
* @param value value.
|
||||
* @return this
|
||||
* @since 1.1
|
||||
*/
|
||||
public OutboundRow append(SqlIdentifier key, Parameter value) {
|
||||
this.rowAsMap.put(key, value);
|
||||
return this;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.util.Map#size()
|
||||
*/
|
||||
@Override
|
||||
public int size() {
|
||||
return this.rowAsMap.size();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.util.Map#isEmpty()
|
||||
*/
|
||||
@Override
|
||||
public boolean isEmpty() {
|
||||
return this.rowAsMap.isEmpty();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Object#clone()
|
||||
*/
|
||||
@Override
|
||||
protected OutboundRow clone() {
|
||||
return new OutboundRow(this);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.util.Map#containsKey(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public boolean containsKey(Object key) {
|
||||
return this.rowAsMap.containsKey(convertKeyIfNecessary(key));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.util.Map#containsValue(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public boolean containsValue(Object value) {
|
||||
return this.rowAsMap.containsValue(value);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.util.Map#get(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public Parameter get(Object key) {
|
||||
return this.rowAsMap.get(convertKeyIfNecessary(key));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.util.Map#put(java.lang.Object, java.lang.Object)
|
||||
*/
|
||||
public Parameter put(String key, Parameter value) {
|
||||
return put(SqlIdentifier.unquoted(key), value);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.util.Map#put(java.lang.Object, java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public Parameter put(SqlIdentifier key, Parameter value) {
|
||||
return this.rowAsMap.put(key, value);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.util.Map#remove(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public Parameter remove(Object key) {
|
||||
return this.rowAsMap.remove(key);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.util.Map#putAll(java.util.Map)
|
||||
*/
|
||||
@Override
|
||||
public void putAll(Map<? extends SqlIdentifier, ? extends Parameter> m) {
|
||||
this.rowAsMap.putAll(m);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.util.Map#clear()
|
||||
*/
|
||||
@Override
|
||||
public void clear() {
|
||||
this.rowAsMap.clear();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.util.Map#keySet()
|
||||
*/
|
||||
@Override
|
||||
public Set<SqlIdentifier> keySet() {
|
||||
return this.rowAsMap.keySet();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.util.Map#values()
|
||||
*/
|
||||
@Override
|
||||
public Collection<Parameter> values() {
|
||||
return this.rowAsMap.values();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.util.Map#entrySet()
|
||||
*/
|
||||
@Override
|
||||
public Set<Entry<SqlIdentifier, Parameter>> entrySet() {
|
||||
return this.rowAsMap.entrySet();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Object#equals(java.lang.Object)
|
||||
*/
|
||||
@Override
|
||||
public boolean equals(final Object o) {
|
||||
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
|
||||
OutboundRow row = (OutboundRow) o;
|
||||
|
||||
return this.rowAsMap.equals(row.rowAsMap);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Object#hashCode()
|
||||
*/
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return this.rowAsMap.hashCode();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return "OutboundRow[" + this.rowAsMap + "]";
|
||||
}
|
||||
|
||||
@Override
|
||||
public void forEach(BiConsumer<? super SqlIdentifier, ? super Parameter> action) {
|
||||
this.rowAsMap.forEach(action);
|
||||
}
|
||||
|
||||
private static Object convertKeyIfNecessary(Object key) {
|
||||
return key instanceof String ? SqlIdentifier.unquoted((String) key) : key;
|
||||
}
|
||||
}
|
||||
@@ -1,61 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019-2022 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
|
||||
*
|
||||
* https://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.r2dbc.mapping;
|
||||
|
||||
import org.springframework.core.KotlinDetector;
|
||||
import org.springframework.data.relational.core.mapping.NamingStrategy;
|
||||
import org.springframework.data.relational.core.mapping.RelationalMappingContext;
|
||||
import org.springframework.data.util.KotlinReflectionUtils;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
|
||||
/**
|
||||
* R2DBC-specific extension to {@link RelationalMappingContext}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public class R2dbcMappingContext extends RelationalMappingContext {
|
||||
|
||||
/**
|
||||
* Create a new {@link R2dbcMappingContext}.
|
||||
*/
|
||||
public R2dbcMappingContext() {
|
||||
setForceQuote(false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a new {@link R2dbcMappingContext} using the given {@link NamingStrategy}.
|
||||
*
|
||||
* @param namingStrategy must not be {@literal null}.
|
||||
*/
|
||||
public R2dbcMappingContext(NamingStrategy namingStrategy) {
|
||||
super(namingStrategy);
|
||||
setForceQuote(false);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mapping.context.AbstractMappingContext#shouldCreatePersistentEntityFor(org.springframework.data.util.TypeInformation)
|
||||
*/
|
||||
@Override
|
||||
protected boolean shouldCreatePersistentEntityFor(TypeInformation<?> type) {
|
||||
|
||||
if (R2dbcSimpleTypeHolder.HOLDER.isSimpleType(type.getType())) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return !KotlinDetector.isKotlinType(type.getType()) || KotlinReflectionUtils.isSupportedKotlinClass(type.getType());
|
||||
}
|
||||
}
|
||||
@@ -1,52 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019-2022 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
|
||||
*
|
||||
* https://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.r2dbc.mapping;
|
||||
|
||||
import io.r2dbc.spi.Row;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.math.BigInteger;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
|
||||
import org.springframework.data.mapping.model.SimpleTypeHolder;
|
||||
|
||||
/**
|
||||
* Simple constant holder for a {@link SimpleTypeHolder} enriched with R2DBC specific simple types.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public class R2dbcSimpleTypeHolder extends SimpleTypeHolder {
|
||||
|
||||
/**
|
||||
* Set of R2DBC simple types.
|
||||
*/
|
||||
public static final Set<Class<?>> R2DBC_SIMPLE_TYPES = Collections.unmodifiableSet(
|
||||
new HashSet<>(Arrays.asList(OutboundRow.class, Row.class, BigInteger.class, BigDecimal.class, UUID.class)));
|
||||
|
||||
public static final SimpleTypeHolder HOLDER = new R2dbcSimpleTypeHolder();
|
||||
|
||||
/**
|
||||
* Create a new {@link R2dbcSimpleTypeHolder} instance.
|
||||
*/
|
||||
private R2dbcSimpleTypeHolder() {
|
||||
super(R2DBC_SIMPLE_TYPES, true);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020-2022 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
|
||||
*
|
||||
* https://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.r2dbc.mapping.event;
|
||||
|
||||
import org.reactivestreams.Publisher;
|
||||
|
||||
import org.springframework.data.mapping.callback.EntityCallback;
|
||||
import org.springframework.data.relational.core.sql.SqlIdentifier;
|
||||
|
||||
/**
|
||||
* Callback being invoked after a domain object is materialized from a row when reading results.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 1.2
|
||||
* @see org.springframework.data.mapping.callback.ReactiveEntityCallbacks
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface AfterConvertCallback<T> extends EntityCallback<T> {
|
||||
|
||||
/**
|
||||
* Entity callback method invoked after a domain object is materialized from a row. Can return either the same or a
|
||||
* modified instance of the domain object.
|
||||
*
|
||||
* @param entity the domain object (the result of the conversion).
|
||||
* @param table name of the table.
|
||||
* @return the domain object that is the result of reading it from a row.
|
||||
*/
|
||||
Publisher<T> onAfterConvert(T entity, SqlIdentifier table);
|
||||
}
|
||||
@@ -1,44 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020-2022 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
|
||||
*
|
||||
* https://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.r2dbc.mapping.event;
|
||||
|
||||
import org.reactivestreams.Publisher;
|
||||
|
||||
import org.springframework.data.mapping.callback.EntityCallback;
|
||||
import org.springframework.data.r2dbc.mapping.OutboundRow;
|
||||
import org.springframework.data.relational.core.sql.SqlIdentifier;
|
||||
|
||||
/**
|
||||
* Entity callback triggered after save of a {@link OutboundRow}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 1.2
|
||||
* @see org.springframework.data.mapping.callback.ReactiveEntityCallbacks
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface AfterSaveCallback<T> extends EntityCallback<T> {
|
||||
|
||||
/**
|
||||
* Entity callback method invoked after a domain object is saved. Can return either the same or a modified instance of
|
||||
* the domain object.
|
||||
*
|
||||
* @param entity the domain object that was saved.
|
||||
* @param outboundRow {@link OutboundRow} representing the {@code entity}.
|
||||
* @param table name of the table.
|
||||
* @return the domain object that was persisted.
|
||||
*/
|
||||
Publisher<T> onAfterSave(T entity, OutboundRow outboundRow, SqlIdentifier table);
|
||||
}
|
||||
@@ -1,42 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019-2022 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
|
||||
*
|
||||
* https://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.r2dbc.mapping.event;
|
||||
|
||||
import org.reactivestreams.Publisher;
|
||||
|
||||
import org.springframework.data.mapping.callback.EntityCallback;
|
||||
import org.springframework.data.relational.core.sql.SqlIdentifier;
|
||||
|
||||
/**
|
||||
* Callback being invoked before a domain object is converted to be persisted.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 1.2
|
||||
* @see org.springframework.data.mapping.callback.ReactiveEntityCallbacks
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface BeforeConvertCallback<T> extends EntityCallback<T> {
|
||||
|
||||
/**
|
||||
* Entity callback method invoked before a domain object is converted to be persisted. Can return either the same or a
|
||||
* modified instance of the domain object.
|
||||
*
|
||||
* @param entity the domain object to save.
|
||||
* @param table name of the table.
|
||||
* @return the domain object to be persisted.
|
||||
*/
|
||||
Publisher<T> onBeforeConvert(T entity, SqlIdentifier table);
|
||||
}
|
||||
@@ -1,48 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019-2022 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
|
||||
*
|
||||
* https://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.r2dbc.mapping.event;
|
||||
|
||||
import org.reactivestreams.Publisher;
|
||||
|
||||
import org.springframework.data.mapping.callback.EntityCallback;
|
||||
import org.springframework.data.r2dbc.mapping.OutboundRow;
|
||||
import org.springframework.data.relational.core.sql.SqlIdentifier;
|
||||
|
||||
/**
|
||||
* Entity callback triggered before save of a row.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 1.2
|
||||
* @see org.springframework.data.mapping.callback.ReactiveEntityCallbacks
|
||||
*/
|
||||
@FunctionalInterface
|
||||
public interface BeforeSaveCallback<T> extends EntityCallback<T> {
|
||||
|
||||
/**
|
||||
* Entity callback method invoked before a domain object is saved. Can return either the same or a modified instance
|
||||
* of the domain object and can modify {@link OutboundRow} contents. This method is called after converting the
|
||||
* {@code entity} to a {@link OutboundRow} so effectively the row is used as outcome of invoking this callback.
|
||||
* Changes to the domain object are not taken into account for saving, only changes to the row. Only transient fields
|
||||
* of the entity should be changed in this callback. To change persistent the entity before being converted, use the
|
||||
* {@link BeforeConvertCallback}.
|
||||
*
|
||||
* @param entity the domain object to save.
|
||||
* @param row {@link OutboundRow} representing the {@code entity}.
|
||||
* @param table name of the table.
|
||||
* @return the domain object to be persisted.
|
||||
*/
|
||||
Publisher<T> onBeforeSave(T entity, OutboundRow row, SqlIdentifier table);
|
||||
}
|
||||
@@ -1,68 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020-2022 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
|
||||
*
|
||||
* https://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.r2dbc.mapping.event;
|
||||
|
||||
import org.reactivestreams.Publisher;
|
||||
|
||||
import org.springframework.beans.factory.ObjectFactory;
|
||||
import org.springframework.core.Ordered;
|
||||
import org.springframework.data.auditing.AuditingHandler;
|
||||
import org.springframework.data.auditing.ReactiveIsNewAwareAuditingHandler;
|
||||
import org.springframework.data.mapping.callback.EntityCallback;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.relational.core.sql.SqlIdentifier;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Reactive {@link EntityCallback} to populate auditing related fields on an entity about to be saved.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 1.2
|
||||
*/
|
||||
public class ReactiveAuditingEntityCallback implements BeforeConvertCallback<Object>, Ordered {
|
||||
|
||||
private final ObjectFactory<ReactiveIsNewAwareAuditingHandler> auditingHandlerFactory;
|
||||
|
||||
/**
|
||||
* Creates a new {@link BeforeConvertCallback} using the given {@link MappingContext} and {@link AuditingHandler}
|
||||
* provided by the given {@link ObjectFactory}.
|
||||
*
|
||||
* @param auditingHandlerFactory must not be {@literal null}.
|
||||
*/
|
||||
public ReactiveAuditingEntityCallback(ObjectFactory<ReactiveIsNewAwareAuditingHandler> auditingHandlerFactory) {
|
||||
|
||||
Assert.notNull(auditingHandlerFactory, "IsNewAwareAuditingHandler must not be null!");
|
||||
this.auditingHandlerFactory = auditingHandlerFactory;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.r2dbc.mapping.event.ReactiveBeforeConvertCallback#onBeforeConvert(java.lang.Object, SqlIdentifier)
|
||||
*/
|
||||
@Override
|
||||
public Publisher<Object> onBeforeConvert(Object entity, SqlIdentifier table) {
|
||||
return auditingHandlerFactory.getObject().markAudited(entity);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.core.Ordered#getOrder()
|
||||
*/
|
||||
@Override
|
||||
public int getOrder() {
|
||||
return 100;
|
||||
}
|
||||
}
|
||||
@@ -1,5 +0,0 @@
|
||||
/**
|
||||
* Mapping event callback infrastructure for the R2DBC row-to-object mapping subsystem.
|
||||
*/
|
||||
@org.springframework.lang.NonNullApi
|
||||
package org.springframework.data.r2dbc.mapping.event;
|
||||
@@ -1,7 +0,0 @@
|
||||
/**
|
||||
* Domain objects for R2DBC.
|
||||
*/
|
||||
@NonNullApi
|
||||
package org.springframework.data.r2dbc.mapping;
|
||||
|
||||
import org.springframework.lang.NonNullApi;
|
||||
@@ -1,6 +0,0 @@
|
||||
/**
|
||||
* Support infrastructure for the configuration of R2DBC-specific repositories.
|
||||
*/
|
||||
@org.springframework.lang.NonNullApi
|
||||
@org.springframework.lang.NonNullFields
|
||||
package org.springframework.data.r2dbc;
|
||||
@@ -1,51 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019-2022 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
|
||||
*
|
||||
* https://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.r2dbc.query;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.data.relational.core.sql.Assignment;
|
||||
import org.springframework.r2dbc.core.binding.Bindings;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Value object representing {@link Assignment}s with their {@link Bindings}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public class BoundAssignments {
|
||||
|
||||
private final Bindings bindings;
|
||||
|
||||
private final List<Assignment> assignments;
|
||||
|
||||
public BoundAssignments(Bindings bindings, List<Assignment> assignments) {
|
||||
|
||||
Assert.notNull(bindings, "Bindings must not be null!");
|
||||
Assert.notNull(assignments, "Assignments must not be null!");
|
||||
|
||||
this.bindings = bindings;
|
||||
this.assignments = assignments;
|
||||
}
|
||||
|
||||
public Bindings getBindings() {
|
||||
return bindings;
|
||||
}
|
||||
|
||||
public List<Assignment> getAssignments() {
|
||||
return assignments;
|
||||
}
|
||||
}
|
||||
@@ -1,49 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019-2022 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
|
||||
*
|
||||
* https://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.r2dbc.query;
|
||||
|
||||
import org.springframework.r2dbc.core.binding.Bindings;
|
||||
import org.springframework.data.relational.core.sql.Condition;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Value object representing a {@link Condition} with its {@link Bindings}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public class BoundCondition {
|
||||
|
||||
private final Bindings bindings;
|
||||
|
||||
private final Condition condition;
|
||||
|
||||
public BoundCondition(Bindings bindings, Condition condition) {
|
||||
|
||||
Assert.notNull(bindings, "Bindings must not be null!");
|
||||
Assert.notNull(condition, "Condition must not be null!");
|
||||
|
||||
this.bindings = bindings;
|
||||
this.condition = condition;
|
||||
}
|
||||
|
||||
public Bindings getBindings() {
|
||||
return bindings;
|
||||
}
|
||||
|
||||
public Condition getCondition() {
|
||||
return condition;
|
||||
}
|
||||
}
|
||||
@@ -1,751 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019-2022 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
|
||||
*
|
||||
* https://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.r2dbc.query;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import org.springframework.data.domain.Sort;
|
||||
import org.springframework.data.mapping.MappingException;
|
||||
import org.springframework.data.mapping.PersistentPropertyPath;
|
||||
import org.springframework.data.mapping.PropertyPath;
|
||||
import org.springframework.data.mapping.PropertyReferenceException;
|
||||
import org.springframework.data.mapping.context.MappingContext;
|
||||
import org.springframework.data.r2dbc.convert.R2dbcConverter;
|
||||
import org.springframework.data.r2dbc.dialect.R2dbcDialect;
|
||||
import org.springframework.data.relational.core.dialect.Escaper;
|
||||
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
|
||||
import org.springframework.data.relational.core.mapping.RelationalPersistentProperty;
|
||||
import org.springframework.data.relational.core.query.Criteria;
|
||||
import org.springframework.data.relational.core.query.CriteriaDefinition;
|
||||
import org.springframework.data.relational.core.query.CriteriaDefinition.Comparator;
|
||||
import org.springframework.data.relational.core.query.ValueFunction;
|
||||
import org.springframework.data.relational.core.sql.*;
|
||||
import org.springframework.data.util.ClassTypeInformation;
|
||||
import org.springframework.data.util.Pair;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.r2dbc.core.Parameter;
|
||||
import org.springframework.r2dbc.core.binding.BindMarker;
|
||||
import org.springframework.r2dbc.core.binding.BindMarkers;
|
||||
import org.springframework.r2dbc.core.binding.Bindings;
|
||||
import org.springframework.r2dbc.core.binding.MutableBindings;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.ClassUtils;
|
||||
|
||||
/**
|
||||
* Maps {@link CriteriaDefinition} and {@link Sort} objects considering mapping metadata and dialect-specific
|
||||
* conversion.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @author Roman Chigvintsev
|
||||
* @author Manousos Mathioudakis
|
||||
*/
|
||||
public class QueryMapper {
|
||||
|
||||
private final R2dbcConverter converter;
|
||||
private final R2dbcDialect dialect;
|
||||
private final MappingContext<? extends RelationalPersistentEntity<?>, RelationalPersistentProperty> mappingContext;
|
||||
|
||||
/**
|
||||
* Creates a new {@link QueryMapper} with the given {@link R2dbcConverter}.
|
||||
*
|
||||
* @param dialect
|
||||
* @param converter must not be {@literal null}.
|
||||
*/
|
||||
@SuppressWarnings({ "unchecked", "rawtypes" })
|
||||
public QueryMapper(R2dbcDialect dialect, R2dbcConverter converter) {
|
||||
|
||||
Assert.notNull(converter, "R2dbcConverter must not be null!");
|
||||
Assert.notNull(dialect, "R2dbcDialect must not be null!");
|
||||
|
||||
this.converter = converter;
|
||||
this.dialect = dialect;
|
||||
this.mappingContext = (MappingContext) converter.getMappingContext();
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a {@link SqlIdentifier} for SQL usage. The resulting String might contain quoting characters.
|
||||
*
|
||||
* @param identifier the identifier to be rendered.
|
||||
* @return an identifier String.
|
||||
* @since 1.1
|
||||
*/
|
||||
public String toSql(SqlIdentifier identifier) {
|
||||
|
||||
Assert.notNull(identifier, "SqlIdentifier must not be null");
|
||||
|
||||
return identifier.toSql(this.dialect.getIdentifierProcessing());
|
||||
}
|
||||
|
||||
/**
|
||||
* Map the {@link Sort} object to apply field name mapping using {@link Class the type to read}.
|
||||
*
|
||||
* @param sort must not be {@literal null}.
|
||||
* @param entity related {@link RelationalPersistentEntity}, can be {@literal null}.
|
||||
* @return
|
||||
*/
|
||||
public Sort getMappedObject(Sort sort, @Nullable RelationalPersistentEntity<?> entity) {
|
||||
|
||||
if (entity == null) {
|
||||
return sort;
|
||||
}
|
||||
|
||||
List<Sort.Order> mappedOrder = new ArrayList<>();
|
||||
|
||||
for (Sort.Order order : sort) {
|
||||
|
||||
Field field = createPropertyField(entity, SqlIdentifier.unquoted(order.getProperty()), this.mappingContext);
|
||||
mappedOrder.add(
|
||||
Sort.Order.by(toSql(field.getMappedColumnName())).with(order.getNullHandling()).with(order.getDirection()));
|
||||
}
|
||||
|
||||
return Sort.by(mappedOrder);
|
||||
}
|
||||
|
||||
/**
|
||||
* Map the {@link Sort} object to apply field name mapping using {@link Class the type to read}.
|
||||
*
|
||||
* @param sort must not be {@literal null}.
|
||||
* @param entity related {@link RelationalPersistentEntity}, can be {@literal null}.
|
||||
* @return
|
||||
* @since 1.1
|
||||
*/
|
||||
public List<OrderByField> getMappedSort(Table table, Sort sort, @Nullable RelationalPersistentEntity<?> entity) {
|
||||
|
||||
List<OrderByField> mappedOrder = new ArrayList<>();
|
||||
|
||||
for (Sort.Order order : sort) {
|
||||
|
||||
Field field = createPropertyField(entity, SqlIdentifier.unquoted(order.getProperty()), this.mappingContext);
|
||||
OrderByField orderBy = OrderByField.from(table.column(field.getMappedColumnName()))
|
||||
.withNullHandling(order.getNullHandling());
|
||||
mappedOrder.add(order.isAscending() ? orderBy.asc() : orderBy.desc());
|
||||
}
|
||||
|
||||
return mappedOrder;
|
||||
}
|
||||
|
||||
/**
|
||||
* Map the {@link Expression} object to apply field name mapping using {@link Class the type to read}.
|
||||
*
|
||||
* @param expression must not be {@literal null}.
|
||||
* @param entity related {@link RelationalPersistentEntity}, can be {@literal null}.
|
||||
* @return the mapped {@link Expression}.
|
||||
* @since 1.1
|
||||
*/
|
||||
public Expression getMappedObject(Expression expression, @Nullable RelationalPersistentEntity<?> entity) {
|
||||
|
||||
if (entity == null || expression instanceof AsteriskFromTable) {
|
||||
return expression;
|
||||
}
|
||||
|
||||
if (expression instanceof Column) {
|
||||
|
||||
Column column = (Column) expression;
|
||||
Field field = createPropertyField(entity, column.getName());
|
||||
TableLike table = column.getTable();
|
||||
|
||||
Column columnFromTable = table.column(field.getMappedColumnName());
|
||||
return column instanceof Aliased ? columnFromTable.as(((Aliased) column).getAlias()) : columnFromTable;
|
||||
}
|
||||
|
||||
if (expression instanceof SimpleFunction) {
|
||||
|
||||
SimpleFunction function = (SimpleFunction) expression;
|
||||
|
||||
List<Expression> arguments = function.getExpressions();
|
||||
List<Expression> mappedArguments = new ArrayList<>(arguments.size());
|
||||
|
||||
for (Expression argument : arguments) {
|
||||
mappedArguments.add(getMappedObject(argument, entity));
|
||||
}
|
||||
|
||||
SimpleFunction mappedFunction = SimpleFunction.create(function.getFunctionName(), mappedArguments);
|
||||
|
||||
return function instanceof Aliased ? mappedFunction.as(((Aliased) function).getAlias()) : mappedFunction;
|
||||
}
|
||||
|
||||
throw new IllegalArgumentException(String.format("Cannot map %s", expression));
|
||||
}
|
||||
|
||||
/**
|
||||
* Map a {@link CriteriaDefinition} object into {@link Condition} and consider value/{@code NULL} {@link Bindings}.
|
||||
*
|
||||
* @param markers bind markers object, must not be {@literal null}.
|
||||
* @param criteria criteria definition to map, must not be {@literal null}.
|
||||
* @param table must not be {@literal null}.
|
||||
* @param entity related {@link RelationalPersistentEntity}, can be {@literal null}.
|
||||
* @return the mapped {@link BoundCondition}.
|
||||
* @since 1.1
|
||||
*/
|
||||
public BoundCondition getMappedObject(BindMarkers markers, CriteriaDefinition criteria, Table table,
|
||||
@Nullable RelationalPersistentEntity<?> entity) {
|
||||
|
||||
Assert.notNull(markers, "BindMarkers must not be null!");
|
||||
Assert.notNull(criteria, "CriteriaDefinition must not be null!");
|
||||
Assert.notNull(table, "Table must not be null!");
|
||||
|
||||
MutableBindings bindings = new MutableBindings(markers);
|
||||
|
||||
if (criteria.isEmpty()) {
|
||||
throw new IllegalArgumentException("Cannot map empty Criteria");
|
||||
}
|
||||
|
||||
Condition mapped = unroll(criteria, table, entity, bindings);
|
||||
|
||||
return new BoundCondition(bindings, mapped);
|
||||
}
|
||||
|
||||
/**
|
||||
* Map a {@link Criteria} object into {@link Condition} and consider value/{@code NULL} {@link Bindings}.
|
||||
*
|
||||
* @param markers bind markers object, must not be {@literal null}.
|
||||
* @param criteria criteria definition to map, must not be {@literal null}.
|
||||
* @param table must not be {@literal null}.
|
||||
* @param entity related {@link RelationalPersistentEntity}, can be {@literal null}.
|
||||
* @return the mapped {@link BoundCondition}.
|
||||
* @deprecated since 1.1.
|
||||
*/
|
||||
@Deprecated
|
||||
public BoundCondition getMappedObject(BindMarkers markers, Criteria criteria, Table table,
|
||||
@Nullable RelationalPersistentEntity<?> entity) {
|
||||
|
||||
Assert.notNull(markers, "BindMarkers must not be null!");
|
||||
Assert.notNull(criteria, "Criteria must not be null!");
|
||||
Assert.notNull(table, "Table must not be null!");
|
||||
|
||||
MutableBindings bindings = new MutableBindings(markers);
|
||||
|
||||
if (criteria.isEmpty()) {
|
||||
throw new IllegalArgumentException("Cannot map empty Criteria");
|
||||
}
|
||||
|
||||
Condition mapped = unroll(criteria, table, entity, bindings);
|
||||
|
||||
return new BoundCondition(bindings, mapped);
|
||||
}
|
||||
|
||||
private Condition unroll(CriteriaDefinition criteria, Table table, @Nullable RelationalPersistentEntity<?> entity,
|
||||
MutableBindings bindings) {
|
||||
|
||||
CriteriaDefinition current = criteria;
|
||||
|
||||
// reverse unroll criteria chain
|
||||
Map<CriteriaDefinition, CriteriaDefinition> forwardChain = new HashMap<>();
|
||||
|
||||
while (current.hasPrevious()) {
|
||||
forwardChain.put(current.getPrevious(), current);
|
||||
current = current.getPrevious();
|
||||
}
|
||||
|
||||
// perform the actual mapping
|
||||
Condition mapped = getCondition(current, bindings, table, entity);
|
||||
while (forwardChain.containsKey(current)) {
|
||||
|
||||
CriteriaDefinition criterion = forwardChain.get(current);
|
||||
Condition result = null;
|
||||
|
||||
Condition condition = getCondition(criterion, bindings, table, entity);
|
||||
if (condition != null) {
|
||||
result = combine(criterion, mapped, criterion.getCombinator(), condition);
|
||||
}
|
||||
|
||||
if (result != null) {
|
||||
mapped = result;
|
||||
}
|
||||
current = criterion;
|
||||
}
|
||||
|
||||
if (mapped == null) {
|
||||
throw new IllegalStateException("Cannot map empty Criteria");
|
||||
}
|
||||
|
||||
return mapped;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Condition unrollGroup(List<? extends CriteriaDefinition> criteria, Table table,
|
||||
CriteriaDefinition.Combinator combinator, @Nullable RelationalPersistentEntity<?> entity,
|
||||
MutableBindings bindings) {
|
||||
|
||||
Condition mapped = null;
|
||||
for (CriteriaDefinition criterion : criteria) {
|
||||
|
||||
if (criterion.isEmpty()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
Condition condition = unroll(criterion, table, entity, bindings);
|
||||
|
||||
mapped = combine(criterion, mapped, combinator, condition);
|
||||
}
|
||||
|
||||
return mapped;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private Condition getCondition(CriteriaDefinition criteria, MutableBindings bindings, Table table,
|
||||
@Nullable RelationalPersistentEntity<?> entity) {
|
||||
|
||||
if (criteria.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (criteria.isGroup()) {
|
||||
|
||||
Condition condition = unrollGroup(criteria.getGroup(), table, criteria.getCombinator(), entity, bindings);
|
||||
|
||||
return condition == null ? null : Conditions.nest(condition);
|
||||
}
|
||||
|
||||
return mapCondition(criteria, bindings, table, entity);
|
||||
}
|
||||
|
||||
private Condition combine(CriteriaDefinition criteria, @Nullable Condition currentCondition,
|
||||
CriteriaDefinition.Combinator combinator, Condition nextCondition) {
|
||||
|
||||
if (currentCondition == null) {
|
||||
currentCondition = nextCondition;
|
||||
} else if (combinator == CriteriaDefinition.Combinator.INITIAL) {
|
||||
currentCondition = currentCondition.and(Conditions.nest(nextCondition));
|
||||
} else if (combinator == CriteriaDefinition.Combinator.AND) {
|
||||
currentCondition = currentCondition.and(nextCondition);
|
||||
} else if (combinator == CriteriaDefinition.Combinator.OR) {
|
||||
currentCondition = currentCondition.or(nextCondition);
|
||||
} else {
|
||||
throw new IllegalStateException("Combinator " + combinator + " not supported");
|
||||
}
|
||||
|
||||
return currentCondition;
|
||||
}
|
||||
|
||||
private Condition mapCondition(CriteriaDefinition criteria, MutableBindings bindings, Table table,
|
||||
@Nullable RelationalPersistentEntity<?> entity) {
|
||||
|
||||
Field propertyField = createPropertyField(entity, criteria.getColumn(), this.mappingContext);
|
||||
Column column = table.column(propertyField.getMappedColumnName());
|
||||
TypeInformation<?> actualType = propertyField.getTypeHint().getRequiredActualType();
|
||||
|
||||
Object mappedValue;
|
||||
Class<?> typeHint;
|
||||
|
||||
if (criteria.getValue() instanceof Parameter) {
|
||||
|
||||
Parameter parameter = (Parameter) criteria.getValue();
|
||||
|
||||
mappedValue = convertValue(parameter.getValue(), propertyField.getTypeHint());
|
||||
typeHint = getTypeHint(mappedValue, actualType.getType(), parameter);
|
||||
} else if (criteria.getValue() instanceof ValueFunction) {
|
||||
|
||||
ValueFunction<Object> valueFunction = (ValueFunction<Object>) criteria.getValue();
|
||||
Object value = valueFunction.apply(getEscaper(criteria.getComparator()));
|
||||
|
||||
mappedValue = convertValue(value, propertyField.getTypeHint());
|
||||
typeHint = actualType.getType();
|
||||
} else {
|
||||
|
||||
mappedValue = convertValue(criteria.getValue(), propertyField.getTypeHint());
|
||||
typeHint = actualType.getType();
|
||||
}
|
||||
|
||||
return createCondition(column, mappedValue, typeHint, bindings, criteria.getComparator(), criteria.isIgnoreCase());
|
||||
}
|
||||
|
||||
private Escaper getEscaper(Comparator comparator) {
|
||||
|
||||
if (comparator == Comparator.LIKE || comparator == Comparator.NOT_LIKE) {
|
||||
return dialect.getLikeEscaper();
|
||||
}
|
||||
|
||||
return Escaper.DEFAULT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Potentially convert the {@link Parameter}.
|
||||
*
|
||||
* @param value
|
||||
* @return
|
||||
* @since 1.2
|
||||
*/
|
||||
public Parameter getBindValue(Parameter value) {
|
||||
|
||||
if (value.isEmpty()) {
|
||||
return Parameter.empty(converter.getTargetType(value.getType()));
|
||||
}
|
||||
|
||||
return Parameter.from(convertValue(value.getValue(), ClassTypeInformation.OBJECT));
|
||||
}
|
||||
|
||||
@Nullable
|
||||
protected Object convertValue(@Nullable Object value, TypeInformation<?> typeInformation) {
|
||||
|
||||
if (value == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (value instanceof Pair) {
|
||||
|
||||
Pair<Object, Object> pair = (Pair<Object, Object>) value;
|
||||
|
||||
Object first = convertValue(pair.getFirst(),
|
||||
typeInformation.getActualType() != null ? typeInformation.getRequiredActualType()
|
||||
: ClassTypeInformation.OBJECT);
|
||||
|
||||
Object second = convertValue(pair.getSecond(),
|
||||
typeInformation.getActualType() != null ? typeInformation.getRequiredActualType()
|
||||
: ClassTypeInformation.OBJECT);
|
||||
|
||||
return Pair.of(first, second);
|
||||
}
|
||||
|
||||
if (value instanceof Iterable) {
|
||||
|
||||
List<Object> mapped = new ArrayList<>();
|
||||
|
||||
for (Object o : (Iterable<?>) value) {
|
||||
mapped.add(convertValue(o, typeInformation.getActualType() != null ? typeInformation.getRequiredActualType()
|
||||
: ClassTypeInformation.OBJECT));
|
||||
}
|
||||
|
||||
return mapped;
|
||||
}
|
||||
|
||||
if (value.getClass().isArray()
|
||||
&& (ClassTypeInformation.OBJECT.equals(typeInformation) || typeInformation.isCollectionLike())) {
|
||||
return value;
|
||||
}
|
||||
|
||||
return this.converter.writeValue(value, typeInformation);
|
||||
}
|
||||
|
||||
protected MappingContext<? extends RelationalPersistentEntity<?>, RelationalPersistentProperty> getMappingContext() {
|
||||
return this.mappingContext;
|
||||
}
|
||||
|
||||
private Condition createCondition(Column column, @Nullable Object mappedValue, Class<?> valueType,
|
||||
MutableBindings bindings, Comparator comparator, boolean ignoreCase) {
|
||||
|
||||
if (comparator.equals(Comparator.IS_NULL)) {
|
||||
return column.isNull();
|
||||
}
|
||||
|
||||
if (comparator.equals(Comparator.IS_NOT_NULL)) {
|
||||
return column.isNotNull();
|
||||
}
|
||||
|
||||
if (comparator == Comparator.IS_TRUE) {
|
||||
Expression bind = booleanBind(column, mappedValue, valueType, bindings, ignoreCase);
|
||||
|
||||
return column.isEqualTo(bind);
|
||||
}
|
||||
|
||||
if (comparator == Comparator.IS_FALSE) {
|
||||
Expression bind = booleanBind(column, mappedValue, valueType, bindings, ignoreCase);
|
||||
|
||||
return column.isEqualTo(bind);
|
||||
}
|
||||
|
||||
Expression columnExpression = column;
|
||||
if (ignoreCase) {
|
||||
columnExpression = Functions.upper(column);
|
||||
}
|
||||
|
||||
if (comparator == Comparator.NOT_IN || comparator == Comparator.IN) {
|
||||
|
||||
Condition condition;
|
||||
|
||||
if (mappedValue instanceof Iterable) {
|
||||
|
||||
List<Expression> expressions = new ArrayList<>(
|
||||
mappedValue instanceof Collection ? ((Collection<?>) mappedValue).size() : 10);
|
||||
|
||||
for (Object o : (Iterable<?>) mappedValue) {
|
||||
|
||||
BindMarker bindMarker = bindings.nextMarker(column.getName().getReference());
|
||||
expressions.add(bind(o, valueType, bindings, bindMarker));
|
||||
}
|
||||
|
||||
condition = Conditions.in(columnExpression, expressions.toArray(new Expression[0]));
|
||||
|
||||
} else {
|
||||
|
||||
BindMarker bindMarker = bindings.nextMarker(column.getName().getReference());
|
||||
Expression expression = bind(mappedValue, valueType, bindings, bindMarker);
|
||||
|
||||
condition = Conditions.in(columnExpression, expression);
|
||||
}
|
||||
|
||||
if (comparator == Comparator.NOT_IN) {
|
||||
condition = condition.not();
|
||||
}
|
||||
|
||||
return condition;
|
||||
}
|
||||
|
||||
if (comparator == Comparator.BETWEEN || comparator == Comparator.NOT_BETWEEN) {
|
||||
|
||||
Pair<Object, Object> pair = (Pair<Object, Object>) mappedValue;
|
||||
|
||||
Expression begin = bind(pair.getFirst(), valueType, bindings,
|
||||
bindings.nextMarker(column.getName().getReference()), ignoreCase);
|
||||
Expression end = bind(pair.getSecond(), valueType, bindings, bindings.nextMarker(column.getName().getReference()),
|
||||
ignoreCase);
|
||||
|
||||
return comparator == Comparator.BETWEEN ? Conditions.between(columnExpression, begin, end)
|
||||
: Conditions.notBetween(columnExpression, begin, end);
|
||||
}
|
||||
|
||||
BindMarker bindMarker = bindings.nextMarker(column.getName().getReference());
|
||||
|
||||
switch (comparator) {
|
||||
case EQ: {
|
||||
Expression expression = bind(mappedValue, valueType, bindings, bindMarker, ignoreCase);
|
||||
return Conditions.isEqual(columnExpression, expression);
|
||||
}
|
||||
case NEQ: {
|
||||
Expression expression = bind(mappedValue, valueType, bindings, bindMarker, ignoreCase);
|
||||
return Conditions.isEqual(columnExpression, expression).not();
|
||||
}
|
||||
case LT: {
|
||||
Expression expression = bind(mappedValue, valueType, bindings, bindMarker);
|
||||
return column.isLess(expression);
|
||||
}
|
||||
case LTE: {
|
||||
Expression expression = bind(mappedValue, valueType, bindings, bindMarker);
|
||||
return column.isLessOrEqualTo(expression);
|
||||
}
|
||||
case GT: {
|
||||
Expression expression = bind(mappedValue, valueType, bindings, bindMarker);
|
||||
return column.isGreater(expression);
|
||||
}
|
||||
case GTE: {
|
||||
Expression expression = bind(mappedValue, valueType, bindings, bindMarker);
|
||||
return column.isGreaterOrEqualTo(expression);
|
||||
}
|
||||
case LIKE: {
|
||||
Expression expression = bind(mappedValue, valueType, bindings, bindMarker, ignoreCase);
|
||||
return Conditions.like(columnExpression, expression);
|
||||
}
|
||||
case NOT_LIKE: {
|
||||
Expression expression = bind(mappedValue, valueType, bindings, bindMarker, ignoreCase);
|
||||
return Conditions.notLike(columnExpression, expression);
|
||||
}
|
||||
default:
|
||||
throw new UnsupportedOperationException("Comparator " + comparator + " not supported");
|
||||
}
|
||||
}
|
||||
|
||||
Field createPropertyField(@Nullable RelationalPersistentEntity<?> entity, SqlIdentifier key) {
|
||||
return entity == null ? new Field(key) : new MetadataBackedField(key, entity, mappingContext);
|
||||
}
|
||||
|
||||
Field createPropertyField(@Nullable RelationalPersistentEntity<?> entity, SqlIdentifier key,
|
||||
MappingContext<? extends RelationalPersistentEntity<?>, RelationalPersistentProperty> mappingContext) {
|
||||
return entity == null ? new Field(key) : new MetadataBackedField(key, entity, mappingContext);
|
||||
}
|
||||
|
||||
Class<?> getTypeHint(@Nullable Object mappedValue, Class<?> propertyType) {
|
||||
return propertyType;
|
||||
}
|
||||
|
||||
Class<?> getTypeHint(@Nullable Object mappedValue, Class<?> propertyType, Parameter parameter) {
|
||||
|
||||
if (mappedValue == null || propertyType.equals(Object.class)) {
|
||||
return parameter.getType();
|
||||
}
|
||||
|
||||
if (mappedValue.getClass().equals(parameter.getValue().getClass())) {
|
||||
return parameter.getType();
|
||||
}
|
||||
|
||||
return propertyType;
|
||||
}
|
||||
|
||||
private Expression bind(@Nullable Object mappedValue, Class<?> valueType, MutableBindings bindings,
|
||||
BindMarker bindMarker) {
|
||||
return bind(mappedValue, valueType, bindings, bindMarker, false);
|
||||
}
|
||||
|
||||
private Expression bind(@Nullable Object mappedValue, Class<?> valueType, MutableBindings bindings,
|
||||
BindMarker bindMarker, boolean ignoreCase) {
|
||||
|
||||
if (mappedValue != null) {
|
||||
bindings.bind(bindMarker, mappedValue);
|
||||
} else {
|
||||
bindings.bindNull(bindMarker, valueType);
|
||||
}
|
||||
|
||||
return ignoreCase ? Functions.upper(SQL.bindMarker(bindMarker.getPlaceholder()))
|
||||
: SQL.bindMarker(bindMarker.getPlaceholder());
|
||||
}
|
||||
|
||||
private Expression booleanBind(Column column, Object mappedValue, Class<?> valueType, MutableBindings bindings,
|
||||
boolean ignoreCase) {
|
||||
BindMarker bindMarker = bindings.nextMarker(column.getName().getReference());
|
||||
|
||||
return bind(mappedValue, valueType, bindings, bindMarker, ignoreCase);
|
||||
}
|
||||
|
||||
/**
|
||||
* Value object to represent a field and its meta-information.
|
||||
*/
|
||||
protected static class Field {
|
||||
|
||||
protected final SqlIdentifier name;
|
||||
|
||||
/**
|
||||
* Creates a new {@link Field} without meta-information but the given name.
|
||||
*
|
||||
* @param name must not be {@literal null} or empty.
|
||||
*/
|
||||
public Field(SqlIdentifier name) {
|
||||
|
||||
Assert.notNull(name, "Name must not be null!");
|
||||
this.name = name;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the key to be used in the mapped document eventually.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public SqlIdentifier getMappedColumnName() {
|
||||
return this.name;
|
||||
}
|
||||
|
||||
public TypeInformation<?> getTypeHint() {
|
||||
return ClassTypeInformation.OBJECT;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extension of {@link Field} to be backed with mapping metadata.
|
||||
*/
|
||||
protected static class MetadataBackedField extends Field {
|
||||
|
||||
private final RelationalPersistentEntity<?> entity;
|
||||
private final MappingContext<? extends RelationalPersistentEntity<?>, RelationalPersistentProperty> mappingContext;
|
||||
private final @Nullable RelationalPersistentProperty property;
|
||||
private final @Nullable PersistentPropertyPath<RelationalPersistentProperty> path;
|
||||
|
||||
/**
|
||||
* Creates a new {@link MetadataBackedField} with the given name, {@link RelationalPersistentEntity} and
|
||||
* {@link MappingContext}.
|
||||
*
|
||||
* @param name must not be {@literal null} or empty.
|
||||
* @param entity must not be {@literal null}.
|
||||
* @param context must not be {@literal null}.
|
||||
*/
|
||||
protected MetadataBackedField(SqlIdentifier name, RelationalPersistentEntity<?> entity,
|
||||
MappingContext<? extends RelationalPersistentEntity<?>, RelationalPersistentProperty> context) {
|
||||
this(name, entity, context, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a new {@link MetadataBackedField} with the given name, {@link RelationalPersistentEntity} and
|
||||
* {@link MappingContext} with the given {@link RelationalPersistentProperty}.
|
||||
*
|
||||
* @param name must not be {@literal null} or empty.
|
||||
* @param entity must not be {@literal null}.
|
||||
* @param context must not be {@literal null}.
|
||||
* @param property may be {@literal null}.
|
||||
*/
|
||||
protected MetadataBackedField(SqlIdentifier name, RelationalPersistentEntity<?> entity,
|
||||
MappingContext<? extends RelationalPersistentEntity<?>, RelationalPersistentProperty> context,
|
||||
@Nullable RelationalPersistentProperty property) {
|
||||
|
||||
super(name);
|
||||
|
||||
Assert.notNull(entity, "RelationalPersistentEntity must not be null!");
|
||||
|
||||
this.entity = entity;
|
||||
this.mappingContext = context;
|
||||
|
||||
this.path = getPath(name.getReference());
|
||||
this.property = this.path == null ? property : this.path.getLeafProperty();
|
||||
}
|
||||
|
||||
@Override
|
||||
public SqlIdentifier getMappedColumnName() {
|
||||
return this.path == null || this.path.getLeafProperty() == null ? super.getMappedColumnName()
|
||||
: this.path.getLeafProperty().getColumnName();
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link PersistentPropertyPath} for the given {@code pathExpression}.
|
||||
*
|
||||
* @param pathExpression the path expression to use.
|
||||
* @return
|
||||
*/
|
||||
@Nullable
|
||||
private PersistentPropertyPath<RelationalPersistentProperty> getPath(String pathExpression) {
|
||||
|
||||
try {
|
||||
|
||||
PropertyPath path = forName(pathExpression);
|
||||
|
||||
if (isPathToJavaLangClassProperty(path)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return this.mappingContext.getPersistentPropertyPath(path);
|
||||
} catch (MappingException | PropertyReferenceException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
private PropertyPath forName(String path) {
|
||||
|
||||
if (entity.getPersistentProperty(path) != null) {
|
||||
return PropertyPath.from(Pattern.quote(path), entity.getTypeInformation());
|
||||
}
|
||||
|
||||
return PropertyPath.from(path, entity.getTypeInformation());
|
||||
}
|
||||
|
||||
private boolean isPathToJavaLangClassProperty(PropertyPath path) {
|
||||
return path.getType().equals(Class.class) && path.getLeafProperty().getOwningType().getType().equals(Class.class);
|
||||
}
|
||||
|
||||
@Override
|
||||
public TypeInformation<?> getTypeHint() {
|
||||
|
||||
if (this.property == null) {
|
||||
return super.getTypeHint();
|
||||
}
|
||||
|
||||
if (this.property.getType().isPrimitive()) {
|
||||
return ClassTypeInformation.from(ClassUtils.resolvePrimitiveIfNecessary(this.property.getType()));
|
||||
}
|
||||
|
||||
if (this.property.getType().isArray()) {
|
||||
return this.property.getTypeInformation();
|
||||
}
|
||||
|
||||
if (this.property.getType().isInterface()
|
||||
|| (java.lang.reflect.Modifier.isAbstract(this.property.getType().getModifiers()))) {
|
||||
return ClassTypeInformation.OBJECT;
|
||||
}
|
||||
|
||||
return this.property.getTypeInformation();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,159 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019-2022 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
|
||||
*
|
||||
* https://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.r2dbc.query;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import org.springframework.data.r2dbc.convert.R2dbcConverter;
|
||||
import org.springframework.data.r2dbc.dialect.R2dbcDialect;
|
||||
import org.springframework.data.relational.core.dialect.Escaper;
|
||||
import org.springframework.data.relational.core.mapping.RelationalPersistentEntity;
|
||||
import org.springframework.data.relational.core.query.Update;
|
||||
import org.springframework.data.relational.core.query.ValueFunction;
|
||||
import org.springframework.data.relational.core.sql.AssignValue;
|
||||
import org.springframework.data.relational.core.sql.Assignment;
|
||||
import org.springframework.data.relational.core.sql.Assignments;
|
||||
import org.springframework.data.relational.core.sql.Column;
|
||||
import org.springframework.data.relational.core.sql.SQL;
|
||||
import org.springframework.data.relational.core.sql.SqlIdentifier;
|
||||
import org.springframework.data.relational.core.sql.Table;
|
||||
import org.springframework.data.util.TypeInformation;
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.r2dbc.core.Parameter;
|
||||
import org.springframework.r2dbc.core.binding.BindMarker;
|
||||
import org.springframework.r2dbc.core.binding.BindMarkers;
|
||||
import org.springframework.r2dbc.core.binding.Bindings;
|
||||
import org.springframework.r2dbc.core.binding.MutableBindings;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* A subclass of {@link QueryMapper} that maps {@link Update} to update assignments.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public class UpdateMapper extends QueryMapper {
|
||||
|
||||
/**
|
||||
* Creates a new {@link QueryMapper} with the given {@link R2dbcConverter}.
|
||||
*
|
||||
* @param dialect must not be {@literal null}.
|
||||
* @param converter must not be {@literal null}.
|
||||
*/
|
||||
public UpdateMapper(R2dbcDialect dialect, R2dbcConverter converter) {
|
||||
super(dialect, converter);
|
||||
}
|
||||
|
||||
/**
|
||||
* Map a {@link org.springframework.data.relational.core.query.Update} object to {@link BoundAssignments} and consider
|
||||
* value/{@code NULL} {@link Bindings}.
|
||||
*
|
||||
* @param markers bind markers object, must not be {@literal null}.
|
||||
* @param update update definition to map, must not be {@literal null}.
|
||||
* @param table must not be {@literal null}.
|
||||
* @param entity related {@link RelationalPersistentEntity}, can be {@literal null}.
|
||||
* @return the mapped {@link BoundAssignments}.
|
||||
* @since 1.1
|
||||
*/
|
||||
public BoundAssignments getMappedObject(BindMarkers markers, Update update, Table table,
|
||||
@Nullable RelationalPersistentEntity<?> entity) {
|
||||
return getMappedObject(markers, update.getAssignments(), table, entity);
|
||||
}
|
||||
|
||||
/**
|
||||
* Map a {@code assignments} object to {@link BoundAssignments} and consider value/{@code NULL} {@link Bindings}.
|
||||
*
|
||||
* @param markers bind markers object, must not be {@literal null}.
|
||||
* @param assignments update/insert definition to map, must not be {@literal null}.
|
||||
* @param table must not be {@literal null}.
|
||||
* @param entity related {@link RelationalPersistentEntity}, can be {@literal null}.
|
||||
* @return the mapped {@link BoundAssignments}.
|
||||
*/
|
||||
public BoundAssignments getMappedObject(BindMarkers markers, Map<SqlIdentifier, ? extends Object> assignments,
|
||||
Table table, @Nullable RelationalPersistentEntity<?> entity) {
|
||||
|
||||
Assert.notNull(markers, "BindMarkers must not be null!");
|
||||
Assert.notNull(assignments, "Assignments must not be null!");
|
||||
Assert.notNull(table, "Table must not be null!");
|
||||
|
||||
MutableBindings bindings = new MutableBindings(markers);
|
||||
List<Assignment> result = new ArrayList<>();
|
||||
|
||||
assignments.forEach((column, value) -> {
|
||||
Assignment assignment = getAssignment(column, value, bindings, table, entity);
|
||||
result.add(assignment);
|
||||
});
|
||||
|
||||
return new BoundAssignments(bindings, result);
|
||||
}
|
||||
|
||||
private Assignment getAssignment(SqlIdentifier columnName, Object value, MutableBindings bindings, Table table,
|
||||
@Nullable RelationalPersistentEntity<?> entity) {
|
||||
|
||||
Field propertyField = createPropertyField(entity, columnName, getMappingContext());
|
||||
Column column = table.column(propertyField.getMappedColumnName());
|
||||
TypeInformation<?> actualType = propertyField.getTypeHint().getRequiredActualType();
|
||||
|
||||
Object mappedValue;
|
||||
Class<?> typeHint;
|
||||
|
||||
if (value instanceof Parameter) {
|
||||
|
||||
Parameter parameter = (Parameter) value;
|
||||
|
||||
mappedValue = convertValue(parameter.getValue(), propertyField.getTypeHint());
|
||||
typeHint = getTypeHint(mappedValue, actualType.getType(), parameter);
|
||||
|
||||
} else if (value instanceof ValueFunction) {
|
||||
|
||||
ValueFunction<Object> valueFunction = (ValueFunction<Object>) value;
|
||||
|
||||
mappedValue = convertValue(valueFunction.apply(Escaper.DEFAULT), propertyField.getTypeHint());
|
||||
|
||||
if (mappedValue == null) {
|
||||
return Assignments.value(column, SQL.nullLiteral());
|
||||
}
|
||||
|
||||
typeHint = actualType.getType();
|
||||
} else {
|
||||
|
||||
mappedValue = convertValue(value, propertyField.getTypeHint());
|
||||
|
||||
if (mappedValue == null) {
|
||||
return Assignments.value(column, SQL.nullLiteral());
|
||||
}
|
||||
|
||||
typeHint = actualType.getType();
|
||||
}
|
||||
|
||||
return createAssignment(column, mappedValue, typeHint, bindings);
|
||||
}
|
||||
|
||||
private Assignment createAssignment(Column column, Object value, Class<?> type, MutableBindings bindings) {
|
||||
|
||||
BindMarker bindMarker = bindings.nextMarker(column.getName().getReference());
|
||||
AssignValue assignValue = Assignments.value(column, SQL.bindMarker(bindMarker.getPlaceholder()));
|
||||
|
||||
if (value == null) {
|
||||
bindings.bindNull(bindMarker, type);
|
||||
} else {
|
||||
bindings.bind(bindMarker, value);
|
||||
}
|
||||
|
||||
return assignValue;
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
/**
|
||||
* Query and update support.
|
||||
*/
|
||||
@org.springframework.lang.NonNullApi
|
||||
@org.springframework.lang.NonNullFields
|
||||
package org.springframework.data.r2dbc.query;
|
||||
@@ -1,39 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019-2022 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
|
||||
*
|
||||
* https://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.r2dbc.repository;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
/**
|
||||
* Indicates a query method should be considered a modifying query that returns nothing or the number of rows affected
|
||||
* by the query.
|
||||
* <p>
|
||||
* Query methods annotated with {@code @Modifying} are typically {@code INSERT}, {@code UPDATE}, {@code DELETE}, and DDL
|
||||
* statements that do not return tabular results. This annotation isn't applicable if the query method returns results
|
||||
* such as {@code INSERT} with generated keys.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @see io.r2dbc.spi.Result#getRowsUpdated()
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target({ ElementType.METHOD, ElementType.ANNOTATION_TYPE })
|
||||
@Documented
|
||||
public @interface Modifying {
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
/*
|
||||
* Copyright 2019-2022 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
|
||||
*
|
||||
* https://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.r2dbc.repository;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.springframework.data.annotation.QueryAnnotation;
|
||||
|
||||
/**
|
||||
* Annotation to provide SQL statements that will get used for executing the method.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Target(ElementType.METHOD)
|
||||
@QueryAnnotation
|
||||
@Documented
|
||||
public @interface Query {
|
||||
|
||||
/**
|
||||
* The SQL statement to execute when the annotated method gets invoked.
|
||||
*/
|
||||
String value();
|
||||
}
|
||||
@@ -1,31 +0,0 @@
|
||||
/*
|
||||
* Copyright 2018-2022 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
|
||||
*
|
||||
* https://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.r2dbc.repository;
|
||||
|
||||
import org.springframework.data.repository.NoRepositoryBean;
|
||||
import org.springframework.data.repository.query.ReactiveQueryByExampleExecutor;
|
||||
import org.springframework.data.repository.reactive.ReactiveCrudRepository;
|
||||
import org.springframework.data.repository.reactive.ReactiveSortingRepository;
|
||||
|
||||
/**
|
||||
* R2DBC specific {@link org.springframework.data.repository.Repository} interface with reactive support.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @author Stephen Cohen
|
||||
* @author Greg Turnquist
|
||||
*/
|
||||
@NoRepositoryBean
|
||||
public interface R2dbcRepository<T, ID> extends ReactiveCrudRepository<T, ID>, ReactiveSortingRepository<T, ID>, ReactiveQueryByExampleExecutor<T> {}
|
||||
@@ -1,133 +0,0 @@
|
||||
/*
|
||||
* Copyright 2018-2022 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
|
||||
*
|
||||
* https://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.r2dbc.repository.config;
|
||||
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.ElementType;
|
||||
import java.lang.annotation.Inherited;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.lang.annotation.Target;
|
||||
|
||||
import org.springframework.beans.factory.FactoryBean;
|
||||
import org.springframework.context.annotation.ComponentScan.Filter;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.data.r2dbc.repository.support.R2dbcRepositoryFactoryBean;
|
||||
import org.springframework.data.repository.config.DefaultRepositoryBaseClass;
|
||||
import org.springframework.data.repository.query.QueryLookupStrategy;
|
||||
import org.springframework.data.repository.query.QueryLookupStrategy.Key;
|
||||
|
||||
/**
|
||||
* Annotation to activate reactive relational repositories using R2DBC. If no base package is configured through either
|
||||
* {@link #value()}, {@link #basePackages()} or {@link #basePackageClasses()} it will trigger scanning of the package of
|
||||
* annotated class.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @author Christoph Strobl
|
||||
*/
|
||||
@Target(ElementType.TYPE)
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Documented
|
||||
@Inherited
|
||||
@Import(R2dbcRepositoriesRegistrar.class)
|
||||
public @interface EnableR2dbcRepositories {
|
||||
|
||||
/**
|
||||
* Alias for the {@link #basePackages()} attribute. Allows for more concise annotation declarations e.g.:
|
||||
* {@code @EnableR2dbcRepositories("org.my.pkg")} instead of
|
||||
* {@code @EnableR2dbcRepositories(basePackages="org.my.pkg")}.
|
||||
*/
|
||||
String[] value() default {};
|
||||
|
||||
/**
|
||||
* Base packages to scan for annotated components. {@link #value()} is an alias for (and mutually exclusive with) this
|
||||
* attribute. Use {@link #basePackageClasses()} for a type-safe alternative to String-based package names.
|
||||
*/
|
||||
String[] basePackages() default {};
|
||||
|
||||
/**
|
||||
* Type-safe alternative to {@link #basePackages()} for specifying the packages to scan for annotated components. The
|
||||
* package of each class specified will be scanned. Consider creating a special no-op marker class or interface in
|
||||
* each package that serves no purpose other than being referenced by this attribute.
|
||||
*/
|
||||
Class<?>[] basePackageClasses() default {};
|
||||
|
||||
/**
|
||||
* Specifies which types are eligible for component scanning. Further narrows the set of candidate components from
|
||||
* everything in {@link #basePackages()} to everything in the base packages that matches the given filter or filters.
|
||||
*/
|
||||
Filter[] includeFilters() default {};
|
||||
|
||||
/**
|
||||
* Specifies which types are not eligible for component scanning.
|
||||
*/
|
||||
Filter[] excludeFilters() default {};
|
||||
|
||||
/**
|
||||
* Returns the postfix to be used when looking up custom repository implementations. Defaults to {@literal Impl}. So
|
||||
* for a repository named {@code PersonRepository} the corresponding implementation class will be looked up scanning
|
||||
* for {@code PersonRepositoryImpl}.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
String repositoryImplementationPostfix() default "Impl";
|
||||
|
||||
/**
|
||||
* Configures the location of where to find the Spring Data named queries properties file. Will default to
|
||||
* {@code META-INF/r2dbc-named-queries.properties} if not configured otherwise.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
String namedQueriesLocation() default "";
|
||||
|
||||
/**
|
||||
* Returns the key of the {@link QueryLookupStrategy} to be used for lookup queries for query methods. Defaults to
|
||||
* {@link Key#CREATE_IF_NOT_FOUND}.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
Key queryLookupStrategy() default Key.CREATE_IF_NOT_FOUND;
|
||||
|
||||
/**
|
||||
* Returns the {@link FactoryBean} class to be used for each repository instance. Defaults to
|
||||
* {@link R2dbcRepositoryFactoryBean}.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
Class<?> repositoryFactoryBeanClass() default R2dbcRepositoryFactoryBean.class;
|
||||
|
||||
/**
|
||||
* Configure the repository base class to be used to create repository proxies for this particular configuration.
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
Class<?> repositoryBaseClass() default DefaultRepositoryBaseClass.class;
|
||||
|
||||
/**
|
||||
* Configures the name of the {@link org.springframework.data.r2dbc.core.R2dbcEntityOperations} bean to be used with
|
||||
* the repositories detected.
|
||||
*
|
||||
* @return
|
||||
* @since 1.1.3
|
||||
*/
|
||||
String entityOperationsRef() default "r2dbcEntityTemplate";
|
||||
|
||||
/**
|
||||
* Configures whether nested repository-interfaces (e.g. defined as inner classes) should be discovered by the
|
||||
* repositories infrastructure.
|
||||
*/
|
||||
boolean considerNestedRepositories() default false;
|
||||
}
|
||||
@@ -1,47 +0,0 @@
|
||||
/*
|
||||
* Copyright 2018-2022 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
|
||||
*
|
||||
* https://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.r2dbc.repository.config;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
|
||||
import org.springframework.data.repository.config.RepositoryBeanDefinitionRegistrarSupport;
|
||||
import org.springframework.data.repository.config.RepositoryConfigurationExtension;
|
||||
|
||||
/**
|
||||
* R2DBC-specific {@link org.springframework.context.annotation.ImportBeanDefinitionRegistrar}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
class R2dbcRepositoriesRegistrar extends RepositoryBeanDefinitionRegistrarSupport {
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.config.RepositoryBeanDefinitionRegistrarSupport#getAnnotation()
|
||||
*/
|
||||
@Override
|
||||
protected Class<? extends Annotation> getAnnotation() {
|
||||
return EnableR2dbcRepositories.class;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.config.RepositoryBeanDefinitionRegistrarSupport#getExtension()
|
||||
*/
|
||||
@Override
|
||||
protected RepositoryConfigurationExtension getExtension() {
|
||||
return new R2dbcRepositoryConfigurationExtension();
|
||||
}
|
||||
}
|
||||
@@ -1,111 +0,0 @@
|
||||
/*
|
||||
* Copyright 2018-2022 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
|
||||
*
|
||||
* https://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.r2dbc.repository.config;
|
||||
|
||||
import java.lang.annotation.Annotation;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
|
||||
import org.springframework.beans.factory.support.BeanDefinitionBuilder;
|
||||
import org.springframework.core.annotation.AnnotationAttributes;
|
||||
import org.springframework.data.r2dbc.repository.R2dbcRepository;
|
||||
import org.springframework.data.r2dbc.repository.support.R2dbcRepositoryFactoryBean;
|
||||
import org.springframework.data.relational.core.mapping.Table;
|
||||
import org.springframework.data.repository.config.AnnotationRepositoryConfigurationSource;
|
||||
import org.springframework.data.repository.config.RepositoryConfigurationExtension;
|
||||
import org.springframework.data.repository.config.RepositoryConfigurationExtensionSupport;
|
||||
import org.springframework.data.repository.config.XmlRepositoryConfigurationSource;
|
||||
import org.springframework.data.repository.core.RepositoryMetadata;
|
||||
|
||||
/**
|
||||
* Reactive {@link RepositoryConfigurationExtension} for R2DBC.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public class R2dbcRepositoryConfigurationExtension extends RepositoryConfigurationExtensionSupport {
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.config.RepositoryConfigurationExtensionSupport#getModuleName()
|
||||
*/
|
||||
@Override
|
||||
public String getModuleName() {
|
||||
return "R2DBC";
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.config.RepositoryConfigurationExtensionSupport#getModulePrefix()
|
||||
*/
|
||||
@Override
|
||||
protected String getModulePrefix() {
|
||||
return "r2dbc";
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.config.RepositoryConfigurationExtension#getRepositoryFactoryBeanClassName()
|
||||
*/
|
||||
public String getRepositoryFactoryBeanClassName() {
|
||||
return R2dbcRepositoryFactoryBean.class.getName();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.config.RepositoryConfigurationExtensionSupport#getIdentifyingAnnotations()
|
||||
*/
|
||||
@Override
|
||||
protected Collection<Class<? extends Annotation>> getIdentifyingAnnotations() {
|
||||
return Collections.singleton(Table.class);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.config.RepositoryConfigurationExtensionSupport#getIdentifyingTypes()
|
||||
*/
|
||||
@Override
|
||||
protected Collection<Class<?>> getIdentifyingTypes() {
|
||||
return Collections.singleton(R2dbcRepository.class);
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.config.RepositoryConfigurationExtensionSupport#postProcess(org.springframework.beans.factory.support.BeanDefinitionBuilder, org.springframework.data.repository.config.XmlRepositoryConfigurationSource)
|
||||
*/
|
||||
@Override
|
||||
public void postProcess(BeanDefinitionBuilder builder, XmlRepositoryConfigurationSource config) {}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.config.RepositoryConfigurationExtensionSupport#postProcess(org.springframework.beans.factory.support.BeanDefinitionBuilder, org.springframework.data.repository.config.AnnotationRepositoryConfigurationSource)
|
||||
*/
|
||||
@Override
|
||||
public void postProcess(BeanDefinitionBuilder builder, AnnotationRepositoryConfigurationSource config) {
|
||||
|
||||
AnnotationAttributes attributes = config.getAttributes();
|
||||
|
||||
builder.addPropertyReference("entityOperations", attributes.getString("entityOperationsRef"));
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.config.RepositoryConfigurationExtensionSupport#useRepositoryConfiguration(org.springframework.data.repository.core.RepositoryMetadata)
|
||||
*/
|
||||
@Override
|
||||
protected boolean useRepositoryConfiguration(RepositoryMetadata metadata) {
|
||||
return metadata.isReactiveRepository();
|
||||
}
|
||||
}
|
||||
@@ -1,6 +0,0 @@
|
||||
/**
|
||||
* Support infrastructure for the configuration of R2DBC-specific repositories.
|
||||
*/
|
||||
@org.springframework.lang.NonNullApi
|
||||
@org.springframework.lang.NonNullFields
|
||||
package org.springframework.data.r2dbc.repository.config;
|
||||
@@ -1,6 +0,0 @@
|
||||
/**
|
||||
* R2DBC-specific repository implementation.
|
||||
*/
|
||||
@org.springframework.lang.NonNullApi
|
||||
@org.springframework.lang.NonNullFields
|
||||
package org.springframework.data.r2dbc.repository;
|
||||
@@ -1,198 +0,0 @@
|
||||
/*
|
||||
* Copyright 2018-2022 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
|
||||
*
|
||||
* https://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.r2dbc.repository.query;
|
||||
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
import org.reactivestreams.Publisher;
|
||||
|
||||
import org.springframework.data.mapping.model.EntityInstantiators;
|
||||
import org.springframework.data.r2dbc.convert.R2dbcConverter;
|
||||
import org.springframework.data.r2dbc.core.R2dbcEntityOperations;
|
||||
import org.springframework.data.r2dbc.repository.query.R2dbcQueryExecution.ResultProcessingConverter;
|
||||
import org.springframework.data.r2dbc.repository.query.R2dbcQueryExecution.ResultProcessingExecution;
|
||||
import org.springframework.data.relational.repository.query.RelationalParameterAccessor;
|
||||
import org.springframework.data.relational.repository.query.RelationalParametersParameterAccessor;
|
||||
import org.springframework.data.repository.query.ParameterAccessor;
|
||||
import org.springframework.data.repository.query.RepositoryQuery;
|
||||
import org.springframework.data.repository.query.ResultProcessor;
|
||||
import org.springframework.data.repository.query.ReturnedType;
|
||||
import org.springframework.data.util.ReflectionUtils;
|
||||
import org.springframework.r2dbc.core.FetchSpec;
|
||||
import org.springframework.r2dbc.core.PreparedOperation;
|
||||
import org.springframework.r2dbc.core.RowsFetchSpec;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
/**
|
||||
* Base class for reactive {@link RepositoryQuery} implementations for R2DBC.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @author Stephen Cohen
|
||||
*/
|
||||
public abstract class AbstractR2dbcQuery implements RepositoryQuery {
|
||||
|
||||
private final R2dbcQueryMethod method;
|
||||
private final R2dbcEntityOperations entityOperations;
|
||||
private final R2dbcConverter converter;
|
||||
private final EntityInstantiators instantiators;
|
||||
|
||||
/**
|
||||
* Creates a new {@link AbstractR2dbcQuery} from the given {@link R2dbcQueryMethod} and {@link R2dbcEntityOperations}.
|
||||
*
|
||||
* @param method must not be {@literal null}.
|
||||
* @param entityOperations must not be {@literal null}.
|
||||
* @param converter must not be {@literal null}.
|
||||
* @since 1.4
|
||||
*/
|
||||
public AbstractR2dbcQuery(R2dbcQueryMethod method, R2dbcEntityOperations entityOperations, R2dbcConverter converter) {
|
||||
|
||||
Assert.notNull(method, "R2dbcQueryMethod must not be null!");
|
||||
Assert.notNull(entityOperations, "R2dbcEntityOperations must not be null!");
|
||||
Assert.notNull(converter, "R2dbcConverter must not be null!");
|
||||
|
||||
this.method = method;
|
||||
this.entityOperations = entityOperations;
|
||||
this.converter = converter;
|
||||
this.instantiators = new EntityInstantiators();
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.query.RepositoryQuery#getQueryMethod()
|
||||
*/
|
||||
public R2dbcQueryMethod getQueryMethod() {
|
||||
return method;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.repository.query.RepositoryQuery#execute(java.lang.Object[])
|
||||
*/
|
||||
public Object execute(Object[] parameters) {
|
||||
|
||||
RelationalParameterAccessor parameterAccessor = new RelationalParametersParameterAccessor(method, parameters);
|
||||
|
||||
return createQuery(parameterAccessor).flatMapMany(it -> executeQuery(parameterAccessor, it));
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private Publisher<?> executeQuery(RelationalParameterAccessor parameterAccessor, PreparedOperation<?> operation) {
|
||||
|
||||
ResultProcessor processor = method.getResultProcessor().withDynamicProjection(parameterAccessor);
|
||||
|
||||
RowsFetchSpec<?> fetchSpec;
|
||||
|
||||
if (isModifyingQuery()) {
|
||||
fetchSpec = entityOperations.getDatabaseClient().sql(operation).fetch();
|
||||
} else if (isExistsQuery()) {
|
||||
fetchSpec = entityOperations.getDatabaseClient().sql(operation).map(row -> true);
|
||||
} else {
|
||||
fetchSpec = entityOperations.query(operation, resolveResultType(processor));
|
||||
}
|
||||
|
||||
R2dbcQueryExecution execution = new ResultProcessingExecution(getExecutionToWrap(processor.getReturnedType()),
|
||||
new ResultProcessingConverter(processor, converter.getMappingContext(), instantiators));
|
||||
|
||||
return execution.execute(RowsFetchSpec.class.cast(fetchSpec));
|
||||
}
|
||||
|
||||
Class<?> resolveResultType(ResultProcessor resultProcessor) {
|
||||
|
||||
ReturnedType returnedType = resultProcessor.getReturnedType();
|
||||
|
||||
if (returnedType.getReturnedType().isAssignableFrom(returnedType.getDomainType())) {
|
||||
return returnedType.getDomainType();
|
||||
}
|
||||
|
||||
return returnedType.isProjecting() ? returnedType.getDomainType() : returnedType.getReturnedType();
|
||||
}
|
||||
|
||||
private R2dbcQueryExecution getExecutionToWrap(ReturnedType returnedType) {
|
||||
|
||||
if (isModifyingQuery()) {
|
||||
|
||||
return fetchSpec -> {
|
||||
|
||||
Assert.isInstanceOf(FetchSpec.class, fetchSpec);
|
||||
|
||||
FetchSpec<?> fs = (FetchSpec<?>) fetchSpec;
|
||||
|
||||
if (Boolean.class.isAssignableFrom(returnedType.getReturnedType())) {
|
||||
return fs.rowsUpdated().map(integer -> integer > 0);
|
||||
}
|
||||
|
||||
if (Number.class.isAssignableFrom(returnedType.getReturnedType())) {
|
||||
|
||||
return fs.rowsUpdated()
|
||||
.map(integer -> converter.getConversionService().convert(integer, returnedType.getReturnedType()));
|
||||
}
|
||||
|
||||
if (ReflectionUtils.isVoid(returnedType.getReturnedType())) {
|
||||
return fs.rowsUpdated().then();
|
||||
}
|
||||
|
||||
return fs.rowsUpdated();
|
||||
};
|
||||
}
|
||||
|
||||
if (isCountQuery()) {
|
||||
return (fetchSpec) -> fetchSpec.first().defaultIfEmpty(0L);
|
||||
}
|
||||
|
||||
if (isExistsQuery()) {
|
||||
return (fetchSpec) -> fetchSpec.first().defaultIfEmpty(false);
|
||||
}
|
||||
|
||||
if (method.isCollectionQuery()) {
|
||||
return RowsFetchSpec::all;
|
||||
}
|
||||
|
||||
return RowsFetchSpec::one;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this query is a modifying one.
|
||||
*
|
||||
* @return
|
||||
* @since 1.1
|
||||
*/
|
||||
protected abstract boolean isModifyingQuery();
|
||||
|
||||
/**
|
||||
* Returns whether the query should get a count projection applied.
|
||||
*
|
||||
* @return
|
||||
* @since 1.2
|
||||
*/
|
||||
protected abstract boolean isCountQuery();
|
||||
|
||||
/**
|
||||
* Returns whether the query should get an exists projection applied.
|
||||
*
|
||||
* @return
|
||||
* @since 1.2
|
||||
*/
|
||||
protected abstract boolean isExistsQuery();
|
||||
|
||||
/**
|
||||
* Creates a {@link BindableQuery} instance using the given {@link ParameterAccessor}
|
||||
*
|
||||
* @param accessor must not be {@literal null}.
|
||||
* @return a mono emitting a {@link BindableQuery}.
|
||||
*/
|
||||
protected abstract Mono<PreparedOperation<?>> createQuery(RelationalParameterAccessor accessor);
|
||||
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
/*
|
||||
* Copyright 2018-2022 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
|
||||
*
|
||||
* https://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.r2dbc.repository.query;
|
||||
|
||||
import java.util.function.Supplier;
|
||||
|
||||
import org.springframework.r2dbc.core.DatabaseClient;
|
||||
|
||||
/**
|
||||
* Interface declaring a query that supplies SQL and can bind parameters to a {@link DatabaseClient.GenericExecuteSpec}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
public interface BindableQuery extends Supplier<String> {
|
||||
|
||||
/**
|
||||
* Bind parameters to the {@link DatabaseClient.GenericExecuteSpec query}.
|
||||
*
|
||||
* @param bindSpec must not be {@literal null}.
|
||||
* @return the bound query object.
|
||||
*/
|
||||
DatabaseClient.GenericExecuteSpec bind(DatabaseClient.GenericExecuteSpec bindSpec);
|
||||
}
|
||||
@@ -1,80 +0,0 @@
|
||||
/*
|
||||
* Copyright 2020-2022 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
|
||||
*
|
||||
* https://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.r2dbc.repository.query;
|
||||
|
||||
import org.springframework.data.mapping.model.SpELExpressionEvaluator;
|
||||
import org.springframework.expression.EvaluationContext;
|
||||
import org.springframework.expression.Expression;
|
||||
import org.springframework.expression.ExpressionParser;
|
||||
import org.springframework.r2dbc.core.Parameter;
|
||||
|
||||
/**
|
||||
* Simple {@link R2dbcSpELExpressionEvaluator} implementation using {@link ExpressionParser} and
|
||||
* {@link EvaluationContext}.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
* @since 1.2
|
||||
*/
|
||||
class DefaultR2dbcSpELExpressionEvaluator implements R2dbcSpELExpressionEvaluator {
|
||||
|
||||
private final ExpressionParser parser;
|
||||
|
||||
private final EvaluationContext context;
|
||||
|
||||
DefaultR2dbcSpELExpressionEvaluator(ExpressionParser parser, EvaluationContext context) {
|
||||
this.parser = parser;
|
||||
this.context = context;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a {@link SpELExpressionEvaluator} that does not support expression evaluation.
|
||||
*
|
||||
* @return a {@link SpELExpressionEvaluator} that does not support expression evaluation.
|
||||
*/
|
||||
public static R2dbcSpELExpressionEvaluator unsupported() {
|
||||
return NoOpExpressionEvaluator.INSTANCE;
|
||||
}
|
||||
|
||||
/*
|
||||
* (non-Javadoc)
|
||||
* @see org.springframework.data.mapping.model.R2dbcSpELExpressionEvaluator#evaluate(java.lang.String)
|
||||
*/
|
||||
@Override
|
||||
public Parameter evaluate(String expression) {
|
||||
|
||||
Expression expr = parser.parseExpression(expression);
|
||||
|
||||
Object value = expr.getValue(context, Object.class);
|
||||
Class<?> valueType = expr.getValueType(context);
|
||||
|
||||
return org.springframework.r2dbc.core.Parameter.fromOrEmpty(value, valueType != null ? valueType : Object.class);
|
||||
}
|
||||
|
||||
/**
|
||||
* {@link SpELExpressionEvaluator} that does not support SpEL evaluation.
|
||||
*
|
||||
* @author Mark Paluch
|
||||
*/
|
||||
enum NoOpExpressionEvaluator implements R2dbcSpELExpressionEvaluator {
|
||||
|
||||
INSTANCE;
|
||||
|
||||
@Override
|
||||
public Parameter evaluate(String expression) {
|
||||
throw new UnsupportedOperationException("Expression evaluation not supported");
|
||||
}
|
||||
}
|
||||
}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user