Skip to content

Commit 019c34f

Browse files
committed
Introduce JdbcClient as a fluent facade for query/update execution
Delegates to JdbcTemplate/NamedParameterJdbcTemplate underneath the covers. Supports parameter objects/records through SimplePropertySqlParameterSource. Closes gh-30931
1 parent b9ba0fc commit 019c34f

13 files changed

+2026
-41
lines changed

spring-jdbc/src/main/java/org/springframework/jdbc/core/JdbcOperations.java

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2021 the original author or authors.
2+
* Copyright 2002-2023 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -35,12 +35,19 @@
3535
* <p>Alternatively, the standard JDBC infrastructure can be mocked.
3636
* However, mocking this interface constitutes significantly less work.
3737
* As an alternative to a mock objects approach to testing data access code,
38-
* consider the powerful integration testing support provided via the <em>Spring
39-
* TestContext Framework</em>, in the {@code spring-test} artifact.
38+
* consider the powerful integration testing support provided via the
39+
* <em>Spring TestContext Framework</em>, in the {@code spring-test} artifact.
40+
*
41+
* <p><b>NOTE: As of 6.1, there is a unified JDBC access facade available in
42+
* the form of {@link org.springframework.jdbc.core.simple.JdbcClient}.</b>
43+
* {@code JdbcClient} provides a fluent API style for common JDBC queries/updates
44+
* with flexible use of indexed or named parameters. It delegates to
45+
* {@code JdbcOperations}/{@code NamedParameterJdbcOperations} for actual execution.
4046
*
4147
* @author Rod Johnson
4248
* @author Juergen Hoeller
4349
* @see JdbcTemplate
50+
* @see org.springframework.jdbc.core.namedparam.NamedParameterJdbcOperations
4451
*/
4552
public interface JdbcOperations {
4653

spring-jdbc/src/main/java/org/springframework/jdbc/core/JdbcTemplate.java

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -65,8 +65,7 @@
6565
* It executes core JDBC workflow, leaving application code to provide SQL
6666
* and extract results. This class executes SQL queries or updates, initiating
6767
* iteration over ResultSets and catching JDBC exceptions and translating
68-
* them to the generic, more informative exception hierarchy defined in the
69-
* {@code org.springframework.dao} package.
68+
* them to the common {@code org.springframework.dao} exception hierarchy.
7069
*
7170
* <p>Code using this class need only implement callback interfaces, giving
7271
* them a clearly defined contract. The {@link PreparedStatementCreator} callback
@@ -75,7 +74,8 @@
7574
* values from a ResultSet. See also {@link PreparedStatementSetter} and
7675
* {@link RowMapper} for two popular alternative callback interfaces.
7776
*
78-
* <p>Can be used within a service implementation via direct instantiation
77+
* <p>An instance of this template class is thread-safe once configured.
78+
* Can be used within a service implementation via direct instantiation
7979
* with a DataSource reference, or get prepared in an application context
8080
* and given to services as bean reference. Note: The DataSource should
8181
* always be configured as a bean in the application context, in the first case
@@ -88,12 +88,17 @@
8888
* <p>All SQL operations performed by this class are logged at debug level,
8989
* using "org.springframework.jdbc.core.JdbcTemplate" as log category.
9090
*
91-
* <p><b>NOTE: An instance of this class is thread-safe once configured.</b>
91+
* <p><b>NOTE: As of 6.1, there is a unified JDBC access facade available in
92+
* the form of {@link org.springframework.jdbc.core.simple.JdbcClient}.</b>
93+
* {@code JdbcClient} provides a fluent API style for common JDBC queries/updates
94+
* with flexible use of indexed or named parameters. It delegates to a
95+
* {@code JdbcTemplate}/{@code NamedParameterJdbcTemplate} for actual execution.
9296
*
9397
* @author Rod Johnson
9498
* @author Juergen Hoeller
9599
* @author Thomas Risberg
96100
* @since May 3, 2001
101+
* @see JdbcOperations
97102
* @see PreparedStatementCreator
98103
* @see PreparedStatementSetter
99104
* @see CallableStatementCreator
@@ -103,6 +108,7 @@
103108
* @see RowCallbackHandler
104109
* @see RowMapper
105110
* @see org.springframework.jdbc.support.SQLExceptionTranslator
111+
* @see org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate
106112
*/
107113
public class JdbcTemplate extends JdbcAccessor implements JdbcOperations {
108114

spring-jdbc/src/main/java/org/springframework/jdbc/core/namedparam/BeanPropertySqlParameterSource.java

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 the original author or authors.
2+
* Copyright 2002-2023 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -32,15 +32,16 @@
3232
/**
3333
* {@link SqlParameterSource} implementation that obtains parameter values
3434
* from bean properties of a given JavaBean object. The names of the bean
35-
* properties have to match the parameter names.
35+
* properties have to match the parameter names. Supports components of
36+
* record classes as well, with accessor methods matching parameter names.
3637
*
37-
* <p>Uses a Spring BeanWrapper for bean property access underneath.
38+
* <p>Uses a Spring {@link BeanWrapper} for bean property access underneath.
3839
*
3940
* @author Thomas Risberg
4041
* @author Juergen Hoeller
4142
* @since 2.0
4243
* @see NamedParameterJdbcTemplate
43-
* @see org.springframework.beans.BeanWrapper
44+
* @see SimplePropertySqlParameterSource
4445
*/
4546
public class BeanPropertySqlParameterSource extends AbstractSqlParameterSource {
4647

spring-jdbc/src/main/java/org/springframework/jdbc/core/namedparam/MapSqlParameterSource.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -143,6 +143,14 @@ public MapSqlParameterSource addValues(@Nullable Map<String, ?> values) {
143143
return this;
144144
}
145145

146+
/**
147+
* Return whether this parameter source has been configured with any values.
148+
* @since 6.1
149+
*/
150+
public boolean hasValues() {
151+
return !this.values.isEmpty();
152+
}
153+
146154
/**
147155
* Expose the current parameter values as read-only Map.
148156
*/

spring-jdbc/src/main/java/org/springframework/jdbc/core/namedparam/NamedParameterJdbcOperations.java

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2021 the original author or authors.
2+
* Copyright 2002-2023 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -40,6 +40,12 @@
4040
* often used directly, but provides a useful option to enhance testability,
4141
* as it can easily be mocked or stubbed.
4242
*
43+
* <p><b>NOTE: As of 6.1, there is a unified JDBC access facade available in
44+
* the form of {@link org.springframework.jdbc.core.simple.JdbcClient}.</b>
45+
* {@code JdbcClient} provides a fluent API style for common JDBC queries/updates
46+
* with flexible use of indexed or named parameters. It delegates to
47+
* {@code JdbcOperations}/{@code NamedParameterJdbcOperations} for actual execution.
48+
*
4349
* @author Thomas Risberg
4450
* @author Juergen Hoeller
4551
* @since 2.0

spring-jdbc/src/main/java/org/springframework/jdbc/core/namedparam/NamedParameterJdbcTemplate.java

Lines changed: 12 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2022 the original author or authors.
2+
* Copyright 2002-2023 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -55,16 +55,25 @@
5555
* done at execution time. It also allows for expanding a {@link java.util.List}
5656
* of values to the appropriate number of placeholders.
5757
*
58-
* <p>The underlying {@link org.springframework.jdbc.core.JdbcTemplate} is
58+
* <p>An instance of this template class is thread-safe once configured.
59+
* The underlying {@link org.springframework.jdbc.core.JdbcTemplate} is
5960
* exposed to allow for convenient access to the traditional
6061
* {@link org.springframework.jdbc.core.JdbcTemplate} methods.
6162
*
62-
* <p><b>NOTE: An instance of this class is thread-safe once configured.</b>
63+
* <p><b>NOTE: As of 6.1, there is a unified JDBC access facade available in
64+
* the form of {@link org.springframework.jdbc.core.simple.JdbcClient}.</b>
65+
* {@code JdbcClient} provides a fluent API style for common JDBC queries/updates
66+
* with flexible use of indexed or named parameters. It delegates to a
67+
* {@code JdbcTemplate}/{@code NamedParameterJdbcTemplate} for actual execution.
6368
*
6469
* @author Thomas Risberg
6570
* @author Juergen Hoeller
6671
* @since 2.0
6772
* @see NamedParameterJdbcOperations
73+
* @see SqlParameterSource
74+
* @see ResultSetExtractor
75+
* @see RowCallbackHandler
76+
* @see RowMapper
6877
* @see org.springframework.jdbc.core.JdbcTemplate
6978
*/
7079
public class NamedParameterJdbcTemplate implements NamedParameterJdbcOperations {
Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
/*
2+
* Copyright 2002-2023 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.jdbc.core.namedparam;
18+
19+
import java.beans.PropertyDescriptor;
20+
import java.lang.reflect.Field;
21+
import java.util.HashMap;
22+
import java.util.Map;
23+
24+
import org.springframework.beans.BeanUtils;
25+
import org.springframework.jdbc.core.StatementCreatorUtils;
26+
import org.springframework.lang.Nullable;
27+
import org.springframework.util.Assert;
28+
import org.springframework.util.ReflectionUtils;
29+
30+
/**
31+
* {@link SqlParameterSource} implementation that obtains parameter values
32+
* from bean properties of a given JavaBean object, from component accessors
33+
* of a record class, or from raw field access.
34+
*
35+
* <p>This is a more flexible variant of {@link BeanPropertySqlParameterSource},
36+
* with the limitation that it is not able to enumerate its
37+
* {@link #getParameterNames() parameter names}.
38+
*
39+
* <p>In terms of its fallback property discovery algorithm, this class is
40+
* similar to {@link org.springframework.validation.SimpleErrors} which is
41+
* also just used for property retrieval purposes (rather than binding).
42+
*
43+
* @author Juergen Hoeller
44+
* @since 6.1
45+
* @see NamedParameterJdbcTemplate
46+
* @see BeanPropertySqlParameterSource
47+
*/
48+
public class SimplePropertySqlParameterSource extends AbstractSqlParameterSource {
49+
50+
private final Object paramObject;
51+
52+
private final Map<String, Object> descriptorMap = new HashMap<>();
53+
54+
55+
/**
56+
* Create a new SqlParameterSource for the given bean, record or field holder.
57+
* @param paramObject the bean, record or field holder instance to wrap
58+
*/
59+
public SimplePropertySqlParameterSource(Object paramObject) {
60+
Assert.notNull(paramObject, "Parameter object must not be null");
61+
this.paramObject = paramObject;
62+
}
63+
64+
65+
@Override
66+
public boolean hasValue(String paramName) {
67+
return (getDescriptor(paramName) != null);
68+
}
69+
70+
@Override
71+
@Nullable
72+
public Object getValue(String paramName) throws IllegalArgumentException {
73+
Object desc = getDescriptor(paramName);
74+
if (desc instanceof PropertyDescriptor pd) {
75+
ReflectionUtils.makeAccessible(pd.getReadMethod());
76+
return ReflectionUtils.invokeMethod(pd.getReadMethod(), this.paramObject);
77+
}
78+
else if (desc instanceof Field field) {
79+
ReflectionUtils.makeAccessible(field);
80+
return ReflectionUtils.getField(field, this.paramObject);
81+
}
82+
throw new IllegalArgumentException("Cannot retrieve value for parameter '" + paramName +
83+
"' - neither a getter method nor a raw field found");
84+
}
85+
86+
/**
87+
* Derives a default SQL type from the corresponding property type.
88+
* @see StatementCreatorUtils#javaTypeToSqlParameterType
89+
*/
90+
@Override
91+
public int getSqlType(String paramName) {
92+
int sqlType = super.getSqlType(paramName);
93+
if (sqlType != TYPE_UNKNOWN) {
94+
return sqlType;
95+
}
96+
Object desc = getDescriptor(paramName);
97+
if (desc instanceof PropertyDescriptor pd) {
98+
return StatementCreatorUtils.javaTypeToSqlParameterType(pd.getPropertyType());
99+
}
100+
else if (desc instanceof Field field) {
101+
return StatementCreatorUtils.javaTypeToSqlParameterType(field.getType());
102+
}
103+
return TYPE_UNKNOWN;
104+
}
105+
106+
@Nullable
107+
private Object getDescriptor(String paramName) {
108+
return this.descriptorMap.computeIfAbsent(paramName, name -> {
109+
Object pd = BeanUtils.getPropertyDescriptor(this.paramObject.getClass(), name);
110+
if (pd == null) {
111+
pd = ReflectionUtils.findField(this.paramObject.getClass(), name);
112+
}
113+
return pd;
114+
});
115+
}
116+
117+
}

0 commit comments

Comments
 (0)