Split files
This commit is contained in:
176
framework-docs/modules/ROOT/pages/data-access/dao.adoc
Normal file
176
framework-docs/modules/ROOT/pages/data-access/dao.adoc
Normal file
@@ -0,0 +1,176 @@
|
||||
[[dao]]
|
||||
= DAO Support
|
||||
|
||||
The Data Access Object (DAO) support in Spring is aimed at making it easy to work with
|
||||
data access technologies (such as JDBC, Hibernate, or JPA) in a consistent way. This
|
||||
lets you switch between the aforementioned persistence technologies fairly easily,
|
||||
and it also lets you code without worrying about catching exceptions that are
|
||||
specific to each technology.
|
||||
|
||||
|
||||
|
||||
[[dao-exceptions]]
|
||||
== Consistent Exception Hierarchy
|
||||
|
||||
Spring provides a convenient translation from technology-specific exceptions, such as
|
||||
`SQLException` to its own exception class hierarchy, which has `DataAccessException` as
|
||||
the root exception. These exceptions wrap the original exception so that there is never
|
||||
any risk that you might lose any information about what might have gone wrong.
|
||||
|
||||
In addition to JDBC exceptions, Spring can also wrap JPA- and Hibernate-specific exceptions,
|
||||
converting them to a set of focused runtime exceptions. This lets you handle most
|
||||
non-recoverable persistence exceptions in only the appropriate layers, without having
|
||||
annoying boilerplate catch-and-throw blocks and exception declarations in your DAOs.
|
||||
(You can still trap and handle exceptions anywhere you need to though.) As mentioned above,
|
||||
JDBC exceptions (including database-specific dialects) are also converted to the same
|
||||
hierarchy, meaning that you can perform some operations with JDBC within a consistent
|
||||
programming model.
|
||||
|
||||
The preceding discussion holds true for the various template classes in Spring's support
|
||||
for various ORM frameworks. If you use the interceptor-based classes, the application must
|
||||
care about handling `HibernateExceptions` and `PersistenceExceptions` itself, preferably by
|
||||
delegating to the `convertHibernateAccessException(..)` or `convertJpaAccessException(..)`
|
||||
methods, respectively, of `SessionFactoryUtils`. These methods convert the exceptions
|
||||
to exceptions that are compatible with the exceptions in the `org.springframework.dao`
|
||||
exception hierarchy. As `PersistenceExceptions` are unchecked, they can get thrown, too
|
||||
(sacrificing generic DAO abstraction in terms of exceptions, though).
|
||||
|
||||
The following image shows the exception hierarchy that Spring provides.
|
||||
(Note that the class hierarchy detailed in the image shows only a subset of the entire
|
||||
`DataAccessException` hierarchy.)
|
||||
|
||||
image::DataAccessException.png[]
|
||||
|
||||
|
||||
|
||||
[[dao-annotations]]
|
||||
== Annotations Used to Configure DAO or Repository Classes
|
||||
|
||||
The best way to guarantee that your Data Access Objects (DAOs) or repositories provide
|
||||
exception translation is to use the `@Repository` annotation. This annotation also
|
||||
lets the component scanning support find and configure your DAOs and repositories
|
||||
without having to provide XML configuration entries for them. The following example shows
|
||||
how to use the `@Repository` annotation:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@Repository // <1>
|
||||
public class SomeMovieFinder implements MovieFinder {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
<1> The `@Repository` annotation.
|
||||
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Repository // <1>
|
||||
class SomeMovieFinder : MovieFinder {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
<1> The `@Repository` annotation.
|
||||
|
||||
|
||||
Any DAO or repository implementation needs access to a persistence resource,
|
||||
depending on the persistence technology used. For example, a JDBC-based repository
|
||||
needs access to a JDBC `DataSource`, and a JPA-based repository needs access to an
|
||||
`EntityManager`. The easiest way to accomplish this is to have this resource dependency
|
||||
injected by using one of the `@Autowired`, `@Inject`, `@Resource` or `@PersistenceContext`
|
||||
annotations. The following example works for a JPA repository:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@Repository
|
||||
public class JpaMovieFinder implements MovieFinder {
|
||||
|
||||
@PersistenceContext
|
||||
private EntityManager entityManager;
|
||||
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Repository
|
||||
class JpaMovieFinder : MovieFinder {
|
||||
|
||||
@PersistenceContext
|
||||
private lateinit var entityManager: EntityManager
|
||||
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
|
||||
If you use the classic Hibernate APIs, you can inject `SessionFactory`, as the following
|
||||
example shows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@Repository
|
||||
public class HibernateMovieFinder implements MovieFinder {
|
||||
|
||||
private SessionFactory sessionFactory;
|
||||
|
||||
@Autowired
|
||||
public void setSessionFactory(SessionFactory sessionFactory) {
|
||||
this.sessionFactory = sessionFactory;
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Repository
|
||||
class HibernateMovieFinder(private val sessionFactory: SessionFactory) : MovieFinder {
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
The last example we show here is for typical JDBC support. You could have the
|
||||
`DataSource` injected into an initialization method or a constructor, where you would create a
|
||||
`JdbcTemplate` and other data access support classes (such as `SimpleJdbcCall` and others) by using
|
||||
this `DataSource`. The following example autowires a `DataSource`:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@Repository
|
||||
public class JdbcMovieFinder implements MovieFinder {
|
||||
|
||||
private JdbcTemplate jdbcTemplate;
|
||||
|
||||
@Autowired
|
||||
public void init(DataSource dataSource) {
|
||||
this.jdbcTemplate = new JdbcTemplate(dataSource);
|
||||
}
|
||||
|
||||
// ...
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Repository
|
||||
class JdbcMovieFinder(dataSource: DataSource) : MovieFinder {
|
||||
|
||||
private val jdbcTemplate = JdbcTemplate(dataSource)
|
||||
|
||||
// ...
|
||||
}
|
||||
----
|
||||
|
||||
NOTE: See the specific coverage of each persistence technology for details on how to
|
||||
configure the application context to take advantage of these annotations.
|
||||
|
||||
|
||||
|
||||
|
||||
58
framework-docs/modules/ROOT/pages/data-access/jdbc.adoc
Normal file
58
framework-docs/modules/ROOT/pages/data-access/jdbc.adoc
Normal file
@@ -0,0 +1,58 @@
|
||||
[[jdbc]]
|
||||
= Data Access with JDBC
|
||||
|
||||
The value provided by the Spring Framework JDBC abstraction is perhaps best shown by
|
||||
the sequence of actions outlined in the following table below. The table shows which actions Spring
|
||||
takes care of and which actions are your responsibility.
|
||||
|
||||
[[jdbc-who-does-what]]
|
||||
.Spring JDBC - who does what?
|
||||
|===
|
||||
| Action| Spring| You
|
||||
|
||||
| Define connection parameters.
|
||||
|
|
||||
| X
|
||||
|
||||
| Open the connection.
|
||||
| X
|
||||
|
|
||||
|
||||
| Specify the SQL statement.
|
||||
|
|
||||
| X
|
||||
|
||||
| Declare parameters and provide parameter values
|
||||
|
|
||||
| X
|
||||
|
||||
| Prepare and run the statement.
|
||||
| X
|
||||
|
|
||||
|
||||
| Set up the loop to iterate through the results (if any).
|
||||
| X
|
||||
|
|
||||
|
||||
| Do the work for each iteration.
|
||||
|
|
||||
| X
|
||||
|
||||
| Process any exception.
|
||||
| X
|
||||
|
|
||||
|
||||
| Handle transactions.
|
||||
| X
|
||||
|
|
||||
|
||||
| Close the connection, the statement, and the resultset.
|
||||
| X
|
||||
|
|
||||
|===
|
||||
|
||||
The Spring Framework takes care of all the low-level details that can make JDBC such a
|
||||
tedious API.
|
||||
|
||||
|
||||
|
||||
284
framework-docs/modules/ROOT/pages/data-access/jdbc/advanced.adoc
Normal file
284
framework-docs/modules/ROOT/pages/data-access/jdbc/advanced.adoc
Normal file
@@ -0,0 +1,284 @@
|
||||
[[jdbc-advanced-jdbc]]
|
||||
= JDBC Batch Operations
|
||||
|
||||
Most JDBC drivers provide improved performance if you batch multiple calls to the same
|
||||
prepared statement. By grouping updates into batches, you limit the number of round trips
|
||||
to the database.
|
||||
|
||||
|
||||
[[jdbc-batch-classic]]
|
||||
== Basic Batch Operations with `JdbcTemplate`
|
||||
|
||||
You accomplish `JdbcTemplate` batch processing by implementing two methods of a special
|
||||
interface, `BatchPreparedStatementSetter`, and passing that implementation in as the second parameter
|
||||
in your `batchUpdate` method call. You can use the `getBatchSize` method to provide the size of
|
||||
the current batch. You can use the `setValues` method to set the values for the parameters of
|
||||
the prepared statement. This method is called the number of times that you
|
||||
specified in the `getBatchSize` call. The following example updates the `t_actor` table
|
||||
based on entries in a list, and the entire list is used as the batch:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
public class JdbcActorDao implements ActorDao {
|
||||
|
||||
private JdbcTemplate jdbcTemplate;
|
||||
|
||||
public void setDataSource(DataSource dataSource) {
|
||||
this.jdbcTemplate = new JdbcTemplate(dataSource);
|
||||
}
|
||||
|
||||
public int[] batchUpdate(final List<Actor> actors) {
|
||||
return this.jdbcTemplate.batchUpdate(
|
||||
"update t_actor set first_name = ?, last_name = ? where id = ?",
|
||||
new BatchPreparedStatementSetter() {
|
||||
public void setValues(PreparedStatement ps, int i) throws SQLException {
|
||||
Actor actor = actors.get(i);
|
||||
ps.setString(1, actor.getFirstName());
|
||||
ps.setString(2, actor.getLastName());
|
||||
ps.setLong(3, actor.getId().longValue());
|
||||
}
|
||||
public int getBatchSize() {
|
||||
return actors.size();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
// ... additional methods
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
class JdbcActorDao(dataSource: DataSource) : ActorDao {
|
||||
|
||||
private val jdbcTemplate = JdbcTemplate(dataSource)
|
||||
|
||||
fun batchUpdate(actors: List<Actor>): IntArray {
|
||||
return jdbcTemplate.batchUpdate(
|
||||
"update t_actor set first_name = ?, last_name = ? where id = ?",
|
||||
object: BatchPreparedStatementSetter {
|
||||
override fun setValues(ps: PreparedStatement, i: Int) {
|
||||
ps.setString(1, actors[i].firstName)
|
||||
ps.setString(2, actors[i].lastName)
|
||||
ps.setLong(3, actors[i].id)
|
||||
}
|
||||
|
||||
override fun getBatchSize() = actors.size
|
||||
})
|
||||
}
|
||||
|
||||
// ... additional methods
|
||||
}
|
||||
----
|
||||
|
||||
If you process a stream of updates or reading from a file, you might have a
|
||||
preferred batch size, but the last batch might not have that number of entries. In this
|
||||
case, you can use the `InterruptibleBatchPreparedStatementSetter` interface, which lets
|
||||
you interrupt a batch once the input source is exhausted. The `isBatchExhausted` method
|
||||
lets you signal the end of the batch.
|
||||
|
||||
|
||||
[[jdbc-batch-list]]
|
||||
== Batch Operations with a List of Objects
|
||||
|
||||
Both the `JdbcTemplate` and the `NamedParameterJdbcTemplate` provides an alternate way
|
||||
of providing the batch update. Instead of implementing a special batch interface, you
|
||||
provide all parameter values in the call as a list. The framework loops over these
|
||||
values and uses an internal prepared statement setter. The API varies, depending on
|
||||
whether you use named parameters. For the named parameters, you provide an array of
|
||||
`SqlParameterSource`, one entry for each member of the batch. You can use the
|
||||
`SqlParameterSourceUtils.createBatch` convenience methods to create this array, passing
|
||||
in an array of bean-style objects (with getter methods corresponding to parameters),
|
||||
`String`-keyed `Map` instances (containing the corresponding parameters as values), or a mix of both.
|
||||
|
||||
The following example shows a batch update using named parameters:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
public class JdbcActorDao implements ActorDao {
|
||||
|
||||
private NamedParameterTemplate namedParameterJdbcTemplate;
|
||||
|
||||
public void setDataSource(DataSource dataSource) {
|
||||
this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(dataSource);
|
||||
}
|
||||
|
||||
public int[] batchUpdate(List<Actor> actors) {
|
||||
return this.namedParameterJdbcTemplate.batchUpdate(
|
||||
"update t_actor set first_name = :firstName, last_name = :lastName where id = :id",
|
||||
SqlParameterSourceUtils.createBatch(actors));
|
||||
}
|
||||
|
||||
// ... additional methods
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
class JdbcActorDao(dataSource: DataSource) : ActorDao {
|
||||
|
||||
private val namedParameterJdbcTemplate = NamedParameterJdbcTemplate(dataSource)
|
||||
|
||||
fun batchUpdate(actors: List<Actor>): IntArray {
|
||||
return this.namedParameterJdbcTemplate.batchUpdate(
|
||||
"update t_actor set first_name = :firstName, last_name = :lastName where id = :id",
|
||||
SqlParameterSourceUtils.createBatch(actors));
|
||||
}
|
||||
|
||||
// ... additional methods
|
||||
}
|
||||
----
|
||||
|
||||
For an SQL statement that uses the classic `?` placeholders, you pass in a list
|
||||
containing an object array with the update values. This object array must have one entry
|
||||
for each placeholder in the SQL statement, and they must be in the same order as they are
|
||||
defined in the SQL statement.
|
||||
|
||||
The following example is the same as the preceding example, except that it uses classic
|
||||
JDBC `?` placeholders:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
public class JdbcActorDao implements ActorDao {
|
||||
|
||||
private JdbcTemplate jdbcTemplate;
|
||||
|
||||
public void setDataSource(DataSource dataSource) {
|
||||
this.jdbcTemplate = new JdbcTemplate(dataSource);
|
||||
}
|
||||
|
||||
public int[] batchUpdate(final List<Actor> actors) {
|
||||
List<Object[]> batch = new ArrayList<>();
|
||||
for (Actor actor : actors) {
|
||||
Object[] values = new Object[] {
|
||||
actor.getFirstName(), actor.getLastName(), actor.getId()};
|
||||
batch.add(values);
|
||||
}
|
||||
return this.jdbcTemplate.batchUpdate(
|
||||
"update t_actor set first_name = ?, last_name = ? where id = ?",
|
||||
batch);
|
||||
}
|
||||
|
||||
// ... additional methods
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
class JdbcActorDao(dataSource: DataSource) : ActorDao {
|
||||
|
||||
private val jdbcTemplate = JdbcTemplate(dataSource)
|
||||
|
||||
fun batchUpdate(actors: List<Actor>): IntArray {
|
||||
val batch = mutableListOf<Array<Any>>()
|
||||
for (actor in actors) {
|
||||
batch.add(arrayOf(actor.firstName, actor.lastName, actor.id))
|
||||
}
|
||||
return jdbcTemplate.batchUpdate(
|
||||
"update t_actor set first_name = ?, last_name = ? where id = ?", batch)
|
||||
}
|
||||
|
||||
// ... additional methods
|
||||
}
|
||||
----
|
||||
|
||||
All of the batch update methods that we described earlier return an `int` array
|
||||
containing the number of affected rows for each batch entry. This count is reported by
|
||||
the JDBC driver. If the count is not available, the JDBC driver returns a value of `-2`.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
In such a scenario, with automatic setting of values on an underlying `PreparedStatement`,
|
||||
the corresponding JDBC type for each value needs to be derived from the given Java type.
|
||||
While this usually works well, there is a potential for issues (for example, with Map-contained
|
||||
`null` values). Spring, by default, calls `ParameterMetaData.getParameterType` in such a
|
||||
case, which can be expensive with your JDBC driver. You should use a recent driver
|
||||
version and consider setting the `spring.jdbc.getParameterType.ignore` property to `true`
|
||||
(as a JVM system property or via the
|
||||
<<appendix.adoc#appendix-spring-properties,`SpringProperties`>> mechanism) if you encounter
|
||||
a performance issue (as reported on Oracle 12c, JBoss, and PostgreSQL).
|
||||
|
||||
Alternatively, you might consider specifying the corresponding JDBC types explicitly,
|
||||
either through a `BatchPreparedStatementSetter` (as shown earlier), through an explicit type
|
||||
array given to a `List<Object[]>` based call, through `registerSqlType` calls on a
|
||||
custom `MapSqlParameterSource` instance, or through a `BeanPropertySqlParameterSource`
|
||||
that derives the SQL type from the Java-declared property type even for a null value.
|
||||
====
|
||||
|
||||
|
||||
[[jdbc-batch-multi]]
|
||||
== Batch Operations with Multiple Batches
|
||||
|
||||
The preceding example of a batch update deals with batches that are so large that you want to
|
||||
break them up into several smaller batches. You can do this with the methods
|
||||
mentioned earlier by making multiple calls to the `batchUpdate` method, but there is now a
|
||||
more convenient method. This method takes, in addition to the SQL statement, a
|
||||
`Collection` of objects that contain the parameters, the number of updates to make for each
|
||||
batch, and a `ParameterizedPreparedStatementSetter` to set the values for the parameters
|
||||
of the prepared statement. The framework loops over the provided values and breaks the
|
||||
update calls into batches of the size specified.
|
||||
|
||||
The following example shows a batch update that uses a batch size of 100:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
public class JdbcActorDao implements ActorDao {
|
||||
|
||||
private JdbcTemplate jdbcTemplate;
|
||||
|
||||
public void setDataSource(DataSource dataSource) {
|
||||
this.jdbcTemplate = new JdbcTemplate(dataSource);
|
||||
}
|
||||
|
||||
public int[][] batchUpdate(final Collection<Actor> actors) {
|
||||
int[][] updateCounts = jdbcTemplate.batchUpdate(
|
||||
"update t_actor set first_name = ?, last_name = ? where id = ?",
|
||||
actors,
|
||||
100,
|
||||
(PreparedStatement ps, Actor actor) -> {
|
||||
ps.setString(1, actor.getFirstName());
|
||||
ps.setString(2, actor.getLastName());
|
||||
ps.setLong(3, actor.getId().longValue());
|
||||
});
|
||||
return updateCounts;
|
||||
}
|
||||
|
||||
// ... additional methods
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
class JdbcActorDao(dataSource: DataSource) : ActorDao {
|
||||
|
||||
private val jdbcTemplate = JdbcTemplate(dataSource)
|
||||
|
||||
fun batchUpdate(actors: List<Actor>): Array<IntArray> {
|
||||
return jdbcTemplate.batchUpdate(
|
||||
"update t_actor set first_name = ?, last_name = ? where id = ?",
|
||||
actors, 100) { ps, argument ->
|
||||
ps.setString(1, argument.firstName)
|
||||
ps.setString(2, argument.lastName)
|
||||
ps.setLong(3, argument.id)
|
||||
}
|
||||
}
|
||||
|
||||
// ... additional methods
|
||||
}
|
||||
----
|
||||
|
||||
The batch update method for this call returns an array of `int` arrays that contains an
|
||||
array entry for each batch with an array of the number of affected rows for each update.
|
||||
The top-level array's length indicates the number of batches run, and the second level
|
||||
array's length indicates the number of updates in that batch. The number of updates in
|
||||
each batch should be the batch size provided for all batches (except that the last one
|
||||
that might be less), depending on the total number of update objects provided. The update
|
||||
count for each update statement is the one reported by the JDBC driver. If the count is
|
||||
not available, the JDBC driver returns a value of `-2`.
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
[[jdbc-choose-style]]
|
||||
= Choosing an Approach for JDBC Database Access
|
||||
|
||||
You can choose among several approaches to form the basis for your JDBC database access.
|
||||
In addition to three flavors of `JdbcTemplate`, a new `SimpleJdbcInsert` and
|
||||
`SimpleJdbcCall` approach optimizes database metadata, and the RDBMS Object style takes a
|
||||
more object-oriented approach similar to that of JDO Query design. Once you start using
|
||||
one of these approaches, you can still mix and match to include a feature from a
|
||||
different approach. All approaches require a JDBC 2.0-compliant driver, and some
|
||||
advanced features require a JDBC 3.0 driver.
|
||||
|
||||
* `JdbcTemplate` is the classic and most popular Spring JDBC approach. This
|
||||
"`lowest-level`" approach and all others use a JdbcTemplate under the covers.
|
||||
* `NamedParameterJdbcTemplate` wraps a `JdbcTemplate` to provide named parameters
|
||||
instead of the traditional JDBC `?` placeholders. This approach provides better
|
||||
documentation and ease of use when you have multiple parameters for an SQL statement.
|
||||
* `SimpleJdbcInsert` and `SimpleJdbcCall` optimize database metadata to limit the amount
|
||||
of necessary configuration. This approach simplifies coding so that you need to
|
||||
provide only the name of the table or procedure and provide a map of parameters matching
|
||||
the column names. This works only if the database provides adequate metadata. If the
|
||||
database does not provide this metadata, you have to provide explicit
|
||||
configuration of the parameters.
|
||||
* RDBMS objects — including `MappingSqlQuery`, `SqlUpdate`, and `StoredProcedure` —
|
||||
require you to create reusable and thread-safe objects during initialization of your
|
||||
data-access layer. This approach is modeled after JDO Query, wherein you define your
|
||||
query string, declare parameters, and compile the query. Once you do that,
|
||||
`execute(...)`, `update(...)`, and `findObject(...)` methods can be called multiple
|
||||
times with various parameter values.
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,223 @@
|
||||
[[jdbc-connections]]
|
||||
= Controlling Database Connections
|
||||
|
||||
This section covers:
|
||||
|
||||
* <<jdbc-datasource>>
|
||||
* <<jdbc-DataSourceUtils>>
|
||||
* <<jdbc-SmartDataSource>>
|
||||
* <<jdbc-AbstractDataSource>>
|
||||
* <<jdbc-SingleConnectionDataSource>>
|
||||
* <<jdbc-DriverManagerDataSource>>
|
||||
* <<jdbc-TransactionAwareDataSourceProxy>>
|
||||
* <<jdbc-DataSourceTransactionManager>>
|
||||
|
||||
|
||||
[[jdbc-datasource]]
|
||||
== Using `DataSource`
|
||||
|
||||
Spring obtains a connection to the database through a `DataSource`. A `DataSource` is
|
||||
part of the JDBC specification and is a generalized connection factory. It lets a
|
||||
container or a framework hide connection pooling and transaction management issues
|
||||
from the application code. As a developer, you need not know details about how to
|
||||
connect to the database. That is the responsibility of the administrator who sets up
|
||||
the datasource. You most likely fill both roles as you develop and test code, but you
|
||||
do not necessarily have to know how the production data source is configured.
|
||||
|
||||
When you use Spring's JDBC layer, you can obtain a data source from JNDI, or you can
|
||||
configure your own with a connection pool implementation provided by a third party.
|
||||
Traditional choices are Apache Commons DBCP and C3P0 with bean-style `DataSource` classes;
|
||||
for a modern JDBC connection pool, consider HikariCP with its builder-style API instead.
|
||||
|
||||
NOTE: You should use the `DriverManagerDataSource` and `SimpleDriverDataSource` classes
|
||||
(as included in the Spring distribution) only for testing purposes! Those variants do not
|
||||
provide pooling and perform poorly when multiple requests for a connection are made.
|
||||
|
||||
The following section uses Spring's `DriverManagerDataSource` implementation.
|
||||
Several other `DataSource` variants are covered later.
|
||||
|
||||
To configure a `DriverManagerDataSource`:
|
||||
|
||||
. Obtain a connection with `DriverManagerDataSource` as you typically obtain a JDBC
|
||||
connection.
|
||||
. Specify the fully qualified class name of the JDBC driver so that the `DriverManager`
|
||||
can load the driver class.
|
||||
. Provide a URL that varies between JDBC drivers. (See the documentation for your driver
|
||||
for the correct value.)
|
||||
. Provide a username and a password to connect to the database.
|
||||
|
||||
The following example shows how to configure a `DriverManagerDataSource` in Java:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
DriverManagerDataSource dataSource = new DriverManagerDataSource();
|
||||
dataSource.setDriverClassName("org.hsqldb.jdbcDriver");
|
||||
dataSource.setUrl("jdbc:hsqldb:hsql://localhost:");
|
||||
dataSource.setUsername("sa");
|
||||
dataSource.setPassword("");
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val dataSource = DriverManagerDataSource().apply {
|
||||
setDriverClassName("org.hsqldb.jdbcDriver")
|
||||
url = "jdbc:hsqldb:hsql://localhost:"
|
||||
username = "sa"
|
||||
password = ""
|
||||
}
|
||||
----
|
||||
|
||||
The following example shows the corresponding XML configuration:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<bean id="dataSource" class="org.springframework.jdbc.datasource.DriverManagerDataSource">
|
||||
<property name="driverClassName" value="${jdbc.driverClassName}"/>
|
||||
<property name="url" value="${jdbc.url}"/>
|
||||
<property name="username" value="${jdbc.username}"/>
|
||||
<property name="password" value="${jdbc.password}"/>
|
||||
</bean>
|
||||
|
||||
<context:property-placeholder location="jdbc.properties"/>
|
||||
----
|
||||
|
||||
The next two examples show the basic connectivity and configuration for DBCP and C3P0.
|
||||
To learn about more options that help control the pooling features, see the product
|
||||
documentation for the respective connection pooling implementations.
|
||||
|
||||
The following example shows DBCP configuration:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
|
||||
<property name="driverClassName" value="${jdbc.driverClassName}"/>
|
||||
<property name="url" value="${jdbc.url}"/>
|
||||
<property name="username" value="${jdbc.username}"/>
|
||||
<property name="password" value="${jdbc.password}"/>
|
||||
</bean>
|
||||
|
||||
<context:property-placeholder location="jdbc.properties"/>
|
||||
----
|
||||
|
||||
The following example shows C3P0 configuration:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<bean id="dataSource" class="com.mchange.v2.c3p0.ComboPooledDataSource" destroy-method="close">
|
||||
<property name="driverClass" value="${jdbc.driverClassName}"/>
|
||||
<property name="jdbcUrl" value="${jdbc.url}"/>
|
||||
<property name="user" value="${jdbc.username}"/>
|
||||
<property name="password" value="${jdbc.password}"/>
|
||||
</bean>
|
||||
|
||||
<context:property-placeholder location="jdbc.properties"/>
|
||||
----
|
||||
|
||||
|
||||
[[jdbc-DataSourceUtils]]
|
||||
== Using `DataSourceUtils`
|
||||
|
||||
The `DataSourceUtils` class is a convenient and powerful helper class that provides
|
||||
`static` methods to obtain connections from JNDI and close connections if necessary. It
|
||||
supports thread-bound connections with, for example, `DataSourceTransactionManager`.
|
||||
|
||||
|
||||
[[jdbc-SmartDataSource]]
|
||||
== Implementing `SmartDataSource`
|
||||
|
||||
The `SmartDataSource` interface should be implemented by classes that can provide a
|
||||
connection to a relational database. It extends the `DataSource` interface to let
|
||||
classes that use it query whether the connection should be closed after a given
|
||||
operation. This usage is efficient when you know that you need to reuse a connection.
|
||||
|
||||
|
||||
[[jdbc-AbstractDataSource]]
|
||||
== Extending `AbstractDataSource`
|
||||
|
||||
`AbstractDataSource` is an `abstract` base class for Spring's `DataSource`
|
||||
implementations. It implements code that is common to all `DataSource` implementations.
|
||||
You should extend the `AbstractDataSource` class if you write your own `DataSource`
|
||||
implementation.
|
||||
|
||||
|
||||
[[jdbc-SingleConnectionDataSource]]
|
||||
== Using `SingleConnectionDataSource`
|
||||
|
||||
The `SingleConnectionDataSource` class is an implementation of the `SmartDataSource`
|
||||
interface that wraps a single `Connection` that is not closed after each use.
|
||||
This is not multi-threading capable.
|
||||
|
||||
If any client code calls `close` on the assumption of a pooled connection (as when using
|
||||
persistence tools), you should set the `suppressClose` property to `true`. This setting
|
||||
returns a close-suppressing proxy that wraps the physical connection. Note that you can
|
||||
no longer cast this to a native Oracle `Connection` or a similar object.
|
||||
|
||||
`SingleConnectionDataSource` is primarily a test class. It typically enables easy testing
|
||||
of code outside an application server, in conjunction with a simple JNDI environment.
|
||||
In contrast to `DriverManagerDataSource`, it reuses the same connection all the time,
|
||||
avoiding excessive creation of physical connections.
|
||||
|
||||
|
||||
|
||||
[[jdbc-DriverManagerDataSource]]
|
||||
== Using `DriverManagerDataSource`
|
||||
|
||||
The `DriverManagerDataSource` class is an implementation of the standard `DataSource`
|
||||
interface that configures a plain JDBC driver through bean properties and returns a new
|
||||
`Connection` every time.
|
||||
|
||||
This implementation is useful for test and stand-alone environments outside of a Jakarta EE
|
||||
container, either as a `DataSource` bean in a Spring IoC container or in conjunction
|
||||
with a simple JNDI environment. Pool-assuming `Connection.close()` calls
|
||||
close the connection, so any `DataSource`-aware persistence code should work. However,
|
||||
using JavaBean-style connection pools (such as `commons-dbcp`) is so easy, even in a test
|
||||
environment, that it is almost always preferable to use such a connection pool over
|
||||
`DriverManagerDataSource`.
|
||||
|
||||
|
||||
[[jdbc-TransactionAwareDataSourceProxy]]
|
||||
== Using `TransactionAwareDataSourceProxy`
|
||||
|
||||
`TransactionAwareDataSourceProxy` is a proxy for a target `DataSource`. The proxy wraps that
|
||||
target `DataSource` to add awareness of Spring-managed transactions. In this respect, it
|
||||
is similar to a transactional JNDI `DataSource`, as provided by a Jakarta EE server.
|
||||
|
||||
NOTE: It is rarely desirable to use this class, except when already existing code must be
|
||||
called and passed a standard JDBC `DataSource` interface implementation. In this case,
|
||||
you can still have this code be usable and, at the same time, have this code
|
||||
participating in Spring managed transactions. It is generally preferable to write your
|
||||
own new code by using the higher level abstractions for resource management, such as
|
||||
`JdbcTemplate` or `DataSourceUtils`.
|
||||
|
||||
See the {api-spring-framework}/jdbc/datasource/TransactionAwareDataSourceProxy.html[`TransactionAwareDataSourceProxy`]
|
||||
javadoc for more details.
|
||||
|
||||
|
||||
[[jdbc-DataSourceTransactionManager]]
|
||||
== Using `DataSourceTransactionManager`
|
||||
|
||||
The `DataSourceTransactionManager` class is a `PlatformTransactionManager`
|
||||
implementation for single JDBC data sources. It binds a JDBC connection from the
|
||||
specified data source to the currently executing thread, potentially allowing for one
|
||||
thread connection per data source.
|
||||
|
||||
Application code is required to retrieve the JDBC connection through
|
||||
`DataSourceUtils.getConnection(DataSource)` instead of Jakarta EE's standard
|
||||
`DataSource.getConnection`. It throws unchecked `org.springframework.dao` exceptions
|
||||
instead of checked `SQLExceptions`. All framework classes (such as `JdbcTemplate`) use this
|
||||
strategy implicitly. If not used with this transaction manager, the lookup strategy
|
||||
behaves exactly like the common one. Thus, it can be used in any case.
|
||||
|
||||
The `DataSourceTransactionManager` class supports custom isolation levels and timeouts
|
||||
that get applied as appropriate JDBC statement query timeouts. To support the latter,
|
||||
application code must either use `JdbcTemplate` or call the
|
||||
`DataSourceUtils.applyTransactionTimeout(..)` method for each created statement.
|
||||
|
||||
You can use this implementation instead of `JtaTransactionManager` in the single-resource
|
||||
case, as it does not require the container to support JTA. Switching between
|
||||
both is just a matter of configuration, provided you stick to the required connection lookup
|
||||
pattern. JTA does not support custom isolation levels.
|
||||
|
||||
|
||||
|
||||
955
framework-docs/modules/ROOT/pages/data-access/jdbc/core.adoc
Normal file
955
framework-docs/modules/ROOT/pages/data-access/jdbc/core.adoc
Normal file
@@ -0,0 +1,955 @@
|
||||
[[jdbc-core]]
|
||||
= Using the JDBC Core Classes to Control Basic JDBC Processing and Error Handling
|
||||
|
||||
This section covers how to use the JDBC core classes to control basic JDBC processing,
|
||||
including error handling. It includes the following topics:
|
||||
|
||||
* <<jdbc-JdbcTemplate>>
|
||||
* <<jdbc-NamedParameterJdbcTemplate>>
|
||||
* <<jdbc-SQLExceptionTranslator>>
|
||||
* <<jdbc-statements-executing>>
|
||||
* <<jdbc-statements-querying>>
|
||||
* <<jdbc-updates>>
|
||||
* <<jdbc-auto-generated-keys>>
|
||||
|
||||
|
||||
[[jdbc-JdbcTemplate]]
|
||||
== Using `JdbcTemplate`
|
||||
|
||||
`JdbcTemplate` is the central class in the JDBC core package. It handles the
|
||||
creation and release of resources, which helps you avoid common errors, such as
|
||||
forgetting to close the connection. It performs the basic tasks of the core JDBC
|
||||
workflow (such as statement creation and execution), leaving application code to provide
|
||||
SQL and extract results. The `JdbcTemplate` class:
|
||||
|
||||
* Runs SQL queries
|
||||
* Updates statements and stored procedure calls
|
||||
* Performs iteration over `ResultSet` instances and extraction of returned parameter values.
|
||||
* Catches JDBC exceptions and translates them to the generic, more informative, exception
|
||||
hierarchy defined in the `org.springframework.dao` package. (See <<dao-exceptions>>.)
|
||||
|
||||
When you use the `JdbcTemplate` for your code, you need only to implement callback
|
||||
interfaces, giving them a clearly defined contract. Given a `Connection` provided by the
|
||||
`JdbcTemplate` class, the `PreparedStatementCreator` callback interface creates a prepared
|
||||
statement, providing SQL and any necessary parameters. The same is true for the
|
||||
`CallableStatementCreator` interface, which creates callable statements. The
|
||||
`RowCallbackHandler` interface extracts values from each row of a `ResultSet`.
|
||||
|
||||
You can use `JdbcTemplate` within a DAO implementation through direct instantiation
|
||||
with a `DataSource` reference, or you can configure it in a Spring IoC container and give it to
|
||||
DAOs as a bean reference.
|
||||
|
||||
NOTE: The `DataSource` should always be configured as a bean in the Spring IoC container. In
|
||||
the first case the bean is given to the service directly; in the second case it is given
|
||||
to the prepared template.
|
||||
|
||||
All SQL issued by this class is logged at the `DEBUG` level under the category
|
||||
corresponding to the fully qualified class name of the template instance (typically
|
||||
`JdbcTemplate`, but it may be different if you use a custom subclass of the
|
||||
`JdbcTemplate` class).
|
||||
|
||||
The following sections provide some examples of `JdbcTemplate` usage. These examples
|
||||
are not an exhaustive list of all of the functionality exposed by the `JdbcTemplate`.
|
||||
See the attendant {api-spring-framework}/jdbc/core/JdbcTemplate.html[javadoc] for that.
|
||||
|
||||
[[jdbc-JdbcTemplate-examples-query]]
|
||||
=== Querying (`SELECT`)
|
||||
|
||||
The following query gets the number of rows in a relation:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
int rowCount = this.jdbcTemplate.queryForObject("select count(*) from t_actor", Integer.class);
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val rowCount = jdbcTemplate.queryForObject<Int>("select count(*) from t_actor")!!
|
||||
----
|
||||
|
||||
The following query uses a bind variable:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
int countOfActorsNamedJoe = this.jdbcTemplate.queryForObject(
|
||||
"select count(*) from t_actor where first_name = ?", Integer.class, "Joe");
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val countOfActorsNamedJoe = jdbcTemplate.queryForObject<Int>(
|
||||
"select count(*) from t_actor where first_name = ?", arrayOf("Joe"))!!
|
||||
----
|
||||
|
||||
|
||||
The following query looks for a `String`:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
String lastName = this.jdbcTemplate.queryForObject(
|
||||
"select last_name from t_actor where id = ?",
|
||||
String.class, 1212L);
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val lastName = this.jdbcTemplate.queryForObject<String>(
|
||||
"select last_name from t_actor where id = ?",
|
||||
arrayOf(1212L))!!
|
||||
----
|
||||
|
||||
The following query finds and populates a single domain object:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
Actor actor = jdbcTemplate.queryForObject(
|
||||
"select first_name, last_name from t_actor where id = ?",
|
||||
(resultSet, rowNum) -> {
|
||||
Actor newActor = new Actor();
|
||||
newActor.setFirstName(resultSet.getString("first_name"));
|
||||
newActor.setLastName(resultSet.getString("last_name"));
|
||||
return newActor;
|
||||
},
|
||||
1212L);
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val actor = jdbcTemplate.queryForObject(
|
||||
"select first_name, last_name from t_actor where id = ?",
|
||||
arrayOf(1212L)) { rs, _ ->
|
||||
Actor(rs.getString("first_name"), rs.getString("last_name"))
|
||||
}
|
||||
----
|
||||
|
||||
The following query finds and populates a list of domain objects:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
List<Actor> actors = this.jdbcTemplate.query(
|
||||
"select first_name, last_name from t_actor",
|
||||
(resultSet, rowNum) -> {
|
||||
Actor actor = new Actor();
|
||||
actor.setFirstName(resultSet.getString("first_name"));
|
||||
actor.setLastName(resultSet.getString("last_name"));
|
||||
return actor;
|
||||
});
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val actors = jdbcTemplate.query("select first_name, last_name from t_actor") { rs, _ ->
|
||||
Actor(rs.getString("first_name"), rs.getString("last_name"))
|
||||
----
|
||||
|
||||
If the last two snippets of code actually existed in the same application, it would make
|
||||
sense to remove the duplication present in the two `RowMapper` lambda expressions and
|
||||
extract them out into a single field that could then be referenced by DAO methods as needed.
|
||||
For example, it may be better to write the preceding code snippet as follows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
private final RowMapper<Actor> actorRowMapper = (resultSet, rowNum) -> {
|
||||
Actor actor = new Actor();
|
||||
actor.setFirstName(resultSet.getString("first_name"));
|
||||
actor.setLastName(resultSet.getString("last_name"));
|
||||
return actor;
|
||||
};
|
||||
|
||||
public List<Actor> findAllActors() {
|
||||
return this.jdbcTemplate.query("select first_name, last_name from t_actor", actorRowMapper);
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val actorMapper = RowMapper<Actor> { rs: ResultSet, rowNum: Int ->
|
||||
Actor(rs.getString("first_name"), rs.getString("last_name"))
|
||||
}
|
||||
|
||||
fun findAllActors(): List<Actor> {
|
||||
return jdbcTemplate.query("select first_name, last_name from t_actor", actorMapper)
|
||||
}
|
||||
----
|
||||
|
||||
[[jdbc-JdbcTemplate-examples-update]]
|
||||
=== Updating (`INSERT`, `UPDATE`, and `DELETE`) with `JdbcTemplate`
|
||||
|
||||
You can use the `update(..)` method to perform insert, update, and delete operations.
|
||||
Parameter values are usually provided as variable arguments or, alternatively, as an object array.
|
||||
|
||||
The following example inserts a new entry:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
this.jdbcTemplate.update(
|
||||
"insert into t_actor (first_name, last_name) values (?, ?)",
|
||||
"Leonor", "Watling");
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
jdbcTemplate.update(
|
||||
"insert into t_actor (first_name, last_name) values (?, ?)",
|
||||
"Leonor", "Watling")
|
||||
----
|
||||
|
||||
The following example updates an existing entry:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
this.jdbcTemplate.update(
|
||||
"update t_actor set last_name = ? where id = ?",
|
||||
"Banjo", 5276L);
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
jdbcTemplate.update(
|
||||
"update t_actor set last_name = ? where id = ?",
|
||||
"Banjo", 5276L)
|
||||
----
|
||||
|
||||
The following example deletes an entry:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
this.jdbcTemplate.update(
|
||||
"delete from t_actor where id = ?",
|
||||
Long.valueOf(actorId));
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
jdbcTemplate.update("delete from t_actor where id = ?", actorId.toLong())
|
||||
----
|
||||
|
||||
[[jdbc-JdbcTemplate-examples-other]]
|
||||
=== Other `JdbcTemplate` Operations
|
||||
|
||||
You can use the `execute(..)` method to run any arbitrary SQL. Consequently, the
|
||||
method is often used for DDL statements. It is heavily overloaded with variants that take
|
||||
callback interfaces, binding variable arrays, and so on. The following example creates a
|
||||
table:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
this.jdbcTemplate.execute("create table mytable (id integer, name varchar(100))");
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
jdbcTemplate.execute("create table mytable (id integer, name varchar(100))")
|
||||
----
|
||||
|
||||
The following example invokes a stored procedure:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
this.jdbcTemplate.update(
|
||||
"call SUPPORT.REFRESH_ACTORS_SUMMARY(?)",
|
||||
Long.valueOf(unionId));
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
jdbcTemplate.update(
|
||||
"call SUPPORT.REFRESH_ACTORS_SUMMARY(?)",
|
||||
unionId.toLong())
|
||||
----
|
||||
|
||||
|
||||
More sophisticated stored procedure support is <<jdbc-StoredProcedure, covered later>>.
|
||||
|
||||
[[jdbc-JdbcTemplate-idioms]]
|
||||
=== `JdbcTemplate` Best Practices
|
||||
|
||||
Instances of the `JdbcTemplate` class are thread-safe, once configured. This is
|
||||
important because it means that you can configure a single instance of a `JdbcTemplate`
|
||||
and then safely inject this shared reference into multiple DAOs (or repositories).
|
||||
The `JdbcTemplate` is stateful, in that it maintains a reference to a `DataSource`, but
|
||||
this state is not conversational state.
|
||||
|
||||
A common practice when using the `JdbcTemplate` class (and the associated
|
||||
<<jdbc-NamedParameterJdbcTemplate, `NamedParameterJdbcTemplate`>> class) is to
|
||||
configure a `DataSource` in your Spring configuration file and then dependency-inject
|
||||
that shared `DataSource` bean into your DAO classes. The `JdbcTemplate` is created in
|
||||
the setter for the `DataSource`. This leads to DAOs that resemble the following:
|
||||
|
||||
--
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
public class JdbcCorporateEventDao implements CorporateEventDao {
|
||||
|
||||
private JdbcTemplate jdbcTemplate;
|
||||
|
||||
public void setDataSource(DataSource dataSource) {
|
||||
this.jdbcTemplate = new JdbcTemplate(dataSource);
|
||||
}
|
||||
|
||||
// JDBC-backed implementations of the methods on the CorporateEventDao follow...
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
class JdbcCorporateEventDao(dataSource: DataSource) : CorporateEventDao {
|
||||
|
||||
private val jdbcTemplate = JdbcTemplate(dataSource)
|
||||
|
||||
// JDBC-backed implementations of the methods on the CorporateEventDao follow...
|
||||
}
|
||||
----
|
||||
--
|
||||
|
||||
The following example shows the corresponding XML configuration:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:context="http://www.springframework.org/schema/context"
|
||||
xsi:schemaLocation="
|
||||
http://www.springframework.org/schema/beans
|
||||
https://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/context
|
||||
https://www.springframework.org/schema/context/spring-context.xsd">
|
||||
|
||||
<bean id="corporateEventDao" class="com.example.JdbcCorporateEventDao">
|
||||
<property name="dataSource" ref="dataSource"/>
|
||||
</bean>
|
||||
|
||||
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
|
||||
<property name="driverClassName" value="${jdbc.driverClassName}"/>
|
||||
<property name="url" value="${jdbc.url}"/>
|
||||
<property name="username" value="${jdbc.username}"/>
|
||||
<property name="password" value="${jdbc.password}"/>
|
||||
</bean>
|
||||
|
||||
<context:property-placeholder location="jdbc.properties"/>
|
||||
|
||||
</beans>
|
||||
----
|
||||
|
||||
An alternative to explicit configuration is to use component-scanning and annotation
|
||||
support for dependency injection. In this case, you can annotate the class with `@Repository`
|
||||
(which makes it a candidate for component-scanning) and annotate the `DataSource` setter
|
||||
method with `@Autowired`. The following example shows how to do so:
|
||||
|
||||
--
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@Repository // <1>
|
||||
public class JdbcCorporateEventDao implements CorporateEventDao {
|
||||
|
||||
private JdbcTemplate jdbcTemplate;
|
||||
|
||||
@Autowired // <2>
|
||||
public void setDataSource(DataSource dataSource) {
|
||||
this.jdbcTemplate = new JdbcTemplate(dataSource); // <3>
|
||||
}
|
||||
|
||||
// JDBC-backed implementations of the methods on the CorporateEventDao follow...
|
||||
}
|
||||
----
|
||||
<1> Annotate the class with `@Repository`.
|
||||
<2> Annotate the `DataSource` setter method with `@Autowired`.
|
||||
<3> Create a new `JdbcTemplate` with the `DataSource`.
|
||||
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Repository // <1>
|
||||
class JdbcCorporateEventDao(dataSource: DataSource) : CorporateEventDao { // <2>
|
||||
|
||||
private val jdbcTemplate = JdbcTemplate(dataSource) // <3>
|
||||
|
||||
// JDBC-backed implementations of the methods on the CorporateEventDao follow...
|
||||
}
|
||||
----
|
||||
<1> Annotate the class with `@Repository`.
|
||||
<2> Constructor injection of the `DataSource`.
|
||||
<3> Create a new `JdbcTemplate` with the `DataSource`.
|
||||
--
|
||||
|
||||
|
||||
The following example shows the corresponding XML configuration:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:context="http://www.springframework.org/schema/context"
|
||||
xsi:schemaLocation="
|
||||
http://www.springframework.org/schema/beans
|
||||
https://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/context
|
||||
https://www.springframework.org/schema/context/spring-context.xsd">
|
||||
|
||||
<!-- Scans within the base package of the application for @Component classes to configure as beans -->
|
||||
<context:component-scan base-package="org.springframework.docs.test" />
|
||||
|
||||
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
|
||||
<property name="driverClassName" value="${jdbc.driverClassName}"/>
|
||||
<property name="url" value="${jdbc.url}"/>
|
||||
<property name="username" value="${jdbc.username}"/>
|
||||
<property name="password" value="${jdbc.password}"/>
|
||||
</bean>
|
||||
|
||||
<context:property-placeholder location="jdbc.properties"/>
|
||||
|
||||
</beans>
|
||||
----
|
||||
|
||||
If you use Spring's `JdbcDaoSupport` class and your various JDBC-backed DAO classes
|
||||
extend from it, your sub-class inherits a `setDataSource(..)` method from the
|
||||
`JdbcDaoSupport` class. You can choose whether to inherit from this class. The
|
||||
`JdbcDaoSupport` class is provided as a convenience only.
|
||||
|
||||
Regardless of which of the above template initialization styles you choose to use (or
|
||||
not), it is seldom necessary to create a new instance of a `JdbcTemplate` class each
|
||||
time you want to run SQL. Once configured, a `JdbcTemplate` instance is thread-safe.
|
||||
If your application accesses multiple
|
||||
databases, you may want multiple `JdbcTemplate` instances, which requires multiple `DataSources` and, subsequently, multiple differently
|
||||
configured `JdbcTemplate` instances.
|
||||
|
||||
|
||||
[[jdbc-NamedParameterJdbcTemplate]]
|
||||
== Using `NamedParameterJdbcTemplate`
|
||||
|
||||
The `NamedParameterJdbcTemplate` class adds support for programming JDBC statements
|
||||
by using named parameters, as opposed to programming JDBC statements using only classic
|
||||
placeholder ( `'?'`) arguments. The `NamedParameterJdbcTemplate` class wraps a
|
||||
`JdbcTemplate` and delegates to the wrapped `JdbcTemplate` to do much of its work. This
|
||||
section describes only those areas of the `NamedParameterJdbcTemplate` class that differ
|
||||
from the `JdbcTemplate` itself -- namely, programming JDBC statements by using named
|
||||
parameters. The following example shows how to use `NamedParameterJdbcTemplate`:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
// some JDBC-backed DAO class...
|
||||
private NamedParameterJdbcTemplate namedParameterJdbcTemplate;
|
||||
|
||||
public void setDataSource(DataSource dataSource) {
|
||||
this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(dataSource);
|
||||
}
|
||||
|
||||
public int countOfActorsByFirstName(String firstName) {
|
||||
|
||||
String sql = "select count(*) from T_ACTOR where first_name = :first_name";
|
||||
|
||||
SqlParameterSource namedParameters = new MapSqlParameterSource("first_name", firstName);
|
||||
|
||||
return this.namedParameterJdbcTemplate.queryForObject(sql, namedParameters, Integer.class);
|
||||
}
|
||||
----
|
||||
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
private val namedParameterJdbcTemplate = NamedParameterJdbcTemplate(dataSource)
|
||||
|
||||
fun countOfActorsByFirstName(firstName: String): Int {
|
||||
val sql = "select count(*) from T_ACTOR where first_name = :first_name"
|
||||
val namedParameters = MapSqlParameterSource("first_name", firstName)
|
||||
return namedParameterJdbcTemplate.queryForObject(sql, namedParameters, Int::class.java)!!
|
||||
}
|
||||
----
|
||||
|
||||
Notice the use of the named parameter notation in the value assigned to the `sql`
|
||||
variable and the corresponding value that is plugged into the `namedParameters`
|
||||
variable (of type `MapSqlParameterSource`).
|
||||
|
||||
Alternatively, you can pass along named parameters and their corresponding values to a
|
||||
`NamedParameterJdbcTemplate` instance by using the `Map`-based style. The remaining
|
||||
methods exposed by the `NamedParameterJdbcOperations` and implemented by the
|
||||
`NamedParameterJdbcTemplate` class follow a similar pattern and are not covered here.
|
||||
|
||||
The following example shows the use of the `Map`-based style:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
// some JDBC-backed DAO class...
|
||||
private NamedParameterJdbcTemplate namedParameterJdbcTemplate;
|
||||
|
||||
public void setDataSource(DataSource dataSource) {
|
||||
this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(dataSource);
|
||||
}
|
||||
|
||||
public int countOfActorsByFirstName(String firstName) {
|
||||
|
||||
String sql = "select count(*) from T_ACTOR where first_name = :first_name";
|
||||
|
||||
Map<String, String> namedParameters = Collections.singletonMap("first_name", firstName);
|
||||
|
||||
return this.namedParameterJdbcTemplate.queryForObject(sql, namedParameters, Integer.class);
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
// some JDBC-backed DAO class...
|
||||
private val namedParameterJdbcTemplate = NamedParameterJdbcTemplate(dataSource)
|
||||
|
||||
fun countOfActorsByFirstName(firstName: String): Int {
|
||||
val sql = "select count(*) from T_ACTOR where first_name = :first_name"
|
||||
val namedParameters = mapOf("first_name" to firstName)
|
||||
return namedParameterJdbcTemplate.queryForObject(sql, namedParameters, Int::class.java)!!
|
||||
}
|
||||
----
|
||||
|
||||
One nice feature related to the `NamedParameterJdbcTemplate` (and existing in the same
|
||||
Java package) is the `SqlParameterSource` interface. You have already seen an example of
|
||||
an implementation of this interface in one of the previous code snippets (the
|
||||
`MapSqlParameterSource` class). An `SqlParameterSource` is a source of named parameter
|
||||
values to a `NamedParameterJdbcTemplate`. The `MapSqlParameterSource` class is a
|
||||
simple implementation that is an adapter around a `java.util.Map`, where the keys
|
||||
are the parameter names and the values are the parameter values.
|
||||
|
||||
Another `SqlParameterSource` implementation is the `BeanPropertySqlParameterSource`
|
||||
class. This class wraps an arbitrary JavaBean (that is, an instance of a class that
|
||||
adheres to https://www.oracle.com/technetwork/java/javase/documentation/spec-136004.html[the
|
||||
JavaBean conventions]) and uses the properties of the wrapped JavaBean as the source
|
||||
of named parameter values.
|
||||
|
||||
The following example shows a typical JavaBean:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
public class Actor {
|
||||
|
||||
private Long id;
|
||||
private String firstName;
|
||||
private String lastName;
|
||||
|
||||
public String getFirstName() {
|
||||
return this.firstName;
|
||||
}
|
||||
|
||||
public String getLastName() {
|
||||
return this.lastName;
|
||||
}
|
||||
|
||||
public Long getId() {
|
||||
return this.id;
|
||||
}
|
||||
|
||||
// setters omitted...
|
||||
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
data class Actor(val id: Long, val firstName: String, val lastName: String)
|
||||
----
|
||||
|
||||
The following example uses a `NamedParameterJdbcTemplate` to return the count of the
|
||||
members of the class shown in the preceding example:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
// some JDBC-backed DAO class...
|
||||
private NamedParameterJdbcTemplate namedParameterJdbcTemplate;
|
||||
|
||||
public void setDataSource(DataSource dataSource) {
|
||||
this.namedParameterJdbcTemplate = new NamedParameterJdbcTemplate(dataSource);
|
||||
}
|
||||
|
||||
public int countOfActors(Actor exampleActor) {
|
||||
|
||||
// notice how the named parameters match the properties of the above 'Actor' class
|
||||
String sql = "select count(*) from T_ACTOR where first_name = :firstName and last_name = :lastName";
|
||||
|
||||
SqlParameterSource namedParameters = new BeanPropertySqlParameterSource(exampleActor);
|
||||
|
||||
return this.namedParameterJdbcTemplate.queryForObject(sql, namedParameters, Integer.class);
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
// some JDBC-backed DAO class...
|
||||
private val namedParameterJdbcTemplate = NamedParameterJdbcTemplate(dataSource)
|
||||
|
||||
private val namedParameterJdbcTemplate = NamedParameterJdbcTemplate(dataSource)
|
||||
|
||||
fun countOfActors(exampleActor: Actor): Int {
|
||||
// notice how the named parameters match the properties of the above 'Actor' class
|
||||
val sql = "select count(*) from T_ACTOR where first_name = :firstName and last_name = :lastName"
|
||||
val namedParameters = BeanPropertySqlParameterSource(exampleActor)
|
||||
return namedParameterJdbcTemplate.queryForObject(sql, namedParameters, Int::class.java)!!
|
||||
}
|
||||
----
|
||||
|
||||
Remember that the `NamedParameterJdbcTemplate` class wraps a classic `JdbcTemplate`
|
||||
template. If you need access to the wrapped `JdbcTemplate` instance to access
|
||||
functionality that is present only in the `JdbcTemplate` class, you can use the
|
||||
`getJdbcOperations()` method to access the wrapped `JdbcTemplate` through the
|
||||
`JdbcOperations` interface.
|
||||
|
||||
See also <<jdbc-JdbcTemplate-idioms>> for guidelines on using the
|
||||
`NamedParameterJdbcTemplate` class in the context of an application.
|
||||
|
||||
|
||||
[[jdbc-SQLExceptionTranslator]]
|
||||
== Using `SQLExceptionTranslator`
|
||||
|
||||
`SQLExceptionTranslator` is an interface to be implemented by classes that can translate
|
||||
between ``SQLException``s and Spring's own `org.springframework.dao.DataAccessException`,
|
||||
which is agnostic in regard to data access strategy. Implementations can be generic (for
|
||||
example, using SQLState codes for JDBC) or proprietary (for example, using Oracle error
|
||||
codes) for greater precision.
|
||||
|
||||
`SQLErrorCodeSQLExceptionTranslator` is the implementation of `SQLExceptionTranslator`
|
||||
that is used by default. This implementation uses specific vendor codes. It is more
|
||||
precise than the `SQLState` implementation. The error code translations are based on
|
||||
codes held in a JavaBean type class called `SQLErrorCodes`. This class is created and
|
||||
populated by an `SQLErrorCodesFactory`, which (as the name suggests) is a factory for
|
||||
creating `SQLErrorCodes` based on the contents of a configuration file named
|
||||
`sql-error-codes.xml`. This file is populated with vendor codes and based on the
|
||||
`DatabaseProductName` taken from `DatabaseMetaData`. The codes for the actual
|
||||
database you are using are used.
|
||||
|
||||
The `SQLErrorCodeSQLExceptionTranslator` applies matching rules in the following sequence:
|
||||
|
||||
. Any custom translation implemented by a subclass. Normally, the provided concrete
|
||||
`SQLErrorCodeSQLExceptionTranslator` is used, so this rule does not apply. It
|
||||
applies only if you have actually provided a subclass implementation.
|
||||
. Any custom implementation of the `SQLExceptionTranslator` interface that is provided
|
||||
as the `customSqlExceptionTranslator` property of the `SQLErrorCodes` class.
|
||||
. The list of instances of the `CustomSQLErrorCodesTranslation` class (provided for the
|
||||
`customTranslations` property of the `SQLErrorCodes` class) are searched for a match.
|
||||
. Error code matching is applied.
|
||||
. Use the fallback translator. `SQLExceptionSubclassTranslator` is the default fallback
|
||||
translator. If this translation is not available, the next fallback translator is
|
||||
the `SQLStateSQLExceptionTranslator`.
|
||||
|
||||
NOTE: The `SQLErrorCodesFactory` is used by default to define `Error` codes and custom exception
|
||||
translations. They are looked up in a file named `sql-error-codes.xml` from the
|
||||
classpath, and the matching `SQLErrorCodes` instance is located based on the database
|
||||
name from the database metadata of the database in use.
|
||||
|
||||
You can extend `SQLErrorCodeSQLExceptionTranslator`, as the following example shows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
public class CustomSQLErrorCodesTranslator extends SQLErrorCodeSQLExceptionTranslator {
|
||||
|
||||
protected DataAccessException customTranslate(String task, String sql, SQLException sqlEx) {
|
||||
if (sqlEx.getErrorCode() == -12345) {
|
||||
return new DeadlockLoserDataAccessException(task, sqlEx);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
class CustomSQLErrorCodesTranslator : SQLErrorCodeSQLExceptionTranslator() {
|
||||
|
||||
override fun customTranslate(task: String, sql: String?, sqlEx: SQLException): DataAccessException? {
|
||||
if (sqlEx.errorCode == -12345) {
|
||||
return DeadlockLoserDataAccessException(task, sqlEx)
|
||||
}
|
||||
return null
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
In the preceding example, the specific error code (`-12345`) is translated, while other errors are
|
||||
left to be translated by the default translator implementation. To use this custom
|
||||
translator, you must pass it to the `JdbcTemplate` through the method
|
||||
`setExceptionTranslator`, and you must use this `JdbcTemplate` for all of the data access
|
||||
processing where this translator is needed. The following example shows how you can use this custom
|
||||
translator:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
private JdbcTemplate jdbcTemplate;
|
||||
|
||||
public void setDataSource(DataSource dataSource) {
|
||||
|
||||
// create a JdbcTemplate and set data source
|
||||
this.jdbcTemplate = new JdbcTemplate();
|
||||
this.jdbcTemplate.setDataSource(dataSource);
|
||||
|
||||
// create a custom translator and set the DataSource for the default translation lookup
|
||||
CustomSQLErrorCodesTranslator tr = new CustomSQLErrorCodesTranslator();
|
||||
tr.setDataSource(dataSource);
|
||||
this.jdbcTemplate.setExceptionTranslator(tr);
|
||||
|
||||
}
|
||||
|
||||
public void updateShippingCharge(long orderId, long pct) {
|
||||
// use the prepared JdbcTemplate for this update
|
||||
this.jdbcTemplate.update("update orders" +
|
||||
" set shipping_charge = shipping_charge * ? / 100" +
|
||||
" where id = ?", pct, orderId);
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
// create a JdbcTemplate and set data source
|
||||
private val jdbcTemplate = JdbcTemplate(dataSource).apply {
|
||||
// create a custom translator and set the DataSource for the default translation lookup
|
||||
exceptionTranslator = CustomSQLErrorCodesTranslator().apply {
|
||||
this.dataSource = dataSource
|
||||
}
|
||||
}
|
||||
|
||||
fun updateShippingCharge(orderId: Long, pct: Long) {
|
||||
// use the prepared JdbcTemplate for this update
|
||||
this.jdbcTemplate!!.update("update orders" +
|
||||
" set shipping_charge = shipping_charge * ? / 100" +
|
||||
" where id = ?", pct, orderId)
|
||||
}
|
||||
----
|
||||
|
||||
The custom translator is passed a data source in order to look up the error codes in
|
||||
`sql-error-codes.xml`.
|
||||
|
||||
|
||||
[[jdbc-statements-executing]]
|
||||
== Running Statements
|
||||
|
||||
Running an SQL statement requires very little code. You need a `DataSource` and a
|
||||
`JdbcTemplate`, including the convenience methods that are provided with the
|
||||
`JdbcTemplate`. The following example shows what you need to include for a minimal but
|
||||
fully functional class that creates a new table:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
import javax.sql.DataSource;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
|
||||
public class ExecuteAStatement {
|
||||
|
||||
private JdbcTemplate jdbcTemplate;
|
||||
|
||||
public void setDataSource(DataSource dataSource) {
|
||||
this.jdbcTemplate = new JdbcTemplate(dataSource);
|
||||
}
|
||||
|
||||
public void doExecute() {
|
||||
this.jdbcTemplate.execute("create table mytable (id integer, name varchar(100))");
|
||||
}
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
import javax.sql.DataSource
|
||||
import org.springframework.jdbc.core.JdbcTemplate
|
||||
|
||||
class ExecuteAStatement(dataSource: DataSource) {
|
||||
|
||||
private val jdbcTemplate = JdbcTemplate(dataSource)
|
||||
|
||||
fun doExecute() {
|
||||
jdbcTemplate.execute("create table mytable (id integer, name varchar(100))")
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
|
||||
[[jdbc-statements-querying]]
|
||||
== Running Queries
|
||||
|
||||
Some query methods return a single value. To retrieve a count or a specific value from
|
||||
one row, use `queryForObject(..)`. The latter converts the returned JDBC `Type` to the
|
||||
Java class that is passed in as an argument. If the type conversion is invalid, an
|
||||
`InvalidDataAccessApiUsageException` is thrown. The following example contains two
|
||||
query methods, one for an `int` and one that queries for a `String`:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
import javax.sql.DataSource;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
|
||||
public class RunAQuery {
|
||||
|
||||
private JdbcTemplate jdbcTemplate;
|
||||
|
||||
public void setDataSource(DataSource dataSource) {
|
||||
this.jdbcTemplate = new JdbcTemplate(dataSource);
|
||||
}
|
||||
|
||||
public int getCount() {
|
||||
return this.jdbcTemplate.queryForObject("select count(*) from mytable", Integer.class);
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return this.jdbcTemplate.queryForObject("select name from mytable", String.class);
|
||||
}
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
import javax.sql.DataSource
|
||||
import org.springframework.jdbc.core.JdbcTemplate
|
||||
|
||||
class RunAQuery(dataSource: DataSource) {
|
||||
|
||||
private val jdbcTemplate = JdbcTemplate(dataSource)
|
||||
|
||||
val count: Int
|
||||
get() = jdbcTemplate.queryForObject("select count(*) from mytable")!!
|
||||
|
||||
val name: String?
|
||||
get() = jdbcTemplate.queryForObject("select name from mytable")
|
||||
}
|
||||
----
|
||||
|
||||
In addition to the single result query methods, several methods return a list with an
|
||||
entry for each row that the query returned. The most generic method is `queryForList(..)`,
|
||||
which returns a `List` where each element is a `Map` containing one entry for each column,
|
||||
using the column name as the key. If you add a method to the preceding example to retrieve a
|
||||
list of all the rows, it might be as follows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
private JdbcTemplate jdbcTemplate;
|
||||
|
||||
public void setDataSource(DataSource dataSource) {
|
||||
this.jdbcTemplate = new JdbcTemplate(dataSource);
|
||||
}
|
||||
|
||||
public List<Map<String, Object>> getList() {
|
||||
return this.jdbcTemplate.queryForList("select * from mytable");
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
private val jdbcTemplate = JdbcTemplate(dataSource)
|
||||
|
||||
fun getList(): List<Map<String, Any>> {
|
||||
return jdbcTemplate.queryForList("select * from mytable")
|
||||
}
|
||||
----
|
||||
|
||||
The returned list would resemble the following:
|
||||
|
||||
[literal,subs="verbatim,quotes"]
|
||||
----
|
||||
[{name=Bob, id=1}, {name=Mary, id=2}]
|
||||
----
|
||||
|
||||
|
||||
[[jdbc-updates]]
|
||||
== Updating the Database
|
||||
|
||||
The following example updates a column for a certain primary key:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
import javax.sql.DataSource;
|
||||
import org.springframework.jdbc.core.JdbcTemplate;
|
||||
|
||||
public class ExecuteAnUpdate {
|
||||
|
||||
private JdbcTemplate jdbcTemplate;
|
||||
|
||||
public void setDataSource(DataSource dataSource) {
|
||||
this.jdbcTemplate = new JdbcTemplate(dataSource);
|
||||
}
|
||||
|
||||
public void setName(int id, String name) {
|
||||
this.jdbcTemplate.update("update mytable set name = ? where id = ?", name, id);
|
||||
}
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
import javax.sql.DataSource
|
||||
import org.springframework.jdbc.core.JdbcTemplate
|
||||
|
||||
class ExecuteAnUpdate(dataSource: DataSource) {
|
||||
|
||||
private val jdbcTemplate = JdbcTemplate(dataSource)
|
||||
|
||||
fun setName(id: Int, name: String) {
|
||||
jdbcTemplate.update("update mytable set name = ? where id = ?", name, id)
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
In the preceding example,
|
||||
an SQL statement has placeholders for row parameters. You can pass the parameter values
|
||||
in as varargs or, alternatively, as an array of objects. Thus, you should explicitly wrap primitives
|
||||
in the primitive wrapper classes, or you should use auto-boxing.
|
||||
|
||||
|
||||
[[jdbc-auto-generated-keys]]
|
||||
== Retrieving Auto-generated Keys
|
||||
|
||||
An `update()` convenience method supports the retrieval of primary keys generated by the
|
||||
database. This support is part of the JDBC 3.0 standard. See Chapter 13.6 of the
|
||||
specification for details. The method takes a `PreparedStatementCreator` as its first
|
||||
argument, and this is the way the required insert statement is specified. The other
|
||||
argument is a `KeyHolder`, which contains the generated key on successful return from the
|
||||
update. There is no standard single way to create an appropriate `PreparedStatement`
|
||||
(which explains why the method signature is the way it is). The following example works
|
||||
on Oracle but may not work on other platforms:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
final String INSERT_SQL = "insert into my_test (name) values(?)";
|
||||
final String name = "Rob";
|
||||
|
||||
KeyHolder keyHolder = new GeneratedKeyHolder();
|
||||
jdbcTemplate.update(connection -> {
|
||||
PreparedStatement ps = connection.prepareStatement(INSERT_SQL, new String[] { "id" });
|
||||
ps.setString(1, name);
|
||||
return ps;
|
||||
}, keyHolder);
|
||||
|
||||
// keyHolder.getKey() now contains the generated key
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val INSERT_SQL = "insert into my_test (name) values(?)"
|
||||
val name = "Rob"
|
||||
|
||||
val keyHolder = GeneratedKeyHolder()
|
||||
jdbcTemplate.update({
|
||||
it.prepareStatement (INSERT_SQL, arrayOf("id")).apply { setString(1, name) }
|
||||
}, keyHolder)
|
||||
|
||||
// keyHolder.getKey() now contains the generated key
|
||||
----
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,276 @@
|
||||
[[jdbc-embedded-database-support]]
|
||||
= Embedded Database Support
|
||||
|
||||
The `org.springframework.jdbc.datasource.embedded` package provides support for embedded
|
||||
Java database engines. Support for https://www.hsqldb.org[HSQL],
|
||||
https://www.h2database.com[H2], and https://db.apache.org/derby[Derby] is provided
|
||||
natively. You can also use an extensible API to plug in new embedded database types and
|
||||
`DataSource` implementations.
|
||||
|
||||
|
||||
[[jdbc-why-embedded-database]]
|
||||
== Why Use an Embedded Database?
|
||||
|
||||
An embedded database can be useful during the development phase of a project because of its
|
||||
lightweight nature. Benefits include ease of configuration, quick startup time,
|
||||
testability, and the ability to rapidly evolve your SQL during development.
|
||||
|
||||
|
||||
[[jdbc-embedded-database-xml]]
|
||||
== Creating an Embedded Database by Using Spring XML
|
||||
|
||||
If you want to expose an embedded database instance as a bean in a Spring
|
||||
`ApplicationContext`, you can use the `embedded-database` tag in the `spring-jdbc` namespace:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<jdbc:embedded-database id="dataSource" generate-name="true">
|
||||
<jdbc:script location="classpath:schema.sql"/>
|
||||
<jdbc:script location="classpath:test-data.sql"/>
|
||||
</jdbc:embedded-database>
|
||||
----
|
||||
|
||||
The preceding configuration creates an embedded HSQL database that is populated with SQL from
|
||||
the `schema.sql` and `test-data.sql` resources in the root of the classpath. In addition, as
|
||||
a best practice, the embedded database is assigned a uniquely generated name. The
|
||||
embedded database is made available to the Spring container as a bean of type
|
||||
`javax.sql.DataSource` that can then be injected into data access objects as needed.
|
||||
|
||||
|
||||
[[jdbc-embedded-database-java]]
|
||||
== Creating an Embedded Database Programmatically
|
||||
|
||||
The `EmbeddedDatabaseBuilder` class provides a fluent API for constructing an embedded
|
||||
database programmatically. You can use this when you need to create an embedded database in a
|
||||
stand-alone environment or in a stand-alone integration test, as in the following example:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
EmbeddedDatabase db = new EmbeddedDatabaseBuilder()
|
||||
.generateUniqueName(true)
|
||||
.setType(H2)
|
||||
.setScriptEncoding("UTF-8")
|
||||
.ignoreFailedDrops(true)
|
||||
.addScript("schema.sql")
|
||||
.addScripts("user_data.sql", "country_data.sql")
|
||||
.build();
|
||||
|
||||
// perform actions against the db (EmbeddedDatabase extends javax.sql.DataSource)
|
||||
|
||||
db.shutdown()
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val db = EmbeddedDatabaseBuilder()
|
||||
.generateUniqueName(true)
|
||||
.setType(H2)
|
||||
.setScriptEncoding("UTF-8")
|
||||
.ignoreFailedDrops(true)
|
||||
.addScript("schema.sql")
|
||||
.addScripts("user_data.sql", "country_data.sql")
|
||||
.build()
|
||||
|
||||
// perform actions against the db (EmbeddedDatabase extends javax.sql.DataSource)
|
||||
|
||||
db.shutdown()
|
||||
----
|
||||
|
||||
See the {api-spring-framework}/jdbc/datasource/embedded/EmbeddedDatabaseBuilder.html[javadoc for `EmbeddedDatabaseBuilder`]
|
||||
for further details on all supported options.
|
||||
|
||||
You can also use the `EmbeddedDatabaseBuilder` to create an embedded database by using Java
|
||||
configuration, as the following example shows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@Configuration
|
||||
public class DataSourceConfig {
|
||||
|
||||
@Bean
|
||||
public DataSource dataSource() {
|
||||
return new EmbeddedDatabaseBuilder()
|
||||
.generateUniqueName(true)
|
||||
.setType(H2)
|
||||
.setScriptEncoding("UTF-8")
|
||||
.ignoreFailedDrops(true)
|
||||
.addScript("schema.sql")
|
||||
.addScripts("user_data.sql", "country_data.sql")
|
||||
.build();
|
||||
}
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Configuration
|
||||
class DataSourceConfig {
|
||||
|
||||
@Bean
|
||||
fun dataSource(): DataSource {
|
||||
return EmbeddedDatabaseBuilder()
|
||||
.generateUniqueName(true)
|
||||
.setType(H2)
|
||||
.setScriptEncoding("UTF-8")
|
||||
.ignoreFailedDrops(true)
|
||||
.addScript("schema.sql")
|
||||
.addScripts("user_data.sql", "country_data.sql")
|
||||
.build()
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
|
||||
[[jdbc-embedded-database-types]]
|
||||
== Selecting the Embedded Database Type
|
||||
|
||||
This section covers how to select one of the three embedded databases that Spring
|
||||
supports. It includes the following topics:
|
||||
|
||||
* <<jdbc-embedded-database-using-HSQL>>
|
||||
* <<jdbc-embedded-database-using-H2>>
|
||||
* <<jdbc-embedded-database-using-Derby>>
|
||||
|
||||
[[jdbc-embedded-database-using-HSQL]]
|
||||
=== Using HSQL
|
||||
|
||||
Spring supports HSQL 1.8.0 and above. HSQL is the default embedded database if no type is
|
||||
explicitly specified. To specify HSQL explicitly, set the `type` attribute of the
|
||||
`embedded-database` tag to `HSQL`. If you use the builder API, call the
|
||||
`setType(EmbeddedDatabaseType)` method with `EmbeddedDatabaseType.HSQL`.
|
||||
|
||||
[[jdbc-embedded-database-using-H2]]
|
||||
=== Using H2
|
||||
|
||||
Spring supports the H2 database. To enable H2, set the `type` attribute of the
|
||||
`embedded-database` tag to `H2`. If you use the builder API, call the
|
||||
`setType(EmbeddedDatabaseType)` method with `EmbeddedDatabaseType.H2`.
|
||||
|
||||
[[jdbc-embedded-database-using-Derby]]
|
||||
=== Using Derby
|
||||
|
||||
Spring supports Apache Derby 10.5 and above. To enable Derby, set the `type`
|
||||
attribute of the `embedded-database` tag to `DERBY`. If you use the builder API,
|
||||
call the `setType(EmbeddedDatabaseType)` method with `EmbeddedDatabaseType.DERBY`.
|
||||
|
||||
|
||||
[[jdbc-embedded-database-dao-testing]]
|
||||
== Testing Data Access Logic with an Embedded Database
|
||||
|
||||
Embedded databases provide a lightweight way to test data access code. The next example is a
|
||||
data access integration test template that uses an embedded database. Using such a template
|
||||
can be useful for one-offs when the embedded database does not need to be reused across test
|
||||
classes. However, if you wish to create an embedded database that is shared within a test suite,
|
||||
consider using the <<testing.adoc#testcontext-framework, Spring TestContext Framework>> and
|
||||
configuring the embedded database as a bean in the Spring `ApplicationContext` as described
|
||||
in <<jdbc-embedded-database-xml>> and <<jdbc-embedded-database-java>>. The following listing
|
||||
shows the test template:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
public class DataAccessIntegrationTestTemplate {
|
||||
|
||||
private EmbeddedDatabase db;
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
// creates an HSQL in-memory database populated from default scripts
|
||||
// classpath:schema.sql and classpath:data.sql
|
||||
db = new EmbeddedDatabaseBuilder()
|
||||
.generateUniqueName(true)
|
||||
.addDefaultScripts()
|
||||
.build();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testDataAccess() {
|
||||
JdbcTemplate template = new JdbcTemplate(db);
|
||||
template.query( /* ... */ );
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
public void tearDown() {
|
||||
db.shutdown();
|
||||
}
|
||||
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
class DataAccessIntegrationTestTemplate {
|
||||
|
||||
private lateinit var db: EmbeddedDatabase
|
||||
|
||||
@BeforeEach
|
||||
fun setUp() {
|
||||
// creates an HSQL in-memory database populated from default scripts
|
||||
// classpath:schema.sql and classpath:data.sql
|
||||
db = EmbeddedDatabaseBuilder()
|
||||
.generateUniqueName(true)
|
||||
.addDefaultScripts()
|
||||
.build()
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDataAccess() {
|
||||
val template = JdbcTemplate(db)
|
||||
template.query( /* ... */)
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
fun tearDown() {
|
||||
db.shutdown()
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
|
||||
[[jdbc-embedded-database-unique-names]]
|
||||
== Generating Unique Names for Embedded Databases
|
||||
|
||||
Development teams often encounter errors with embedded databases if their test suite
|
||||
inadvertently attempts to recreate additional instances of the same database. This can
|
||||
happen quite easily if an XML configuration file or `@Configuration` class is responsible
|
||||
for creating an embedded database and the corresponding configuration is then reused
|
||||
across multiple testing scenarios within the same test suite (that is, within the same JVM
|
||||
process) -- for example, integration tests against embedded databases whose
|
||||
`ApplicationContext` configuration differs only with regard to which bean definition
|
||||
profiles are active.
|
||||
|
||||
The root cause of such errors is the fact that Spring's `EmbeddedDatabaseFactory` (used
|
||||
internally by both the `<jdbc:embedded-database>` XML namespace element and the
|
||||
`EmbeddedDatabaseBuilder` for Java configuration) sets the name of the embedded database to
|
||||
`testdb` if not otherwise specified. For the case of `<jdbc:embedded-database>`, the
|
||||
embedded database is typically assigned a name equal to the bean's `id` (often,
|
||||
something like `dataSource`). Thus, subsequent attempts to create an embedded database
|
||||
do not result in a new database. Instead, the same JDBC connection URL is reused,
|
||||
and attempts to create a new embedded database actually point to an existing
|
||||
embedded database created from the same configuration.
|
||||
|
||||
To address this common issue, Spring Framework 4.2 provides support for generating
|
||||
unique names for embedded databases. To enable the use of generated names, use one of
|
||||
the following options.
|
||||
|
||||
* `EmbeddedDatabaseFactory.setGenerateUniqueDatabaseName()`
|
||||
* `EmbeddedDatabaseBuilder.generateUniqueName()`
|
||||
* `<jdbc:embedded-database generate-name="true" ... >`
|
||||
|
||||
|
||||
[[jdbc-embedded-database-extension]]
|
||||
== Extending the Embedded Database Support
|
||||
|
||||
You can extend Spring JDBC embedded database support in two ways:
|
||||
|
||||
* Implement `EmbeddedDatabaseConfigurer` to support a new embedded database type.
|
||||
* Implement `DataSourceFactory` to support a new `DataSource` implementation, such as a
|
||||
connection pool to manage embedded database connections.
|
||||
|
||||
We encourage you to contribute extensions to the Spring community at
|
||||
https://github.com/spring-projects/spring-framework/issues[GitHub Issues].
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,146 @@
|
||||
[[jdbc-initializing-datasource]]
|
||||
= Initializing a `DataSource`
|
||||
|
||||
The `org.springframework.jdbc.datasource.init` package provides support for initializing
|
||||
an existing `DataSource`. The embedded database support provides one option for creating
|
||||
and initializing a `DataSource` for an application. However, you may sometimes need to initialize
|
||||
an instance that runs on a server somewhere.
|
||||
|
||||
|
||||
[[jdbc-initializing-datasource-xml]]
|
||||
== Initializing a Database by Using Spring XML
|
||||
|
||||
If you want to initialize a database and you can provide a reference to a `DataSource`
|
||||
bean, you can use the `initialize-database` tag in the `spring-jdbc` namespace:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<jdbc:initialize-database data-source="dataSource">
|
||||
<jdbc:script location="classpath:com/foo/sql/db-schema.sql"/>
|
||||
<jdbc:script location="classpath:com/foo/sql/db-test-data.sql"/>
|
||||
</jdbc:initialize-database>
|
||||
----
|
||||
|
||||
The preceding example runs the two specified scripts against the database. The first
|
||||
script creates a schema, and the second populates tables with a test data set. The script
|
||||
locations can also be patterns with wildcards in the usual Ant style used for resources
|
||||
in Spring (for example,
|
||||
`classpath{asterisk}:/com/foo/{asterisk}{asterisk}/sql/{asterisk}-data.sql`). If you use a
|
||||
pattern, the scripts are run in the lexical order of their URL or filename.
|
||||
|
||||
The default behavior of the database initializer is to unconditionally run the provided
|
||||
scripts. This may not always be what you want -- for instance, if you run
|
||||
the scripts against a database that already has test data in it. The likelihood
|
||||
of accidentally deleting data is reduced by following the common pattern (shown earlier)
|
||||
of creating the tables first and then inserting the data. The first step fails if
|
||||
the tables already exist.
|
||||
|
||||
However, to gain more control over the creation and deletion of existing data, the XML
|
||||
namespace provides a few additional options. The first is a flag to switch the
|
||||
initialization on and off. You can set this according to the environment (such as pulling a
|
||||
boolean value from system properties or from an environment bean). The following example gets a value from a system property:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<jdbc:initialize-database data-source="dataSource"
|
||||
enabled="#{systemProperties.INITIALIZE_DATABASE}"> <1>
|
||||
<jdbc:script location="..."/>
|
||||
</jdbc:initialize-database>
|
||||
----
|
||||
<1> Get the value for `enabled` from a system property called `INITIALIZE_DATABASE`.
|
||||
|
||||
|
||||
The second option to control what happens with existing data is to be more tolerant of
|
||||
failures. To this end, you can control the ability of the initializer to ignore certain
|
||||
errors in the SQL it runs from the scripts, as the following example shows:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<jdbc:initialize-database data-source="dataSource" ignore-failures="DROPS">
|
||||
<jdbc:script location="..."/>
|
||||
</jdbc:initialize-database>
|
||||
----
|
||||
|
||||
In the preceding example, we are saying that we expect that, sometimes, the scripts are run
|
||||
against an empty database, and there are some `DROP` statements in the scripts that
|
||||
would, therefore, fail. So failed SQL `DROP` statements will be ignored, but other failures
|
||||
will cause an exception. This is useful if your SQL dialect doesn't support `DROP ... IF
|
||||
EXISTS` (or similar) but you want to unconditionally remove all test data before
|
||||
re-creating it. In that case the first script is usually a set of `DROP` statements,
|
||||
followed by a set of `CREATE` statements.
|
||||
|
||||
The `ignore-failures` option can be set to `NONE` (the default), `DROPS` (ignore failed
|
||||
drops), or `ALL` (ignore all failures).
|
||||
|
||||
Each statement should be separated by `;` or a new line if the `;` character is not
|
||||
present at all in the script. You can control that globally or script by script, as the
|
||||
following example shows:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<jdbc:initialize-database data-source="dataSource" separator="@@"> <1>
|
||||
<jdbc:script location="classpath:com/myapp/sql/db-schema.sql" separator=";"/> <2>
|
||||
<jdbc:script location="classpath:com/myapp/sql/db-test-data-1.sql"/>
|
||||
<jdbc:script location="classpath:com/myapp/sql/db-test-data-2.sql"/>
|
||||
</jdbc:initialize-database>
|
||||
----
|
||||
<1> Set the separator scripts to `@@`.
|
||||
<2> Set the separator for `db-schema.sql` to `;`.
|
||||
|
||||
In this example, the two `test-data` scripts use `@@` as statement separator and only
|
||||
the `db-schema.sql` uses `;`. This configuration specifies that the default separator
|
||||
is `@@` and overrides that default for the `db-schema` script.
|
||||
|
||||
If you need more control than you get from the XML namespace, you can use the
|
||||
`DataSourceInitializer` directly and define it as a component in your application.
|
||||
|
||||
[[jdbc-client-component-initialization]]
|
||||
=== Initialization of Other Components that Depend on the Database
|
||||
|
||||
A large class of applications (those that do not use the database until after the Spring context has
|
||||
started) can use the database initializer with no further
|
||||
complications. If your application is not one of those, you might need to read the rest
|
||||
of this section.
|
||||
|
||||
The database initializer depends on a `DataSource` instance and runs the scripts
|
||||
provided in its initialization callback (analogous to an `init-method` in an XML bean
|
||||
definition, a `@PostConstruct` method in a component, or the `afterPropertiesSet()`
|
||||
method in a component that implements `InitializingBean`). If other beans depend on the
|
||||
same data source and use the data source in an initialization callback, there
|
||||
might be a problem because the data has not yet been initialized. A common example of
|
||||
this is a cache that initializes eagerly and loads data from the database on application
|
||||
startup.
|
||||
|
||||
To get around this issue, you have two options: change your cache initialization strategy
|
||||
to a later phase or ensure that the database initializer is initialized first.
|
||||
|
||||
Changing your cache initialization strategy might be easy if the application is in your control and not otherwise.
|
||||
Some suggestions for how to implement this include:
|
||||
|
||||
* Make the cache initialize lazily on first usage, which improves application startup
|
||||
time.
|
||||
* Have your cache or a separate component that initializes the cache implement
|
||||
`Lifecycle` or `SmartLifecycle`. When the application context starts, you can
|
||||
automatically start a `SmartLifecycle` by setting its `autoStartup` flag, and you can
|
||||
manually start a `Lifecycle` by calling `ConfigurableApplicationContext.start()`
|
||||
on the enclosing context.
|
||||
* Use a Spring `ApplicationEvent` or similar custom observer mechanism to trigger the
|
||||
cache initialization. `ContextRefreshedEvent` is always published by the context when
|
||||
it is ready for use (after all beans have been initialized), so that is often a useful
|
||||
hook (this is how the `SmartLifecycle` works by default).
|
||||
|
||||
Ensuring that the database initializer is initialized first can also be easy. Some suggestions on how to implement this include:
|
||||
|
||||
* Rely on the default behavior of the Spring `BeanFactory`, which is that beans are
|
||||
initialized in registration order. You can easily arrange that by adopting the common
|
||||
practice of a set of `<import/>` elements in XML configuration that order your
|
||||
application modules and ensuring that the database and database initialization are
|
||||
listed first.
|
||||
* Separate the `DataSource` and the business components that use it and control their
|
||||
startup order by putting them in separate `ApplicationContext` instances (for example, the
|
||||
parent context contains the `DataSource`, and the child context contains the business
|
||||
components). This structure is common in Spring web applications but can be more
|
||||
generally applied.
|
||||
|
||||
|
||||
|
||||
544
framework-docs/modules/ROOT/pages/data-access/jdbc/object.adoc
Normal file
544
framework-docs/modules/ROOT/pages/data-access/jdbc/object.adoc
Normal file
@@ -0,0 +1,544 @@
|
||||
[[jdbc-object]]
|
||||
= Modeling JDBC Operations as Java Objects
|
||||
|
||||
The `org.springframework.jdbc.object` package contains classes that let you access
|
||||
the database in a more object-oriented manner. As an example, you can run queries
|
||||
and get the results back as a list that contains business objects with the relational
|
||||
column data mapped to the properties of the business object. You can also run stored
|
||||
procedures and run update, delete, and insert statements.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
Many Spring developers believe that the various RDBMS operation classes described below
|
||||
(with the exception of the <<jdbc-StoredProcedure, `StoredProcedure`>> class) can often
|
||||
be replaced with straight `JdbcTemplate` calls. Often, it is simpler to write a DAO
|
||||
method that calls a method on a `JdbcTemplate` directly (as opposed to
|
||||
encapsulating a query as a full-blown class).
|
||||
|
||||
However, if you are getting measurable value from using the RDBMS operation classes,
|
||||
you should continue to use these classes.
|
||||
====
|
||||
|
||||
|
||||
[[jdbc-SqlQuery]]
|
||||
== Understanding `SqlQuery`
|
||||
|
||||
`SqlQuery` is a reusable, thread-safe class that encapsulates an SQL query. Subclasses
|
||||
must implement the `newRowMapper(..)` method to provide a `RowMapper` instance that can
|
||||
create one object per row obtained from iterating over the `ResultSet` that is created
|
||||
during the execution of the query. The `SqlQuery` class is rarely used directly, because
|
||||
the `MappingSqlQuery` subclass provides a much more convenient implementation for
|
||||
mapping rows to Java classes. Other implementations that extend `SqlQuery` are
|
||||
`MappingSqlQueryWithParameters` and `UpdatableSqlQuery`.
|
||||
|
||||
|
||||
[[jdbc-MappingSqlQuery]]
|
||||
== Using `MappingSqlQuery`
|
||||
|
||||
`MappingSqlQuery` is a reusable query in which concrete subclasses must implement the
|
||||
abstract `mapRow(..)` method to convert each row of the supplied `ResultSet` into an
|
||||
object of the type specified. The following example shows a custom query that maps the
|
||||
data from the `t_actor` relation to an instance of the `Actor` class:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
public class ActorMappingQuery extends MappingSqlQuery<Actor> {
|
||||
|
||||
public ActorMappingQuery(DataSource ds) {
|
||||
super(ds, "select id, first_name, last_name from t_actor where id = ?");
|
||||
declareParameter(new SqlParameter("id", Types.INTEGER));
|
||||
compile();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Actor mapRow(ResultSet rs, int rowNumber) throws SQLException {
|
||||
Actor actor = new Actor();
|
||||
actor.setId(rs.getLong("id"));
|
||||
actor.setFirstName(rs.getString("first_name"));
|
||||
actor.setLastName(rs.getString("last_name"));
|
||||
return actor;
|
||||
}
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
class ActorMappingQuery(ds: DataSource) : MappingSqlQuery<Actor>(ds, "select id, first_name, last_name from t_actor where id = ?") {
|
||||
|
||||
init {
|
||||
declareParameter(SqlParameter("id", Types.INTEGER))
|
||||
compile()
|
||||
}
|
||||
|
||||
override fun mapRow(rs: ResultSet, rowNumber: Int) = Actor(
|
||||
rs.getLong("id"),
|
||||
rs.getString("first_name"),
|
||||
rs.getString("last_name")
|
||||
)
|
||||
}
|
||||
|
||||
----
|
||||
|
||||
The class extends `MappingSqlQuery` parameterized with the `Actor` type. The constructor
|
||||
for this customer query takes a `DataSource` as the only parameter. In this
|
||||
constructor, you can call the constructor on the superclass with the `DataSource` and the SQL
|
||||
that should be run to retrieve the rows for this query. This SQL is used to
|
||||
create a `PreparedStatement`, so it may contain placeholders for any parameters to be
|
||||
passed in during execution. You must declare each parameter by using the `declareParameter`
|
||||
method passing in an `SqlParameter`. The `SqlParameter` takes a name, and the JDBC type
|
||||
as defined in `java.sql.Types`. After you define all parameters, you can call the
|
||||
`compile()` method so that the statement can be prepared and later run. This class is
|
||||
thread-safe after it is compiled, so, as long as these instances are created when the DAO
|
||||
is initialized, they can be kept as instance variables and be reused. The following
|
||||
example shows how to define such a class:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
private ActorMappingQuery actorMappingQuery;
|
||||
|
||||
@Autowired
|
||||
public void setDataSource(DataSource dataSource) {
|
||||
this.actorMappingQuery = new ActorMappingQuery(dataSource);
|
||||
}
|
||||
|
||||
public Customer getCustomer(Long id) {
|
||||
return actorMappingQuery.findObject(id);
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
private val actorMappingQuery = ActorMappingQuery(dataSource)
|
||||
|
||||
fun getCustomer(id: Long) = actorMappingQuery.findObject(id)
|
||||
----
|
||||
|
||||
The method in the preceding example retrieves the customer with the `id` that is passed in as the
|
||||
only parameter. Since we want only one object to be returned, we call the `findObject` convenience
|
||||
method with the `id` as the parameter. If we had instead a query that returned a
|
||||
list of objects and took additional parameters, we would use one of the `execute`
|
||||
methods that takes an array of parameter values passed in as varargs. The following
|
||||
example shows such a method:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
public List<Actor> searchForActors(int age, String namePattern) {
|
||||
return actorSearchMappingQuery.execute(age, namePattern);
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
fun searchForActors(age: Int, namePattern: String) =
|
||||
actorSearchMappingQuery.execute(age, namePattern)
|
||||
----
|
||||
|
||||
|
||||
[[jdbc-SqlUpdate]]
|
||||
== Using `SqlUpdate`
|
||||
|
||||
The `SqlUpdate` class encapsulates an SQL update. As with a query, an update object is
|
||||
reusable, and, as with all `RdbmsOperation` classes, an update can have parameters and is
|
||||
defined in SQL. This class provides a number of `update(..)` methods analogous to the
|
||||
`execute(..)` methods of query objects. The `SqlUpdate` class is concrete. It can be
|
||||
subclassed -- for example, to add a custom update method.
|
||||
However, you do not have to subclass the `SqlUpdate`
|
||||
class, since it can easily be parameterized by setting SQL and declaring parameters.
|
||||
The following example creates a custom update method named `execute`:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
import java.sql.Types;
|
||||
import javax.sql.DataSource;
|
||||
import org.springframework.jdbc.core.SqlParameter;
|
||||
import org.springframework.jdbc.object.SqlUpdate;
|
||||
|
||||
public class UpdateCreditRating extends SqlUpdate {
|
||||
|
||||
public UpdateCreditRating(DataSource ds) {
|
||||
setDataSource(ds);
|
||||
setSql("update customer set credit_rating = ? where id = ?");
|
||||
declareParameter(new SqlParameter("creditRating", Types.NUMERIC));
|
||||
declareParameter(new SqlParameter("id", Types.NUMERIC));
|
||||
compile();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param id for the Customer to be updated
|
||||
* @param rating the new value for credit rating
|
||||
* @return number of rows updated
|
||||
*/
|
||||
public int execute(int id, int rating) {
|
||||
return update(rating, id);
|
||||
}
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
import java.sql.Types
|
||||
import javax.sql.DataSource
|
||||
import org.springframework.jdbc.core.SqlParameter
|
||||
import org.springframework.jdbc.`object`.SqlUpdate
|
||||
|
||||
class UpdateCreditRating(ds: DataSource) : SqlUpdate() {
|
||||
|
||||
init {
|
||||
setDataSource(ds)
|
||||
sql = "update customer set credit_rating = ? where id = ?"
|
||||
declareParameter(SqlParameter("creditRating", Types.NUMERIC))
|
||||
declareParameter(SqlParameter("id", Types.NUMERIC))
|
||||
compile()
|
||||
}
|
||||
|
||||
/**
|
||||
* @param id for the Customer to be updated
|
||||
* @param rating the new value for credit rating
|
||||
* @return number of rows updated
|
||||
*/
|
||||
fun execute(id: Int, rating: Int): Int {
|
||||
return update(rating, id)
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
|
||||
[[jdbc-StoredProcedure]]
|
||||
== Using `StoredProcedure`
|
||||
|
||||
The `StoredProcedure` class is an `abstract` superclass for object abstractions of RDBMS
|
||||
stored procedures.
|
||||
|
||||
The inherited `sql` property is the name of the stored procedure in the RDBMS.
|
||||
|
||||
To define a parameter for the `StoredProcedure` class, you can use an `SqlParameter` or one
|
||||
of its subclasses. You must specify the parameter name and SQL type in the constructor,
|
||||
as the following code snippet shows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
new SqlParameter("in_id", Types.NUMERIC),
|
||||
new SqlOutParameter("out_first_name", Types.VARCHAR),
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
SqlParameter("in_id", Types.NUMERIC),
|
||||
SqlOutParameter("out_first_name", Types.VARCHAR),
|
||||
----
|
||||
|
||||
The SQL type is specified using the `java.sql.Types` constants.
|
||||
|
||||
The first line (with the `SqlParameter`) declares an IN parameter. You can use IN parameters
|
||||
both for stored procedure calls and for queries using the `SqlQuery` and its
|
||||
subclasses (covered in <<jdbc-SqlQuery>>).
|
||||
|
||||
The second line (with the `SqlOutParameter`) declares an `out` parameter to be used in the
|
||||
stored procedure call. There is also an `SqlInOutParameter` for `InOut` parameters
|
||||
(parameters that provide an `in` value to the procedure and that also return a value).
|
||||
|
||||
For `in` parameters, in addition to the name and the SQL type, you can specify a
|
||||
scale for numeric data or a type name for custom database types. For `out` parameters,
|
||||
you can provide a `RowMapper` to handle mapping of rows returned from a `REF` cursor.
|
||||
Another option is to specify an `SqlReturnType` that lets you define customized
|
||||
handling of the return values.
|
||||
|
||||
The next example of a simple DAO uses a `StoredProcedure` to call a function
|
||||
(`sysdate()`), which comes with any Oracle database. To use the stored procedure
|
||||
functionality, you have to create a class that extends `StoredProcedure`. In this
|
||||
example, the `StoredProcedure` class is an inner class. However, if you need to reuse the
|
||||
`StoredProcedure`, you can declare it as a top-level class. This example has no input
|
||||
parameters, but an output parameter is declared as a date type by using the
|
||||
`SqlOutParameter` class. The `execute()` method runs the procedure and extracts the
|
||||
returned date from the results `Map`. The results `Map` has an entry for each declared
|
||||
output parameter (in this case, only one) by using the parameter name as the key.
|
||||
The following listing shows our custom StoredProcedure class:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
import java.sql.Types;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import javax.sql.DataSource;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.jdbc.core.SqlOutParameter;
|
||||
import org.springframework.jdbc.object.StoredProcedure;
|
||||
|
||||
public class StoredProcedureDao {
|
||||
|
||||
private GetSysdateProcedure getSysdate;
|
||||
|
||||
@Autowired
|
||||
public void init(DataSource dataSource) {
|
||||
this.getSysdate = new GetSysdateProcedure(dataSource);
|
||||
}
|
||||
|
||||
public Date getSysdate() {
|
||||
return getSysdate.execute();
|
||||
}
|
||||
|
||||
private class GetSysdateProcedure extends StoredProcedure {
|
||||
|
||||
private static final String SQL = "sysdate";
|
||||
|
||||
public GetSysdateProcedure(DataSource dataSource) {
|
||||
setDataSource(dataSource);
|
||||
setFunction(true);
|
||||
setSql(SQL);
|
||||
declareParameter(new SqlOutParameter("date", Types.DATE));
|
||||
compile();
|
||||
}
|
||||
|
||||
public Date execute() {
|
||||
// the 'sysdate' sproc has no input parameters, so an empty Map is supplied...
|
||||
Map<String, Object> results = execute(new HashMap<String, Object>());
|
||||
Date sysdate = (Date) results.get("date");
|
||||
return sysdate;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
import java.sql.Types
|
||||
import java.util.Date
|
||||
import java.util.Map
|
||||
import javax.sql.DataSource
|
||||
import org.springframework.jdbc.core.SqlOutParameter
|
||||
import org.springframework.jdbc.object.StoredProcedure
|
||||
|
||||
class StoredProcedureDao(dataSource: DataSource) {
|
||||
|
||||
private val SQL = "sysdate"
|
||||
|
||||
private val getSysdate = GetSysdateProcedure(dataSource)
|
||||
|
||||
val sysdate: Date
|
||||
get() = getSysdate.execute()
|
||||
|
||||
private inner class GetSysdateProcedure(dataSource: DataSource) : StoredProcedure() {
|
||||
|
||||
init {
|
||||
setDataSource(dataSource)
|
||||
isFunction = true
|
||||
sql = SQL
|
||||
declareParameter(SqlOutParameter("date", Types.DATE))
|
||||
compile()
|
||||
}
|
||||
|
||||
fun execute(): Date {
|
||||
// the 'sysdate' sproc has no input parameters, so an empty Map is supplied...
|
||||
val results = execute(mutableMapOf<String, Any>())
|
||||
return results["date"] as Date
|
||||
}
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
The following example of a `StoredProcedure` has two output parameters (in this case,
|
||||
Oracle REF cursors):
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import javax.sql.DataSource;
|
||||
import oracle.jdbc.OracleTypes;
|
||||
import org.springframework.jdbc.core.SqlOutParameter;
|
||||
import org.springframework.jdbc.object.StoredProcedure;
|
||||
|
||||
public class TitlesAndGenresStoredProcedure extends StoredProcedure {
|
||||
|
||||
private static final String SPROC_NAME = "AllTitlesAndGenres";
|
||||
|
||||
public TitlesAndGenresStoredProcedure(DataSource dataSource) {
|
||||
super(dataSource, SPROC_NAME);
|
||||
declareParameter(new SqlOutParameter("titles", OracleTypes.CURSOR, new TitleMapper()));
|
||||
declareParameter(new SqlOutParameter("genres", OracleTypes.CURSOR, new GenreMapper()));
|
||||
compile();
|
||||
}
|
||||
|
||||
public Map<String, Object> execute() {
|
||||
// again, this sproc has no input parameters, so an empty Map is supplied
|
||||
return super.execute(new HashMap<String, Object>());
|
||||
}
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
import java.util.HashMap
|
||||
import javax.sql.DataSource
|
||||
import oracle.jdbc.OracleTypes
|
||||
import org.springframework.jdbc.core.SqlOutParameter
|
||||
import org.springframework.jdbc.`object`.StoredProcedure
|
||||
|
||||
class TitlesAndGenresStoredProcedure(dataSource: DataSource) : StoredProcedure(dataSource, SPROC_NAME) {
|
||||
|
||||
companion object {
|
||||
private const val SPROC_NAME = "AllTitlesAndGenres"
|
||||
}
|
||||
|
||||
init {
|
||||
declareParameter(SqlOutParameter("titles", OracleTypes.CURSOR, TitleMapper()))
|
||||
declareParameter(SqlOutParameter("genres", OracleTypes.CURSOR, GenreMapper()))
|
||||
compile()
|
||||
}
|
||||
|
||||
fun execute(): Map<String, Any> {
|
||||
// again, this sproc has no input parameters, so an empty Map is supplied
|
||||
return super.execute(HashMap<String, Any>())
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Notice how the overloaded variants of the `declareParameter(..)` method that have been
|
||||
used in the `TitlesAndGenresStoredProcedure` constructor are passed `RowMapper`
|
||||
implementation instances. This is a very convenient and powerful way to reuse existing
|
||||
functionality. The next two examples provide code for the two `RowMapper` implementations.
|
||||
|
||||
The `TitleMapper` class maps a `ResultSet` to a `Title` domain object for each row in
|
||||
the supplied `ResultSet`, as follows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import com.foo.domain.Title;
|
||||
import org.springframework.jdbc.core.RowMapper;
|
||||
|
||||
public final class TitleMapper implements RowMapper<Title> {
|
||||
|
||||
public Title mapRow(ResultSet rs, int rowNum) throws SQLException {
|
||||
Title title = new Title();
|
||||
title.setId(rs.getLong("id"));
|
||||
title.setName(rs.getString("name"));
|
||||
return title;
|
||||
}
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
import java.sql.ResultSet
|
||||
import com.foo.domain.Title
|
||||
import org.springframework.jdbc.core.RowMapper
|
||||
|
||||
class TitleMapper : RowMapper<Title> {
|
||||
|
||||
override fun mapRow(rs: ResultSet, rowNum: Int) =
|
||||
Title(rs.getLong("id"), rs.getString("name"))
|
||||
}
|
||||
----
|
||||
|
||||
The `GenreMapper` class maps a `ResultSet` to a `Genre` domain object for each row in
|
||||
the supplied `ResultSet`, as follows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import com.foo.domain.Genre;
|
||||
import org.springframework.jdbc.core.RowMapper;
|
||||
|
||||
public final class GenreMapper implements RowMapper<Genre> {
|
||||
|
||||
public Genre mapRow(ResultSet rs, int rowNum) throws SQLException {
|
||||
return new Genre(rs.getString("name"));
|
||||
}
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
import java.sql.ResultSet
|
||||
import com.foo.domain.Genre
|
||||
import org.springframework.jdbc.core.RowMapper
|
||||
|
||||
class GenreMapper : RowMapper<Genre> {
|
||||
|
||||
override fun mapRow(rs: ResultSet, rowNum: Int): Genre {
|
||||
return Genre(rs.getString("name"))
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
To pass parameters to a stored procedure that has one or more input parameters in its
|
||||
definition in the RDBMS, you can code a strongly typed `execute(..)` method that would
|
||||
delegate to the untyped `execute(Map)` method in the superclass, as the following example shows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
import java.sql.Types;
|
||||
import java.util.Date;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import javax.sql.DataSource;
|
||||
import oracle.jdbc.OracleTypes;
|
||||
import org.springframework.jdbc.core.SqlOutParameter;
|
||||
import org.springframework.jdbc.core.SqlParameter;
|
||||
import org.springframework.jdbc.object.StoredProcedure;
|
||||
|
||||
public class TitlesAfterDateStoredProcedure extends StoredProcedure {
|
||||
|
||||
private static final String SPROC_NAME = "TitlesAfterDate";
|
||||
private static final String CUTOFF_DATE_PARAM = "cutoffDate";
|
||||
|
||||
public TitlesAfterDateStoredProcedure(DataSource dataSource) {
|
||||
super(dataSource, SPROC_NAME);
|
||||
declareParameter(new SqlParameter(CUTOFF_DATE_PARAM, Types.DATE);
|
||||
declareParameter(new SqlOutParameter("titles", OracleTypes.CURSOR, new TitleMapper()));
|
||||
compile();
|
||||
}
|
||||
|
||||
public Map<String, Object> execute(Date cutoffDate) {
|
||||
Map<String, Object> inputs = new HashMap<String, Object>();
|
||||
inputs.put(CUTOFF_DATE_PARAM, cutoffDate);
|
||||
return super.execute(inputs);
|
||||
}
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
import java.sql.Types
|
||||
import java.util.Date
|
||||
import javax.sql.DataSource
|
||||
import oracle.jdbc.OracleTypes
|
||||
import org.springframework.jdbc.core.SqlOutParameter
|
||||
import org.springframework.jdbc.core.SqlParameter
|
||||
import org.springframework.jdbc.`object`.StoredProcedure
|
||||
|
||||
class TitlesAfterDateStoredProcedure(dataSource: DataSource) : StoredProcedure(dataSource, SPROC_NAME) {
|
||||
|
||||
companion object {
|
||||
private const val SPROC_NAME = "TitlesAfterDate"
|
||||
private const val CUTOFF_DATE_PARAM = "cutoffDate"
|
||||
}
|
||||
|
||||
init {
|
||||
declareParameter(SqlParameter(CUTOFF_DATE_PARAM, Types.DATE))
|
||||
declareParameter(SqlOutParameter("titles", OracleTypes.CURSOR, TitleMapper()))
|
||||
compile()
|
||||
}
|
||||
|
||||
fun execute(cutoffDate: Date) = super.execute(
|
||||
mapOf<String, Any>(CUTOFF_DATE_PARAM to cutoffDate))
|
||||
}
|
||||
----
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
[[jdbc-packages]]
|
||||
= Package Hierarchy
|
||||
|
||||
The Spring Framework's JDBC abstraction framework consists of four different packages:
|
||||
|
||||
* `core`: The `org.springframework.jdbc.core` package contains the `JdbcTemplate` class and its
|
||||
various callback interfaces, plus a variety of related classes. A subpackage named
|
||||
`org.springframework.jdbc.core.simple` contains the `SimpleJdbcInsert` and
|
||||
`SimpleJdbcCall` classes. Another subpackage named
|
||||
`org.springframework.jdbc.core.namedparam` contains the `NamedParameterJdbcTemplate`
|
||||
class and the related support classes. See <<jdbc-core>>, <<jdbc-advanced-jdbc>>, and
|
||||
<<jdbc-simple-jdbc>>.
|
||||
|
||||
* `datasource`: The `org.springframework.jdbc.datasource` package contains a utility class for easy
|
||||
`DataSource` access and various simple `DataSource` implementations that you can use for
|
||||
testing and running unmodified JDBC code outside of a Jakarta EE container. A subpackage
|
||||
named `org.springfamework.jdbc.datasource.embedded` provides support for creating
|
||||
embedded databases by using Java database engines, such as HSQL, H2, and Derby. See
|
||||
<<jdbc-connections>> and <<jdbc-embedded-database-support>>.
|
||||
|
||||
* `object`: The `org.springframework.jdbc.object` package contains classes that represent RDBMS
|
||||
queries, updates, and stored procedures as thread-safe, reusable objects. See
|
||||
<<jdbc-object>>. This approach is modeled by JDO, although objects returned by queries
|
||||
are naturally disconnected from the database. This higher-level of JDBC abstraction
|
||||
depends on the lower-level abstraction in the `org.springframework.jdbc.core` package.
|
||||
|
||||
* `support`: The `org.springframework.jdbc.support` package provides `SQLException` translation
|
||||
functionality and some utility classes. Exceptions thrown during JDBC processing are
|
||||
translated to exceptions defined in the `org.springframework.dao` package. This means
|
||||
that code using the Spring JDBC abstraction layer does not need to implement JDBC or
|
||||
RDBMS-specific error handling. All translated exceptions are unchecked, which gives you
|
||||
the option of catching the exceptions from which you can recover while letting other
|
||||
exceptions be propagated to the caller. See <<jdbc-SQLExceptionTranslator>>.
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,322 @@
|
||||
[[jdbc-parameter-handling]]
|
||||
= Common Problems with Parameter and Data Value Handling
|
||||
|
||||
Common problems with parameters and data values exist in the different approaches
|
||||
provided by Spring Framework's JDBC support. This section covers how to address them.
|
||||
|
||||
|
||||
[[jdbc-type-information]]
|
||||
== Providing SQL Type Information for Parameters
|
||||
|
||||
Usually, Spring determines the SQL type of the parameters based on the type of parameter
|
||||
passed in. It is possible to explicitly provide the SQL type to be used when setting
|
||||
parameter values. This is sometimes necessary to correctly set `NULL` values.
|
||||
|
||||
You can provide SQL type information in several ways:
|
||||
|
||||
* Many update and query methods of the `JdbcTemplate` take an additional parameter in
|
||||
the form of an `int` array. This array is used to indicate the SQL type of the
|
||||
corresponding parameter by using constant values from the `java.sql.Types` class. Provide
|
||||
one entry for each parameter.
|
||||
* You can use the `SqlParameterValue` class to wrap the parameter value that needs this
|
||||
additional information. To do so, create a new instance for each value and pass in the SQL type
|
||||
and the parameter value in the constructor. You can also provide an optional scale
|
||||
parameter for numeric values.
|
||||
* For methods that work with named parameters, you can use the `SqlParameterSource` classes,
|
||||
`BeanPropertySqlParameterSource` or `MapSqlParameterSource`. They both have methods
|
||||
for registering the SQL type for any of the named parameter values.
|
||||
|
||||
|
||||
[[jdbc-lob]]
|
||||
== Handling BLOB and CLOB objects
|
||||
|
||||
You can store images, other binary data, and large chunks of text in the database. These
|
||||
large objects are called BLOBs (Binary Large OBject) for binary data and CLOBs (Character
|
||||
Large OBject) for character data. In Spring, you can handle these large objects by using
|
||||
the `JdbcTemplate` directly and also when using the higher abstractions provided by RDBMS
|
||||
Objects and the `SimpleJdbc` classes. All of these approaches use an implementation of
|
||||
the `LobHandler` interface for the actual management of the LOB (Large OBject) data.
|
||||
`LobHandler` provides access to a `LobCreator` class, through the `getLobCreator` method,
|
||||
that is used for creating new LOB objects to be inserted.
|
||||
|
||||
`LobCreator` and `LobHandler` provide the following support for LOB input and output:
|
||||
|
||||
* BLOB
|
||||
** `byte[]`: `getBlobAsBytes` and `setBlobAsBytes`
|
||||
** `InputStream`: `getBlobAsBinaryStream` and `setBlobAsBinaryStream`
|
||||
* CLOB
|
||||
** `String`: `getClobAsString` and `setClobAsString`
|
||||
** `InputStream`: `getClobAsAsciiStream` and `setClobAsAsciiStream`
|
||||
** `Reader`: `getClobAsCharacterStream` and `setClobAsCharacterStream`
|
||||
|
||||
The next example shows how to create and insert a BLOB. Later we show how to read
|
||||
it back from the database.
|
||||
|
||||
This example uses a `JdbcTemplate` and an implementation of the
|
||||
`AbstractLobCreatingPreparedStatementCallback`. It implements one method,
|
||||
`setValues`. This method provides a `LobCreator` that we use to set the values for the
|
||||
LOB columns in your SQL insert statement.
|
||||
|
||||
For this example, we assume that there is a variable, `lobHandler`, that is already
|
||||
set to an instance of a `DefaultLobHandler`. You typically set this value through
|
||||
dependency injection.
|
||||
|
||||
The following example shows how to create and insert a BLOB:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
final File blobIn = new File("spring2004.jpg");
|
||||
final InputStream blobIs = new FileInputStream(blobIn);
|
||||
final File clobIn = new File("large.txt");
|
||||
final InputStream clobIs = new FileInputStream(clobIn);
|
||||
final InputStreamReader clobReader = new InputStreamReader(clobIs);
|
||||
|
||||
jdbcTemplate.execute(
|
||||
"INSERT INTO lob_table (id, a_clob, a_blob) VALUES (?, ?, ?)",
|
||||
new AbstractLobCreatingPreparedStatementCallback(lobHandler) { // <1>
|
||||
protected void setValues(PreparedStatement ps, LobCreator lobCreator) throws SQLException {
|
||||
ps.setLong(1, 1L);
|
||||
lobCreator.setClobAsCharacterStream(ps, 2, clobReader, (int)clobIn.length()); // <2>
|
||||
lobCreator.setBlobAsBinaryStream(ps, 3, blobIs, (int)blobIn.length()); // <3>
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
blobIs.close();
|
||||
clobReader.close();
|
||||
----
|
||||
<1> Pass in the `lobHandler` that (in this example) is a plain `DefaultLobHandler`.
|
||||
<2> Using the method `setClobAsCharacterStream` to pass in the contents of the CLOB.
|
||||
<3> Using the method `setBlobAsBinaryStream` to pass in the contents of the BLOB.
|
||||
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val blobIn = File("spring2004.jpg")
|
||||
val blobIs = FileInputStream(blobIn)
|
||||
val clobIn = File("large.txt")
|
||||
val clobIs = FileInputStream(clobIn)
|
||||
val clobReader = InputStreamReader(clobIs)
|
||||
|
||||
jdbcTemplate.execute(
|
||||
"INSERT INTO lob_table (id, a_clob, a_blob) VALUES (?, ?, ?)",
|
||||
object: AbstractLobCreatingPreparedStatementCallback(lobHandler) { // <1>
|
||||
override fun setValues(ps: PreparedStatement, lobCreator: LobCreator) {
|
||||
ps.setLong(1, 1L)
|
||||
lobCreator.setClobAsCharacterStream(ps, 2, clobReader, clobIn.length().toInt()) // <2>
|
||||
lobCreator.setBlobAsBinaryStream(ps, 3, blobIs, blobIn.length().toInt()) // <3>
|
||||
}
|
||||
}
|
||||
)
|
||||
blobIs.close()
|
||||
clobReader.close()
|
||||
----
|
||||
<1> Pass in the `lobHandler` that (in this example) is a plain `DefaultLobHandler`.
|
||||
<2> Using the method `setClobAsCharacterStream` to pass in the contents of the CLOB.
|
||||
<3> Using the method `setBlobAsBinaryStream` to pass in the contents of the BLOB.
|
||||
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
If you invoke the `setBlobAsBinaryStream`, `setClobAsAsciiStream`, or
|
||||
`setClobAsCharacterStream` method on the `LobCreator` returned from
|
||||
`DefaultLobHandler.getLobCreator()`, you can optionally specify a negative value for the
|
||||
`contentLength` argument. If the specified content length is negative, the
|
||||
`DefaultLobHandler` uses the JDBC 4.0 variants of the set-stream methods without a
|
||||
length parameter. Otherwise, it passes the specified length on to the driver.
|
||||
|
||||
See the documentation for the JDBC driver you use to verify that it supports streaming a
|
||||
LOB without providing the content length.
|
||||
====
|
||||
|
||||
Now it is time to read the LOB data from the database. Again, you use a `JdbcTemplate`
|
||||
with the same instance variable `lobHandler` and a reference to a `DefaultLobHandler`.
|
||||
The following example shows how to do so:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
List<Map<String, Object>> l = jdbcTemplate.query("select id, a_clob, a_blob from lob_table",
|
||||
new RowMapper<Map<String, Object>>() {
|
||||
public Map<String, Object> mapRow(ResultSet rs, int i) throws SQLException {
|
||||
Map<String, Object> results = new HashMap<String, Object>();
|
||||
String clobText = lobHandler.getClobAsString(rs, "a_clob"); // <1>
|
||||
results.put("CLOB", clobText);
|
||||
byte[] blobBytes = lobHandler.getBlobAsBytes(rs, "a_blob"); // <2>
|
||||
results.put("BLOB", blobBytes);
|
||||
return results;
|
||||
}
|
||||
});
|
||||
----
|
||||
<1> Using the method `getClobAsString` to retrieve the contents of the CLOB.
|
||||
<2> Using the method `getBlobAsBytes` to retrieve the contents of the BLOB.
|
||||
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val l = jdbcTemplate.query("select id, a_clob, a_blob from lob_table") { rs, _ ->
|
||||
val clobText = lobHandler.getClobAsString(rs, "a_clob") // <1>
|
||||
val blobBytes = lobHandler.getBlobAsBytes(rs, "a_blob") // <2>
|
||||
mapOf("CLOB" to clobText, "BLOB" to blobBytes)
|
||||
}
|
||||
----
|
||||
<1> Using the method `getClobAsString` to retrieve the contents of the CLOB.
|
||||
<2> Using the method `getBlobAsBytes` to retrieve the contents of the BLOB.
|
||||
|
||||
|
||||
[[jdbc-in-clause]]
|
||||
== Passing in Lists of Values for IN Clause
|
||||
|
||||
The SQL standard allows for selecting rows based on an expression that includes a
|
||||
variable list of values. A typical example would be `select * from T_ACTOR where id in
|
||||
(1, 2, 3)`. This variable list is not directly supported for prepared statements by the
|
||||
JDBC standard. You cannot declare a variable number of placeholders. You need a number
|
||||
of variations with the desired number of placeholders prepared, or you need to generate
|
||||
the SQL string dynamically once you know how many placeholders are required. The named
|
||||
parameter support provided in the `NamedParameterJdbcTemplate` and `JdbcTemplate` takes
|
||||
the latter approach. You can pass in the values as a `java.util.List` of primitive objects. This
|
||||
list is used to insert the required placeholders and pass in the values during
|
||||
statement execution.
|
||||
|
||||
NOTE: Be careful when passing in many values. The JDBC standard does not guarantee that you
|
||||
can use more than 100 values for an `in` expression list. Various databases exceed this
|
||||
number, but they usually have a hard limit for how many values are allowed. For example, Oracle's
|
||||
limit is 1000.
|
||||
|
||||
In addition to the primitive values in the value list, you can create a `java.util.List`
|
||||
of object arrays. This list can support multiple expressions being defined for the `in`
|
||||
clause, such as `+++select * from T_ACTOR where (id, last_name) in ((1, 'Johnson'), (2,
|
||||
'Harrop'))+++`. This, of course, requires that your database supports this syntax.
|
||||
|
||||
|
||||
[[jdbc-complex-types]]
|
||||
== Handling Complex Types for Stored Procedure Calls
|
||||
|
||||
When you call stored procedures, you can sometimes use complex types specific to the
|
||||
database. To accommodate these types, Spring provides a `SqlReturnType` for handling
|
||||
them when they are returned from the stored procedure call and `SqlTypeValue` when they
|
||||
are passed in as a parameter to the stored procedure.
|
||||
|
||||
The `SqlReturnType` interface has a single method (named `getTypeValue`) that must be
|
||||
implemented. This interface is used as part of the declaration of an `SqlOutParameter`.
|
||||
The following example shows returning the value of an Oracle `STRUCT` object of the user
|
||||
declared type `ITEM_TYPE`:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
public class TestItemStoredProcedure extends StoredProcedure {
|
||||
|
||||
public TestItemStoredProcedure(DataSource dataSource) {
|
||||
// ...
|
||||
declareParameter(new SqlOutParameter("item", OracleTypes.STRUCT, "ITEM_TYPE",
|
||||
(CallableStatement cs, int colIndx, int sqlType, String typeName) -> {
|
||||
STRUCT struct = (STRUCT) cs.getObject(colIndx);
|
||||
Object[] attr = struct.getAttributes();
|
||||
TestItem item = new TestItem();
|
||||
item.setId(((Number) attr[0]).longValue());
|
||||
item.setDescription((String) attr[1]);
|
||||
item.setExpirationDate((java.util.Date) attr[2]);
|
||||
return item;
|
||||
}));
|
||||
// ...
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
class TestItemStoredProcedure(dataSource: DataSource) : StoredProcedure() {
|
||||
|
||||
init {
|
||||
// ...
|
||||
declareParameter(SqlOutParameter("item", OracleTypes.STRUCT, "ITEM_TYPE") { cs, colIndx, sqlType, typeName ->
|
||||
val struct = cs.getObject(colIndx) as STRUCT
|
||||
val attr = struct.getAttributes()
|
||||
TestItem((attr[0] as Long, attr[1] as String, attr[2] as Date)
|
||||
})
|
||||
// ...
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
You can use `SqlTypeValue` to pass the value of a Java object (such as `TestItem`) to a
|
||||
stored procedure. The `SqlTypeValue` interface has a single method (named
|
||||
`createTypeValue`) that you must implement. The active connection is passed in, and you
|
||||
can use it to create database-specific objects, such as `StructDescriptor` instances
|
||||
or `ArrayDescriptor` instances. The following example creates a `StructDescriptor` instance:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
final TestItem testItem = new TestItem(123L, "A test item",
|
||||
new SimpleDateFormat("yyyy-M-d").parse("2010-12-31"));
|
||||
|
||||
SqlTypeValue value = new AbstractSqlTypeValue() {
|
||||
protected Object createTypeValue(Connection conn, int sqlType, String typeName) throws SQLException {
|
||||
StructDescriptor itemDescriptor = new StructDescriptor(typeName, conn);
|
||||
Struct item = new STRUCT(itemDescriptor, conn,
|
||||
new Object[] {
|
||||
testItem.getId(),
|
||||
testItem.getDescription(),
|
||||
new java.sql.Date(testItem.getExpirationDate().getTime())
|
||||
});
|
||||
return item;
|
||||
}
|
||||
};
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val (id, description, expirationDate) = TestItem(123L, "A test item",
|
||||
SimpleDateFormat("yyyy-M-d").parse("2010-12-31"))
|
||||
|
||||
val value = object : AbstractSqlTypeValue() {
|
||||
override fun createTypeValue(conn: Connection, sqlType: Int, typeName: String?): Any {
|
||||
val itemDescriptor = StructDescriptor(typeName, conn)
|
||||
return STRUCT(itemDescriptor, conn,
|
||||
arrayOf(id, description, java.sql.Date(expirationDate.time)))
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
You can now add this `SqlTypeValue` to the `Map` that contains the input parameters for the
|
||||
`execute` call of the stored procedure.
|
||||
|
||||
Another use for the `SqlTypeValue` is passing in an array of values to an Oracle stored
|
||||
procedure. Oracle has its own internal `ARRAY` class that must be used in this case, and
|
||||
you can use the `SqlTypeValue` to create an instance of the Oracle `ARRAY` and populate
|
||||
it with values from the Java `ARRAY`, as the following example shows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
final Long[] ids = new Long[] {1L, 2L};
|
||||
|
||||
SqlTypeValue value = new AbstractSqlTypeValue() {
|
||||
protected Object createTypeValue(Connection conn, int sqlType, String typeName) throws SQLException {
|
||||
ArrayDescriptor arrayDescriptor = new ArrayDescriptor(typeName, conn);
|
||||
ARRAY idArray = new ARRAY(arrayDescriptor, conn, ids);
|
||||
return idArray;
|
||||
}
|
||||
};
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
class TestItemStoredProcedure(dataSource: DataSource) : StoredProcedure() {
|
||||
|
||||
init {
|
||||
val ids = arrayOf(1L, 2L)
|
||||
val value = object : AbstractSqlTypeValue() {
|
||||
override fun createTypeValue(conn: Connection, sqlType: Int, typeName: String?): Any {
|
||||
val arrayDescriptor = ArrayDescriptor(typeName, conn)
|
||||
return ARRAY(arrayDescriptor, conn, ids)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
|
||||
|
||||
707
framework-docs/modules/ROOT/pages/data-access/jdbc/simple.adoc
Normal file
707
framework-docs/modules/ROOT/pages/data-access/jdbc/simple.adoc
Normal file
@@ -0,0 +1,707 @@
|
||||
[[jdbc-simple-jdbc]]
|
||||
= Simplifying JDBC Operations with the `SimpleJdbc` Classes
|
||||
|
||||
The `SimpleJdbcInsert` and `SimpleJdbcCall` classes provide a simplified configuration
|
||||
by taking advantage of database metadata that can be retrieved through the JDBC driver.
|
||||
This means that you have less to configure up front, although you can override or turn off
|
||||
the metadata processing if you prefer to provide all the details in your code.
|
||||
|
||||
|
||||
[[jdbc-simple-jdbc-insert-1]]
|
||||
== Inserting Data by Using `SimpleJdbcInsert`
|
||||
|
||||
We start by looking at the `SimpleJdbcInsert` class with the minimal amount of
|
||||
configuration options. You should instantiate the `SimpleJdbcInsert` in the data access
|
||||
layer's initialization method. For this example, the initializing method is the
|
||||
`setDataSource` method. You do not need to subclass the `SimpleJdbcInsert` class. Instead,
|
||||
you can create a new instance and set the table name by using the `withTableName` method.
|
||||
Configuration methods for this class follow the `fluid` style that returns the instance
|
||||
of the `SimpleJdbcInsert`, which lets you chain all configuration methods. The following
|
||||
example uses only one configuration method (we show examples of multiple methods later):
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
public class JdbcActorDao implements ActorDao {
|
||||
|
||||
private SimpleJdbcInsert insertActor;
|
||||
|
||||
public void setDataSource(DataSource dataSource) {
|
||||
this.insertActor = new SimpleJdbcInsert(dataSource).withTableName("t_actor");
|
||||
}
|
||||
|
||||
public void add(Actor actor) {
|
||||
Map<String, Object> parameters = new HashMap<>(3);
|
||||
parameters.put("id", actor.getId());
|
||||
parameters.put("first_name", actor.getFirstName());
|
||||
parameters.put("last_name", actor.getLastName());
|
||||
insertActor.execute(parameters);
|
||||
}
|
||||
|
||||
// ... additional methods
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
class JdbcActorDao(dataSource: DataSource) : ActorDao {
|
||||
|
||||
private val insertActor = SimpleJdbcInsert(dataSource).withTableName("t_actor")
|
||||
|
||||
fun add(actor: Actor) {
|
||||
val parameters = mutableMapOf<String, Any>()
|
||||
parameters["id"] = actor.id
|
||||
parameters["first_name"] = actor.firstName
|
||||
parameters["last_name"] = actor.lastName
|
||||
insertActor.execute(parameters)
|
||||
}
|
||||
|
||||
// ... additional methods
|
||||
}
|
||||
----
|
||||
|
||||
The `execute` method used here takes a plain `java.util.Map` as its only parameter. The
|
||||
important thing to note here is that the keys used for the `Map` must match the column
|
||||
names of the table, as defined in the database. This is because we read the metadata
|
||||
to construct the actual insert statement.
|
||||
|
||||
|
||||
[[jdbc-simple-jdbc-insert-2]]
|
||||
== Retrieving Auto-generated Keys by Using `SimpleJdbcInsert`
|
||||
|
||||
The next example uses the same insert as the preceding example, but, instead of passing in the `id`, it
|
||||
retrieves the auto-generated key and sets it on the new `Actor` object. When it creates
|
||||
the `SimpleJdbcInsert`, in addition to specifying the table name, it specifies the name
|
||||
of the generated key column with the `usingGeneratedKeyColumns` method. The following
|
||||
listing shows how it works:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
public class JdbcActorDao implements ActorDao {
|
||||
|
||||
private SimpleJdbcInsert insertActor;
|
||||
|
||||
public void setDataSource(DataSource dataSource) {
|
||||
this.insertActor = new SimpleJdbcInsert(dataSource)
|
||||
.withTableName("t_actor")
|
||||
.usingGeneratedKeyColumns("id");
|
||||
}
|
||||
|
||||
public void add(Actor actor) {
|
||||
Map<String, Object> parameters = new HashMap<>(2);
|
||||
parameters.put("first_name", actor.getFirstName());
|
||||
parameters.put("last_name", actor.getLastName());
|
||||
Number newId = insertActor.executeAndReturnKey(parameters);
|
||||
actor.setId(newId.longValue());
|
||||
}
|
||||
|
||||
// ... additional methods
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
class JdbcActorDao(dataSource: DataSource) : ActorDao {
|
||||
|
||||
private val insertActor = SimpleJdbcInsert(dataSource)
|
||||
.withTableName("t_actor").usingGeneratedKeyColumns("id")
|
||||
|
||||
fun add(actor: Actor): Actor {
|
||||
val parameters = mapOf(
|
||||
"first_name" to actor.firstName,
|
||||
"last_name" to actor.lastName)
|
||||
val newId = insertActor.executeAndReturnKey(parameters);
|
||||
return actor.copy(id = newId.toLong())
|
||||
}
|
||||
|
||||
// ... additional methods
|
||||
}
|
||||
----
|
||||
|
||||
The main difference when you run the insert by using this second approach is that you do not
|
||||
add the `id` to the `Map`, and you call the `executeAndReturnKey` method. This returns a
|
||||
`java.lang.Number` object with which you can create an instance of the numerical type that
|
||||
is used in your domain class. You cannot rely on all databases to return a specific Java
|
||||
class here. `java.lang.Number` is the base class that you can rely on. If you have
|
||||
multiple auto-generated columns or the generated values are non-numeric, you can
|
||||
use a `KeyHolder` that is returned from the `executeAndReturnKeyHolder` method.
|
||||
|
||||
|
||||
[[jdbc-simple-jdbc-insert-3]]
|
||||
== Specifying Columns for a `SimpleJdbcInsert`
|
||||
|
||||
You can limit the columns for an insert by specifying a list of column names with the
|
||||
`usingColumns` method, as the following example shows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
public class JdbcActorDao implements ActorDao {
|
||||
|
||||
private SimpleJdbcInsert insertActor;
|
||||
|
||||
public void setDataSource(DataSource dataSource) {
|
||||
this.insertActor = new SimpleJdbcInsert(dataSource)
|
||||
.withTableName("t_actor")
|
||||
.usingColumns("first_name", "last_name")
|
||||
.usingGeneratedKeyColumns("id");
|
||||
}
|
||||
|
||||
public void add(Actor actor) {
|
||||
Map<String, Object> parameters = new HashMap<>(2);
|
||||
parameters.put("first_name", actor.getFirstName());
|
||||
parameters.put("last_name", actor.getLastName());
|
||||
Number newId = insertActor.executeAndReturnKey(parameters);
|
||||
actor.setId(newId.longValue());
|
||||
}
|
||||
|
||||
// ... additional methods
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
class JdbcActorDao(dataSource: DataSource) : ActorDao {
|
||||
|
||||
private val insertActor = SimpleJdbcInsert(dataSource)
|
||||
.withTableName("t_actor")
|
||||
.usingColumns("first_name", "last_name")
|
||||
.usingGeneratedKeyColumns("id")
|
||||
|
||||
fun add(actor: Actor): Actor {
|
||||
val parameters = mapOf(
|
||||
"first_name" to actor.firstName,
|
||||
"last_name" to actor.lastName)
|
||||
val newId = insertActor.executeAndReturnKey(parameters);
|
||||
return actor.copy(id = newId.toLong())
|
||||
}
|
||||
|
||||
// ... additional methods
|
||||
}
|
||||
----
|
||||
|
||||
The execution of the insert is the same as if you had relied on the metadata to determine
|
||||
which columns to use.
|
||||
|
||||
|
||||
[[jdbc-simple-jdbc-parameters]]
|
||||
== Using `SqlParameterSource` to Provide Parameter Values
|
||||
|
||||
Using a `Map` to provide parameter values works fine, but it is not the most convenient
|
||||
class to use. Spring provides a couple of implementations of the `SqlParameterSource`
|
||||
interface that you can use instead. The first one is `BeanPropertySqlParameterSource`,
|
||||
which is a very convenient class if you have a JavaBean-compliant class that contains
|
||||
your values. It uses the corresponding getter method to extract the parameter
|
||||
values. The following example shows how to use `BeanPropertySqlParameterSource`:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
public class JdbcActorDao implements ActorDao {
|
||||
|
||||
private SimpleJdbcInsert insertActor;
|
||||
|
||||
public void setDataSource(DataSource dataSource) {
|
||||
this.insertActor = new SimpleJdbcInsert(dataSource)
|
||||
.withTableName("t_actor")
|
||||
.usingGeneratedKeyColumns("id");
|
||||
}
|
||||
|
||||
public void add(Actor actor) {
|
||||
SqlParameterSource parameters = new BeanPropertySqlParameterSource(actor);
|
||||
Number newId = insertActor.executeAndReturnKey(parameters);
|
||||
actor.setId(newId.longValue());
|
||||
}
|
||||
|
||||
// ... additional methods
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
class JdbcActorDao(dataSource: DataSource) : ActorDao {
|
||||
|
||||
private val insertActor = SimpleJdbcInsert(dataSource)
|
||||
.withTableName("t_actor")
|
||||
.usingGeneratedKeyColumns("id")
|
||||
|
||||
fun add(actor: Actor): Actor {
|
||||
val parameters = BeanPropertySqlParameterSource(actor)
|
||||
val newId = insertActor.executeAndReturnKey(parameters)
|
||||
return actor.copy(id = newId.toLong())
|
||||
}
|
||||
|
||||
// ... additional methods
|
||||
}
|
||||
----
|
||||
|
||||
Another option is the `MapSqlParameterSource` that resembles a `Map` but provides a more
|
||||
convenient `addValue` method that can be chained. The following example shows how to use it:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
public class JdbcActorDao implements ActorDao {
|
||||
|
||||
private SimpleJdbcInsert insertActor;
|
||||
|
||||
public void setDataSource(DataSource dataSource) {
|
||||
this.insertActor = new SimpleJdbcInsert(dataSource)
|
||||
.withTableName("t_actor")
|
||||
.usingGeneratedKeyColumns("id");
|
||||
}
|
||||
|
||||
public void add(Actor actor) {
|
||||
SqlParameterSource parameters = new MapSqlParameterSource()
|
||||
.addValue("first_name", actor.getFirstName())
|
||||
.addValue("last_name", actor.getLastName());
|
||||
Number newId = insertActor.executeAndReturnKey(parameters);
|
||||
actor.setId(newId.longValue());
|
||||
}
|
||||
|
||||
// ... additional methods
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
class JdbcActorDao(dataSource: DataSource) : ActorDao {
|
||||
|
||||
private val insertActor = SimpleJdbcInsert(dataSource)
|
||||
.withTableName("t_actor")
|
||||
.usingGeneratedKeyColumns("id")
|
||||
|
||||
fun add(actor: Actor): Actor {
|
||||
val parameters = MapSqlParameterSource()
|
||||
.addValue("first_name", actor.firstName)
|
||||
.addValue("last_name", actor.lastName)
|
||||
val newId = insertActor.executeAndReturnKey(parameters)
|
||||
return actor.copy(id = newId.toLong())
|
||||
}
|
||||
|
||||
// ... additional methods
|
||||
}
|
||||
----
|
||||
|
||||
As you can see, the configuration is the same. Only the executing code has to change to
|
||||
use these alternative input classes.
|
||||
|
||||
|
||||
[[jdbc-simple-jdbc-call-1]]
|
||||
== Calling a Stored Procedure with `SimpleJdbcCall`
|
||||
|
||||
The `SimpleJdbcCall` class uses metadata in the database to look up names of `in`
|
||||
and `out` parameters so that you do not have to explicitly declare them. You can
|
||||
declare parameters if you prefer to do that or if you have parameters (such as `ARRAY`
|
||||
or `STRUCT`) that do not have an automatic mapping to a Java class. The first example
|
||||
shows a simple procedure that returns only scalar values in `VARCHAR` and `DATE` format
|
||||
from a MySQL database. The example procedure reads a specified actor entry and returns
|
||||
`first_name`, `last_name`, and `birth_date` columns in the form of `out` parameters.
|
||||
The following listing shows the first example:
|
||||
|
||||
[source,sql,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
CREATE PROCEDURE read_actor (
|
||||
IN in_id INTEGER,
|
||||
OUT out_first_name VARCHAR(100),
|
||||
OUT out_last_name VARCHAR(100),
|
||||
OUT out_birth_date DATE)
|
||||
BEGIN
|
||||
SELECT first_name, last_name, birth_date
|
||||
INTO out_first_name, out_last_name, out_birth_date
|
||||
FROM t_actor where id = in_id;
|
||||
END;
|
||||
----
|
||||
|
||||
The `in_id` parameter contains the `id` of the actor that you are looking up. The `out`
|
||||
parameters return the data read from the table.
|
||||
|
||||
You can declare `SimpleJdbcCall` in a manner similar to declaring `SimpleJdbcInsert`. You
|
||||
should instantiate and configure the class in the initialization method of your data-access
|
||||
layer. Compared to the `StoredProcedure` class, you need not create a subclass
|
||||
and you need not to declare parameters that can be looked up in the database metadata.
|
||||
The following example of a `SimpleJdbcCall` configuration uses the preceding stored
|
||||
procedure (the only configuration option, in addition to the `DataSource`, is the name
|
||||
of the stored procedure):
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
public class JdbcActorDao implements ActorDao {
|
||||
|
||||
private SimpleJdbcCall procReadActor;
|
||||
|
||||
public void setDataSource(DataSource dataSource) {
|
||||
this.procReadActor = new SimpleJdbcCall(dataSource)
|
||||
.withProcedureName("read_actor");
|
||||
}
|
||||
|
||||
public Actor readActor(Long id) {
|
||||
SqlParameterSource in = new MapSqlParameterSource()
|
||||
.addValue("in_id", id);
|
||||
Map out = procReadActor.execute(in);
|
||||
Actor actor = new Actor();
|
||||
actor.setId(id);
|
||||
actor.setFirstName((String) out.get("out_first_name"));
|
||||
actor.setLastName((String) out.get("out_last_name"));
|
||||
actor.setBirthDate((Date) out.get("out_birth_date"));
|
||||
return actor;
|
||||
}
|
||||
|
||||
// ... additional methods
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
class JdbcActorDao(dataSource: DataSource) : ActorDao {
|
||||
|
||||
private val procReadActor = SimpleJdbcCall(dataSource)
|
||||
.withProcedureName("read_actor")
|
||||
|
||||
|
||||
fun readActor(id: Long): Actor {
|
||||
val source = MapSqlParameterSource().addValue("in_id", id)
|
||||
val output = procReadActor.execute(source)
|
||||
return Actor(
|
||||
id,
|
||||
output["out_first_name"] as String,
|
||||
output["out_last_name"] as String,
|
||||
output["out_birth_date"] as Date)
|
||||
}
|
||||
|
||||
// ... additional methods
|
||||
}
|
||||
----
|
||||
|
||||
The code you write for the execution of the call involves creating an `SqlParameterSource`
|
||||
containing the IN parameter. You must match the name provided for the input value
|
||||
with that of the parameter name declared in the stored procedure. The case does not have
|
||||
to match because you use metadata to determine how database objects should be referred to
|
||||
in a stored procedure. What is specified in the source for the stored procedure is not
|
||||
necessarily the way it is stored in the database. Some databases transform names to all
|
||||
upper case, while others use lower case or use the case as specified.
|
||||
|
||||
The `execute` method takes the IN parameters and returns a `Map` that contains any `out`
|
||||
parameters keyed by the name, as specified in the stored procedure. In this case, they are
|
||||
`out_first_name`, `out_last_name`, and `out_birth_date`.
|
||||
|
||||
The last part of the `execute` method creates an `Actor` instance to use to return the
|
||||
data retrieved. Again, it is important to use the names of the `out` parameters as they
|
||||
are declared in the stored procedure. Also, the case in the names of the `out`
|
||||
parameters stored in the results map matches that of the `out` parameter names in the
|
||||
database, which could vary between databases. To make your code more portable, you should
|
||||
do a case-insensitive lookup or instruct Spring to use a `LinkedCaseInsensitiveMap`.
|
||||
To do the latter, you can create your own `JdbcTemplate` and set the `setResultsMapCaseInsensitive`
|
||||
property to `true`. Then you can pass this customized `JdbcTemplate` instance into
|
||||
the constructor of your `SimpleJdbcCall`. The following example shows this configuration:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
public class JdbcActorDao implements ActorDao {
|
||||
|
||||
private SimpleJdbcCall procReadActor;
|
||||
|
||||
public void setDataSource(DataSource dataSource) {
|
||||
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
|
||||
jdbcTemplate.setResultsMapCaseInsensitive(true);
|
||||
this.procReadActor = new SimpleJdbcCall(jdbcTemplate)
|
||||
.withProcedureName("read_actor");
|
||||
}
|
||||
|
||||
// ... additional methods
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
class JdbcActorDao(dataSource: DataSource) : ActorDao {
|
||||
|
||||
private var procReadActor = SimpleJdbcCall(JdbcTemplate(dataSource).apply {
|
||||
isResultsMapCaseInsensitive = true
|
||||
}).withProcedureName("read_actor")
|
||||
|
||||
// ... additional methods
|
||||
}
|
||||
----
|
||||
|
||||
By taking this action, you avoid conflicts in the case used for the names of your
|
||||
returned `out` parameters.
|
||||
|
||||
|
||||
[[jdbc-simple-jdbc-call-2]]
|
||||
== Explicitly Declaring Parameters to Use for a `SimpleJdbcCall`
|
||||
|
||||
Earlier in this chapter, we described how parameters are deduced from metadata, but you can declare them
|
||||
explicitly if you wish. You can do so by creating and configuring `SimpleJdbcCall` with
|
||||
the `declareParameters` method, which takes a variable number of `SqlParameter` objects
|
||||
as input. See the <<jdbc-params, next section>> for details on how to define an `SqlParameter`.
|
||||
|
||||
NOTE: Explicit declarations are necessary if the database you use is not a Spring-supported
|
||||
database. Currently, Spring supports metadata lookup of stored procedure calls for the
|
||||
following databases: Apache Derby, DB2, MySQL, Microsoft SQL Server, Oracle, and Sybase.
|
||||
We also support metadata lookup of stored functions for MySQL, Microsoft SQL Server,
|
||||
and Oracle.
|
||||
|
||||
You can opt to explicitly declare one, some, or all of the parameters. The parameter
|
||||
metadata is still used where you do not explicitly declare parameters. To bypass all
|
||||
processing of metadata lookups for potential parameters and use only the declared
|
||||
parameters, you can call the method `withoutProcedureColumnMetaDataAccess` as part of the
|
||||
declaration. Suppose that you have two or more different call signatures declared for a
|
||||
database function. In this case, you call `useInParameterNames` to specify the list
|
||||
of IN parameter names to include for a given signature.
|
||||
|
||||
The following example shows a fully declared procedure call and uses the information from
|
||||
the preceding example:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
public class JdbcActorDao implements ActorDao {
|
||||
|
||||
private SimpleJdbcCall procReadActor;
|
||||
|
||||
public void setDataSource(DataSource dataSource) {
|
||||
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
|
||||
jdbcTemplate.setResultsMapCaseInsensitive(true);
|
||||
this.procReadActor = new SimpleJdbcCall(jdbcTemplate)
|
||||
.withProcedureName("read_actor")
|
||||
.withoutProcedureColumnMetaDataAccess()
|
||||
.useInParameterNames("in_id")
|
||||
.declareParameters(
|
||||
new SqlParameter("in_id", Types.NUMERIC),
|
||||
new SqlOutParameter("out_first_name", Types.VARCHAR),
|
||||
new SqlOutParameter("out_last_name", Types.VARCHAR),
|
||||
new SqlOutParameter("out_birth_date", Types.DATE)
|
||||
);
|
||||
}
|
||||
|
||||
// ... additional methods
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
class JdbcActorDao(dataSource: DataSource) : ActorDao {
|
||||
|
||||
private val procReadActor = SimpleJdbcCall(JdbcTemplate(dataSource).apply {
|
||||
isResultsMapCaseInsensitive = true
|
||||
}).withProcedureName("read_actor")
|
||||
.withoutProcedureColumnMetaDataAccess()
|
||||
.useInParameterNames("in_id")
|
||||
.declareParameters(
|
||||
SqlParameter("in_id", Types.NUMERIC),
|
||||
SqlOutParameter("out_first_name", Types.VARCHAR),
|
||||
SqlOutParameter("out_last_name", Types.VARCHAR),
|
||||
SqlOutParameter("out_birth_date", Types.DATE)
|
||||
)
|
||||
|
||||
// ... additional methods
|
||||
}
|
||||
----
|
||||
|
||||
The execution and end results of the two examples are the same. The second example specifies all
|
||||
details explicitly rather than relying on metadata.
|
||||
|
||||
|
||||
[[jdbc-params]]
|
||||
== How to Define `SqlParameters`
|
||||
|
||||
To define a parameter for the `SimpleJdbc` classes and also for the RDBMS operations
|
||||
classes (covered in <<jdbc-object>>) you can use `SqlParameter` or one of its subclasses.
|
||||
To do so, you typically specify the parameter name and SQL type in the constructor. The SQL type
|
||||
is specified by using the `java.sql.Types` constants. Earlier in this chapter, we saw declarations
|
||||
similar to the following:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
new SqlParameter("in_id", Types.NUMERIC),
|
||||
new SqlOutParameter("out_first_name", Types.VARCHAR),
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
SqlParameter("in_id", Types.NUMERIC),
|
||||
SqlOutParameter("out_first_name", Types.VARCHAR),
|
||||
----
|
||||
|
||||
The first line with the `SqlParameter` declares an IN parameter. You can use IN parameters
|
||||
for both stored procedure calls and for queries by using the `SqlQuery` and its
|
||||
subclasses (covered in <<jdbc-SqlQuery>>).
|
||||
|
||||
The second line (with the `SqlOutParameter`) declares an `out` parameter to be used in a
|
||||
stored procedure call. There is also an `SqlInOutParameter` for `InOut` parameters
|
||||
(parameters that provide an IN value to the procedure and that also return a value).
|
||||
|
||||
NOTE: Only parameters declared as `SqlParameter` and `SqlInOutParameter` are used to
|
||||
provide input values. This is different from the `StoredProcedure` class, which (for
|
||||
backwards compatibility reasons) lets input values be provided for parameters
|
||||
declared as `SqlOutParameter`.
|
||||
|
||||
For IN parameters, in addition to the name and the SQL type, you can specify a scale for
|
||||
numeric data or a type name for custom database types. For `out` parameters, you can
|
||||
provide a `RowMapper` to handle mapping of rows returned from a `REF` cursor. Another
|
||||
option is to specify an `SqlReturnType` that provides an opportunity to define
|
||||
customized handling of the return values.
|
||||
|
||||
|
||||
[[jdbc-simple-jdbc-call-3]]
|
||||
== Calling a Stored Function by Using `SimpleJdbcCall`
|
||||
|
||||
You can call a stored function in almost the same way as you call a stored procedure, except
|
||||
that you provide a function name rather than a procedure name. You use the
|
||||
`withFunctionName` method as part of the configuration to indicate that you want to make
|
||||
a call to a function, and the corresponding string for a function call is generated. A
|
||||
specialized call (`executeFunction`) is used to run the function, and it
|
||||
returns the function return value as an object of a specified type, which means you do
|
||||
not have to retrieve the return value from the results map. A similar convenience method
|
||||
(named `executeObject`) is also available for stored procedures that have only one `out`
|
||||
parameter. The following example (for MySQL) is based on a stored function named `get_actor_name`
|
||||
that returns an actor's full name:
|
||||
|
||||
[source,sql,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
CREATE FUNCTION get_actor_name (in_id INTEGER)
|
||||
RETURNS VARCHAR(200) READS SQL DATA
|
||||
BEGIN
|
||||
DECLARE out_name VARCHAR(200);
|
||||
SELECT concat(first_name, ' ', last_name)
|
||||
INTO out_name
|
||||
FROM t_actor where id = in_id;
|
||||
RETURN out_name;
|
||||
END;
|
||||
----
|
||||
|
||||
To call this function, we again create a `SimpleJdbcCall` in the initialization method,
|
||||
as the following example shows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
public class JdbcActorDao implements ActorDao {
|
||||
|
||||
private SimpleJdbcCall funcGetActorName;
|
||||
|
||||
public void setDataSource(DataSource dataSource) {
|
||||
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
|
||||
jdbcTemplate.setResultsMapCaseInsensitive(true);
|
||||
this.funcGetActorName = new SimpleJdbcCall(jdbcTemplate)
|
||||
.withFunctionName("get_actor_name");
|
||||
}
|
||||
|
||||
public String getActorName(Long id) {
|
||||
SqlParameterSource in = new MapSqlParameterSource()
|
||||
.addValue("in_id", id);
|
||||
String name = funcGetActorName.executeFunction(String.class, in);
|
||||
return name;
|
||||
}
|
||||
|
||||
// ... additional methods
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
class JdbcActorDao(dataSource: DataSource) : ActorDao {
|
||||
|
||||
private val jdbcTemplate = JdbcTemplate(dataSource).apply {
|
||||
isResultsMapCaseInsensitive = true
|
||||
}
|
||||
private val funcGetActorName = SimpleJdbcCall(jdbcTemplate)
|
||||
.withFunctionName("get_actor_name")
|
||||
|
||||
fun getActorName(id: Long): String {
|
||||
val source = MapSqlParameterSource().addValue("in_id", id)
|
||||
return funcGetActorName.executeFunction(String::class.java, source)
|
||||
}
|
||||
|
||||
// ... additional methods
|
||||
}
|
||||
----
|
||||
|
||||
The `executeFunction` method used returns a `String` that contains the return value from the
|
||||
function call.
|
||||
|
||||
|
||||
[[jdbc-simple-jdbc-call-4]]
|
||||
== Returning a `ResultSet` or REF Cursor from a `SimpleJdbcCall`
|
||||
|
||||
Calling a stored procedure or function that returns a result set is a bit tricky. Some
|
||||
databases return result sets during the JDBC results processing, while others require an
|
||||
explicitly registered `out` parameter of a specific type. Both approaches need
|
||||
additional processing to loop over the result set and process the returned rows. With
|
||||
the `SimpleJdbcCall`, you can use the `returningResultSet` method and declare a `RowMapper`
|
||||
implementation to be used for a specific parameter. If the result set is
|
||||
returned during the results processing, there are no names defined, so the returned
|
||||
results must match the order in which you declare the `RowMapper`
|
||||
implementations. The name specified is still used to store the processed list of results
|
||||
in the results map that is returned from the `execute` statement.
|
||||
|
||||
The next example (for MySQL) uses a stored procedure that takes no IN parameters and returns
|
||||
all rows from the `t_actor` table:
|
||||
|
||||
[source,sql,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
CREATE PROCEDURE read_all_actors()
|
||||
BEGIN
|
||||
SELECT a.id, a.first_name, a.last_name, a.birth_date FROM t_actor a;
|
||||
END;
|
||||
----
|
||||
|
||||
To call this procedure, you can declare the `RowMapper`. Because the class to which you want
|
||||
to map follows the JavaBean rules, you can use a `BeanPropertyRowMapper` that is created by
|
||||
passing in the required class to map to in the `newInstance` method.
|
||||
The following example shows how to do so:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
public class JdbcActorDao implements ActorDao {
|
||||
|
||||
private SimpleJdbcCall procReadAllActors;
|
||||
|
||||
public void setDataSource(DataSource dataSource) {
|
||||
JdbcTemplate jdbcTemplate = new JdbcTemplate(dataSource);
|
||||
jdbcTemplate.setResultsMapCaseInsensitive(true);
|
||||
this.procReadAllActors = new SimpleJdbcCall(jdbcTemplate)
|
||||
.withProcedureName("read_all_actors")
|
||||
.returningResultSet("actors",
|
||||
BeanPropertyRowMapper.newInstance(Actor.class));
|
||||
}
|
||||
|
||||
public List getActorsList() {
|
||||
Map m = procReadAllActors.execute(new HashMap<String, Object>(0));
|
||||
return (List) m.get("actors");
|
||||
}
|
||||
|
||||
// ... additional methods
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
class JdbcActorDao(dataSource: DataSource) : ActorDao {
|
||||
|
||||
private val procReadAllActors = SimpleJdbcCall(JdbcTemplate(dataSource).apply {
|
||||
isResultsMapCaseInsensitive = true
|
||||
}).withProcedureName("read_all_actors")
|
||||
.returningResultSet("actors",
|
||||
BeanPropertyRowMapper.newInstance(Actor::class.java))
|
||||
|
||||
fun getActorsList(): List<Actor> {
|
||||
val m = procReadAllActors.execute(mapOf<String, Any>())
|
||||
return m["actors"] as List<Actor>
|
||||
}
|
||||
|
||||
// ... additional methods
|
||||
}
|
||||
----
|
||||
|
||||
The `execute` call passes in an empty `Map`, because this call does not take any parameters.
|
||||
The list of actors is then retrieved from the results map and returned to the caller.
|
||||
|
||||
|
||||
|
||||
7
framework-docs/modules/ROOT/pages/data-access/orm.adoc
Normal file
7
framework-docs/modules/ROOT/pages/data-access/orm.adoc
Normal file
@@ -0,0 +1,7 @@
|
||||
[[orm]]
|
||||
= Object Relational Mapping (ORM) Data Access
|
||||
|
||||
This section covers data access when you use Object Relational Mapping (ORM).
|
||||
|
||||
|
||||
|
||||
108
framework-docs/modules/ROOT/pages/data-access/orm/general.adoc
Normal file
108
framework-docs/modules/ROOT/pages/data-access/orm/general.adoc
Normal file
@@ -0,0 +1,108 @@
|
||||
[[orm-general]]
|
||||
= General ORM Integration Considerations
|
||||
|
||||
This section highlights considerations that apply to all ORM technologies.
|
||||
The <<orm-hibernate>> section provides more details and also show these features and
|
||||
configurations in a concrete context.
|
||||
|
||||
The major goal of Spring's ORM integration is clear application layering (with any data
|
||||
access and transaction technology) and for loose coupling of application objects -- no
|
||||
more business service dependencies on the data access or transaction strategy, no more
|
||||
hard-coded resource lookups, no more hard-to-replace singletons, no more custom service
|
||||
registries. The goal is to have one simple and consistent approach to wiring up application objects, keeping
|
||||
them as reusable and free from container dependencies as possible. All the individual
|
||||
data access features are usable on their own but integrate nicely with Spring's
|
||||
application context concept, providing XML-based configuration and cross-referencing of
|
||||
plain JavaBean instances that need not be Spring-aware. In a typical Spring application,
|
||||
many important objects are JavaBeans: data access templates, data access objects,
|
||||
transaction managers, business services that use the data access objects and transaction
|
||||
managers, web view resolvers, web controllers that use the business services, and so on.
|
||||
|
||||
|
||||
[[orm-resource-mngmnt]]
|
||||
== Resource and Transaction Management
|
||||
|
||||
Typical business applications are cluttered with repetitive resource management code.
|
||||
Many projects try to invent their own solutions, sometimes sacrificing proper handling
|
||||
of failures for programming convenience. Spring advocates simple solutions for proper
|
||||
resource handling, namely IoC through templating in the case of JDBC and applying AOP
|
||||
interceptors for the ORM technologies.
|
||||
|
||||
The infrastructure provides proper resource handling and appropriate conversion of
|
||||
specific API exceptions to an unchecked infrastructure exception hierarchy. Spring
|
||||
introduces a DAO exception hierarchy, applicable to any data access strategy. For direct
|
||||
JDBC, the `JdbcTemplate` class mentioned in a <<jdbc-JdbcTemplate, previous section>>
|
||||
provides connection handling and proper conversion of `SQLException` to the
|
||||
`DataAccessException` hierarchy, including translation of database-specific SQL error
|
||||
codes to meaningful exception classes. For ORM technologies, see the
|
||||
<<orm-exception-translation, next section>> for how to get the same exception
|
||||
translation benefits.
|
||||
|
||||
When it comes to transaction management, the `JdbcTemplate` class hooks in to the Spring
|
||||
transaction support and supports both JTA and JDBC transactions, through respective
|
||||
Spring transaction managers. For the supported ORM technologies, Spring offers Hibernate
|
||||
and JPA support through the Hibernate and JPA transaction managers as well as JTA support.
|
||||
For details on transaction support, see the <<transaction>> chapter.
|
||||
|
||||
|
||||
[[orm-exception-translation]]
|
||||
== Exception Translation
|
||||
|
||||
When you use Hibernate or JPA in a DAO, you must decide how to handle the persistence
|
||||
technology's native exception classes. The DAO throws a subclass of a `HibernateException`
|
||||
or `PersistenceException`, depending on the technology. These exceptions are all runtime
|
||||
exceptions and do not have to be declared or caught. You may also have to deal with
|
||||
`IllegalArgumentException` and `IllegalStateException`. This means that callers can only
|
||||
treat exceptions as being generally fatal, unless they want to depend on the persistence
|
||||
technology's own exception structure. Catching specific causes (such as an optimistic
|
||||
locking failure) is not possible without tying the caller to the implementation strategy.
|
||||
This trade-off might be acceptable to applications that are strongly ORM-based or
|
||||
do not need any special exception treatment (or both). However, Spring lets exception
|
||||
translation be applied transparently through the `@Repository` annotation. The following
|
||||
examples (one for Java configuration and one for XML configuration) show how to do so:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@Repository
|
||||
public class ProductDaoImpl implements ProductDao {
|
||||
|
||||
// class body here...
|
||||
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Repository
|
||||
class ProductDaoImpl : ProductDao {
|
||||
|
||||
// class body here...
|
||||
|
||||
}
|
||||
----
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<beans>
|
||||
|
||||
<!-- Exception translation bean post processor -->
|
||||
<bean class="org.springframework.dao.annotation.PersistenceExceptionTranslationPostProcessor"/>
|
||||
|
||||
<bean id="myProductDao" class="product.ProductDaoImpl"/>
|
||||
|
||||
</beans>
|
||||
----
|
||||
|
||||
The postprocessor automatically looks for all exception translators (implementations of
|
||||
the `PersistenceExceptionTranslator` interface) and advises all beans marked with the
|
||||
`@Repository` annotation so that the discovered translators can intercept and apply the
|
||||
appropriate translation on the thrown exceptions.
|
||||
|
||||
In summary, you can implement DAOs based on the plain persistence technology's API and
|
||||
annotations while still benefiting from Spring-managed transactions, dependency
|
||||
injection, and transparent exception conversion (if desired) to Spring's custom
|
||||
exception hierarchies.
|
||||
|
||||
|
||||
|
||||
490
framework-docs/modules/ROOT/pages/data-access/orm/hibernate.adoc
Normal file
490
framework-docs/modules/ROOT/pages/data-access/orm/hibernate.adoc
Normal file
@@ -0,0 +1,490 @@
|
||||
[[orm-hibernate]]
|
||||
= Hibernate
|
||||
|
||||
We start with a coverage of https://hibernate.org/[Hibernate 5] in a Spring environment,
|
||||
using it to demonstrate the approach that Spring takes towards integrating OR mappers.
|
||||
This section covers many issues in detail and shows different variations of DAO
|
||||
implementations and transaction demarcation. Most of these patterns can be directly
|
||||
translated to all other supported ORM tools. The later sections in this chapter then
|
||||
cover the other ORM technologies and show brief examples.
|
||||
|
||||
NOTE: As of Spring Framework 5.3, Spring requires Hibernate ORM 5.2+ for Spring's
|
||||
`HibernateJpaVendorAdapter` as well as for a native Hibernate `SessionFactory` setup.
|
||||
It is strongly recommended to go with Hibernate ORM 5.4 for a newly started application.
|
||||
For use with `HibernateJpaVendorAdapter`, Hibernate Search needs to be upgraded to 5.11.6.
|
||||
|
||||
|
||||
[[orm-session-factory-setup]]
|
||||
== `SessionFactory` Setup in a Spring Container
|
||||
|
||||
To avoid tying application objects to hard-coded resource lookups, you can define
|
||||
resources (such as a JDBC `DataSource` or a Hibernate `SessionFactory`) as beans in the
|
||||
Spring container. Application objects that need to access resources receive references
|
||||
to such predefined instances through bean references, as illustrated in the DAO
|
||||
definition in the <<orm-hibernate-straight, next section>>.
|
||||
|
||||
The following excerpt from an XML application context definition shows how to set up a
|
||||
JDBC `DataSource` and a Hibernate `SessionFactory` on top of it:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<beans>
|
||||
|
||||
<bean id="myDataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
|
||||
<property name="driverClassName" value="org.hsqldb.jdbcDriver"/>
|
||||
<property name="url" value="jdbc:hsqldb:hsql://localhost:9001"/>
|
||||
<property name="username" value="sa"/>
|
||||
<property name="password" value=""/>
|
||||
</bean>
|
||||
|
||||
<bean id="mySessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
|
||||
<property name="dataSource" ref="myDataSource"/>
|
||||
<property name="mappingResources">
|
||||
<list>
|
||||
<value>product.hbm.xml</value>
|
||||
</list>
|
||||
</property>
|
||||
<property name="hibernateProperties">
|
||||
<value>
|
||||
hibernate.dialect=org.hibernate.dialect.HSQLDialect
|
||||
</value>
|
||||
</property>
|
||||
</bean>
|
||||
|
||||
</beans>
|
||||
----
|
||||
|
||||
Switching from a local Jakarta Commons DBCP `BasicDataSource` to a JNDI-located
|
||||
`DataSource` (usually managed by an application server) is only a matter of
|
||||
configuration, as the following example shows:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<beans>
|
||||
<jee:jndi-lookup id="myDataSource" jndi-name="java:comp/env/jdbc/myds"/>
|
||||
</beans>
|
||||
----
|
||||
|
||||
You can also access a JNDI-located `SessionFactory`, using Spring's
|
||||
`JndiObjectFactoryBean` / `<jee:jndi-lookup>` to retrieve and expose it.
|
||||
However, that is typically not common outside of an EJB context.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
Spring also provides a `LocalSessionFactoryBuilder` variant, seamlessly integrating
|
||||
with `@Bean` style configuration and programmatic setup (no `FactoryBean` involved).
|
||||
|
||||
Both `LocalSessionFactoryBean` and `LocalSessionFactoryBuilder` support background
|
||||
bootstrapping, with Hibernate initialization running in parallel to the application
|
||||
bootstrap thread on a given bootstrap executor (such as a `SimpleAsyncTaskExecutor`).
|
||||
On `LocalSessionFactoryBean`, this is available through the `bootstrapExecutor`
|
||||
property. On the programmatic `LocalSessionFactoryBuilder`, there is an overloaded
|
||||
`buildSessionFactory` method that takes a bootstrap executor argument.
|
||||
|
||||
As of Spring Framework 5.1, such a native Hibernate setup can also expose a JPA
|
||||
`EntityManagerFactory` for standard JPA interaction next to native Hibernate access.
|
||||
See <<orm-jpa-hibernate, Native Hibernate Setup for JPA>> for details.
|
||||
====
|
||||
|
||||
|
||||
[[orm-hibernate-straight]]
|
||||
== Implementing DAOs Based on the Plain Hibernate API
|
||||
|
||||
Hibernate has a feature called contextual sessions, wherein Hibernate itself manages
|
||||
one current `Session` per transaction. This is roughly equivalent to Spring's
|
||||
synchronization of one Hibernate `Session` per transaction. A corresponding DAO
|
||||
implementation resembles the following example, based on the plain Hibernate API:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
public class ProductDaoImpl implements ProductDao {
|
||||
|
||||
private SessionFactory sessionFactory;
|
||||
|
||||
public void setSessionFactory(SessionFactory sessionFactory) {
|
||||
this.sessionFactory = sessionFactory;
|
||||
}
|
||||
|
||||
public Collection loadProductsByCategory(String category) {
|
||||
return this.sessionFactory.getCurrentSession()
|
||||
.createQuery("from test.Product product where product.category=?")
|
||||
.setParameter(0, category)
|
||||
.list();
|
||||
}
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
class ProductDaoImpl(private val sessionFactory: SessionFactory) : ProductDao {
|
||||
|
||||
fun loadProductsByCategory(category: String): Collection<*> {
|
||||
return sessionFactory.currentSession
|
||||
.createQuery("from test.Product product where product.category=?")
|
||||
.setParameter(0, category)
|
||||
.list()
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
This style is similar to that of the Hibernate reference documentation and examples,
|
||||
except for holding the `SessionFactory` in an instance variable. We strongly recommend
|
||||
such an instance-based setup over the old-school `static` `HibernateUtil` class from
|
||||
Hibernate's CaveatEmptor sample application. (In general, do not keep any resources in
|
||||
`static` variables unless absolutely necessary.)
|
||||
|
||||
The preceding DAO example follows the dependency injection pattern. It fits nicely into a Spring IoC
|
||||
container, as it would if coded against Spring's `HibernateTemplate`.
|
||||
You can also set up such a DAO in plain Java (for example, in unit tests). To do so,
|
||||
instantiate it and call `setSessionFactory(..)` with the desired factory reference. As a
|
||||
Spring bean definition, the DAO would resemble the following:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<beans>
|
||||
|
||||
<bean id="myProductDao" class="product.ProductDaoImpl">
|
||||
<property name="sessionFactory" ref="mySessionFactory"/>
|
||||
</bean>
|
||||
|
||||
</beans>
|
||||
----
|
||||
|
||||
The main advantage of this DAO style is that it depends on Hibernate API only. No import
|
||||
of any Spring class is required. This is appealing from a non-invasiveness
|
||||
perspective and may feel more natural to Hibernate developers.
|
||||
|
||||
However, the DAO throws plain `HibernateException` (which is unchecked, so it does not have
|
||||
to be declared or caught), which means that callers can treat exceptions only as being
|
||||
generally fatal -- unless they want to depend on Hibernate's own exception hierarchy.
|
||||
Catching specific causes (such as an optimistic locking failure) is not possible without
|
||||
tying the caller to the implementation strategy. This trade off might be acceptable to
|
||||
applications that are strongly Hibernate-based, do not need any special exception
|
||||
treatment, or both.
|
||||
|
||||
Fortunately, Spring's `LocalSessionFactoryBean` supports Hibernate's
|
||||
`SessionFactory.getCurrentSession()` method for any Spring transaction strategy,
|
||||
returning the current Spring-managed transactional `Session`, even with
|
||||
`HibernateTransactionManager`. The standard behavior of that method remains
|
||||
to return the current `Session` associated with the ongoing JTA transaction, if any.
|
||||
This behavior applies regardless of whether you use Spring's
|
||||
`JtaTransactionManager`, EJB container managed transactions (CMTs), or JTA.
|
||||
|
||||
In summary, you can implement DAOs based on the plain Hibernate API, while still being
|
||||
able to participate in Spring-managed transactions.
|
||||
|
||||
|
||||
[[orm-hibernate-tx-declarative]]
|
||||
== Declarative Transaction Demarcation
|
||||
|
||||
We recommend that you use Spring's declarative transaction support, which lets you
|
||||
replace explicit transaction demarcation API calls in your Java code with an AOP
|
||||
transaction interceptor. You can configure this transaction interceptor in a Spring
|
||||
container by using either Java annotations or XML. This declarative transaction capability
|
||||
lets you keep business services free of repetitive transaction demarcation code and
|
||||
focus on adding business logic, which is the real value of your application.
|
||||
|
||||
NOTE: Before you continue, we are strongly encourage you to read <<transaction-declarative>>
|
||||
if you have not already done so.
|
||||
|
||||
You can annotate the service layer with `@Transactional` annotations and instruct the
|
||||
Spring container to find these annotations and provide transactional semantics for
|
||||
these annotated methods. The following example shows how to do so:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
public class ProductServiceImpl implements ProductService {
|
||||
|
||||
private ProductDao productDao;
|
||||
|
||||
public void setProductDao(ProductDao productDao) {
|
||||
this.productDao = productDao;
|
||||
}
|
||||
|
||||
@Transactional
|
||||
public void increasePriceOfAllProductsInCategory(final String category) {
|
||||
List productsToChange = this.productDao.loadProductsByCategory(category);
|
||||
// ...
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public List<Product> findAllProducts() {
|
||||
return this.productDao.findAllProducts();
|
||||
}
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
class ProductServiceImpl(private val productDao: ProductDao) : ProductService {
|
||||
|
||||
@Transactional
|
||||
fun increasePriceOfAllProductsInCategory(category: String) {
|
||||
val productsToChange = productDao.loadProductsByCategory(category)
|
||||
// ...
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
fun findAllProducts() = productDao.findAllProducts()
|
||||
}
|
||||
----
|
||||
|
||||
In the container, you need to set up the `PlatformTransactionManager` implementation
|
||||
(as a bean) and a `<tx:annotation-driven/>` entry, opting into `@Transactional`
|
||||
processing at runtime. The following example shows how to do so:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:aop="http://www.springframework.org/schema/aop"
|
||||
xmlns:tx="http://www.springframework.org/schema/tx"
|
||||
xsi:schemaLocation="
|
||||
http://www.springframework.org/schema/beans
|
||||
https://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/tx
|
||||
https://www.springframework.org/schema/tx/spring-tx.xsd
|
||||
http://www.springframework.org/schema/aop
|
||||
https://www.springframework.org/schema/aop/spring-aop.xsd">
|
||||
|
||||
<!-- SessionFactory, DataSource, etc. omitted -->
|
||||
|
||||
<bean id="transactionManager"
|
||||
class="org.springframework.orm.hibernate5.HibernateTransactionManager">
|
||||
<property name="sessionFactory" ref="sessionFactory"/>
|
||||
</bean>
|
||||
|
||||
<tx:annotation-driven/>
|
||||
|
||||
<bean id="myProductService" class="product.SimpleProductService">
|
||||
<property name="productDao" ref="myProductDao"/>
|
||||
</bean>
|
||||
|
||||
</beans>
|
||||
----
|
||||
|
||||
|
||||
[[orm-hibernate-tx-programmatic]]
|
||||
== Programmatic Transaction Demarcation
|
||||
|
||||
You can demarcate transactions in a higher level of the application, on top of
|
||||
lower-level data access services that span any number of operations. Nor do restrictions
|
||||
exist on the implementation of the surrounding business service. It needs only a Spring
|
||||
`PlatformTransactionManager`. Again, the latter can come from anywhere, but preferably
|
||||
as a bean reference through a `setTransactionManager(..)` method. Also, the
|
||||
`productDAO` should be set by a `setProductDao(..)` method. The following pair of snippets show
|
||||
a transaction manager and a business service definition in a Spring application context
|
||||
and an example for a business method implementation:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<beans>
|
||||
|
||||
<bean id="myTxManager" class="org.springframework.orm.hibernate5.HibernateTransactionManager">
|
||||
<property name="sessionFactory" ref="mySessionFactory"/>
|
||||
</bean>
|
||||
|
||||
<bean id="myProductService" class="product.ProductServiceImpl">
|
||||
<property name="transactionManager" ref="myTxManager"/>
|
||||
<property name="productDao" ref="myProductDao"/>
|
||||
</bean>
|
||||
|
||||
</beans>
|
||||
----
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
public class ProductServiceImpl implements ProductService {
|
||||
|
||||
private TransactionTemplate transactionTemplate;
|
||||
private ProductDao productDao;
|
||||
|
||||
public void setTransactionManager(PlatformTransactionManager transactionManager) {
|
||||
this.transactionTemplate = new TransactionTemplate(transactionManager);
|
||||
}
|
||||
|
||||
public void setProductDao(ProductDao productDao) {
|
||||
this.productDao = productDao;
|
||||
}
|
||||
|
||||
public void increasePriceOfAllProductsInCategory(final String category) {
|
||||
this.transactionTemplate.execute(new TransactionCallbackWithoutResult() {
|
||||
public void doInTransactionWithoutResult(TransactionStatus status) {
|
||||
List productsToChange = this.productDao.loadProductsByCategory(category);
|
||||
// do the price increase...
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
class ProductServiceImpl(transactionManager: PlatformTransactionManager,
|
||||
private val productDao: ProductDao) : ProductService {
|
||||
|
||||
private val transactionTemplate = TransactionTemplate(transactionManager)
|
||||
|
||||
fun increasePriceOfAllProductsInCategory(category: String) {
|
||||
transactionTemplate.execute {
|
||||
val productsToChange = productDao.loadProductsByCategory(category)
|
||||
// do the price increase...
|
||||
}
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Spring's `TransactionInterceptor` lets any checked application exception be thrown
|
||||
with the callback code, while `TransactionTemplate` is restricted to unchecked
|
||||
exceptions within the callback. `TransactionTemplate` triggers a rollback in case of
|
||||
an unchecked application exception or if the transaction is marked rollback-only by
|
||||
the application (by setting `TransactionStatus`). By default, `TransactionInterceptor`
|
||||
behaves the same way but allows configurable rollback policies per method.
|
||||
|
||||
|
||||
[[orm-hibernate-tx-strategies]]
|
||||
== Transaction Management Strategies
|
||||
|
||||
Both `TransactionTemplate` and `TransactionInterceptor` delegate the actual transaction
|
||||
handling to a `PlatformTransactionManager` instance (which can be a
|
||||
`HibernateTransactionManager` (for a single Hibernate `SessionFactory`) by using a
|
||||
`ThreadLocal` `Session` under the hood) or a `JtaTransactionManager` (delegating to the
|
||||
JTA subsystem of the container) for Hibernate applications. You can even use a custom
|
||||
`PlatformTransactionManager` implementation. Switching from native Hibernate transaction
|
||||
management to JTA (such as when facing distributed transaction requirements for certain
|
||||
deployments of your application) is only a matter of configuration. You can replace
|
||||
the Hibernate transaction manager with Spring's JTA transaction implementation. Both
|
||||
transaction demarcation and data access code work without changes, because they
|
||||
use the generic transaction management APIs.
|
||||
|
||||
For distributed transactions across multiple Hibernate session factories, you can combine
|
||||
`JtaTransactionManager` as a transaction strategy with multiple
|
||||
`LocalSessionFactoryBean` definitions. Each DAO then gets one specific `SessionFactory`
|
||||
reference passed into its corresponding bean property. If all underlying JDBC data
|
||||
sources are transactional container ones, a business service can demarcate transactions
|
||||
across any number of DAOs and any number of session factories without special regard, as
|
||||
long as it uses `JtaTransactionManager` as the strategy.
|
||||
|
||||
Both `HibernateTransactionManager` and `JtaTransactionManager` allow for proper
|
||||
JVM-level cache handling with Hibernate, without container-specific transaction manager
|
||||
lookup or a JCA connector (if you do not use EJB to initiate transactions).
|
||||
|
||||
`HibernateTransactionManager` can export the Hibernate JDBC `Connection` to plain JDBC
|
||||
access code for a specific `DataSource`. This ability allows for high-level
|
||||
transaction demarcation with mixed Hibernate and JDBC data access completely without
|
||||
JTA, provided you access only one database. `HibernateTransactionManager` automatically
|
||||
exposes the Hibernate transaction as a JDBC transaction if you have set up the passed-in
|
||||
`SessionFactory` with a `DataSource` through the `dataSource` property of the
|
||||
`LocalSessionFactoryBean` class. Alternatively, you can specify explicitly the
|
||||
`DataSource` for which the transactions are supposed to be exposed through the
|
||||
`dataSource` property of the `HibernateTransactionManager` class.
|
||||
|
||||
|
||||
[[orm-hibernate-resources]]
|
||||
== Comparing Container-managed and Locally Defined Resources
|
||||
|
||||
You can switch between a container-managed JNDI `SessionFactory` and a locally defined
|
||||
one without having to change a single line of application code. Whether to keep
|
||||
resource definitions in the container or locally within the application is mainly a
|
||||
matter of the transaction strategy that you use. Compared to a Spring-defined local
|
||||
`SessionFactory`, a manually registered JNDI `SessionFactory` does not provide any
|
||||
benefits. Deploying a `SessionFactory` through Hibernate's JCA connector provides the
|
||||
added value of participating in the Jakarta EE server's management infrastructure, but does
|
||||
not add actual value beyond that.
|
||||
|
||||
Spring's transaction support is not bound to a container. When configured with any strategy
|
||||
other than JTA, transaction support also works in a stand-alone or test environment.
|
||||
Especially in the typical case of single-database transactions, Spring's single-resource
|
||||
local transaction support is a lightweight and powerful alternative to JTA. When you use
|
||||
local EJB stateless session beans to drive transactions, you depend both on an EJB
|
||||
container and on JTA, even if you access only a single database and use only stateless
|
||||
session beans to provide declarative transactions through container-managed
|
||||
transactions. Direct use of JTA programmatically also requires a Jakarta EE environment.
|
||||
|
||||
Spring-driven transactions can work as well with a locally defined Hibernate
|
||||
`SessionFactory` as they do with a local JDBC `DataSource`, provided they access a
|
||||
single database. Thus, you need only use Spring's JTA transaction strategy when you
|
||||
have distributed transaction requirements. A JCA connector requires container-specific
|
||||
deployment steps, and (obviously) JCA support in the first place. This configuration
|
||||
requires more work than deploying a simple web application with local resource
|
||||
definitions and Spring-driven transactions.
|
||||
|
||||
All things considered, if you do not use EJBs, stick with local `SessionFactory` setup
|
||||
and Spring's `HibernateTransactionManager` or `JtaTransactionManager`. You get all of
|
||||
the benefits, including proper transactional JVM-level caching and distributed
|
||||
transactions, without the inconvenience of container deployment. JNDI registration of a
|
||||
Hibernate `SessionFactory` through the JCA connector adds value only when used in
|
||||
conjunction with EJBs.
|
||||
|
||||
|
||||
[[orm-hibernate-invalid-jdbc-access-error]]
|
||||
== Spurious Application Server Warnings with Hibernate
|
||||
|
||||
In some JTA environments with very strict `XADataSource` implementations (currently
|
||||
some WebLogic Server and WebSphere versions), when Hibernate is configured without
|
||||
regard to the JTA transaction manager for that environment, spurious warnings or
|
||||
exceptions can show up in the application server log. These warnings or exceptions
|
||||
indicate that the connection being accessed is no longer valid or JDBC access is no
|
||||
longer valid, possibly because the transaction is no longer active. As an example,
|
||||
here is an actual exception from WebLogic:
|
||||
|
||||
[literal]
|
||||
[subs="verbatim,quotes"]
|
||||
----
|
||||
java.sql.SQLException: The transaction is no longer active - status: 'Committed'. No
|
||||
further JDBC access is allowed within this transaction.
|
||||
----
|
||||
|
||||
Another common problem is a connection leak after JTA transactions, with Hibernate
|
||||
sessions (and potentially underlying JDBC connections) not getting closed properly.
|
||||
|
||||
You can resolve such issues by making Hibernate aware of the JTA transaction manager,
|
||||
to which it synchronizes (along with Spring). You have two options for doing this:
|
||||
|
||||
* Pass your Spring `JtaTransactionManager` bean to your Hibernate setup. The easiest
|
||||
way is a bean reference into the `jtaTransactionManager` property for your
|
||||
`LocalSessionFactoryBean` bean (see <<transaction-strategies-hibernate>>).
|
||||
Spring then makes the corresponding JTA strategies available to Hibernate.
|
||||
* You may also configure Hibernate's JTA-related properties explicitly, in particular
|
||||
"hibernate.transaction.coordinator_class", "hibernate.connection.handling_mode"
|
||||
and potentially "hibernate.transaction.jta.platform" in your "hibernateProperties"
|
||||
on `LocalSessionFactoryBean` (see Hibernate's manual for details on those properties).
|
||||
|
||||
The remainder of this section describes the sequence of events that occur with and
|
||||
without Hibernate's awareness of the JTA `PlatformTransactionManager`.
|
||||
|
||||
When Hibernate is not configured with any awareness of the JTA transaction manager,
|
||||
the following events occur when a JTA transaction commits:
|
||||
|
||||
* The JTA transaction commits.
|
||||
* Spring's `JtaTransactionManager` is synchronized to the JTA transaction, so it is
|
||||
called back through an `afterCompletion` callback by the JTA transaction manager.
|
||||
* Among other activities, this synchronization can trigger a callback by Spring to
|
||||
Hibernate, through Hibernate's `afterTransactionCompletion` callback (used to clear
|
||||
the Hibernate cache), followed by an explicit `close()` call on the Hibernate session,
|
||||
which causes Hibernate to attempt to `close()` the JDBC Connection.
|
||||
* In some environments, this `Connection.close()` call then triggers the warning or
|
||||
error, as the application server no longer considers the `Connection` to be usable,
|
||||
because the transaction has already been committed.
|
||||
|
||||
When Hibernate is configured with awareness of the JTA transaction manager,
|
||||
the following events occur when a JTA transaction commits:
|
||||
|
||||
* The JTA transaction is ready to commit.
|
||||
* Spring's `JtaTransactionManager` is synchronized to the JTA transaction, so the
|
||||
transaction is called back through a `beforeCompletion` callback by the JTA
|
||||
transaction manager.
|
||||
* Spring is aware that Hibernate itself is synchronized to the JTA transaction and
|
||||
behaves differently than in the previous scenario. In particular, it aligns with
|
||||
Hibernate's transactional resource management.
|
||||
* The JTA transaction commits.
|
||||
* Hibernate is synchronized to the JTA transaction, so the transaction is called back
|
||||
through an `afterCompletion` callback by the JTA transaction manager and can
|
||||
properly clear its cache.
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,66 @@
|
||||
[[orm-introduction]]
|
||||
= Introduction to ORM with Spring
|
||||
|
||||
The Spring Framework supports integration with the Java Persistence API (JPA) and
|
||||
supports native Hibernate for resource management, data access object (DAO) implementations,
|
||||
and transaction strategies. For example, for Hibernate, there is first-class support with
|
||||
several convenient IoC features that address many typical Hibernate integration issues.
|
||||
You can configure all of the supported features for OR (object relational) mapping
|
||||
tools through Dependency Injection. They can participate in Spring's resource and
|
||||
transaction management, and they comply with Spring's generic transaction and DAO
|
||||
exception hierarchies. The recommended integration style is to code DAOs against plain
|
||||
Hibernate or JPA APIs.
|
||||
|
||||
Spring adds significant enhancements to the ORM layer of your choice when you create
|
||||
data access applications. You can leverage as much of the integration support as you
|
||||
wish, and you should compare this integration effort with the cost and risk of building
|
||||
a similar infrastructure in-house. You can use much of the ORM support as you would a
|
||||
library, regardless of technology, because everything is designed as a set of reusable
|
||||
JavaBeans. ORM in a Spring IoC container facilitates configuration and deployment. Thus,
|
||||
most examples in this section show configuration inside a Spring container.
|
||||
|
||||
The benefits of using the Spring Framework to create your ORM DAOs include:
|
||||
|
||||
* *Easier testing.* Spring's IoC approach makes it easy to swap the implementations
|
||||
and configuration locations of Hibernate `SessionFactory` instances, JDBC `DataSource`
|
||||
instances, transaction managers, and mapped object implementations (if needed). This
|
||||
in turn makes it much easier to test each piece of persistence-related code in
|
||||
isolation.
|
||||
* *Common data access exceptions.* Spring can wrap exceptions from your ORM tool,
|
||||
converting them from proprietary (potentially checked) exceptions to a common runtime
|
||||
`DataAccessException` hierarchy. This feature lets you handle most persistence
|
||||
exceptions, which are non-recoverable, only in the appropriate layers, without
|
||||
annoying boilerplate catches, throws, and exception declarations. You can still trap
|
||||
and handle exceptions as necessary. Remember that JDBC exceptions (including
|
||||
DB-specific dialects) are also converted to the same hierarchy, meaning that you can
|
||||
perform some operations with JDBC within a consistent programming model.
|
||||
* *General resource management.* Spring application contexts can handle the location
|
||||
and configuration of Hibernate `SessionFactory` instances, JPA `EntityManagerFactory`
|
||||
instances, JDBC `DataSource` instances, and other related resources. This makes these
|
||||
values easy to manage and change. Spring offers efficient, easy, and safe handling of
|
||||
persistence resources. For example, related code that uses Hibernate generally needs to
|
||||
use the same Hibernate `Session` to ensure efficiency and proper transaction handling.
|
||||
Spring makes it easy to create and bind a `Session` to the current thread transparently,
|
||||
by exposing a current `Session` through the Hibernate `SessionFactory`. Thus, Spring
|
||||
solves many chronic problems of typical Hibernate usage, for any local or JTA
|
||||
transaction environment.
|
||||
* *Integrated transaction management.* You can wrap your ORM code with a declarative,
|
||||
aspect-oriented programming (AOP) style method interceptor either through the
|
||||
`@Transactional` annotation or by explicitly configuring the transaction AOP advice in
|
||||
an XML configuration file. In both cases, transaction semantics and exception handling
|
||||
(rollback and so on) are handled for you. As discussed in <<orm-resource-mngmnt>>,
|
||||
you can also swap various transaction managers, without affecting your ORM-related code.
|
||||
For example, you can swap between local transactions and JTA, with the same full services
|
||||
(such as declarative transactions) available in both scenarios. Additionally,
|
||||
JDBC-related code can fully integrate transactionally with the code you use to do ORM.
|
||||
This is useful for data access that is not suitable for ORM (such as batch processing and
|
||||
BLOB streaming) but that still needs to share common transactions with ORM operations.
|
||||
|
||||
TIP: For more comprehensive ORM support, including support for alternative database
|
||||
technologies such as MongoDB, you might want to check out the
|
||||
https://projects.spring.io/spring-data/[Spring Data] suite of projects. If you are
|
||||
a JPA user, the https://spring.io/guides/gs/accessing-data-jpa/[Getting Started Accessing
|
||||
Data with JPA] guide from https://spring.io provides a great introduction.
|
||||
|
||||
|
||||
|
||||
577
framework-docs/modules/ROOT/pages/data-access/orm/jpa.adoc
Normal file
577
framework-docs/modules/ROOT/pages/data-access/orm/jpa.adoc
Normal file
@@ -0,0 +1,577 @@
|
||||
[[orm-jpa]]
|
||||
= JPA
|
||||
|
||||
The Spring JPA, available under the `org.springframework.orm.jpa` package, offers
|
||||
comprehensive support for the
|
||||
https://www.oracle.com/technetwork/articles/javaee/jpa-137156.html[Java Persistence
|
||||
API] in a manner similar to the integration with Hibernate while being aware of
|
||||
the underlying implementation in order to provide additional features.
|
||||
|
||||
|
||||
[[orm-jpa-setup]]
|
||||
== Three Options for JPA Setup in a Spring Environment
|
||||
|
||||
The Spring JPA support offers three ways of setting up the JPA `EntityManagerFactory`
|
||||
that is used by the application to obtain an entity manager.
|
||||
|
||||
* <<orm-jpa-setup-lemfb>>
|
||||
* <<orm-jpa-setup-jndi>>
|
||||
* <<orm-jpa-setup-lcemfb>>
|
||||
|
||||
[[orm-jpa-setup-lemfb]]
|
||||
=== Using `LocalEntityManagerFactoryBean`
|
||||
|
||||
You can use this option only in simple deployment environments such as stand-alone
|
||||
applications and integration tests.
|
||||
|
||||
The `LocalEntityManagerFactoryBean` creates an `EntityManagerFactory` suitable for
|
||||
simple deployment environments where the application uses only JPA for data access.
|
||||
The factory bean uses the JPA `PersistenceProvider` auto-detection mechanism (according
|
||||
to JPA's Java SE bootstrapping) and, in most cases, requires you to specify only the
|
||||
persistence unit name. The following XML example configures such a bean:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<beans>
|
||||
<bean id="myEmf" class="org.springframework.orm.jpa.LocalEntityManagerFactoryBean">
|
||||
<property name="persistenceUnitName" value="myPersistenceUnit"/>
|
||||
</bean>
|
||||
</beans>
|
||||
----
|
||||
|
||||
This form of JPA deployment is the simplest and the most limited. You cannot refer to an
|
||||
existing JDBC `DataSource` bean definition, and no support for global transactions
|
||||
exists. Furthermore, weaving (byte-code transformation) of persistent classes is
|
||||
provider-specific, often requiring a specific JVM agent to be specified on startup. This
|
||||
option is sufficient only for stand-alone applications and test environments, for which
|
||||
the JPA specification is designed.
|
||||
|
||||
[[orm-jpa-setup-jndi]]
|
||||
=== Obtaining an EntityManagerFactory from JNDI
|
||||
|
||||
You can use this option when deploying to a Jakarta EE server. Check your server's documentation
|
||||
on how to deploy a custom JPA provider into your server, allowing for a different
|
||||
provider than the server's default.
|
||||
|
||||
Obtaining an `EntityManagerFactory` from JNDI (for example in a Jakarta EE environment),
|
||||
is a matter of changing the XML configuration, as the following example shows:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<beans>
|
||||
<jee:jndi-lookup id="myEmf" jndi-name="persistence/myPersistenceUnit"/>
|
||||
</beans>
|
||||
----
|
||||
|
||||
This action assumes standard Jakarta EE bootstrapping. The Jakarta EE server auto-detects
|
||||
persistence units (in effect, `META-INF/persistence.xml` files in application jars) and
|
||||
`persistence-unit-ref` entries in the Jakarta EE deployment descriptor (for example,
|
||||
`web.xml`) and defines environment naming context locations for those persistence units.
|
||||
|
||||
In such a scenario, the entire persistence unit deployment, including the weaving
|
||||
(byte-code transformation) of persistent classes, is up to the Jakarta EE server. The JDBC
|
||||
`DataSource` is defined through a JNDI location in the `META-INF/persistence.xml` file.
|
||||
`EntityManager` transactions are integrated with the server's JTA subsystem. Spring merely
|
||||
uses the obtained `EntityManagerFactory`, passing it on to application objects through
|
||||
dependency injection and managing transactions for the persistence unit (typically
|
||||
through `JtaTransactionManager`).
|
||||
|
||||
If you use multiple persistence units in the same application, the bean names of such
|
||||
JNDI-retrieved persistence units should match the persistence unit names that the
|
||||
application uses to refer to them (for example, in `@PersistenceUnit` and
|
||||
`@PersistenceContext` annotations).
|
||||
|
||||
[[orm-jpa-setup-lcemfb]]
|
||||
=== Using `LocalContainerEntityManagerFactoryBean`
|
||||
|
||||
You can use this option for full JPA capabilities in a Spring-based application environment.
|
||||
This includes web containers such as Tomcat, stand-alone applications, and
|
||||
integration tests with sophisticated persistence requirements.
|
||||
|
||||
NOTE: If you want to specifically configure a Hibernate setup, an immediate alternative
|
||||
is to set up a native Hibernate `LocalSessionFactoryBean` instead of a plain JPA
|
||||
`LocalContainerEntityManagerFactoryBean`, letting it interact with JPA access code
|
||||
as well as native Hibernate access code.
|
||||
See <<orm-jpa-hibernate, Native Hibernate setup for JPA interaction>> for details.
|
||||
|
||||
The `LocalContainerEntityManagerFactoryBean` gives full control over
|
||||
`EntityManagerFactory` configuration and is appropriate for environments where
|
||||
fine-grained customization is required. The `LocalContainerEntityManagerFactoryBean`
|
||||
creates a `PersistenceUnitInfo` instance based on the `persistence.xml` file, the
|
||||
supplied `dataSourceLookup` strategy, and the specified `loadTimeWeaver`. It is, thus,
|
||||
possible to work with custom data sources outside of JNDI and to control the weaving
|
||||
process. The following example shows a typical bean definition for a
|
||||
`LocalContainerEntityManagerFactoryBean`:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<beans>
|
||||
<bean id="myEmf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
|
||||
<property name="dataSource" ref="someDataSource"/>
|
||||
<property name="loadTimeWeaver">
|
||||
<bean class="org.springframework.instrument.classloading.InstrumentationLoadTimeWeaver"/>
|
||||
</property>
|
||||
</bean>
|
||||
</beans>
|
||||
----
|
||||
|
||||
The following example shows a typical `persistence.xml` file:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<persistence xmlns="http://java.sun.com/xml/ns/persistence" version="1.0">
|
||||
<persistence-unit name="myUnit" transaction-type="RESOURCE_LOCAL">
|
||||
<mapping-file>META-INF/orm.xml</mapping-file>
|
||||
<exclude-unlisted-classes/>
|
||||
</persistence-unit>
|
||||
</persistence>
|
||||
----
|
||||
|
||||
NOTE: The `<exclude-unlisted-classes/>` shortcut indicates that no scanning for
|
||||
annotated entity classes is supposed to occur. An explicit 'true' value
|
||||
(`<exclude-unlisted-classes>true</exclude-unlisted-classes/>`) also means no scan.
|
||||
`<exclude-unlisted-classes>false</exclude-unlisted-classes/>` does trigger a scan.
|
||||
However, we recommend omitting the `exclude-unlisted-classes` element
|
||||
if you want entity class scanning to occur.
|
||||
|
||||
Using the `LocalContainerEntityManagerFactoryBean` is the most powerful JPA setup
|
||||
option, allowing for flexible local configuration within the application. It supports
|
||||
links to an existing JDBC `DataSource`, supports both local and global transactions, and
|
||||
so on. However, it also imposes requirements on the runtime environment, such as the
|
||||
availability of a weaving-capable class loader if the persistence provider demands
|
||||
byte-code transformation.
|
||||
|
||||
This option may conflict with the built-in JPA capabilities of a Jakarta EE server. In a
|
||||
full Jakarta EE environment, consider obtaining your `EntityManagerFactory` from JNDI.
|
||||
Alternatively, specify a custom `persistenceXmlLocation` on your
|
||||
`LocalContainerEntityManagerFactoryBean` definition (for example,
|
||||
META-INF/my-persistence.xml) and include only a descriptor with that name in your
|
||||
application jar files. Because the Jakarta EE server looks only for default
|
||||
`META-INF/persistence.xml` files, it ignores such custom persistence units and, hence,
|
||||
avoids conflicts with a Spring-driven JPA setup upfront. (This applies to Resin 3.1, for
|
||||
example.)
|
||||
|
||||
.When is load-time weaving required?
|
||||
****
|
||||
Not all JPA providers require a JVM agent. Hibernate is an example of one that does not.
|
||||
If your provider does not require an agent or you have other alternatives, such as
|
||||
applying enhancements at build time through a custom compiler or an Ant task, you should not use the
|
||||
load-time weaver.
|
||||
****
|
||||
|
||||
The `LoadTimeWeaver` interface is a Spring-provided class that lets JPA
|
||||
`ClassTransformer` instances be plugged in a specific manner, depending on whether the
|
||||
environment is a web container or application server. Hooking `ClassTransformers`
|
||||
through an
|
||||
https://docs.oracle.com/javase/6/docs/api/java/lang/instrument/package-summary.html[agent]
|
||||
is typically not efficient. The agents work against the entire virtual machine and
|
||||
inspect every class that is loaded, which is usually undesirable in a production
|
||||
server environment.
|
||||
|
||||
Spring provides a number of `LoadTimeWeaver` implementations for various environments,
|
||||
letting `ClassTransformer` instances be applied only for each class loader and not
|
||||
for each VM.
|
||||
|
||||
See the <<core.adoc#aop-aj-ltw-spring, Spring configuration>> in the AOP chapter for
|
||||
more insight regarding the `LoadTimeWeaver` implementations and their setup, either
|
||||
generic or customized to various platforms (such as Tomcat, JBoss and WebSphere).
|
||||
|
||||
As described in <<core.adoc#aop-aj-ltw-spring, Spring configuration>>, you can configure
|
||||
a context-wide `LoadTimeWeaver` by using the `@EnableLoadTimeWeaving` annotation or the
|
||||
`context:load-time-weaver` XML element. Such a global weaver is automatically picked up
|
||||
by all JPA `LocalContainerEntityManagerFactoryBean` instances. The following example
|
||||
shows the preferred way of setting up a load-time weaver, delivering auto-detection
|
||||
of the platform (e.g. Tomcat's weaving-capable class loader or Spring's JVM agent)
|
||||
and automatic propagation of the weaver to all weaver-aware beans:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<context:load-time-weaver/>
|
||||
<bean id="emf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
|
||||
...
|
||||
</bean>
|
||||
----
|
||||
|
||||
However, you can, if needed, manually specify a dedicated weaver through the
|
||||
`loadTimeWeaver` property, as the following example shows:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<bean id="emf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
|
||||
<property name="loadTimeWeaver">
|
||||
<bean class="org.springframework.instrument.classloading.ReflectiveLoadTimeWeaver"/>
|
||||
</property>
|
||||
</bean>
|
||||
----
|
||||
|
||||
No matter how the LTW is configured, by using this technique, JPA applications relying on
|
||||
instrumentation can run in the target platform (for example, Tomcat) without needing an agent.
|
||||
This is especially important when the hosting applications rely on different JPA
|
||||
implementations, because the JPA transformers are applied only at the class-loader level and
|
||||
are, thus, isolated from each other.
|
||||
|
||||
[[orm-jpa-setup-multiple]]
|
||||
=== Dealing with Multiple Persistence Units
|
||||
|
||||
For applications that rely on multiple persistence units locations (stored in various
|
||||
JARS in the classpath, for example), Spring offers the `PersistenceUnitManager` to act as
|
||||
a central repository and to avoid the persistence units discovery process, which can be
|
||||
expensive. The default implementation lets multiple locations be specified. These locations are
|
||||
parsed and later retrieved through the persistence unit name. (By default, the classpath
|
||||
is searched for `META-INF/persistence.xml` files.) The following example configures
|
||||
multiple locations:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim"]
|
||||
----
|
||||
<bean id="pum" class="org.springframework.orm.jpa.persistenceunit.DefaultPersistenceUnitManager">
|
||||
<property name="persistenceXmlLocations">
|
||||
<list>
|
||||
<value>org/springframework/orm/jpa/domain/persistence-multi.xml</value>
|
||||
<value>classpath:/my/package/**/custom-persistence.xml</value>
|
||||
<value>classpath*:META-INF/persistence.xml</value>
|
||||
</list>
|
||||
</property>
|
||||
<property name="dataSources">
|
||||
<map>
|
||||
<entry key="localDataSource" value-ref="local-db"/>
|
||||
<entry key="remoteDataSource" value-ref="remote-db"/>
|
||||
</map>
|
||||
</property>
|
||||
<!-- if no datasource is specified, use this one -->
|
||||
<property name="defaultDataSource" ref="remoteDataSource"/>
|
||||
</bean>
|
||||
|
||||
<bean id="emf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
|
||||
<property name="persistenceUnitManager" ref="pum"/>
|
||||
<property name="persistenceUnitName" value="myCustomUnit"/>
|
||||
</bean>
|
||||
----
|
||||
|
||||
The default implementation allows customization of the `PersistenceUnitInfo` instances
|
||||
(before they are fed to the JPA provider) either declaratively (through its properties, which
|
||||
affect all hosted units) or programmatically (through the
|
||||
`PersistenceUnitPostProcessor`, which allows persistence unit selection). If no
|
||||
`PersistenceUnitManager` is specified, one is created and used internally by
|
||||
`LocalContainerEntityManagerFactoryBean`.
|
||||
|
||||
[[orm-jpa-setup-background]]
|
||||
=== Background Bootstrapping
|
||||
|
||||
`LocalContainerEntityManagerFactoryBean` supports background bootstrapping through
|
||||
the `bootstrapExecutor` property, as the following example shows:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<bean id="emf" class="org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean">
|
||||
<property name="bootstrapExecutor">
|
||||
<bean class="org.springframework.core.task.SimpleAsyncTaskExecutor"/>
|
||||
</property>
|
||||
</bean>
|
||||
----
|
||||
|
||||
The actual JPA provider bootstrapping is handed off to the specified executor and then,
|
||||
running in parallel, to the application bootstrap thread. The exposed `EntityManagerFactory`
|
||||
proxy can be injected into other application components and is even able to respond to
|
||||
`EntityManagerFactoryInfo` configuration inspection. However, once the actual JPA provider
|
||||
is being accessed by other components (for example, calling `createEntityManager`), those calls
|
||||
block until the background bootstrapping has completed. In particular, when you use
|
||||
Spring Data JPA, make sure to set up deferred bootstrapping for its repositories as well.
|
||||
|
||||
|
||||
[[orm-jpa-dao]]
|
||||
== Implementing DAOs Based on JPA: `EntityManagerFactory` and `EntityManager`
|
||||
|
||||
NOTE: Although `EntityManagerFactory` instances are thread-safe, `EntityManager` instances are
|
||||
not. The injected JPA `EntityManager` behaves like an `EntityManager` fetched from an
|
||||
application server's JNDI environment, as defined by the JPA specification. It delegates
|
||||
all calls to the current transactional `EntityManager`, if any. Otherwise, it falls back
|
||||
to a newly created `EntityManager` per operation, in effect making its usage thread-safe.
|
||||
|
||||
It is possible to write code against the plain JPA without any Spring dependencies, by
|
||||
using an injected `EntityManagerFactory` or `EntityManager`. Spring can understand the
|
||||
`@PersistenceUnit` and `@PersistenceContext` annotations both at the field and the method level
|
||||
if a `PersistenceAnnotationBeanPostProcessor` is enabled. The following example shows a plain JPA DAO implementation
|
||||
that uses the `@PersistenceUnit` annotation:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
public class ProductDaoImpl implements ProductDao {
|
||||
|
||||
private EntityManagerFactory emf;
|
||||
|
||||
@PersistenceUnit
|
||||
public void setEntityManagerFactory(EntityManagerFactory emf) {
|
||||
this.emf = emf;
|
||||
}
|
||||
|
||||
public Collection loadProductsByCategory(String category) {
|
||||
EntityManager em = this.emf.createEntityManager();
|
||||
try {
|
||||
Query query = em.createQuery("from Product as p where p.category = ?1");
|
||||
query.setParameter(1, category);
|
||||
return query.getResultList();
|
||||
}
|
||||
finally {
|
||||
if (em != null) {
|
||||
em.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
class ProductDaoImpl : ProductDao {
|
||||
|
||||
private lateinit var emf: EntityManagerFactory
|
||||
|
||||
@PersistenceUnit
|
||||
fun setEntityManagerFactory(emf: EntityManagerFactory) {
|
||||
this.emf = emf
|
||||
}
|
||||
|
||||
fun loadProductsByCategory(category: String): Collection<*> {
|
||||
val em = this.emf.createEntityManager()
|
||||
val query = em.createQuery("from Product as p where p.category = ?1");
|
||||
query.setParameter(1, category);
|
||||
return query.resultList;
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
The preceding DAO has no dependency on Spring and still fits nicely into a Spring
|
||||
application context. Moreover, the DAO takes advantage of annotations to require the
|
||||
injection of the default `EntityManagerFactory`, as the following example bean definition shows:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<beans>
|
||||
|
||||
<!-- bean post-processor for JPA annotations -->
|
||||
<bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor"/>
|
||||
|
||||
<bean id="myProductDao" class="product.ProductDaoImpl"/>
|
||||
|
||||
</beans>
|
||||
----
|
||||
|
||||
As an alternative to explicitly defining a `PersistenceAnnotationBeanPostProcessor`,
|
||||
consider using the Spring `context:annotation-config` XML element in your application
|
||||
context configuration. Doing so automatically registers all Spring standard
|
||||
post-processors for annotation-based configuration, including
|
||||
`CommonAnnotationBeanPostProcessor` and so on.
|
||||
|
||||
Consider the following example:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<beans>
|
||||
|
||||
<!-- post-processors for all standard config annotations -->
|
||||
<context:annotation-config/>
|
||||
|
||||
<bean id="myProductDao" class="product.ProductDaoImpl"/>
|
||||
|
||||
</beans>
|
||||
----
|
||||
|
||||
The main problem with such a DAO is that it always creates a new `EntityManager` through
|
||||
the factory. You can avoid this by requesting a transactional `EntityManager` (also
|
||||
called a "`shared EntityManager`" because it is a shared, thread-safe proxy for the actual
|
||||
transactional EntityManager) to be injected instead of the factory. The following example shows how to do so:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
public class ProductDaoImpl implements ProductDao {
|
||||
|
||||
@PersistenceContext
|
||||
private EntityManager em;
|
||||
|
||||
public Collection loadProductsByCategory(String category) {
|
||||
Query query = em.createQuery("from Product as p where p.category = :category");
|
||||
query.setParameter("category", category);
|
||||
return query.getResultList();
|
||||
}
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
class ProductDaoImpl : ProductDao {
|
||||
|
||||
@PersistenceContext
|
||||
private lateinit var em: EntityManager
|
||||
|
||||
fun loadProductsByCategory(category: String): Collection<*> {
|
||||
val query = em.createQuery("from Product as p where p.category = :category")
|
||||
query.setParameter("category", category)
|
||||
return query.resultList
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
The `@PersistenceContext` annotation has an optional attribute called `type`, which defaults to
|
||||
`PersistenceContextType.TRANSACTION`. You can use this default to receive a shared
|
||||
`EntityManager` proxy. The alternative, `PersistenceContextType.EXTENDED`, is a completely
|
||||
different affair. This results in a so-called extended `EntityManager`, which is not
|
||||
thread-safe and, hence, must not be used in a concurrently accessed component, such as a
|
||||
Spring-managed singleton bean. Extended `EntityManager` instances are only supposed to be used in
|
||||
stateful components that, for example, reside in a session, with the lifecycle of the
|
||||
`EntityManager` not tied to a current transaction but rather being completely up to the
|
||||
application.
|
||||
|
||||
.Method- and field-level Injection
|
||||
****
|
||||
You can apply annotations that indicate dependency injections (such as `@PersistenceUnit` and
|
||||
`@PersistenceContext`) on field or methods inside a class -- hence the
|
||||
expressions "`method-level injection`" and "`field-level injection`". Field-level
|
||||
annotations are concise and easier to use while method-level annotations allow for further
|
||||
processing of the injected dependency. In both cases, the member visibility (public,
|
||||
protected, or private) does not matter.
|
||||
|
||||
What about class-level annotations?
|
||||
|
||||
On the Jakarta EE platform, they are used for dependency declaration and not for resource
|
||||
injection.
|
||||
****
|
||||
|
||||
The injected `EntityManager` is Spring-managed (aware of the ongoing transaction).
|
||||
Even though the new DAO implementation uses method-level
|
||||
injection of an `EntityManager` instead of an `EntityManagerFactory`, no change is
|
||||
required in the application context XML, due to annotation usage.
|
||||
|
||||
The main advantage of this DAO style is that it depends only on the Java Persistence API.
|
||||
No import of any Spring class is required. Moreover, as the JPA annotations are understood,
|
||||
the injections are applied automatically by the Spring container. This is appealing from
|
||||
a non-invasiveness perspective and can feel more natural to JPA developers.
|
||||
|
||||
|
||||
[[orm-jpa-tx]]
|
||||
== Spring-driven JPA transactions
|
||||
|
||||
NOTE: We strongly encourage you to read <<transaction-declarative>>, if you have not
|
||||
already done so, to get more detailed coverage of Spring's declarative transaction support.
|
||||
|
||||
The recommended strategy for JPA is local transactions through JPA's native transaction
|
||||
support. Spring's `JpaTransactionManager` provides many capabilities known from local
|
||||
JDBC transactions (such as transaction-specific isolation levels and resource-level
|
||||
read-only optimizations) against any regular JDBC connection pool (no XA requirement).
|
||||
|
||||
Spring JPA also lets a configured `JpaTransactionManager` expose a JPA transaction
|
||||
to JDBC access code that accesses the same `DataSource`, provided that the registered
|
||||
`JpaDialect` supports retrieval of the underlying JDBC `Connection`.
|
||||
Spring provides dialects for the EclipseLink and Hibernate JPA implementations.
|
||||
See the <<orm-jpa-dialect, next section>> for details on the `JpaDialect` mechanism.
|
||||
|
||||
NOTE: As an immediate alternative, Spring's native `HibernateTransactionManager` is capable
|
||||
of interacting with JPA access code, adapting to several Hibernate specifics and providing
|
||||
JDBC interaction. This makes particular sense in combination with `LocalSessionFactoryBean`
|
||||
setup. See <<orm-jpa-hibernate, Native Hibernate Setup for JPA Interaction>> for details.
|
||||
|
||||
|
||||
[[orm-jpa-dialect]]
|
||||
== Understanding `JpaDialect` and `JpaVendorAdapter`
|
||||
|
||||
As an advanced feature, `JpaTransactionManager` and subclasses of
|
||||
`AbstractEntityManagerFactoryBean` allow a custom `JpaDialect` to be passed into the
|
||||
`jpaDialect` bean property. A `JpaDialect` implementation can enable the following advanced
|
||||
features supported by Spring, usually in a vendor-specific manner:
|
||||
|
||||
* Applying specific transaction semantics (such as custom isolation level or transaction
|
||||
timeout)
|
||||
* Retrieving the transactional JDBC `Connection` (for exposure to JDBC-based DAOs)
|
||||
* Advanced translation of `PersistenceExceptions` to Spring `DataAccessExceptions`
|
||||
|
||||
This is particularly valuable for special transaction semantics and for advanced
|
||||
translation of exception. The default implementation (`DefaultJpaDialect`) does
|
||||
not provide any special abilities and, if the features listed earlier are required, you have
|
||||
to specify the appropriate dialect.
|
||||
|
||||
TIP: As an even broader provider adaptation facility primarily for Spring's full-featured
|
||||
`LocalContainerEntityManagerFactoryBean` setup, `JpaVendorAdapter` combines the
|
||||
capabilities of `JpaDialect` with other provider-specific defaults. Specifying a
|
||||
`HibernateJpaVendorAdapter` or `EclipseLinkJpaVendorAdapter` is the most convenient
|
||||
way of auto-configuring an `EntityManagerFactory` setup for Hibernate or EclipseLink,
|
||||
respectively. Note that those provider adapters are primarily designed for use with
|
||||
Spring-driven transaction management (that is, for use with `JpaTransactionManager`).
|
||||
|
||||
See the {api-spring-framework}/orm/jpa/JpaDialect.html[`JpaDialect`] and
|
||||
{api-spring-framework}/orm/jpa/JpaVendorAdapter.html[`JpaVendorAdapter`] javadoc for
|
||||
more details of its operations and how they are used within Spring's JPA support.
|
||||
|
||||
|
||||
[[orm-jpa-jta]]
|
||||
== Setting up JPA with JTA Transaction Management
|
||||
|
||||
As an alternative to `JpaTransactionManager`, Spring also allows for multi-resource
|
||||
transaction coordination through JTA, either in a Jakarta EE environment or with a
|
||||
stand-alone transaction coordinator, such as Atomikos. Aside from choosing Spring's
|
||||
`JtaTransactionManager` instead of `JpaTransactionManager`, you need to take few further
|
||||
steps:
|
||||
|
||||
* The underlying JDBC connection pools need to be XA-capable and be integrated with
|
||||
your transaction coordinator. This is usually straightforward in a Jakarta EE environment,
|
||||
exposing a different kind of `DataSource` through JNDI. See your application server
|
||||
documentation for details. Analogously, a standalone transaction coordinator usually
|
||||
comes with special XA-integrated `DataSource` variants. Again, check its documentation.
|
||||
|
||||
* The JPA `EntityManagerFactory` setup needs to be configured for JTA. This is
|
||||
provider-specific, typically through special properties to be specified as `jpaProperties`
|
||||
on `LocalContainerEntityManagerFactoryBean`. In the case of Hibernate, these properties
|
||||
are even version-specific. See your Hibernate documentation for details.
|
||||
|
||||
* Spring's `HibernateJpaVendorAdapter` enforces certain Spring-oriented defaults, such
|
||||
as the connection release mode, `on-close`, which matches Hibernate's own default in
|
||||
Hibernate 5.0 but not any more in Hibernate 5.1+. For a JTA setup, make sure to declare
|
||||
your persistence unit transaction type as "JTA". Alternatively, set Hibernate 5.2's
|
||||
`hibernate.connection.handling_mode` property to
|
||||
`DELAYED_ACQUISITION_AND_RELEASE_AFTER_STATEMENT` to restore Hibernate's own default.
|
||||
See <<orm-hibernate-invalid-jdbc-access-error>> for related notes.
|
||||
|
||||
* Alternatively, consider obtaining the `EntityManagerFactory` from your application
|
||||
server itself (that is, through a JNDI lookup instead of a locally declared
|
||||
`LocalContainerEntityManagerFactoryBean`). A server-provided `EntityManagerFactory`
|
||||
might require special definitions in your server configuration (making the deployment
|
||||
less portable) but is set up for the server's JTA environment.
|
||||
|
||||
|
||||
[[orm-jpa-hibernate]]
|
||||
== Native Hibernate Setup and Native Hibernate Transactions for JPA Interaction
|
||||
|
||||
A native `LocalSessionFactoryBean` setup in combination with `HibernateTransactionManager`
|
||||
allows for interaction with `@PersistenceContext` and other JPA access code. A Hibernate
|
||||
`SessionFactory` natively implements JPA's `EntityManagerFactory` interface now
|
||||
and a Hibernate `Session` handle natively is a JPA `EntityManager`.
|
||||
Spring's JPA support facilities automatically detect native Hibernate sessions.
|
||||
|
||||
Such native Hibernate setup can, therefore, serve as a replacement for a standard JPA
|
||||
`LocalContainerEntityManagerFactoryBean` and `JpaTransactionManager` combination
|
||||
in many scenarios, allowing for interaction with `SessionFactory.getCurrentSession()`
|
||||
(and also `HibernateTemplate`) next to `@PersistenceContext EntityManager` within
|
||||
the same local transaction. Such a setup also provides stronger Hibernate integration
|
||||
and more configuration flexibility, because it is not constrained by JPA bootstrap contracts.
|
||||
|
||||
You do not need `HibernateJpaVendorAdapter` configuration in such a scenario,
|
||||
since Spring's native Hibernate setup provides even more features
|
||||
(for example, custom Hibernate Integrator setup, Hibernate 5.3 bean container integration,
|
||||
and stronger optimizations for read-only transactions). Last but not least, you can also
|
||||
express native Hibernate setup through `LocalSessionFactoryBuilder`,
|
||||
seamlessly integrating with `@Bean` style configuration (no `FactoryBean` involved).
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
`LocalSessionFactoryBean` and `LocalSessionFactoryBuilder` support background
|
||||
bootstrapping, just as the JPA `LocalContainerEntityManagerFactoryBean` does.
|
||||
See <<orm-jpa-setup-background, Background Bootstrapping>> for an introduction.
|
||||
|
||||
On `LocalSessionFactoryBean`, this is available through the `bootstrapExecutor`
|
||||
property. On the programmatic `LocalSessionFactoryBuilder`, an overloaded
|
||||
`buildSessionFactory` method takes a bootstrap executor argument.
|
||||
====
|
||||
|
||||
|
||||
|
||||
|
||||
563
framework-docs/modules/ROOT/pages/data-access/oxm.adoc
Normal file
563
framework-docs/modules/ROOT/pages/data-access/oxm.adoc
Normal file
@@ -0,0 +1,563 @@
|
||||
[[oxm]]
|
||||
= Marshalling XML by Using Object-XML Mappers
|
||||
|
||||
|
||||
|
||||
[[oxm-introduction]]
|
||||
== Introduction
|
||||
|
||||
This chapter, describes Spring's Object-XML Mapping support. Object-XML
|
||||
Mapping (O-X mapping for short) is the act of converting an XML document to and from
|
||||
an object. This conversion process is also known as XML Marshalling, or XML
|
||||
Serialization. This chapter uses these terms interchangeably.
|
||||
|
||||
Within the field of O-X mapping, a marshaller is responsible for serializing an
|
||||
object (graph) to XML. In similar fashion, an unmarshaller deserializes the XML to
|
||||
an object graph. This XML can take the form of a DOM document, an input or output
|
||||
stream, or a SAX handler.
|
||||
|
||||
Some of the benefits of using Spring for your O/X mapping needs are:
|
||||
|
||||
* <<oxm-ease-of-configuration>>
|
||||
* <<oxm-consistent-interfaces>>
|
||||
* <<oxm-consistent-exception-hierarchy>>
|
||||
|
||||
|
||||
[[oxm-ease-of-configuration]]
|
||||
=== Ease of configuration
|
||||
|
||||
Spring's bean factory makes it easy to configure marshallers, without needing to
|
||||
construct JAXB context, JiBX binding factories, and so on. You can configure the marshallers
|
||||
as you would any other bean in your application context. Additionally, XML namespace-based
|
||||
configuration is available for a number of marshallers, making the configuration even
|
||||
simpler.
|
||||
|
||||
|
||||
[[oxm-consistent-interfaces]]
|
||||
=== Consistent Interfaces
|
||||
|
||||
Spring's O-X mapping operates through two global interfaces: {api-spring-framework}/oxm/Marshaller.html[`Marshaller`] and
|
||||
{api-spring-framework}/oxm/Unmarshaller.html[`Unmarshaller`]. These abstractions let you switch O-X mapping frameworks
|
||||
with relative ease, with little or no change required on the classes that do the
|
||||
marshalling. This approach has the additional benefit of making it possible to do XML
|
||||
marshalling with a mix-and-match approach (for example, some marshalling performed using JAXB
|
||||
and some by XStream) in a non-intrusive fashion, letting you use the strength of each
|
||||
technology.
|
||||
|
||||
|
||||
[[oxm-consistent-exception-hierarchy]]
|
||||
=== Consistent Exception Hierarchy
|
||||
|
||||
Spring provides a conversion from exceptions from the underlying O-X mapping tool to its
|
||||
own exception hierarchy with the `XmlMappingException` as the root exception.
|
||||
These runtime exceptions wrap the original exception so that no information is lost.
|
||||
|
||||
|
||||
|
||||
[[oxm-marshaller-unmarshaller]]
|
||||
== `Marshaller` and `Unmarshaller`
|
||||
|
||||
As stated in the <<oxm-introduction, introduction>>, a marshaller serializes an object
|
||||
to XML, and an unmarshaller deserializes XML stream to an object. This section describes
|
||||
the two Spring interfaces used for this purpose.
|
||||
|
||||
|
||||
[[oxm-marshaller]]
|
||||
=== Understanding `Marshaller`
|
||||
|
||||
Spring abstracts all marshalling operations behind the
|
||||
`org.springframework.oxm.Marshaller` interface, the main method of which follows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
public interface Marshaller {
|
||||
|
||||
/**
|
||||
* Marshal the object graph with the given root into the provided Result.
|
||||
*/
|
||||
void marshal(Object graph, Result result) throws XmlMappingException, IOException;
|
||||
}
|
||||
----
|
||||
|
||||
The `Marshaller` interface has one main method, which marshals the given object to a
|
||||
given `javax.xml.transform.Result`. The result is a tagging interface that basically
|
||||
represents an XML output abstraction. Concrete implementations wrap various XML
|
||||
representations, as the following table indicates:
|
||||
|
||||
[[oxm-marshaller-tbl]]
|
||||
|===
|
||||
| Result implementation| Wraps XML representation
|
||||
|
||||
| `DOMResult`
|
||||
| `org.w3c.dom.Node`
|
||||
|
||||
| `SAXResult`
|
||||
| `org.xml.sax.ContentHandler`
|
||||
|
||||
| `StreamResult`
|
||||
| `java.io.File`, `java.io.OutputStream`, or `java.io.Writer`
|
||||
|===
|
||||
|
||||
NOTE: Although the `marshal()` method accepts a plain object as its first parameter, most
|
||||
`Marshaller` implementations cannot handle arbitrary objects. Instead, an object class
|
||||
must be mapped in a mapping file, be marked with an annotation, be registered with the
|
||||
marshaller, or have a common base class. Refer to the later sections in this chapter
|
||||
to determine how your O-X technology manages this.
|
||||
|
||||
|
||||
[[oxm-unmarshaller]]
|
||||
=== Understanding `Unmarshaller`
|
||||
|
||||
Similar to the `Marshaller`, we have the `org.springframework.oxm.Unmarshaller`
|
||||
interface, which the following listing shows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
public interface Unmarshaller {
|
||||
|
||||
/**
|
||||
* Unmarshal the given provided Source into an object graph.
|
||||
*/
|
||||
Object unmarshal(Source source) throws XmlMappingException, IOException;
|
||||
}
|
||||
----
|
||||
|
||||
This interface also has one method, which reads from the given
|
||||
`javax.xml.transform.Source` (an XML input abstraction) and returns the object read. As
|
||||
with `Result`, `Source` is a tagging interface that has three concrete implementations. Each
|
||||
wraps a different XML representation, as the following table indicates:
|
||||
|
||||
[[oxm-unmarshaller-tbl]]
|
||||
|===
|
||||
| Source implementation| Wraps XML representation
|
||||
|
||||
| `DOMSource`
|
||||
| `org.w3c.dom.Node`
|
||||
|
||||
| `SAXSource`
|
||||
| `org.xml.sax.InputSource`, and `org.xml.sax.XMLReader`
|
||||
|
||||
| `StreamSource`
|
||||
| `java.io.File`, `java.io.InputStream`, or `java.io.Reader`
|
||||
|===
|
||||
|
||||
Even though there are two separate marshalling interfaces (`Marshaller` and
|
||||
`Unmarshaller`), all implementations in Spring-WS implement both in one class.
|
||||
This means that you can wire up one marshaller class and refer to it both as a
|
||||
marshaller and as an unmarshaller in your `applicationContext.xml`.
|
||||
|
||||
|
||||
[[oxm-xmlmappingexception]]
|
||||
=== Understanding `XmlMappingException`
|
||||
|
||||
Spring converts exceptions from the underlying O-X mapping tool to its own exception
|
||||
hierarchy with the `XmlMappingException` as the root exception.
|
||||
These runtime exceptions wrap the original exception so that no information will be lost.
|
||||
|
||||
Additionally, the `MarshallingFailureException` and `UnmarshallingFailureException`
|
||||
provide a distinction between marshalling and unmarshalling operations, even though the
|
||||
underlying O-X mapping tool does not do so.
|
||||
|
||||
The O-X Mapping exception hierarchy is shown in the following figure:
|
||||
|
||||
image::oxm-exceptions.png[]
|
||||
|
||||
|
||||
|
||||
[[oxm-usage]]
|
||||
== Using `Marshaller` and `Unmarshaller`
|
||||
|
||||
You can use Spring's OXM for a wide variety of situations. In the following example, we
|
||||
use it to marshal the settings of a Spring-managed application as an XML file. In the following example, we
|
||||
use a simple JavaBean to represent the settings:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
public class Settings {
|
||||
|
||||
private boolean fooEnabled;
|
||||
|
||||
public boolean isFooEnabled() {
|
||||
return fooEnabled;
|
||||
}
|
||||
|
||||
public void setFooEnabled(boolean fooEnabled) {
|
||||
this.fooEnabled = fooEnabled;
|
||||
}
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
class Settings {
|
||||
var isFooEnabled: Boolean = false
|
||||
}
|
||||
----
|
||||
|
||||
The application class uses this bean to store its settings. Besides a main method, the
|
||||
class has two methods: `saveSettings()` saves the settings bean to a file named
|
||||
`settings.xml`, and `loadSettings()` loads these settings again. The following `main()` method
|
||||
constructs a Spring application context and calls these two methods:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import javax.xml.transform.stream.StreamResult;
|
||||
import javax.xml.transform.stream.StreamSource;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.support.ClassPathXmlApplicationContext;
|
||||
import org.springframework.oxm.Marshaller;
|
||||
import org.springframework.oxm.Unmarshaller;
|
||||
|
||||
public class Application {
|
||||
|
||||
private static final String FILE_NAME = "settings.xml";
|
||||
private Settings settings = new Settings();
|
||||
private Marshaller marshaller;
|
||||
private Unmarshaller unmarshaller;
|
||||
|
||||
public void setMarshaller(Marshaller marshaller) {
|
||||
this.marshaller = marshaller;
|
||||
}
|
||||
|
||||
public void setUnmarshaller(Unmarshaller unmarshaller) {
|
||||
this.unmarshaller = unmarshaller;
|
||||
}
|
||||
|
||||
public void saveSettings() throws IOException {
|
||||
try (FileOutputStream os = new FileOutputStream(FILE_NAME)) {
|
||||
this.marshaller.marshal(settings, new StreamResult(os));
|
||||
}
|
||||
}
|
||||
|
||||
public void loadSettings() throws IOException {
|
||||
try (FileInputStream is = new FileInputStream(FILE_NAME)) {
|
||||
this.settings = (Settings) this.unmarshaller.unmarshal(new StreamSource(is));
|
||||
}
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws IOException {
|
||||
ApplicationContext appContext =
|
||||
new ClassPathXmlApplicationContext("applicationContext.xml");
|
||||
Application application = (Application) appContext.getBean("application");
|
||||
application.saveSettings();
|
||||
application.loadSettings();
|
||||
}
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
class Application {
|
||||
|
||||
lateinit var marshaller: Marshaller
|
||||
|
||||
lateinit var unmarshaller: Unmarshaller
|
||||
|
||||
fun saveSettings() {
|
||||
FileOutputStream(FILE_NAME).use { outputStream -> marshaller.marshal(settings, StreamResult(outputStream)) }
|
||||
}
|
||||
|
||||
fun loadSettings() {
|
||||
FileInputStream(FILE_NAME).use { inputStream -> settings = unmarshaller.unmarshal(StreamSource(inputStream)) as Settings }
|
||||
}
|
||||
}
|
||||
|
||||
private const val FILE_NAME = "settings.xml"
|
||||
|
||||
fun main(args: Array<String>) {
|
||||
val appContext = ClassPathXmlApplicationContext("applicationContext.xml")
|
||||
val application = appContext.getBean("application") as Application
|
||||
application.saveSettings()
|
||||
application.loadSettings()
|
||||
}
|
||||
----
|
||||
|
||||
The `Application` requires both a `marshaller` and an `unmarshaller` property to be set. We
|
||||
can do so by using the following `applicationContext.xml`:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<beans>
|
||||
<bean id="application" class="Application">
|
||||
<property name="marshaller" ref="xstreamMarshaller" />
|
||||
<property name="unmarshaller" ref="xstreamMarshaller" />
|
||||
</bean>
|
||||
<bean id="xstreamMarshaller" class="org.springframework.oxm.xstream.XStreamMarshaller"/>
|
||||
</beans>
|
||||
----
|
||||
|
||||
This application context uses XStream, but we could have used any of the other marshaller
|
||||
instances described later in this chapter. Note that, by default, XStream does not require
|
||||
any further configuration, so the bean definition is rather simple. Also note that the
|
||||
`XStreamMarshaller` implements both `Marshaller` and `Unmarshaller`, so we can refer to the
|
||||
`xstreamMarshaller` bean in both the `marshaller` and `unmarshaller` property of the
|
||||
application.
|
||||
|
||||
This sample application produces the following `settings.xml` file:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<settings foo-enabled="false"/>
|
||||
----
|
||||
|
||||
|
||||
|
||||
[[oxm-schema-based-config]]
|
||||
== XML Configuration Namespace
|
||||
|
||||
You can configure marshallers more concisely by using tags from the OXM namespace.
|
||||
To make these tags available, you must first reference the appropriate schema in the
|
||||
preamble of the XML configuration file. The following example shows how to do so:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:oxm="http://www.springframework.org/schema/oxm" <1>
|
||||
xsi:schemaLocation="http://www.springframework.org/schema/beans
|
||||
https://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/oxm
|
||||
https://www.springframework.org/schema/oxm/spring-oxm.xsd"> <2>
|
||||
----
|
||||
<1> Reference the `oxm` schema.
|
||||
<2> Specify the `oxm` schema location.
|
||||
|
||||
|
||||
The schema makes the following elements available:
|
||||
|
||||
* <<oxm-jaxb2-xsd, `jaxb2-marshaller`>>
|
||||
* <<oxm-jibx-xsd, `jibx-marshaller`>>
|
||||
|
||||
Each tag is explained in its respective marshaller's section. As an example, though,
|
||||
the configuration of a JAXB2 marshaller might resemble the following:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<oxm:jaxb2-marshaller id="marshaller" contextPath="org.springframework.ws.samples.airline.schema"/>
|
||||
----
|
||||
|
||||
|
||||
|
||||
[[oxm-jaxb]]
|
||||
== JAXB
|
||||
|
||||
The JAXB binding compiler translates a W3C XML Schema into one or more Java classes, a
|
||||
`jaxb.properties` file, and possibly some resource files. JAXB also offers a way to
|
||||
generate a schema from annotated Java classes.
|
||||
|
||||
Spring supports the JAXB 2.0 API as XML marshalling strategies, following the
|
||||
`Marshaller` and `Unmarshaller` interfaces described in <<oxm-marshaller-unmarshaller>>.
|
||||
The corresponding integration classes reside in the `org.springframework.oxm.jaxb`
|
||||
package.
|
||||
|
||||
|
||||
[[oxm-jaxb2]]
|
||||
=== Using `Jaxb2Marshaller`
|
||||
|
||||
The `Jaxb2Marshaller` class implements both of Spring's `Marshaller` and `Unmarshaller`
|
||||
interfaces. It requires a context path to operate. You can set the context path by setting the
|
||||
`contextPath` property. The context path is a list of colon-separated Java package
|
||||
names that contain schema derived classes. It also offers a `classesToBeBound` property,
|
||||
which allows you to set an array of classes to be supported by the marshaller. Schema
|
||||
validation is performed by specifying one or more schema resources to the bean, as the following example shows:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<beans>
|
||||
<bean id="jaxb2Marshaller" class="org.springframework.oxm.jaxb.Jaxb2Marshaller">
|
||||
<property name="classesToBeBound">
|
||||
<list>
|
||||
<value>org.springframework.oxm.jaxb.Flight</value>
|
||||
<value>org.springframework.oxm.jaxb.Flights</value>
|
||||
</list>
|
||||
</property>
|
||||
<property name="schema" value="classpath:org/springframework/oxm/schema.xsd"/>
|
||||
</bean>
|
||||
|
||||
...
|
||||
|
||||
</beans>
|
||||
----
|
||||
|
||||
[[oxm-jaxb2-xsd]]
|
||||
==== XML Configuration Namespace
|
||||
|
||||
The `jaxb2-marshaller` element configures a `org.springframework.oxm.jaxb.Jaxb2Marshaller`,
|
||||
as the following example shows:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<oxm:jaxb2-marshaller id="marshaller" contextPath="org.springframework.ws.samples.airline.schema"/>
|
||||
----
|
||||
|
||||
Alternatively, you can provide the list of classes to bind to the marshaller by using the
|
||||
`class-to-be-bound` child element:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<oxm:jaxb2-marshaller id="marshaller">
|
||||
<oxm:class-to-be-bound name="org.springframework.ws.samples.airline.schema.Airport"/>
|
||||
<oxm:class-to-be-bound name="org.springframework.ws.samples.airline.schema.Flight"/>
|
||||
...
|
||||
</oxm:jaxb2-marshaller>
|
||||
----
|
||||
|
||||
The following table describes the available attributes:
|
||||
|
||||
|===
|
||||
| Attribute| Description| Required
|
||||
|
||||
| `id`
|
||||
| The ID of the marshaller
|
||||
| No
|
||||
|
||||
| `contextPath`
|
||||
| The JAXB Context path
|
||||
| No
|
||||
|===
|
||||
|
||||
|
||||
|
||||
[[oxm-jibx]]
|
||||
== JiBX
|
||||
|
||||
The JiBX framework offers a solution similar to that which Hibernate provides for ORM: A
|
||||
binding definition defines the rules for how your Java objects are converted to or from
|
||||
XML. After preparing the binding and compiling the classes, a JiBX binding compiler
|
||||
enhances the class files and adds code to handle converting instances of the classes
|
||||
from or to XML.
|
||||
|
||||
For more information on JiBX, see the http://jibx.sourceforge.net/[JiBX web
|
||||
site]. The Spring integration classes reside in the `org.springframework.oxm.jibx`
|
||||
package.
|
||||
|
||||
|
||||
[[oxm-jibx-marshaller]]
|
||||
=== Using `JibxMarshaller`
|
||||
|
||||
The `JibxMarshaller` class implements both the `Marshaller` and `Unmarshaller`
|
||||
interface. To operate, it requires the name of the class to marshal in, which you can
|
||||
set using the `targetClass` property. Optionally, you can set the binding name by setting the
|
||||
`bindingName` property. In the following example, we bind the `Flights` class:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<beans>
|
||||
<bean id="jibxFlightsMarshaller" class="org.springframework.oxm.jibx.JibxMarshaller">
|
||||
<property name="targetClass">org.springframework.oxm.jibx.Flights</property>
|
||||
</bean>
|
||||
...
|
||||
</beans>
|
||||
----
|
||||
|
||||
A `JibxMarshaller` is configured for a single class. If you want to marshal multiple
|
||||
classes, you have to configure multiple `JibxMarshaller` instances with different `targetClass`
|
||||
property values.
|
||||
|
||||
[[oxm-jibx-xsd]]
|
||||
==== XML Configuration Namespace
|
||||
|
||||
The `jibx-marshaller` tag configures a `org.springframework.oxm.jibx.JibxMarshaller`,
|
||||
as the following example shows:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<oxm:jibx-marshaller id="marshaller" target-class="org.springframework.ws.samples.airline.schema.Flight"/>
|
||||
----
|
||||
|
||||
The following table describes the available attributes:
|
||||
|
||||
|===
|
||||
| Attribute| Description| Required
|
||||
|
||||
| `id`
|
||||
| The ID of the marshaller
|
||||
| No
|
||||
|
||||
| `target-class`
|
||||
| The target class for this marshaller
|
||||
| Yes
|
||||
|
||||
| `bindingName`
|
||||
| The binding name used by this marshaller
|
||||
| No
|
||||
|===
|
||||
|
||||
|
||||
|
||||
[[oxm-xstream]]
|
||||
== XStream
|
||||
|
||||
XStream is a simple library to serialize objects to XML and back again. It does not
|
||||
require any mapping and generates clean XML.
|
||||
|
||||
For more information on XStream, see the https://x-stream.github.io/[XStream
|
||||
web site]. The Spring integration classes reside in the
|
||||
`org.springframework.oxm.xstream` package.
|
||||
|
||||
|
||||
[[oxm-xstream-marshaller]]
|
||||
=== Using `XStreamMarshaller`
|
||||
|
||||
The `XStreamMarshaller` does not require any configuration and can be configured in an
|
||||
application context directly. To further customize the XML, you can set an alias map,
|
||||
which consists of string aliases mapped to classes, as the following example shows:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<beans>
|
||||
<bean id="xstreamMarshaller" class="org.springframework.oxm.xstream.XStreamMarshaller">
|
||||
<property name="aliases">
|
||||
<props>
|
||||
<prop key="Flight">org.springframework.oxm.xstream.Flight</prop>
|
||||
</props>
|
||||
</property>
|
||||
</bean>
|
||||
...
|
||||
</beans>
|
||||
----
|
||||
|
||||
[WARNING]
|
||||
=====
|
||||
By default, XStream lets arbitrary classes be unmarshalled, which can lead to
|
||||
unsafe Java serialization effects. As such, we do not recommend using the
|
||||
`XStreamMarshaller` to unmarshal XML from external sources (that is, the Web), as this can
|
||||
result in security vulnerabilities.
|
||||
|
||||
If you choose to use the `XStreamMarshaller` to unmarshal XML from an external source,
|
||||
set the `supportedClasses` property on the `XStreamMarshaller`, as the following example shows:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<bean id="xstreamMarshaller" class="org.springframework.oxm.xstream.XStreamMarshaller">
|
||||
<property name="supportedClasses" value="org.springframework.oxm.xstream.Flight"/>
|
||||
...
|
||||
</bean>
|
||||
----
|
||||
|
||||
Doing so ensures that only the registered classes are eligible for unmarshalling.
|
||||
|
||||
Additionally, you can register
|
||||
{api-spring-framework}/oxm/xstream/XStreamMarshaller.html#setConverters(com.thoughtworks.xstream.converters.ConverterMatcher...)[custom
|
||||
converters] to make sure that only your supported classes can be unmarshalled. You might
|
||||
want to add a `CatchAllConverter` as the last converter in the list, in addition to
|
||||
converters that explicitly support the domain classes that should be supported. As a
|
||||
result, default XStream converters with lower priorities and possible security
|
||||
vulnerabilities do not get invoked.
|
||||
=====
|
||||
|
||||
NOTE: Note that XStream is an XML serialization library, not a data binding library.
|
||||
Therefore, it has limited namespace support. As a result, it is rather unsuitable for usage
|
||||
within Web Services.
|
||||
|
||||
|
||||
|
||||
|
||||
include:../:data-access/appendix.adoc[leveloffset=+1]
|
||||
654
framework-docs/modules/ROOT/pages/data-access/r2dbc.adoc
Normal file
654
framework-docs/modules/ROOT/pages/data-access/r2dbc.adoc
Normal file
@@ -0,0 +1,654 @@
|
||||
[[r2dbc]]
|
||||
= Data Access with R2DBC
|
||||
|
||||
https://r2dbc.io[R2DBC] ("Reactive Relational Database Connectivity") is a community-driven
|
||||
specification effort to standardize access to SQL databases using reactive patterns.
|
||||
|
||||
|
||||
[[r2dbc-packages]]
|
||||
== Package Hierarchy
|
||||
|
||||
The Spring Framework's R2DBC abstraction framework consists of two different packages:
|
||||
|
||||
* `core`: The `org.springframework.r2dbc.core` package contains the `DatabaseClient`
|
||||
class plus a variety of related classes. See <<r2dbc-core>>.
|
||||
|
||||
* `connection`: The `org.springframework.r2dbc.connection` package contains a utility class
|
||||
for easy `ConnectionFactory` access and various simple `ConnectionFactory` implementations
|
||||
that you can use for testing and running unmodified R2DBC. See <<r2dbc-connections>>.
|
||||
|
||||
|
||||
[[r2dbc-core]]
|
||||
== Using the R2DBC Core Classes to Control Basic R2DBC Processing and Error Handling
|
||||
|
||||
This section covers how to use the R2DBC core classes to control basic R2DBC processing,
|
||||
including error handling. It includes the following topics:
|
||||
|
||||
* <<r2dbc-DatabaseClient>>
|
||||
* <<r2dbc-DatabaseClient-examples-statement>>
|
||||
* <<r2dbc-DatabaseClient-examples-query>>
|
||||
* <<r2dbc-DatabaseClient-examples-update>>
|
||||
* <<r2dbc-DatabaseClient-filter>>
|
||||
* <<r2dbc-auto-generated-keys>>
|
||||
|
||||
[[r2dbc-DatabaseClient]]
|
||||
=== Using `DatabaseClient`
|
||||
|
||||
`DatabaseClient` is the central class in the R2DBC core package. It handles the
|
||||
creation and release of resources, which helps to avoid common errors, such as
|
||||
forgetting to close the connection. It performs the basic tasks of the core R2DBC
|
||||
workflow (such as statement creation and execution), leaving application code to provide
|
||||
SQL and extract results. The `DatabaseClient` class:
|
||||
|
||||
* Runs SQL queries
|
||||
* Update statements and stored procedure calls
|
||||
* Performs iteration over `Result` instances
|
||||
* Catches R2DBC exceptions and translates them to the generic, more informative, exception
|
||||
hierarchy defined in the `org.springframework.dao` package. (See <<dao-exceptions>>.)
|
||||
|
||||
The client has a functional, fluent API using reactive types for declarative composition.
|
||||
|
||||
When you use the `DatabaseClient` for your code, you need only to implement
|
||||
`java.util.function` interfaces, giving them a clearly defined contract.
|
||||
Given a `Connection` provided by the `DatabaseClient` class, a `Function`
|
||||
callback creates a `Publisher`. The same is true for mapping functions that
|
||||
extract a `Row` result.
|
||||
|
||||
You can use `DatabaseClient` within a DAO implementation through direct instantiation
|
||||
with a `ConnectionFactory` reference, or you can configure it in a Spring IoC container
|
||||
and give it to DAOs as a bean reference.
|
||||
|
||||
The simplest way to create a `DatabaseClient` object is through a static factory method, as follows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
DatabaseClient client = DatabaseClient.create(connectionFactory);
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val client = DatabaseClient.create(connectionFactory)
|
||||
----
|
||||
|
||||
NOTE: The `ConnectionFactory` should always be configured as a bean in the Spring IoC
|
||||
container.
|
||||
|
||||
The preceding method creates a `DatabaseClient` with default settings.
|
||||
|
||||
You can also obtain a `Builder` instance from `DatabaseClient.builder()`.
|
||||
You can customize the client by calling the following methods:
|
||||
|
||||
* `….bindMarkers(…)`: Supply a specific `BindMarkersFactory` to configure named
|
||||
parameter to database bind marker translation.
|
||||
* `….executeFunction(…)`: Set the `ExecuteFunction` how `Statement` objects get
|
||||
run.
|
||||
* `….namedParameters(false)`: Disable named parameter expansion. Enabled by default.
|
||||
|
||||
TIP: Dialects are resolved by {api-spring-framework}/r2dbc/core/binding/BindMarkersFactoryResolver.html[`BindMarkersFactoryResolver`]
|
||||
from a `ConnectionFactory`, typically by inspecting `ConnectionFactoryMetadata`.
|
||||
+
|
||||
You can let Spring auto-discover your `BindMarkersFactory` by registering a
|
||||
class that implements `org.springframework.r2dbc.core.binding.BindMarkersFactoryResolver$BindMarkerFactoryProvider`
|
||||
through `META-INF/spring.factories`.
|
||||
`BindMarkersFactoryResolver` discovers bind marker provider implementations from
|
||||
the class path using Spring's `SpringFactoriesLoader`.
|
||||
+
|
||||
|
||||
Currently supported databases are:
|
||||
|
||||
* H2
|
||||
* MariaDB
|
||||
* Microsoft SQL Server
|
||||
* MySQL
|
||||
* Postgres
|
||||
|
||||
All SQL issued by this class is logged at the `DEBUG` level under the category
|
||||
corresponding to the fully qualified class name of the client instance (typically
|
||||
`DefaultDatabaseClient`). Additionally, each execution registers a checkpoint in
|
||||
the reactive sequence to aid debugging.
|
||||
|
||||
The following sections provide some examples of `DatabaseClient` usage. These examples
|
||||
are not an exhaustive list of all of the functionality exposed by the `DatabaseClient`.
|
||||
See the attendant {api-spring-framework}/r2dbc/core/DatabaseClient.html[javadoc] for that.
|
||||
|
||||
[[r2dbc-DatabaseClient-examples-statement]]
|
||||
==== Executing Statements
|
||||
|
||||
`DatabaseClient` provides the basic functionality of running a statement.
|
||||
The following example shows what you need to include for minimal but fully functional
|
||||
code that creates a new table:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
Mono<Void> completion = client.sql("CREATE TABLE person (id VARCHAR(255) PRIMARY KEY, name VARCHAR(255), age INTEGER);")
|
||||
.then();
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
client.sql("CREATE TABLE person (id VARCHAR(255) PRIMARY KEY, name VARCHAR(255), age INTEGER);")
|
||||
.await()
|
||||
----
|
||||
|
||||
`DatabaseClient` is designed for convenient, fluent usage.
|
||||
It exposes intermediate, continuation, and terminal methods at each stage of the
|
||||
execution specification. The preceding example above uses `then()` to return a completion
|
||||
`Publisher` that completes as soon as the query (or queries, if the SQL query contains
|
||||
multiple statements) completes.
|
||||
|
||||
NOTE: `execute(…)` accepts either the SQL query string or a query `Supplier<String>`
|
||||
to defer the actual query creation until execution.
|
||||
|
||||
[[r2dbc-DatabaseClient-examples-query]]
|
||||
==== Querying (`SELECT`)
|
||||
|
||||
SQL queries can return values through `Row` objects or the number of affected rows.
|
||||
`DatabaseClient` can return the number of updated rows or the rows themselves,
|
||||
depending on the issued query.
|
||||
|
||||
The following query gets the `id` and `name` columns from a table:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
Mono<Map<String, Object>> first = client.sql("SELECT id, name FROM person")
|
||||
.fetch().first();
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val first = client.sql("SELECT id, name FROM person")
|
||||
.fetch().awaitSingle()
|
||||
----
|
||||
|
||||
The following query uses a bind variable:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
Mono<Map<String, Object>> first = client.sql("SELECT id, name FROM person WHERE first_name = :fn")
|
||||
.bind("fn", "Joe")
|
||||
.fetch().first();
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val first = client.sql("SELECT id, name FROM person WHERE first_name = :fn")
|
||||
.bind("fn", "Joe")
|
||||
.fetch().awaitSingle()
|
||||
----
|
||||
|
||||
You might have noticed the use of `fetch()` in the example above. `fetch()` is a
|
||||
continuation operator that lets you specify how much data you want to consume.
|
||||
|
||||
Calling `first()` returns the first row from the result and discards remaining rows.
|
||||
You can consume data with the following operators:
|
||||
|
||||
* `first()` return the first row of the entire result. Its Kotlin Coroutine variant
|
||||
is named `awaitSingle()` for non-nullable return values and `awaitSingleOrNull()`
|
||||
if the value is optional.
|
||||
* `one()` returns exactly one result and fails if the result contains more rows.
|
||||
Using Kotlin Coroutines, `awaitOne()` for exactly one value or `awaitOneOrNull()`
|
||||
if the value may be `null`.
|
||||
* `all()` returns all rows of the result. When using Kotlin Coroutines, use `flow()`.
|
||||
* `rowsUpdated()` returns the number of affected rows (`INSERT`/`UPDATE`/`DELETE`
|
||||
count). Its Kotlin Coroutine variant is named `awaitRowsUpdated()`.
|
||||
|
||||
Without specifying further mapping details, queries return tabular results
|
||||
as `Map` whose keys are case-insensitive column names that map to their column value.
|
||||
|
||||
You can take control over result mapping by supplying a `Function<Row, T>` that gets
|
||||
called for each `Row` so it can return arbitrary values (singular values,
|
||||
collections and maps, and objects).
|
||||
|
||||
The following example extracts the `name` column and emits its value:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
Flux<String> names = client.sql("SELECT name FROM person")
|
||||
.map(row -> row.get("name", String.class))
|
||||
.all();
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val names = client.sql("SELECT name FROM person")
|
||||
.map{ row: Row -> row.get("name", String.class) }
|
||||
.flow()
|
||||
----
|
||||
|
||||
|
||||
[[r2dbc-DatabaseClient-mapping-null]]
|
||||
.What about `null`?
|
||||
****
|
||||
Relational database results can contain `null` values.
|
||||
The Reactive Streams specification forbids the emission of `null` values.
|
||||
That requirement mandates proper `null` handling in the extractor function.
|
||||
While you can obtain `null` values from a `Row`, you must not emit a `null`
|
||||
value. You must wrap any `null` values in an object (for example, `Optional`
|
||||
for singular values) to make sure a `null` value is never returned directly
|
||||
by your extractor function.
|
||||
****
|
||||
|
||||
[[r2dbc-DatabaseClient-examples-update]]
|
||||
==== Updating (`INSERT`, `UPDATE`, and `DELETE`) with `DatabaseClient`
|
||||
|
||||
The only difference of modifying statements is that these statements typically
|
||||
do not return tabular data so you use `rowsUpdated()` to consume results.
|
||||
|
||||
The following example shows an `UPDATE` statement that returns the number
|
||||
of updated rows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
Mono<Integer> affectedRows = client.sql("UPDATE person SET first_name = :fn")
|
||||
.bind("fn", "Joe")
|
||||
.fetch().rowsUpdated();
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val affectedRows = client.sql("UPDATE person SET first_name = :fn")
|
||||
.bind("fn", "Joe")
|
||||
.fetch().awaitRowsUpdated()
|
||||
----
|
||||
|
||||
[[r2dbc-DatabaseClient-named-parameters]]
|
||||
==== Binding Values to Queries
|
||||
|
||||
A typical application requires parameterized SQL statements to select or
|
||||
update rows according to some input. These are typically `SELECT` statements
|
||||
constrained by a `WHERE` clause or `INSERT` and `UPDATE` statements that accept
|
||||
input parameters. Parameterized statements bear the risk of SQL injection if
|
||||
parameters are not escaped properly. `DatabaseClient` leverages R2DBC's
|
||||
`bind` API to eliminate the risk of SQL injection for query parameters.
|
||||
You can provide a parameterized SQL statement with the `execute(…)` operator
|
||||
and bind parameters to the actual `Statement`. Your R2DBC driver then runs
|
||||
the statement by using prepared statements and parameter substitution.
|
||||
|
||||
Parameter binding supports two binding strategies:
|
||||
|
||||
* By Index, using zero-based parameter indexes.
|
||||
* By Name, using the placeholder name.
|
||||
|
||||
The following example shows parameter binding for a query:
|
||||
|
||||
====
|
||||
[source,java]
|
||||
----
|
||||
db.sql("INSERT INTO person (id, name, age) VALUES(:id, :name, :age)")
|
||||
.bind("id", "joe")
|
||||
.bind("name", "Joe")
|
||||
.bind("age", 34);
|
||||
----
|
||||
====
|
||||
|
||||
.R2DBC Native Bind Markers
|
||||
****
|
||||
R2DBC uses database-native bind markers that depend on the actual database vendor.
|
||||
As an example, Postgres uses indexed markers, such as `$1`, `$2`, `$n`.
|
||||
Another example is SQL Server, which uses named bind markers prefixed with `@`.
|
||||
|
||||
This is different from JDBC, which requires `?` as bind markers.
|
||||
In JDBC, the actual drivers translate `?` bind markers to database-native
|
||||
markers as part of their statement execution.
|
||||
|
||||
Spring Framework's R2DBC support lets you use native bind markers or named bind
|
||||
markers with the `:name` syntax.
|
||||
|
||||
Named parameter support leverages a `BindMarkersFactory` instance to expand named
|
||||
parameters to native bind markers at the time of query execution, which gives you
|
||||
a certain degree of query portability across various database vendors.
|
||||
****
|
||||
|
||||
The query-preprocessor unrolls named `Collection` parameters into a series of bind
|
||||
markers to remove the need of dynamic query creation based on the number of arguments.
|
||||
Nested object arrays are expanded to allow usage of (for example) select lists.
|
||||
|
||||
Consider the following query:
|
||||
|
||||
[source,sql]
|
||||
----
|
||||
SELECT id, name, state FROM table WHERE (name, age) IN (('John', 35), ('Ann', 50))
|
||||
----
|
||||
|
||||
The preceding query can be parameterized and run as follows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
List<Object[]> tuples = new ArrayList<>();
|
||||
tuples.add(new Object[] {"John", 35});
|
||||
tuples.add(new Object[] {"Ann", 50});
|
||||
|
||||
client.sql("SELECT id, name, state FROM table WHERE (name, age) IN (:tuples)")
|
||||
.bind("tuples", tuples);
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val tuples: MutableList<Array<Any>> = ArrayList()
|
||||
tuples.add(arrayOf("John", 35))
|
||||
tuples.add(arrayOf("Ann", 50))
|
||||
|
||||
client.sql("SELECT id, name, state FROM table WHERE (name, age) IN (:tuples)")
|
||||
.bind("tuples", tuples)
|
||||
----
|
||||
|
||||
NOTE: Usage of select lists is vendor-dependent.
|
||||
|
||||
The following example shows a simpler variant using `IN` predicates:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
client.sql("SELECT id, name, state FROM table WHERE age IN (:ages)")
|
||||
.bind("ages", Arrays.asList(35, 50));
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val tuples: MutableList<Array<Any>> = ArrayList()
|
||||
tuples.add(arrayOf("John", 35))
|
||||
tuples.add(arrayOf("Ann", 50))
|
||||
|
||||
client.sql("SELECT id, name, state FROM table WHERE age IN (:ages)")
|
||||
.bind("tuples", arrayOf(35, 50))
|
||||
----
|
||||
|
||||
NOTE: R2DBC itself does not support Collection-like values. Nevertheless,
|
||||
expanding a given `List` in the example above works for named parameters
|
||||
in Spring's R2DBC support, e.g. for use in `IN` clauses as shown above.
|
||||
However, inserting or updating array-typed columns (e.g. in Postgres)
|
||||
requires an array type that is supported by the underlying R2DBC driver:
|
||||
typically a Java array, e.g. `String[]` to update a `text[]` column.
|
||||
Do not pass `Collection<String>` or the like as an array parameter.
|
||||
|
||||
[[r2dbc-DatabaseClient-filter]]
|
||||
==== Statement Filters
|
||||
|
||||
Sometimes it you need to fine-tune options on the actual `Statement`
|
||||
before it gets run. Register a `Statement` filter
|
||||
(`StatementFilterFunction`) through `DatabaseClient` to intercept and
|
||||
modify statements in their execution, as the following example shows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
client.sql("INSERT INTO table (name, state) VALUES(:name, :state)")
|
||||
.filter((s, next) -> next.execute(s.returnGeneratedValues("id")))
|
||||
.bind("name", …)
|
||||
.bind("state", …);
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
client.sql("INSERT INTO table (name, state) VALUES(:name, :state)")
|
||||
.filter { s: Statement, next: ExecuteFunction -> next.execute(s.returnGeneratedValues("id")) }
|
||||
.bind("name", …)
|
||||
.bind("state", …)
|
||||
----
|
||||
|
||||
`DatabaseClient` exposes also simplified `filter(…)` overload accepting `Function<Statement, Statement>`:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
client.sql("INSERT INTO table (name, state) VALUES(:name, :state)")
|
||||
.filter(statement -> s.returnGeneratedValues("id"));
|
||||
|
||||
client.sql("SELECT id, name, state FROM table")
|
||||
.filter(statement -> s.fetchSize(25));
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
client.sql("INSERT INTO table (name, state) VALUES(:name, :state)")
|
||||
.filter { statement -> s.returnGeneratedValues("id") }
|
||||
|
||||
client.sql("SELECT id, name, state FROM table")
|
||||
.filter { statement -> s.fetchSize(25) }
|
||||
----
|
||||
|
||||
`StatementFilterFunction` implementations allow filtering of the
|
||||
`Statement` and filtering of `Result` objects.
|
||||
|
||||
[[r2dbc-DatabaseClient-idioms]]
|
||||
==== `DatabaseClient` Best Practices
|
||||
|
||||
Instances of the `DatabaseClient` class are thread-safe, once configured. This is
|
||||
important because it means that you can configure a single instance of a `DatabaseClient`
|
||||
and then safely inject this shared reference into multiple DAOs (or repositories).
|
||||
The `DatabaseClient` is stateful, in that it maintains a reference to a `ConnectionFactory`,
|
||||
but this state is not conversational state.
|
||||
|
||||
A common practice when using the `DatabaseClient` class is to configure a `ConnectionFactory`
|
||||
in your Spring configuration file and then dependency-inject
|
||||
that shared `ConnectionFactory` bean into your DAO classes. The `DatabaseClient` is created in
|
||||
the setter for the `ConnectionFactory`. This leads to DAOs that resemble the following:
|
||||
|
||||
--
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
public class R2dbcCorporateEventDao implements CorporateEventDao {
|
||||
|
||||
private DatabaseClient databaseClient;
|
||||
|
||||
public void setConnectionFactory(ConnectionFactory connectionFactory) {
|
||||
this.databaseClient = DatabaseClient.create(connectionFactory);
|
||||
}
|
||||
|
||||
// R2DBC-backed implementations of the methods on the CorporateEventDao follow...
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
class R2dbcCorporateEventDao(connectionFactory: ConnectionFactory) : CorporateEventDao {
|
||||
|
||||
private val databaseClient = DatabaseClient.create(connectionFactory)
|
||||
|
||||
// R2DBC-backed implementations of the methods on the CorporateEventDao follow...
|
||||
}
|
||||
----
|
||||
--
|
||||
|
||||
An alternative to explicit configuration is to use component-scanning and annotation
|
||||
support for dependency injection. In this case, you can annotate the class with `@Component`
|
||||
(which makes it a candidate for component-scanning) and annotate the `ConnectionFactory` setter
|
||||
method with `@Autowired`. The following example shows how to do so:
|
||||
|
||||
--
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@Component // <1>
|
||||
public class R2dbcCorporateEventDao implements CorporateEventDao {
|
||||
|
||||
private DatabaseClient databaseClient;
|
||||
|
||||
@Autowired // <2>
|
||||
public void setConnectionFactory(ConnectionFactory connectionFactory) {
|
||||
this.databaseClient = DatabaseClient.create(connectionFactory); // <3>
|
||||
}
|
||||
|
||||
// R2DBC-backed implementations of the methods on the CorporateEventDao follow...
|
||||
}
|
||||
----
|
||||
<1> Annotate the class with `@Component`.
|
||||
<2> Annotate the `ConnectionFactory` setter method with `@Autowired`.
|
||||
<3> Create a new `DatabaseClient` with the `ConnectionFactory`.
|
||||
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Component // <1>
|
||||
class R2dbcCorporateEventDao(connectionFactory: ConnectionFactory) : CorporateEventDao { // <2>
|
||||
|
||||
private val databaseClient = DatabaseClient(connectionFactory) // <3>
|
||||
|
||||
// R2DBC-backed implementations of the methods on the CorporateEventDao follow...
|
||||
}
|
||||
----
|
||||
<1> Annotate the class with `@Component`.
|
||||
<2> Constructor injection of the `ConnectionFactory`.
|
||||
<3> Create a new `DatabaseClient` with the `ConnectionFactory`.
|
||||
--
|
||||
|
||||
Regardless of which of the above template initialization styles you choose to use (or
|
||||
not), it is seldom necessary to create a new instance of a `DatabaseClient` class each
|
||||
time you want to run SQL. Once configured, a `DatabaseClient` instance is thread-safe.
|
||||
If your application accesses multiple
|
||||
databases, you may want multiple `DatabaseClient` instances, which requires multiple
|
||||
`ConnectionFactory` and, subsequently, multiple differently configured `DatabaseClient`
|
||||
instances.
|
||||
|
||||
[[r2dbc-auto-generated-keys]]
|
||||
== Retrieving Auto-generated Keys
|
||||
|
||||
`INSERT` statements may generate keys when inserting rows into a table
|
||||
that defines an auto-increment or identity column. To get full control over
|
||||
the column name to generate, simply register a `StatementFilterFunction` that
|
||||
requests the generated key for the desired column.
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
Mono<Integer> generatedId = client.sql("INSERT INTO table (name, state) VALUES(:name, :state)")
|
||||
.filter(statement -> s.returnGeneratedValues("id"))
|
||||
.map(row -> row.get("id", Integer.class))
|
||||
.first();
|
||||
|
||||
// generatedId emits the generated key once the INSERT statement has finished
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val generatedId = client.sql("INSERT INTO table (name, state) VALUES(:name, :state)")
|
||||
.filter { statement -> s.returnGeneratedValues("id") }
|
||||
.map { row -> row.get("id", Integer.class) }
|
||||
.awaitOne()
|
||||
|
||||
// generatedId emits the generated key once the INSERT statement has finished
|
||||
----
|
||||
|
||||
|
||||
[[r2dbc-connections]]
|
||||
== Controlling Database Connections
|
||||
|
||||
This section covers:
|
||||
|
||||
* <<r2dbc-ConnectionFactory>>
|
||||
* <<r2dbc-ConnectionFactoryUtils>>
|
||||
* <<r2dbc-SingleConnectionFactory>>
|
||||
* <<r2dbc-TransactionAwareConnectionFactoryProxy>>
|
||||
* <<r2dbc-R2dbcTransactionManager>>
|
||||
|
||||
|
||||
[[r2dbc-ConnectionFactory]]
|
||||
=== Using `ConnectionFactory`
|
||||
|
||||
Spring obtains an R2DBC connection to the database through a `ConnectionFactory`.
|
||||
A `ConnectionFactory` is part of the R2DBC specification and is a common entry-point
|
||||
for drivers. It lets a container or a framework hide connection pooling
|
||||
and transaction management issues from the application code. As a developer,
|
||||
you need not know details about how to connect to the database. That is the
|
||||
responsibility of the administrator who sets up the `ConnectionFactory`. You
|
||||
most likely fill both roles as you develop and test code, but you do not
|
||||
necessarily have to know how the production data source is configured.
|
||||
|
||||
When you use Spring's R2DBC layer, you can configure your own with a
|
||||
connection pool implementation provided by a third party. A popular
|
||||
implementation is R2DBC Pool (`r2dbc-pool`). Implementations in the Spring
|
||||
distribution are meant only for testing purposes and do not provide pooling.
|
||||
|
||||
To configure a `ConnectionFactory`:
|
||||
|
||||
. Obtain a connection with `ConnectionFactory` as you typically obtain an R2DBC `ConnectionFactory`.
|
||||
. Provide an R2DBC URL
|
||||
(See the documentation for your driver for the correct value).
|
||||
|
||||
The following example shows how to configure a `ConnectionFactory`:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
ConnectionFactory factory = ConnectionFactories.get("r2dbc:h2:mem:///test?options=DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE");
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val factory = ConnectionFactories.get("r2dbc:h2:mem:///test?options=DB_CLOSE_DELAY=-1;DB_CLOSE_ON_EXIT=FALSE");
|
||||
----
|
||||
|
||||
|
||||
[[r2dbc-ConnectionFactoryUtils]]
|
||||
=== Using `ConnectionFactoryUtils`
|
||||
|
||||
|
||||
The `ConnectionFactoryUtils` class is a convenient and powerful helper class
|
||||
that provides `static` methods to obtain connections from `ConnectionFactory`
|
||||
and close connections (if necessary).
|
||||
|
||||
It supports subscriber ``Context``-bound connections with, for example
|
||||
`R2dbcTransactionManager`.
|
||||
|
||||
|
||||
[[r2dbc-SingleConnectionFactory]]
|
||||
=== Using `SingleConnectionFactory`
|
||||
|
||||
The `SingleConnectionFactory` class is an implementation of `DelegatingConnectionFactory`
|
||||
interface that wraps a single `Connection` that is not closed after each use.
|
||||
|
||||
If any client code calls `close` on the assumption of a pooled connection (as when using
|
||||
persistence tools), you should set the `suppressClose` property to `true`. This setting
|
||||
returns a close-suppressing proxy that wraps the physical connection. Note that you can
|
||||
no longer cast this to a native `Connection` or a similar object.
|
||||
|
||||
`SingleConnectionFactory` is primarily a test class and may be used for specific requirements
|
||||
such as pipelining if your R2DBC driver permits for such use.
|
||||
In contrast to a pooled `ConnectionFactory`, it reuses the same connection all the time, avoiding
|
||||
excessive creation of physical connections.
|
||||
|
||||
|
||||
[[r2dbc-TransactionAwareConnectionFactoryProxy]]
|
||||
=== Using `TransactionAwareConnectionFactoryProxy`
|
||||
|
||||
`TransactionAwareConnectionFactoryProxy` is a proxy for a target `ConnectionFactory`.
|
||||
The proxy wraps that target `ConnectionFactory` to add awareness of Spring-managed transactions.
|
||||
|
||||
NOTE: Using this class is required if you use a R2DBC client that is not integrated otherwise
|
||||
with Spring's R2DBC support. In this case, you can still use this client and, at
|
||||
the same time, have this client participating in Spring managed transactions. It is generally
|
||||
preferable to integrate a R2DBC client with proper access to `ConnectionFactoryUtils`
|
||||
for resource management.
|
||||
|
||||
See the {api-spring-framework}/r2dbc/connection/TransactionAwareConnectionFactoryProxy.html[`TransactionAwareConnectionFactoryProxy`]
|
||||
javadoc for more details.
|
||||
|
||||
|
||||
[[r2dbc-R2dbcTransactionManager]]
|
||||
=== Using `R2dbcTransactionManager`
|
||||
|
||||
The `R2dbcTransactionManager` class is a `ReactiveTransactionManager` implementation for
|
||||
single R2DBC data sources. It binds an R2DBC connection from the specified connection factory
|
||||
to the subscriber `Context`, potentially allowing for one subscriber connection for each
|
||||
connection factory.
|
||||
|
||||
Application code is required to retrieve the R2DBC connection through
|
||||
`ConnectionFactoryUtils.getConnection(ConnectionFactory)`, instead of R2DBC's standard
|
||||
`ConnectionFactory.create()`.
|
||||
|
||||
All framework classes (such as `DatabaseClient`) use this strategy implicitly.
|
||||
If not used with this transaction manager, the lookup strategy behaves exactly like the common one.
|
||||
Thus, it can be used in any case.
|
||||
|
||||
The `R2dbcTransactionManager` class supports custom isolation levels that get applied to the connection.
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,40 @@
|
||||
[[transaction]]
|
||||
= Transaction Management
|
||||
|
||||
Comprehensive transaction support is among the most compelling reasons to use the Spring
|
||||
Framework. The Spring Framework provides a consistent abstraction for transaction
|
||||
management that delivers the following benefits:
|
||||
|
||||
* A consistent programming model across different transaction APIs, such as Java
|
||||
Transaction API (JTA), JDBC, Hibernate, and the Java Persistence API (JPA).
|
||||
* Support for <<transaction-declarative, declarative transaction management>>.
|
||||
* A simpler API for <<transaction-programmatic, programmatic>> transaction management
|
||||
than complex transaction APIs, such as JTA.
|
||||
* Excellent integration with Spring's data access abstractions.
|
||||
|
||||
The following sections describe the Spring Framework's transaction features and
|
||||
technologies:
|
||||
|
||||
* <<transaction-motivation, Advantages of the Spring Framework's transaction support
|
||||
model>> describes why you would use the Spring Framework's transaction abstraction
|
||||
instead of EJB Container-Managed Transactions (CMT) or choosing to drive local
|
||||
transactions through a proprietary API, such as Hibernate.
|
||||
* <<transaction-strategies, Understanding the Spring Framework transaction abstraction>>
|
||||
outlines the core classes and describes how to configure and obtain `DataSource`
|
||||
instances from a variety of sources.
|
||||
* <<tx-resource-synchronization, Synchronizing resources with transactions>> describes
|
||||
how the application code ensures that resources are created, reused, and cleaned up
|
||||
properly.
|
||||
* <<transaction-declarative, Declarative transaction management>> describes support for
|
||||
declarative transaction management.
|
||||
* <<transaction-programmatic, Programmatic transaction management>> covers support for
|
||||
programmatic (that is, explicitly coded) transaction management.
|
||||
* <<transaction-event, Transaction bound event>> describes how you could use application
|
||||
events within a transaction.
|
||||
|
||||
The chapter also includes discussions of best practices,
|
||||
<<transaction-application-server-integration, application server integration>>,
|
||||
and <<transaction-solutions-to-common-problems, solutions to common problems>>.
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,51 @@
|
||||
[[transaction-application-server-integration]]
|
||||
= Application server-specific integration
|
||||
|
||||
Spring's transaction abstraction is generally application server-agnostic. Additionally,
|
||||
Spring's `JtaTransactionManager` class (which can optionally perform a JNDI lookup for
|
||||
the JTA `UserTransaction` and `TransactionManager` objects) autodetects the location for
|
||||
the latter object, which varies by application server. Having access to the JTA
|
||||
`TransactionManager` allows for enhanced transaction semantics -- in particular,
|
||||
supporting transaction suspension. See the
|
||||
{api-spring-framework}/transaction/jta/JtaTransactionManager.html[`JtaTransactionManager`]
|
||||
javadoc for details.
|
||||
|
||||
Spring's `JtaTransactionManager` is the standard choice to run on Jakarta EE application
|
||||
servers and is known to work on all common servers. Advanced functionality, such as
|
||||
transaction suspension, works on many servers as well (including GlassFish, JBoss and
|
||||
Geronimo) without any special configuration required. However, for fully supported
|
||||
transaction suspension and further advanced integration, Spring includes special adapters
|
||||
for WebLogic Server and WebSphere. These adapters are discussed in the following
|
||||
sections.
|
||||
|
||||
For standard scenarios, including WebLogic Server and WebSphere, consider using the
|
||||
convenient `<tx:jta-transaction-manager/>` configuration element. When configured,
|
||||
this element automatically detects the underlying server and chooses the best
|
||||
transaction manager available for the platform. This means that you need not explicitly
|
||||
configure server-specific adapter classes (as discussed in the following sections).
|
||||
Rather, they are chosen automatically, with the standard
|
||||
`JtaTransactionManager` as the default fallback.
|
||||
|
||||
|
||||
[[transaction-application-server-integration-websphere]]
|
||||
== IBM WebSphere
|
||||
|
||||
On WebSphere 6.1.0.9 and above, the recommended Spring JTA transaction manager to use is
|
||||
`WebSphereUowTransactionManager`. This special adapter uses IBM's `UOWManager` API,
|
||||
which is available in WebSphere Application Server 6.1.0.9 and later. With this adapter,
|
||||
Spring-driven transaction suspension (suspend and resume as initiated by
|
||||
`PROPAGATION_REQUIRES_NEW`) is officially supported by IBM.
|
||||
|
||||
|
||||
[[transaction-application-server-integration-weblogic]]
|
||||
== Oracle WebLogic Server
|
||||
|
||||
On WebLogic Server 9.0 or above, you would typically use the
|
||||
`WebLogicJtaTransactionManager` instead of the stock `JtaTransactionManager` class. This
|
||||
special WebLogic-specific subclass of the normal `JtaTransactionManager` supports the
|
||||
full power of Spring's transaction definitions in a WebLogic-managed transaction
|
||||
environment, beyond standard JTA semantics. Features include transaction names,
|
||||
per-transaction isolation levels, and proper resuming of transactions in all cases.
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,53 @@
|
||||
[[transaction-declarative]]
|
||||
= Declarative Transaction Management
|
||||
|
||||
NOTE: Most Spring Framework users choose declarative transaction management. This option has
|
||||
the least impact on application code and, hence, is most consistent with the ideals of a
|
||||
non-invasive lightweight container.
|
||||
|
||||
The Spring Framework's declarative transaction management is made possible with Spring
|
||||
aspect-oriented programming (AOP). However, as the transactional aspects code comes
|
||||
with the Spring Framework distribution and may be used in a boilerplate fashion, AOP
|
||||
concepts do not generally have to be understood to make effective use of this code.
|
||||
|
||||
The Spring Framework's declarative transaction management is similar to EJB CMT, in that
|
||||
you can specify transaction behavior (or lack of it) down to the individual method level.
|
||||
You can make a `setRollbackOnly()` call within a transaction context, if
|
||||
necessary. The differences between the two types of transaction management are:
|
||||
|
||||
* Unlike EJB CMT, which is tied to JTA, the Spring Framework's declarative transaction
|
||||
management works in any environment. It can work with JTA transactions or local
|
||||
transactions by using JDBC, JPA, or Hibernate by adjusting the configuration
|
||||
files.
|
||||
* You can apply the Spring Framework declarative transaction management to any class,
|
||||
not merely special classes such as EJBs.
|
||||
* The Spring Framework offers declarative
|
||||
<<transaction-declarative-rolling-back, rollback rules>>, a feature with no EJB
|
||||
equivalent. Both programmatic and declarative support for rollback rules is provided.
|
||||
* The Spring Framework lets you customize transactional behavior by using AOP.
|
||||
For example, you can insert custom behavior in the case of transaction rollback. You
|
||||
can also add arbitrary advice, along with transactional advice. With EJB CMT, you
|
||||
cannot influence the container's transaction management, except with
|
||||
`setRollbackOnly()`.
|
||||
* The Spring Framework does not support propagation of transaction contexts across
|
||||
remote calls, as high-end application servers do. If you need this feature, we
|
||||
recommend that you use EJB. However, consider carefully before using such a feature,
|
||||
because, normally, one does not want transactions to span remote calls.
|
||||
|
||||
The concept of rollback rules is important. They let you specify which exceptions
|
||||
(and throwables) should cause automatic rollback. You can specify this declaratively, in
|
||||
configuration, not in Java code. So, although you can still call `setRollbackOnly()` on
|
||||
the `TransactionStatus` object to roll back the current transaction back, most often you
|
||||
can specify a rule that `MyApplicationException` must always result in rollback. The
|
||||
significant advantage to this option is that business objects do not depend on the
|
||||
transaction infrastructure. For example, they typically do not need to import Spring
|
||||
transaction APIs or other Spring APIs.
|
||||
|
||||
Although EJB container default behavior automatically rolls back the transaction on a
|
||||
system exception (usually a runtime exception), EJB CMT does not roll back the
|
||||
transaction automatically on an application exception (that is, a checked exception
|
||||
other than `java.rmi.RemoteException`). While the Spring default behavior for
|
||||
declarative transaction management follows EJB convention (roll back is automatic only
|
||||
on unchecked exceptions), it is often useful to customize this behavior.
|
||||
|
||||
|
||||
@@ -0,0 +1,598 @@
|
||||
[[transaction-declarative-annotations]]
|
||||
= Using `@Transactional`
|
||||
|
||||
In addition to the XML-based declarative approach to transaction configuration, you can
|
||||
use an annotation-based approach. Declaring transaction semantics directly in the Java
|
||||
source code puts the declarations much closer to the affected code. There is not much
|
||||
danger of undue coupling, because code that is meant to be used transactionally is
|
||||
almost always deployed that way anyway.
|
||||
|
||||
NOTE: The standard `jakarta.transaction.Transactional` annotation is also supported as
|
||||
a drop-in replacement to Spring's own annotation. Please refer to the JTA documentation
|
||||
for more details.
|
||||
|
||||
The ease-of-use afforded by the use of the `@Transactional` annotation is best
|
||||
illustrated with an example, which is explained in the text that follows.
|
||||
Consider the following class definition:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
// the service class that we want to make transactional
|
||||
@Transactional
|
||||
public class DefaultFooService implements FooService {
|
||||
|
||||
@Override
|
||||
public Foo getFoo(String fooName) {
|
||||
// ...
|
||||
}
|
||||
|
||||
@Override
|
||||
public Foo getFoo(String fooName, String barName) {
|
||||
// ...
|
||||
}
|
||||
|
||||
@Override
|
||||
public void insertFoo(Foo foo) {
|
||||
// ...
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateFoo(Foo foo) {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
// the service class that we want to make transactional
|
||||
@Transactional
|
||||
class DefaultFooService : FooService {
|
||||
|
||||
override fun getFoo(fooName: String): Foo {
|
||||
// ...
|
||||
}
|
||||
|
||||
override fun getFoo(fooName: String, barName: String): Foo {
|
||||
// ...
|
||||
}
|
||||
|
||||
override fun insertFoo(foo: Foo) {
|
||||
// ...
|
||||
}
|
||||
|
||||
override fun updateFoo(foo: Foo) {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Used at the class level as above, the annotation indicates a default for all methods of
|
||||
the declaring class (as well as its subclasses). Alternatively, each method can be
|
||||
annotated individually. See <<transaction-declarative-annotations-method-visibility>> for
|
||||
further details on which methods Spring considers transactional. Note that a class-level
|
||||
annotation does not apply to ancestor classes up the class hierarchy; in such a scenario,
|
||||
inherited methods need to be locally redeclared in order to participate in a
|
||||
subclass-level annotation.
|
||||
|
||||
When a POJO class such as the one above is defined as a bean in a Spring context,
|
||||
you can make the bean instance transactional through an `@EnableTransactionManagement`
|
||||
annotation in a `@Configuration` class. See the
|
||||
{api-spring-framework}/transaction/annotation/EnableTransactionManagement.html[javadoc]
|
||||
for full details.
|
||||
|
||||
In XML configuration, the `<tx:annotation-driven/>` tag provides similar convenience:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<!-- from the file 'context.xml' -->
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:aop="http://www.springframework.org/schema/aop"
|
||||
xmlns:tx="http://www.springframework.org/schema/tx"
|
||||
xsi:schemaLocation="
|
||||
http://www.springframework.org/schema/beans
|
||||
https://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/tx
|
||||
https://www.springframework.org/schema/tx/spring-tx.xsd
|
||||
http://www.springframework.org/schema/aop
|
||||
https://www.springframework.org/schema/aop/spring-aop.xsd">
|
||||
|
||||
<!-- this is the service object that we want to make transactional -->
|
||||
<bean id="fooService" class="x.y.service.DefaultFooService"/>
|
||||
|
||||
<!-- enable the configuration of transactional behavior based on annotations -->
|
||||
<!-- a TransactionManager is still required -->
|
||||
<tx:annotation-driven transaction-manager="txManager"/> <1>
|
||||
|
||||
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
|
||||
<!-- (this dependency is defined somewhere else) -->
|
||||
<property name="dataSource" ref="dataSource"/>
|
||||
</bean>
|
||||
|
||||
<!-- other <bean/> definitions here -->
|
||||
|
||||
</beans>
|
||||
----
|
||||
<1> The line that makes the bean instance transactional.
|
||||
|
||||
|
||||
TIP: You can omit the `transaction-manager` attribute in the `<tx:annotation-driven/>`
|
||||
tag if the bean name of the `TransactionManager` that you want to wire in has the name
|
||||
`transactionManager`. If the `TransactionManager` bean that you want to dependency-inject
|
||||
has any other name, you have to use the `transaction-manager` attribute, as in the
|
||||
preceding example.
|
||||
|
||||
Reactive transactional methods use reactive return types in contrast to imperative
|
||||
programming arrangements as the following listing shows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
// the reactive service class that we want to make transactional
|
||||
@Transactional
|
||||
public class DefaultFooService implements FooService {
|
||||
|
||||
@Override
|
||||
public Publisher<Foo> getFoo(String fooName) {
|
||||
// ...
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Foo> getFoo(String fooName, String barName) {
|
||||
// ...
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> insertFoo(Foo foo) {
|
||||
// ...
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> updateFoo(Foo foo) {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
// the reactive service class that we want to make transactional
|
||||
@Transactional
|
||||
class DefaultFooService : FooService {
|
||||
|
||||
override fun getFoo(fooName: String): Flow<Foo> {
|
||||
// ...
|
||||
}
|
||||
|
||||
override fun getFoo(fooName: String, barName: String): Mono<Foo> {
|
||||
// ...
|
||||
}
|
||||
|
||||
override fun insertFoo(foo: Foo): Mono<Void> {
|
||||
// ...
|
||||
}
|
||||
|
||||
override fun updateFoo(foo: Foo): Mono<Void> {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Note that there are special considerations for the returned `Publisher` with regards to
|
||||
Reactive Streams cancellation signals. See the <<tx-prog-operator-cancel>> section under
|
||||
"Using the TransactionalOperator" for more details.
|
||||
|
||||
|
||||
[[transaction-declarative-annotations-method-visibility]]
|
||||
.Method visibility and `@Transactional`
|
||||
[NOTE]
|
||||
====
|
||||
When you use transactional proxies with Spring's standard configuration, you should apply
|
||||
the `@Transactional` annotation only to methods with `public` visibility. If you do
|
||||
annotate `protected`, `private`, or package-visible methods with the `@Transactional`
|
||||
annotation, no error is raised, but the annotated method does not exhibit the configured
|
||||
transactional settings. If you need to annotate non-public methods, consider the tip in
|
||||
the following paragraph for class-based proxies or consider using AspectJ compile-time or
|
||||
load-time weaving (described later).
|
||||
|
||||
When using `@EnableTransactionManagement` in a `@Configuration` class, `protected` or
|
||||
package-visible methods can also be made transactional for class-based proxies by
|
||||
registering a custom `transactionAttributeSource` bean like in the following example.
|
||||
Note, however, that transactional methods in interface-based proxies must always be
|
||||
`public` and defined in the proxied interface.
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
/**
|
||||
* Register a custom AnnotationTransactionAttributeSource with the
|
||||
* publicMethodsOnly flag set to false to enable support for
|
||||
* protected and package-private @Transactional methods in
|
||||
* class-based proxies.
|
||||
*
|
||||
* @see ProxyTransactionManagementConfiguration#transactionAttributeSource()
|
||||
*/
|
||||
@Bean
|
||||
TransactionAttributeSource transactionAttributeSource() {
|
||||
return new AnnotationTransactionAttributeSource(false);
|
||||
}
|
||||
----
|
||||
|
||||
The _Spring TestContext Framework_ supports non-private `@Transactional` test methods by
|
||||
default. See <<testing.adoc#testcontext-tx,Transaction Management>> in the testing
|
||||
chapter for examples.
|
||||
====
|
||||
|
||||
You can apply the `@Transactional` annotation to an interface definition, a method
|
||||
on an interface, a class definition, or a method on a class. However, the
|
||||
mere presence of the `@Transactional` annotation is not enough to activate the
|
||||
transactional behavior. The `@Transactional` annotation is merely metadata that can
|
||||
be consumed by some runtime infrastructure that is `@Transactional`-aware and that
|
||||
can use the metadata to configure the appropriate beans with transactional behavior.
|
||||
In the preceding example, the `<tx:annotation-driven/>` element switches on the
|
||||
transactional behavior.
|
||||
|
||||
TIP: The Spring team recommends that you annotate only concrete classes (and methods of
|
||||
concrete classes) with the `@Transactional` annotation, as opposed to annotating interfaces.
|
||||
You certainly can place the `@Transactional` annotation on an interface (or an interface
|
||||
method), but this works only as you would expect it to if you use interface-based
|
||||
proxies. The fact that Java annotations are not inherited from interfaces means that,
|
||||
if you use class-based proxies (`proxy-target-class="true"`) or the weaving-based
|
||||
aspect (`mode="aspectj"`), the transaction settings are not recognized by the proxying
|
||||
and weaving infrastructure, and the object is not wrapped in a transactional proxy.
|
||||
|
||||
NOTE: In proxy mode (which is the default), only external method calls coming in through
|
||||
the proxy are intercepted. This means that self-invocation (in effect, a method within
|
||||
the target object calling another method of the target object) does not lead to an actual
|
||||
transaction at runtime even if the invoked method is marked with `@Transactional`. Also,
|
||||
the proxy must be fully initialized to provide the expected behavior, so you should not
|
||||
rely on this feature in your initialization code -- for example, in a `@PostConstruct`
|
||||
method.
|
||||
|
||||
Consider using AspectJ mode (see the `mode` attribute in the following table) if you
|
||||
expect self-invocations to be wrapped with transactions as well. In this case, there is
|
||||
no proxy in the first place. Instead, the target class is woven (that is, its byte code
|
||||
is modified) to support `@Transactional` runtime behavior on any kind of method.
|
||||
|
||||
[[tx-annotation-driven-settings]]
|
||||
.Annotation driven transaction settings
|
||||
|===
|
||||
| XML Attribute| Annotation Attribute| Default| Description
|
||||
|
||||
| `transaction-manager`
|
||||
| N/A (see {api-spring-framework}/transaction/annotation/TransactionManagementConfigurer.html[`TransactionManagementConfigurer`] javadoc)
|
||||
| `transactionManager`
|
||||
| Name of the transaction manager to use. Required only if the name of the transaction
|
||||
manager is not `transactionManager`, as in the preceding example.
|
||||
|
||||
| `mode`
|
||||
| `mode`
|
||||
| `proxy`
|
||||
| The default mode (`proxy`) processes annotated beans to be proxied by using Spring's AOP
|
||||
framework (following proxy semantics, as discussed earlier, applying to method calls
|
||||
coming in through the proxy only). The alternative mode (`aspectj`) instead weaves the
|
||||
affected classes with Spring's AspectJ transaction aspect, modifying the target class
|
||||
byte code to apply to any kind of method call. AspectJ weaving requires
|
||||
`spring-aspects.jar` in the classpath as well as having load-time weaving (or compile-time
|
||||
weaving) enabled. (See <<core.adoc#aop-aj-ltw-spring, Spring configuration>>
|
||||
for details on how to set up load-time weaving.)
|
||||
|
||||
| `proxy-target-class`
|
||||
| `proxyTargetClass`
|
||||
| `false`
|
||||
| Applies to `proxy` mode only. Controls what type of transactional proxies are created
|
||||
for classes annotated with the `@Transactional` annotation. If the
|
||||
`proxy-target-class` attribute is set to `true`, class-based proxies are created.
|
||||
If `proxy-target-class` is `false` or if the attribute is omitted, then standard JDK
|
||||
interface-based proxies are created. (See <<core.adoc#aop-proxying, Proxying Mechanisms>>
|
||||
for a detailed examination of the different proxy types.)
|
||||
|
||||
| `order`
|
||||
| `order`
|
||||
| `Ordered.LOWEST_PRECEDENCE`
|
||||
| Defines the order of the transaction advice that is applied to beans annotated with
|
||||
`@Transactional`. (For more information about the rules related to ordering of AOP
|
||||
advice, see <<core.adoc#aop-ataspectj-advice-ordering, Advice Ordering>>.)
|
||||
No specified ordering means that the AOP subsystem determines the order of the advice.
|
||||
|===
|
||||
|
||||
NOTE: The default advice mode for processing `@Transactional` annotations is `proxy`,
|
||||
which allows for interception of calls through the proxy only. Local calls within the
|
||||
same class cannot get intercepted that way. For a more advanced mode of interception,
|
||||
consider switching to `aspectj` mode in combination with compile-time or load-time weaving.
|
||||
|
||||
NOTE: The `proxy-target-class` attribute controls what type of transactional proxies are
|
||||
created for classes annotated with the `@Transactional` annotation. If
|
||||
`proxy-target-class` is set to `true`, class-based proxies are created. If
|
||||
`proxy-target-class` is `false` or if the attribute is omitted, standard JDK
|
||||
interface-based proxies are created. (See <<core.adoc#aop-proxying, Proxying Mechanisms>>
|
||||
for a discussion of the different proxy types.)
|
||||
|
||||
NOTE: `@EnableTransactionManagement` and `<tx:annotation-driven/>` look for
|
||||
`@Transactional` only on beans in the same application context in which they are defined.
|
||||
This means that, if you put annotation-driven configuration in a `WebApplicationContext`
|
||||
for a `DispatcherServlet`, it checks for `@Transactional` beans only in your controllers
|
||||
and not in your services. See <<web.adoc#mvc-servlet, MVC>> for more information.
|
||||
|
||||
The most derived location takes precedence when evaluating the transactional settings
|
||||
for a method. In the case of the following example, the `DefaultFooService` class is
|
||||
annotated at the class level with the settings for a read-only transaction, but the
|
||||
`@Transactional` annotation on the `updateFoo(Foo)` method in the same class takes
|
||||
precedence over the transactional settings defined at the class level.
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@Transactional(readOnly = true)
|
||||
public class DefaultFooService implements FooService {
|
||||
|
||||
public Foo getFoo(String fooName) {
|
||||
// ...
|
||||
}
|
||||
|
||||
// these settings have precedence for this method
|
||||
@Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
|
||||
public void updateFoo(Foo foo) {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Transactional(readOnly = true)
|
||||
class DefaultFooService : FooService {
|
||||
|
||||
override fun getFoo(fooName: String): Foo {
|
||||
// ...
|
||||
}
|
||||
|
||||
// these settings have precedence for this method
|
||||
@Transactional(readOnly = false, propagation = Propagation.REQUIRES_NEW)
|
||||
override fun updateFoo(foo: Foo) {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
|
||||
[[transaction-declarative-attransactional-settings]]
|
||||
== `@Transactional` Settings
|
||||
|
||||
The `@Transactional` annotation is metadata that specifies that an interface, class,
|
||||
or method must have transactional semantics (for example, "start a brand new read-only
|
||||
transaction when this method is invoked, suspending any existing transaction").
|
||||
The default `@Transactional` settings are as follows:
|
||||
|
||||
* The propagation setting is `PROPAGATION_REQUIRED.`
|
||||
* The isolation level is `ISOLATION_DEFAULT.`
|
||||
* The transaction is read-write.
|
||||
* The transaction timeout defaults to the default timeout of the underlying transaction
|
||||
system, or to none if timeouts are not supported.
|
||||
* Any `RuntimeException` or `Error` triggers rollback, and any checked `Exception` does
|
||||
not.
|
||||
|
||||
You can change these default settings. The following table summarizes the various
|
||||
properties of the `@Transactional` annotation:
|
||||
|
||||
[[tx-attransactional-properties]]
|
||||
.@Transactional Settings
|
||||
|===
|
||||
| Property| Type| Description
|
||||
|
||||
| <<tx-multiple-tx-mgrs-with-attransactional,value>>
|
||||
| `String`
|
||||
| Optional qualifier that specifies the transaction manager to be used.
|
||||
|
||||
| `transactionManager`
|
||||
| `String`
|
||||
| Alias for `value`.
|
||||
|
||||
| `label`
|
||||
| Array of `String` labels to add an expressive description to the transaction.
|
||||
| Labels may be evaluated by transaction managers to associate implementation-specific behavior with the actual transaction.
|
||||
|
||||
| <<tx-propagation,propagation>>
|
||||
| `enum`: `Propagation`
|
||||
| Optional propagation setting.
|
||||
|
||||
| `isolation`
|
||||
| `enum`: `Isolation`
|
||||
| Optional isolation level. Applies only to propagation values of `REQUIRED` or `REQUIRES_NEW`.
|
||||
|
||||
| `timeout`
|
||||
| `int` (in seconds of granularity)
|
||||
| Optional transaction timeout. Applies only to propagation values of `REQUIRED` or `REQUIRES_NEW`.
|
||||
|
||||
| `timeoutString`
|
||||
| `String` (in seconds of granularity)
|
||||
| Alternative for specifying the `timeout` in seconds as a `String` value -- for example, as a placeholder.
|
||||
|
||||
| `readOnly`
|
||||
| `boolean`
|
||||
| Read-write versus read-only transaction. Only applicable to values of `REQUIRED` or `REQUIRES_NEW`.
|
||||
|
||||
| `rollbackFor`
|
||||
| Array of `Class` objects, which must be derived from `Throwable.`
|
||||
| Optional array of exception types that must cause rollback.
|
||||
|
||||
| `rollbackForClassName`
|
||||
| Array of exception name patterns.
|
||||
| Optional array of exception name patterns that must cause rollback.
|
||||
|
||||
| `noRollbackFor`
|
||||
| Array of `Class` objects, which must be derived from `Throwable.`
|
||||
| Optional array of exception types that must not cause rollback.
|
||||
|
||||
| `noRollbackForClassName`
|
||||
| Array of exception name patterns.
|
||||
| Optional array of exception name patterns that must not cause rollback.
|
||||
|===
|
||||
|
||||
TIP: See <<transaction-declarative-rollback-rules, Rollback rules>> for further details
|
||||
on rollback rule semantics, patterns, and warnings regarding possible unintentional
|
||||
matches for pattern-based rollback rules.
|
||||
|
||||
Currently, you cannot have explicit control over the name of a transaction, where 'name'
|
||||
means the transaction name that appears in a transaction monitor, if applicable
|
||||
(for example, WebLogic's transaction monitor), and in logging output. For declarative
|
||||
transactions, the transaction name is always the fully-qualified class name + `.`
|
||||
+ the method name of the transactionally advised class. For example, if the
|
||||
`handlePayment(..)` method of the `BusinessService` class started a transaction, the
|
||||
name of the transaction would be: `com.example.BusinessService.handlePayment`.
|
||||
|
||||
[[tx-multiple-tx-mgrs-with-attransactional]]
|
||||
== Multiple Transaction Managers with `@Transactional`
|
||||
|
||||
Most Spring applications need only a single transaction manager, but there may be
|
||||
situations where you want multiple independent transaction managers in a single
|
||||
application. You can use the `value` or `transactionManager` attribute of the
|
||||
`@Transactional` annotation to optionally specify the identity of the
|
||||
`TransactionManager` to be used. This can either be the bean name or the qualifier value
|
||||
of the transaction manager bean. For example, using the qualifier notation, you can
|
||||
combine the following Java code with the following transaction manager bean declarations
|
||||
in the application context:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
public class TransactionalService {
|
||||
|
||||
@Transactional("order")
|
||||
public void setSomething(String name) { ... }
|
||||
|
||||
@Transactional("account")
|
||||
public void doSomething() { ... }
|
||||
|
||||
@Transactional("reactive-account")
|
||||
public Mono<Void> doSomethingReactive() { ... }
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
class TransactionalService {
|
||||
|
||||
@Transactional("order")
|
||||
fun setSomething(name: String) {
|
||||
// ...
|
||||
}
|
||||
|
||||
@Transactional("account")
|
||||
fun doSomething() {
|
||||
// ...
|
||||
}
|
||||
|
||||
@Transactional("reactive-account")
|
||||
fun doSomethingReactive(): Mono<Void> {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
The following listing shows the bean declarations:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<tx:annotation-driven/>
|
||||
|
||||
<bean id="transactionManager1" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
|
||||
...
|
||||
<qualifier value="order"/>
|
||||
</bean>
|
||||
|
||||
<bean id="transactionManager2" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
|
||||
...
|
||||
<qualifier value="account"/>
|
||||
</bean>
|
||||
|
||||
<bean id="transactionManager3" class="org.springframework.data.r2dbc.connectionfactory.R2dbcTransactionManager">
|
||||
...
|
||||
<qualifier value="reactive-account"/>
|
||||
</bean>
|
||||
----
|
||||
|
||||
In this case, the individual methods on `TransactionalService` run under separate
|
||||
transaction managers, differentiated by the `order`, `account`, and `reactive-account`
|
||||
qualifiers. The default `<tx:annotation-driven>` target bean name, `transactionManager`,
|
||||
is still used if no specifically qualified `TransactionManager` bean is found.
|
||||
|
||||
[[tx-custom-attributes]]
|
||||
== Custom Composed Annotations
|
||||
|
||||
If you find you repeatedly use the same attributes with `@Transactional` on many different
|
||||
methods, <<core.adoc#beans-meta-annotations, Spring's meta-annotation support>> lets you
|
||||
define custom composed annotations for your specific use cases. For example, consider the
|
||||
following annotation definitions:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@Target({ElementType.METHOD, ElementType.TYPE})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Transactional(transactionManager = "order", label = "causal-consistency")
|
||||
public @interface OrderTx {
|
||||
}
|
||||
|
||||
@Target({ElementType.METHOD, ElementType.TYPE})
|
||||
@Retention(RetentionPolicy.RUNTIME)
|
||||
@Transactional(transactionManager = "account", label = "retryable")
|
||||
public @interface AccountTx {
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.TYPE)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
@Transactional(transactionManager = "order", label = ["causal-consistency"])
|
||||
annotation class OrderTx
|
||||
|
||||
@Target(AnnotationTarget.FUNCTION, AnnotationTarget.TYPE)
|
||||
@Retention(AnnotationRetention.RUNTIME)
|
||||
@Transactional(transactionManager = "account", label = ["retryable"])
|
||||
annotation class AccountTx
|
||||
----
|
||||
|
||||
The preceding annotations let us write the example from the previous section as follows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
public class TransactionalService {
|
||||
|
||||
@OrderTx
|
||||
public void setSomething(String name) {
|
||||
// ...
|
||||
}
|
||||
|
||||
@AccountTx
|
||||
public void doSomething() {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
class TransactionalService {
|
||||
|
||||
@OrderTx
|
||||
fun setSomething(name: String) {
|
||||
// ...
|
||||
}
|
||||
|
||||
@AccountTx
|
||||
fun doSomething() {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
In the preceding example, we used the syntax to define the transaction manager qualifier
|
||||
and transactional labels, but we could also have included propagation behavior,
|
||||
rollback rules, timeouts, and other features.
|
||||
|
||||
|
||||
@@ -0,0 +1,216 @@
|
||||
[[transaction-declarative-applying-more-than-just-tx-advice]]
|
||||
= Advising Transactional Operations
|
||||
|
||||
Suppose you want to run both transactional operations and some basic profiling advice.
|
||||
How do you effect this in the context of `<tx:annotation-driven/>`?
|
||||
|
||||
When you invoke the `updateFoo(Foo)` method, you want to see the following actions:
|
||||
|
||||
* The configured profiling aspect starts.
|
||||
* The transactional advice runs.
|
||||
* The method on the advised object runs.
|
||||
* The transaction commits.
|
||||
* The profiling aspect reports the exact duration of the whole transactional method invocation.
|
||||
|
||||
NOTE: This chapter is not concerned with explaining AOP in any great detail (except as it
|
||||
applies to transactions). See <<core.adoc#aop,AOP>> for detailed coverage of the AOP
|
||||
configuration and AOP in general.
|
||||
|
||||
The following code shows the simple profiling aspect discussed earlier:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
|
||||
.Java
|
||||
----
|
||||
package x.y;
|
||||
|
||||
import org.aspectj.lang.ProceedingJoinPoint;
|
||||
import org.springframework.util.StopWatch;
|
||||
import org.springframework.core.Ordered;
|
||||
|
||||
public class SimpleProfiler implements Ordered {
|
||||
|
||||
private int order;
|
||||
|
||||
// allows us to control the ordering of advice
|
||||
public int getOrder() {
|
||||
return this.order;
|
||||
}
|
||||
|
||||
public void setOrder(int order) {
|
||||
this.order = order;
|
||||
}
|
||||
|
||||
// this method is the around advice
|
||||
public Object profile(ProceedingJoinPoint call) throws Throwable {
|
||||
Object returnValue;
|
||||
StopWatch clock = new StopWatch(getClass().getName());
|
||||
try {
|
||||
clock.start(call.toShortString());
|
||||
returnValue = call.proceed();
|
||||
} finally {
|
||||
clock.stop();
|
||||
System.out.println(clock.prettyPrint());
|
||||
}
|
||||
return returnValue;
|
||||
}
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim",role="secondary",chomp="-packages"]
|
||||
.Kotlin
|
||||
----
|
||||
package x.y
|
||||
|
||||
import org.aspectj.lang.ProceedingJoinPoint
|
||||
import org.springframework.util.StopWatch
|
||||
import org.springframework.core.Ordered
|
||||
|
||||
class SimpleProfiler : Ordered {
|
||||
|
||||
private var order: Int = 0
|
||||
|
||||
// allows us to control the ordering of advice
|
||||
override fun getOrder(): Int {
|
||||
return this.order
|
||||
}
|
||||
|
||||
fun setOrder(order: Int) {
|
||||
this.order = order
|
||||
}
|
||||
|
||||
// this method is the around advice
|
||||
fun profile(call: ProceedingJoinPoint): Any {
|
||||
var returnValue: Any
|
||||
val clock = StopWatch(javaClass.name)
|
||||
try {
|
||||
clock.start(call.toShortString())
|
||||
returnValue = call.proceed()
|
||||
} finally {
|
||||
clock.stop()
|
||||
println(clock.prettyPrint())
|
||||
}
|
||||
return returnValue
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
The ordering of advice
|
||||
is controlled through the `Ordered` interface. For full details on advice ordering, see
|
||||
<<core.adoc#aop-ataspectj-advice-ordering,Advice ordering>>.
|
||||
|
||||
The following configuration creates a `fooService` bean that has profiling and
|
||||
transactional aspects applied to it in the desired order:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim"]
|
||||
----
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:aop="http://www.springframework.org/schema/aop"
|
||||
xmlns:tx="http://www.springframework.org/schema/tx"
|
||||
xsi:schemaLocation="
|
||||
http://www.springframework.org/schema/beans
|
||||
https://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/tx
|
||||
https://www.springframework.org/schema/tx/spring-tx.xsd
|
||||
http://www.springframework.org/schema/aop
|
||||
https://www.springframework.org/schema/aop/spring-aop.xsd">
|
||||
|
||||
<bean id="fooService" class="x.y.service.DefaultFooService"/>
|
||||
|
||||
<!-- this is the aspect -->
|
||||
<bean id="profiler" class="x.y.SimpleProfiler">
|
||||
<!-- run before the transactional advice (hence the lower order number) -->
|
||||
<property name="order" value="1"/>
|
||||
</bean>
|
||||
|
||||
<tx:annotation-driven transaction-manager="txManager" order="200"/>
|
||||
|
||||
<aop:config>
|
||||
<!-- this advice runs around the transactional advice -->
|
||||
<aop:aspect id="profilingAspect" ref="profiler">
|
||||
<aop:pointcut id="serviceMethodWithReturnValue"
|
||||
expression="execution(!void x.y..*Service.*(..))"/>
|
||||
<aop:around method="profile" pointcut-ref="serviceMethodWithReturnValue"/>
|
||||
</aop:aspect>
|
||||
</aop:config>
|
||||
|
||||
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
|
||||
<property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"/>
|
||||
<property name="url" value="jdbc:oracle:thin:@rj-t42:1521:elvis"/>
|
||||
<property name="username" value="scott"/>
|
||||
<property name="password" value="tiger"/>
|
||||
</bean>
|
||||
|
||||
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
|
||||
<property name="dataSource" ref="dataSource"/>
|
||||
</bean>
|
||||
|
||||
</beans>
|
||||
----
|
||||
|
||||
You can configure any number
|
||||
of additional aspects in similar fashion.
|
||||
|
||||
The following example creates the same setup as the previous two examples but uses the purely XML
|
||||
declarative approach:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim"]
|
||||
----
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:aop="http://www.springframework.org/schema/aop"
|
||||
xmlns:tx="http://www.springframework.org/schema/tx"
|
||||
xsi:schemaLocation="
|
||||
http://www.springframework.org/schema/beans
|
||||
https://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/tx
|
||||
https://www.springframework.org/schema/tx/spring-tx.xsd
|
||||
http://www.springframework.org/schema/aop
|
||||
https://www.springframework.org/schema/aop/spring-aop.xsd">
|
||||
|
||||
<bean id="fooService" class="x.y.service.DefaultFooService"/>
|
||||
|
||||
<!-- the profiling advice -->
|
||||
<bean id="profiler" class="x.y.SimpleProfiler">
|
||||
<!-- run before the transactional advice (hence the lower order number) -->
|
||||
<property name="order" value="1"/>
|
||||
</bean>
|
||||
|
||||
<aop:config>
|
||||
<aop:pointcut id="entryPointMethod" expression="execution(* x.y..*Service.*(..))"/>
|
||||
<!-- runs after the profiling advice (cf. the order attribute) -->
|
||||
|
||||
<aop:advisor advice-ref="txAdvice" pointcut-ref="entryPointMethod" order="2"/>
|
||||
<!-- order value is higher than the profiling aspect -->
|
||||
|
||||
<aop:aspect id="profilingAspect" ref="profiler">
|
||||
<aop:pointcut id="serviceMethodWithReturnValue"
|
||||
expression="execution(!void x.y..*Service.*(..))"/>
|
||||
<aop:around method="profile" pointcut-ref="serviceMethodWithReturnValue"/>
|
||||
</aop:aspect>
|
||||
|
||||
</aop:config>
|
||||
|
||||
<tx:advice id="txAdvice" transaction-manager="txManager">
|
||||
<tx:attributes>
|
||||
<tx:method name="get*" read-only="true"/>
|
||||
<tx:method name="*"/>
|
||||
</tx:attributes>
|
||||
</tx:advice>
|
||||
|
||||
<!-- other <bean/> definitions such as a DataSource and a TransactionManager here -->
|
||||
|
||||
</beans>
|
||||
----
|
||||
|
||||
The result of the preceding configuration is a `fooService` bean that has profiling and
|
||||
transactional aspects applied to it in that order. If you want the profiling advice
|
||||
to run after the transactional advice on the way in and before the
|
||||
transactional advice on the way out, you can swap the value of the profiling
|
||||
aspect bean's `order` property so that it is higher than the transactional advice's
|
||||
order value.
|
||||
|
||||
You can configure additional aspects in similar fashion.
|
||||
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
[[transaction-declarative-aspectj]]
|
||||
= Using `@Transactional` with AspectJ
|
||||
|
||||
You can also use the Spring Framework's `@Transactional` support outside of a Spring
|
||||
container by means of an AspectJ aspect. To do so, first annotate your classes
|
||||
(and optionally your classes' methods) with the `@Transactional` annotation,
|
||||
and then link (weave) your application with the
|
||||
`org.springframework.transaction.aspectj.AnnotationTransactionAspect` defined in the
|
||||
`spring-aspects.jar` file. You must also configure the aspect with a transaction
|
||||
manager. You can use the Spring Framework's IoC container to take care of
|
||||
dependency-injecting the aspect. The simplest way to configure the transaction
|
||||
management aspect is to use the `<tx:annotation-driven/>` element and specify the `mode`
|
||||
attribute to `aspectj` as described in <<transaction-declarative-annotations>>. Because
|
||||
we focus here on applications that run outside of a Spring container, we show
|
||||
you how to do it programmatically.
|
||||
|
||||
NOTE: Prior to continuing, you may want to read <<transaction-declarative-annotations>> and
|
||||
<<core.adoc#aop, AOP>> respectively.
|
||||
|
||||
The following example shows how to create a transaction manager and configure the
|
||||
`AnnotationTransactionAspect` to use it:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
// construct an appropriate transaction manager
|
||||
DataSourceTransactionManager txManager = new DataSourceTransactionManager(getDataSource());
|
||||
|
||||
// configure the AnnotationTransactionAspect to use it; this must be done before executing any transactional methods
|
||||
AnnotationTransactionAspect.aspectOf().setTransactionManager(txManager);
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
// construct an appropriate transaction manager
|
||||
val txManager = DataSourceTransactionManager(getDataSource())
|
||||
|
||||
// configure the AnnotationTransactionAspect to use it; this must be done before executing any transactional methods
|
||||
AnnotationTransactionAspect.aspectOf().transactionManager = txManager
|
||||
----
|
||||
|
||||
NOTE: When you use this aspect, you must annotate the implementation class (or the methods
|
||||
within that class or both), not the interface (if any) that the class implements. AspectJ
|
||||
follows Java's rule that annotations on interfaces are not inherited.
|
||||
|
||||
The `@Transactional` annotation on a class specifies the default transaction semantics
|
||||
for the execution of any public method in the class.
|
||||
|
||||
The `@Transactional` annotation on a method within the class overrides the default
|
||||
transaction semantics given by the class annotation (if present). You can annotate any method,
|
||||
regardless of visibility.
|
||||
|
||||
To weave your applications with the `AnnotationTransactionAspect`, you must either build
|
||||
your application with AspectJ (see the
|
||||
https://www.eclipse.org/aspectj/doc/released/devguide/index.html[AspectJ Development
|
||||
Guide]) or use load-time weaving. See <<core.adoc#aop-aj-ltw,Load-time weaving with
|
||||
AspectJ in the Spring Framework>> for a discussion of load-time weaving with AspectJ.
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,114 @@
|
||||
[[transaction-declarative-diff-tx]]
|
||||
= Configuring Different Transactional Semantics for Different Beans
|
||||
|
||||
Consider the scenario where you have a number of service layer objects, and you want to
|
||||
apply a totally different transactional configuration to each of them. You can do so
|
||||
by defining distinct `<aop:advisor/>` elements with differing `pointcut` and
|
||||
`advice-ref` attribute values.
|
||||
|
||||
As a point of comparison, first assume that all of your service layer classes are
|
||||
defined in a root `x.y.service` package. To make all beans that are instances of classes
|
||||
defined in that package (or in subpackages) and that have names ending in `Service` have
|
||||
the default transactional configuration, you could write the following:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim"]
|
||||
----
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:aop="http://www.springframework.org/schema/aop"
|
||||
xmlns:tx="http://www.springframework.org/schema/tx"
|
||||
xsi:schemaLocation="
|
||||
http://www.springframework.org/schema/beans
|
||||
https://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/tx
|
||||
https://www.springframework.org/schema/tx/spring-tx.xsd
|
||||
http://www.springframework.org/schema/aop
|
||||
https://www.springframework.org/schema/aop/spring-aop.xsd">
|
||||
|
||||
<aop:config>
|
||||
|
||||
<aop:pointcut id="serviceOperation"
|
||||
expression="execution(* x.y.service..*Service.*(..))"/>
|
||||
|
||||
<aop:advisor pointcut-ref="serviceOperation" advice-ref="txAdvice"/>
|
||||
|
||||
</aop:config>
|
||||
|
||||
<!-- these two beans will be transactional... -->
|
||||
<bean id="fooService" class="x.y.service.DefaultFooService"/>
|
||||
<bean id="barService" class="x.y.service.extras.SimpleBarService"/>
|
||||
|
||||
<!-- ... and these two beans won't -->
|
||||
<bean id="anotherService" class="org.xyz.SomeService"/> <!-- (not in the right package) -->
|
||||
<bean id="barManager" class="x.y.service.SimpleBarManager"/> <!-- (doesn't end in 'Service') -->
|
||||
|
||||
<tx:advice id="txAdvice">
|
||||
<tx:attributes>
|
||||
<tx:method name="get*" read-only="true"/>
|
||||
<tx:method name="*"/>
|
||||
</tx:attributes>
|
||||
</tx:advice>
|
||||
|
||||
<!-- other transaction infrastructure beans such as a TransactionManager omitted... -->
|
||||
|
||||
</beans>
|
||||
----
|
||||
|
||||
The following example shows how to configure two distinct beans with totally different
|
||||
transactional settings:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim"]
|
||||
----
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:aop="http://www.springframework.org/schema/aop"
|
||||
xmlns:tx="http://www.springframework.org/schema/tx"
|
||||
xsi:schemaLocation="
|
||||
http://www.springframework.org/schema/beans
|
||||
https://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/tx
|
||||
https://www.springframework.org/schema/tx/spring-tx.xsd
|
||||
http://www.springframework.org/schema/aop
|
||||
https://www.springframework.org/schema/aop/spring-aop.xsd">
|
||||
|
||||
<aop:config>
|
||||
|
||||
<aop:pointcut id="defaultServiceOperation"
|
||||
expression="execution(* x.y.service.*Service.*(..))"/>
|
||||
|
||||
<aop:pointcut id="noTxServiceOperation"
|
||||
expression="execution(* x.y.service.ddl.DefaultDdlManager.*(..))"/>
|
||||
|
||||
<aop:advisor pointcut-ref="defaultServiceOperation" advice-ref="defaultTxAdvice"/>
|
||||
|
||||
<aop:advisor pointcut-ref="noTxServiceOperation" advice-ref="noTxAdvice"/>
|
||||
|
||||
</aop:config>
|
||||
|
||||
<!-- this bean will be transactional (see the 'defaultServiceOperation' pointcut) -->
|
||||
<bean id="fooService" class="x.y.service.DefaultFooService"/>
|
||||
|
||||
<!-- this bean will also be transactional, but with totally different transactional settings -->
|
||||
<bean id="anotherFooService" class="x.y.service.ddl.DefaultDdlManager"/>
|
||||
|
||||
<tx:advice id="defaultTxAdvice">
|
||||
<tx:attributes>
|
||||
<tx:method name="get*" read-only="true"/>
|
||||
<tx:method name="*"/>
|
||||
</tx:attributes>
|
||||
</tx:advice>
|
||||
|
||||
<tx:advice id="noTxAdvice">
|
||||
<tx:attributes>
|
||||
<tx:method name="*" propagation="NEVER"/>
|
||||
</tx:attributes>
|
||||
</tx:advice>
|
||||
|
||||
<!-- other transaction infrastructure beans such as a TransactionManager omitted... -->
|
||||
|
||||
</beans>
|
||||
----
|
||||
|
||||
|
||||
@@ -0,0 +1,399 @@
|
||||
[[transaction-declarative-first-example]]
|
||||
= Example of Declarative Transaction Implementation
|
||||
|
||||
Consider the following interface and its attendant implementation. This example uses
|
||||
`Foo` and `Bar` classes as placeholders so that you can concentrate on the transaction
|
||||
usage without focusing on a particular domain model. For the purposes of this example,
|
||||
the fact that the `DefaultFooService` class throws `UnsupportedOperationException`
|
||||
instances in the body of each implemented method is good. That behavior lets you see
|
||||
transactions being created and then rolled back in response to the
|
||||
`UnsupportedOperationException` instance. The following listing shows the `FooService`
|
||||
interface:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
|
||||
.Java
|
||||
----
|
||||
// the service interface that we want to make transactional
|
||||
|
||||
package x.y.service;
|
||||
|
||||
public interface FooService {
|
||||
|
||||
Foo getFoo(String fooName);
|
||||
|
||||
Foo getFoo(String fooName, String barName);
|
||||
|
||||
void insertFoo(Foo foo);
|
||||
|
||||
void updateFoo(Foo foo);
|
||||
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
|
||||
.Kotlin
|
||||
----
|
||||
// the service interface that we want to make transactional
|
||||
|
||||
package x.y.service
|
||||
|
||||
interface FooService {
|
||||
|
||||
fun getFoo(fooName: String): Foo
|
||||
|
||||
fun getFoo(fooName: String, barName: String): Foo
|
||||
|
||||
fun insertFoo(foo: Foo)
|
||||
|
||||
fun updateFoo(foo: Foo)
|
||||
}
|
||||
----
|
||||
|
||||
The following example shows an implementation of the preceding interface:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
|
||||
.Java
|
||||
----
|
||||
package x.y.service;
|
||||
|
||||
public class DefaultFooService implements FooService {
|
||||
|
||||
@Override
|
||||
public Foo getFoo(String fooName) {
|
||||
// ...
|
||||
}
|
||||
|
||||
@Override
|
||||
public Foo getFoo(String fooName, String barName) {
|
||||
// ...
|
||||
}
|
||||
|
||||
@Override
|
||||
public void insertFoo(Foo foo) {
|
||||
// ...
|
||||
}
|
||||
|
||||
@Override
|
||||
public void updateFoo(Foo foo) {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
|
||||
.Kotlin
|
||||
----
|
||||
package x.y.service
|
||||
|
||||
class DefaultFooService : FooService {
|
||||
|
||||
override fun getFoo(fooName: String): Foo {
|
||||
// ...
|
||||
}
|
||||
|
||||
override fun getFoo(fooName: String, barName: String): Foo {
|
||||
// ...
|
||||
}
|
||||
|
||||
override fun insertFoo(foo: Foo) {
|
||||
// ...
|
||||
}
|
||||
|
||||
override fun updateFoo(foo: Foo) {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Assume that the first two methods of the `FooService` interface, `getFoo(String)` and
|
||||
`getFoo(String, String)`, must run in the context of a transaction with read-only
|
||||
semantics and that the other methods, `insertFoo(Foo)` and `updateFoo(Foo)`, must
|
||||
run in the context of a transaction with read-write semantics. The following
|
||||
configuration is explained in detail in the next few paragraphs:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim"]
|
||||
----
|
||||
<!-- from the file 'context.xml' -->
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:aop="http://www.springframework.org/schema/aop"
|
||||
xmlns:tx="http://www.springframework.org/schema/tx"
|
||||
xsi:schemaLocation="
|
||||
http://www.springframework.org/schema/beans
|
||||
https://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/tx
|
||||
https://www.springframework.org/schema/tx/spring-tx.xsd
|
||||
http://www.springframework.org/schema/aop
|
||||
https://www.springframework.org/schema/aop/spring-aop.xsd">
|
||||
|
||||
<!-- this is the service object that we want to make transactional -->
|
||||
<bean id="fooService" class="x.y.service.DefaultFooService"/>
|
||||
|
||||
<!-- the transactional advice (what 'happens'; see the <aop:advisor/> bean below) -->
|
||||
<tx:advice id="txAdvice" transaction-manager="txManager">
|
||||
<!-- the transactional semantics... -->
|
||||
<tx:attributes>
|
||||
<!-- all methods starting with 'get' are read-only -->
|
||||
<tx:method name="get*" read-only="true"/>
|
||||
<!-- other methods use the default transaction settings (see below) -->
|
||||
<tx:method name="*"/>
|
||||
</tx:attributes>
|
||||
</tx:advice>
|
||||
|
||||
<!-- ensure that the above transactional advice runs for any execution
|
||||
of an operation defined by the FooService interface -->
|
||||
<aop:config>
|
||||
<aop:pointcut id="fooServiceOperation" expression="execution(* x.y.service.FooService.*(..))"/>
|
||||
<aop:advisor advice-ref="txAdvice" pointcut-ref="fooServiceOperation"/>
|
||||
</aop:config>
|
||||
|
||||
<!-- don't forget the DataSource -->
|
||||
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
|
||||
<property name="driverClassName" value="oracle.jdbc.driver.OracleDriver"/>
|
||||
<property name="url" value="jdbc:oracle:thin:@rj-t42:1521:elvis"/>
|
||||
<property name="username" value="scott"/>
|
||||
<property name="password" value="tiger"/>
|
||||
</bean>
|
||||
|
||||
<!-- similarly, don't forget the TransactionManager -->
|
||||
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
|
||||
<property name="dataSource" ref="dataSource"/>
|
||||
</bean>
|
||||
|
||||
<!-- other <bean/> definitions here -->
|
||||
|
||||
</beans>
|
||||
----
|
||||
|
||||
Examine the preceding configuration. It assumes that you want to make a service object,
|
||||
the `fooService` bean, transactional. The transaction semantics to apply are encapsulated
|
||||
in the `<tx:advice/>` definition. The `<tx:advice/>` definition reads as "all methods
|
||||
starting with `get` are to run in the context of a read-only transaction, and all
|
||||
other methods are to run with the default transaction semantics". The
|
||||
`transaction-manager` attribute of the `<tx:advice/>` tag is set to the name of the
|
||||
`TransactionManager` bean that is going to drive the transactions (in this case, the
|
||||
`txManager` bean).
|
||||
|
||||
TIP: You can omit the `transaction-manager` attribute in the transactional advice
|
||||
(`<tx:advice/>`) if the bean name of the `TransactionManager` that you want to
|
||||
wire in has the name `transactionManager`. If the `TransactionManager` bean that
|
||||
you want to wire in has any other name, you must use the `transaction-manager`
|
||||
attribute explicitly, as in the preceding example.
|
||||
|
||||
The `<aop:config/>` definition ensures that the transactional advice defined by the
|
||||
`txAdvice` bean runs at the appropriate points in the program. First, you define a
|
||||
pointcut that matches the execution of any operation defined in the `FooService` interface
|
||||
(`fooServiceOperation`). Then you associate the pointcut with the `txAdvice` by using an
|
||||
advisor. The result indicates that, at the execution of a `fooServiceOperation`,
|
||||
the advice defined by `txAdvice` is run.
|
||||
|
||||
The expression defined within the `<aop:pointcut/>` element is an AspectJ pointcut
|
||||
expression. See <<core.adoc#aop, the AOP section>> for more details on pointcut
|
||||
expressions in Spring.
|
||||
|
||||
A common requirement is to make an entire service layer transactional. The best way to
|
||||
do this is to change the pointcut expression to match any operation in your
|
||||
service layer. The following example shows how to do so:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim"]
|
||||
----
|
||||
<aop:config>
|
||||
<aop:pointcut id="fooServiceMethods" expression="execution(* x.y.service.*.*(..))"/>
|
||||
<aop:advisor advice-ref="txAdvice" pointcut-ref="fooServiceMethods"/>
|
||||
</aop:config>
|
||||
----
|
||||
|
||||
NOTE: In the preceding example, it is assumed that all your service interfaces are defined
|
||||
in the `x.y.service` package. See <<core.adoc#aop, the AOP section>> for more details.
|
||||
|
||||
Now that we have analyzed the configuration, you may be asking yourself,
|
||||
"What does all this configuration actually do?"
|
||||
|
||||
The configuration shown earlier is used to create a transactional proxy around the object
|
||||
that is created from the `fooService` bean definition. The proxy is configured with
|
||||
the transactional advice so that, when an appropriate method is invoked on the proxy,
|
||||
a transaction is started, suspended, marked as read-only, and so on, depending on the
|
||||
transaction configuration associated with that method. Consider the following program
|
||||
that test drives the configuration shown earlier:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
public final class Boot {
|
||||
|
||||
public static void main(final String[] args) throws Exception {
|
||||
ApplicationContext ctx = new ClassPathXmlApplicationContext("context.xml");
|
||||
FooService fooService = ctx.getBean(FooService.class);
|
||||
fooService.insertFoo(new Foo());
|
||||
}
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
import org.springframework.beans.factory.getBean
|
||||
|
||||
fun main() {
|
||||
val ctx = ClassPathXmlApplicationContext("context.xml")
|
||||
val fooService = ctx.getBean<FooService>("fooService")
|
||||
fooService.insertFoo(Foo())
|
||||
}
|
||||
----
|
||||
|
||||
The output from running the preceding program should resemble the following (the Log4J
|
||||
output and the stack trace from the `UnsupportedOperationException` thrown by the
|
||||
`insertFoo(..)` method of the `DefaultFooService` class have been truncated for clarity):
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<!-- the Spring container is starting up... -->
|
||||
[AspectJInvocationContextExposingAdvisorAutoProxyCreator] - Creating implicit proxy for bean 'fooService' with 0 common interceptors and 1 specific interceptors
|
||||
|
||||
<!-- the DefaultFooService is actually proxied -->
|
||||
[JdkDynamicAopProxy] - Creating JDK dynamic proxy for [x.y.service.DefaultFooService]
|
||||
|
||||
<!-- ... the insertFoo(..) method is now being invoked on the proxy -->
|
||||
[TransactionInterceptor] - Getting transaction for x.y.service.FooService.insertFoo
|
||||
|
||||
<!-- the transactional advice kicks in here... -->
|
||||
[DataSourceTransactionManager] - Creating new transaction with name [x.y.service.FooService.insertFoo]
|
||||
[DataSourceTransactionManager] - Acquired Connection [org.apache.commons.dbcp.PoolableConnection@a53de4] for JDBC transaction
|
||||
|
||||
<!-- the insertFoo(..) method from DefaultFooService throws an exception... -->
|
||||
[RuleBasedTransactionAttribute] - Applying rules to determine whether transaction should rollback on java.lang.UnsupportedOperationException
|
||||
[TransactionInterceptor] - Invoking rollback for transaction on x.y.service.FooService.insertFoo due to throwable [java.lang.UnsupportedOperationException]
|
||||
|
||||
<!-- and the transaction is rolled back (by default, RuntimeException instances cause rollback) -->
|
||||
[DataSourceTransactionManager] - Rolling back JDBC transaction on Connection [org.apache.commons.dbcp.PoolableConnection@a53de4]
|
||||
[DataSourceTransactionManager] - Releasing JDBC Connection after transaction
|
||||
[DataSourceUtils] - Returning JDBC Connection to DataSource
|
||||
|
||||
Exception in thread "main" java.lang.UnsupportedOperationException at x.y.service.DefaultFooService.insertFoo(DefaultFooService.java:14)
|
||||
<!-- AOP infrastructure stack trace elements removed for clarity -->
|
||||
at $Proxy0.insertFoo(Unknown Source)
|
||||
at Boot.main(Boot.java:11)
|
||||
----
|
||||
|
||||
To use reactive transaction management the code has to use reactive types.
|
||||
|
||||
NOTE: Spring Framework uses the `ReactiveAdapterRegistry` to determine whether a method
|
||||
return type is reactive.
|
||||
|
||||
The following listing shows a modified version of the previously used `FooService`, but
|
||||
this time the code uses reactive types:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
|
||||
.Java
|
||||
----
|
||||
// the reactive service interface that we want to make transactional
|
||||
|
||||
package x.y.service;
|
||||
|
||||
public interface FooService {
|
||||
|
||||
Flux<Foo> getFoo(String fooName);
|
||||
|
||||
Publisher<Foo> getFoo(String fooName, String barName);
|
||||
|
||||
Mono<Void> insertFoo(Foo foo);
|
||||
|
||||
Mono<Void> updateFoo(Foo foo);
|
||||
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
|
||||
.Kotlin
|
||||
----
|
||||
// the reactive service interface that we want to make transactional
|
||||
|
||||
package x.y.service
|
||||
|
||||
interface FooService {
|
||||
|
||||
fun getFoo(fooName: String): Flow<Foo>
|
||||
|
||||
fun getFoo(fooName: String, barName: String): Publisher<Foo>
|
||||
|
||||
fun insertFoo(foo: Foo) : Mono<Void>
|
||||
|
||||
fun updateFoo(foo: Foo) : Mono<Void>
|
||||
}
|
||||
----
|
||||
|
||||
The following example shows an implementation of the preceding interface:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary",chomp="-packages"]
|
||||
.Java
|
||||
----
|
||||
package x.y.service;
|
||||
|
||||
public class DefaultFooService implements FooService {
|
||||
|
||||
@Override
|
||||
public Flux<Foo> getFoo(String fooName) {
|
||||
// ...
|
||||
}
|
||||
|
||||
@Override
|
||||
public Publisher<Foo> getFoo(String fooName, String barName) {
|
||||
// ...
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> insertFoo(Foo foo) {
|
||||
// ...
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Void> updateFoo(Foo foo) {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary",chomp="-packages"]
|
||||
.Kotlin
|
||||
----
|
||||
package x.y.service
|
||||
|
||||
class DefaultFooService : FooService {
|
||||
|
||||
override fun getFoo(fooName: String): Flow<Foo> {
|
||||
// ...
|
||||
}
|
||||
|
||||
override fun getFoo(fooName: String, barName: String): Publisher<Foo> {
|
||||
// ...
|
||||
}
|
||||
|
||||
override fun insertFoo(foo: Foo): Mono<Void> {
|
||||
// ...
|
||||
}
|
||||
|
||||
override fun updateFoo(foo: Foo): Mono<Void> {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
Imperative and reactive transaction management share the same semantics for transaction
|
||||
boundary and transaction attribute definitions. The main difference between imperative
|
||||
and reactive transactions is the deferred nature of the latter. `TransactionInterceptor`
|
||||
decorates the returned reactive type with a transactional operator to begin and clean up
|
||||
the transaction. Therefore, calling a transactional reactive method defers the actual
|
||||
transaction management to a subscription type that activates processing of the reactive
|
||||
type.
|
||||
|
||||
Another aspect of reactive transaction management relates to data escaping which is a
|
||||
natural consequence of the programming model.
|
||||
|
||||
Method return values of imperative transactions are returned from transactional methods
|
||||
upon successful termination of a method so that partially computed results do not escape
|
||||
the method closure.
|
||||
|
||||
Reactive transaction methods return a reactive wrapper type which represents a
|
||||
computation sequence along with a promise to begin and complete the computation.
|
||||
|
||||
A `Publisher` can emit data while a transaction is ongoing but not necessarily completed.
|
||||
Therefore, methods that depend upon successful completion of an entire transaction need
|
||||
to ensure completion and buffer results in the calling code.
|
||||
|
||||
|
||||
@@ -0,0 +1,170 @@
|
||||
[[transaction-declarative-rolling-back]]
|
||||
= Rolling Back a Declarative Transaction
|
||||
|
||||
The previous section outlined the basics of how to specify transactional settings for
|
||||
classes, typically service layer classes, declaratively in your application. This section
|
||||
describes how you can control the rollback of transactions in a simple, declarative
|
||||
fashion in XML configuration. For details on controlling rollback semantics declaratively
|
||||
with the `@Transactional` annotation, see
|
||||
<<transaction-declarative-attransactional-settings>>.
|
||||
|
||||
The recommended way to indicate to the Spring Framework's transaction infrastructure
|
||||
that a transaction's work is to be rolled back is to throw an `Exception` from code that
|
||||
is currently executing in the context of a transaction. The Spring Framework's
|
||||
transaction infrastructure code catches any unhandled `Exception` as it bubbles up
|
||||
the call stack and makes a determination whether to mark the transaction for rollback.
|
||||
|
||||
In its default configuration, the Spring Framework's transaction infrastructure code
|
||||
marks a transaction for rollback only in the case of runtime, unchecked exceptions.
|
||||
That is, when the thrown exception is an instance or subclass of `RuntimeException`.
|
||||
(`Error` instances also, by default, result in a rollback).
|
||||
|
||||
As of Spring Framework 5.2, the default configuration also provides support for
|
||||
Vavr's `Try` method to trigger transaction rollbacks when it returns a 'Failure'.
|
||||
This allows you to handle functional-style errors using Try and have the transaction
|
||||
automatically rolled back in case of a failure. For more information on Vavr's Try,
|
||||
refer to the [official Vavr documentation](https://www.vavr.io/vavr-docs/#_try).
|
||||
|
||||
Here's an example of how to use Vavr's Try with a transactional method:
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@Transactional
|
||||
public Try<String> myTransactionalMethod() {
|
||||
// If myDataAccessOperation throws an exception, it will be caught by the
|
||||
// Try instance created with Try.of() and wrapped inside the Failure class
|
||||
// which can be checked using the isFailure() method on the Try instance.
|
||||
return Try.of(delegate::myDataAccessOperation);
|
||||
}
|
||||
----
|
||||
|
||||
Checked exceptions that are thrown from a transactional method do not result in a rollback
|
||||
in the default configuration. You can configure exactly which `Exception` types mark a
|
||||
transaction for rollback, including checked exceptions by specifying _rollback rules_.
|
||||
|
||||
.Rollback rules
|
||||
[[transaction-declarative-rollback-rules]]
|
||||
[NOTE]
|
||||
====
|
||||
Rollback rules determine if a transaction should be rolled back when a given exception is
|
||||
thrown, and the rules are based on exception types or exception patterns.
|
||||
|
||||
Rollback rules may be configured in XML via the `rollback-for` and `no-rollback-for`
|
||||
attributes, which allow rules to be defined as patterns. When using
|
||||
<<transaction-declarative-attransactional-settings,`@Transactional`>>, rollback rules may
|
||||
be configured via the `rollbackFor`/`noRollbackFor` and
|
||||
`rollbackForClassName`/`noRollbackForClassName` attributes, which allow rules to be
|
||||
defined based on exception types or patterns, respectively.
|
||||
|
||||
When a rollback rule is defined with an exception type, that type will be used to match
|
||||
against the type of a thrown exception and its super types, providing type safety and
|
||||
avoiding any unintentional matches that may occur when using a pattern. For example, a
|
||||
value of `jakarta.servlet.ServletException.class` will only match thrown exceptions of
|
||||
type `jakarta.servlet.ServletException` and its subclasses.
|
||||
|
||||
When a rollback rule is defined with an exception pattern, the pattern can be a fully
|
||||
qualified class name or a substring of a fully qualified class name for an exception type
|
||||
(which must be a subclass of `Throwable`), with no wildcard support at present. For
|
||||
example, a value of `"jakarta.servlet.ServletException"` or `"ServletException"` will
|
||||
match `jakarta.servlet.ServletException` and its subclasses.
|
||||
|
||||
[WARNING]
|
||||
=====
|
||||
You must carefully consider how specific a pattern is and whether to include package
|
||||
information (which isn't mandatory). For example, `"Exception"` will match nearly
|
||||
anything and will probably hide other rules. `"java.lang.Exception"` would be correct if
|
||||
`"Exception"` were meant to define a rule for all checked exceptions. With more unique
|
||||
exception names such as `"BaseBusinessException"` there is likely no need to use the
|
||||
fully qualified class name for the exception pattern.
|
||||
|
||||
Furthermore, pattern-based rollback rules may result in unintentional matches for
|
||||
similarly named exceptions and nested classes. This is due to the fact that a thrown
|
||||
exception is considered to be a match for a given pattern-based rollback rule if the name
|
||||
of the thrown exception contains the exception pattern configured for the rollback rule.
|
||||
For example, given a rule configured to match on `"com.example.CustomException"`, that
|
||||
rule will match against an exception named `com.example.CustomExceptionV2` (an exception
|
||||
in the same package as `CustomException` but with an additional suffix) or an exception
|
||||
named `com.example.CustomException$AnotherException` (an exception declared as a nested
|
||||
class in `CustomException`).
|
||||
=====
|
||||
====
|
||||
|
||||
The following XML snippet demonstrates how to configure rollback for a checked,
|
||||
application-specific `Exception` type by supplying an _exception pattern_ via the
|
||||
`rollback-for` attribute:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<tx:advice id="txAdvice" transaction-manager="txManager">
|
||||
<tx:attributes>
|
||||
<tx:method name="get*" read-only="true" rollback-for="NoProductInStockException"/>
|
||||
<tx:method name="*"/>
|
||||
</tx:attributes>
|
||||
</tx:advice>
|
||||
----
|
||||
|
||||
If you do not want a transaction rolled back when an exception is thrown, you can also
|
||||
specify 'no rollback' rules. The following example tells the Spring Framework's
|
||||
transaction infrastructure to commit the attendant transaction even in the face of an
|
||||
unhandled `InstrumentNotFoundException`:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<tx:advice id="txAdvice">
|
||||
<tx:attributes>
|
||||
<tx:method name="updateStock" no-rollback-for="InstrumentNotFoundException"/>
|
||||
<tx:method name="*"/>
|
||||
</tx:attributes>
|
||||
</tx:advice>
|
||||
----
|
||||
|
||||
When the Spring Framework's transaction infrastructure catches an exception and consults
|
||||
the configured rollback rules to determine whether to mark the transaction for rollback,
|
||||
the strongest matching rule wins. So, in the case of the following configuration, any
|
||||
exception other than an `InstrumentNotFoundException` results in a rollback of the
|
||||
attendant transaction:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<tx:advice id="txAdvice">
|
||||
<tx:attributes>
|
||||
<tx:method name="*" rollback-for="Throwable" no-rollback-for="InstrumentNotFoundException"/>
|
||||
</tx:attributes>
|
||||
</tx:advice>
|
||||
----
|
||||
|
||||
You can also indicate a required rollback programmatically. Although simple, this process
|
||||
is quite invasive and tightly couples your code to the Spring Framework's transaction
|
||||
infrastructure. The following example shows how to programmatically indicate a required
|
||||
rollback:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
public void resolvePosition() {
|
||||
try {
|
||||
// some business logic...
|
||||
} catch (NoProductInStockException ex) {
|
||||
// trigger rollback programmatically
|
||||
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
|
||||
}
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
fun resolvePosition() {
|
||||
try {
|
||||
// some business logic...
|
||||
} catch (ex: NoProductInStockException) {
|
||||
// trigger rollback programmatically
|
||||
TransactionAspectSupport.currentTransactionStatus().setRollbackOnly();
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
You are strongly encouraged to use the declarative approach to rollback, if at all
|
||||
possible. Programmatic rollback is available should you absolutely need it, but its
|
||||
usage flies in the face of achieving a clean POJO-based architecture.
|
||||
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
[[tx-decl-explained]]
|
||||
= Understanding the Spring Framework's Declarative Transaction Implementation
|
||||
|
||||
It is not sufficient merely to tell you to annotate your classes with the
|
||||
`@Transactional` annotation, add `@EnableTransactionManagement` to your configuration,
|
||||
and expect you to understand how it all works. To provide a deeper understanding, this
|
||||
section explains the inner workings of the Spring Framework's declarative transaction
|
||||
infrastructure in the context of transaction-related issues.
|
||||
|
||||
The most important concepts to grasp with regard to the Spring Framework's declarative
|
||||
transaction support are that this support is enabled
|
||||
<<core.adoc#aop-understanding-aop-proxies, via AOP proxies>> and that the transactional
|
||||
advice is driven by metadata (currently XML- or annotation-based). The combination of AOP
|
||||
with transactional metadata yields an AOP proxy that uses a `TransactionInterceptor` in
|
||||
conjunction with an appropriate `TransactionManager` implementation to drive transactions
|
||||
around method invocations.
|
||||
|
||||
NOTE: Spring AOP is covered in <<core.adoc#aop, the AOP section>>.
|
||||
|
||||
Spring Framework's `TransactionInterceptor` provides transaction management for
|
||||
imperative and reactive programming models. The interceptor detects the desired flavor of
|
||||
transaction management by inspecting the method return type. Methods returning a reactive
|
||||
type such as `Publisher` or Kotlin `Flow` (or a subtype of those) qualify for reactive
|
||||
transaction management. All other return types including `void` use the code path for
|
||||
imperative transaction management.
|
||||
|
||||
Transaction management flavors impact which transaction manager is required. Imperative
|
||||
transactions require a `PlatformTransactionManager`, while reactive transactions use
|
||||
`ReactiveTransactionManager` implementations.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
`@Transactional` commonly works with thread-bound transactions managed by
|
||||
`PlatformTransactionManager`, exposing a transaction to all data access operations within
|
||||
the current execution thread. Note: This does _not_ propagate to newly started threads
|
||||
within the method.
|
||||
|
||||
A reactive transaction managed by `ReactiveTransactionManager` uses the Reactor context
|
||||
instead of thread-local attributes. As a consequence, all participating data access
|
||||
operations need to execute within the same Reactor context in the same reactive pipeline.
|
||||
====
|
||||
|
||||
The following image shows a conceptual view of calling a method on a transactional proxy:
|
||||
|
||||
image::tx.png[]
|
||||
|
||||
|
||||
@@ -0,0 +1,72 @@
|
||||
[[tx-propagation]]
|
||||
= Transaction Propagation
|
||||
|
||||
This section describes some semantics of transaction propagation in Spring. Note
|
||||
that this section is not a proper introduction to transaction propagation. Rather, it
|
||||
details some of the semantics regarding transaction propagation in Spring.
|
||||
|
||||
In Spring-managed transactions, be aware of the difference between physical and
|
||||
logical transactions, and how the propagation setting applies to this difference.
|
||||
|
||||
[[tx-propagation-required]]
|
||||
== Understanding `PROPAGATION_REQUIRED`
|
||||
|
||||
image::tx_prop_required.png[]
|
||||
|
||||
`PROPAGATION_REQUIRED` enforces a physical transaction, either locally for the current
|
||||
scope if no transaction exists yet or participating in an existing 'outer' transaction
|
||||
defined for a larger scope. This is a fine default in common call stack arrangements
|
||||
within the same thread (for example, a service facade that delegates to several repository methods
|
||||
where all the underlying resources have to participate in the service-level transaction).
|
||||
|
||||
NOTE: By default, a participating transaction joins the characteristics of the outer scope,
|
||||
silently ignoring the local isolation level, timeout value, or read-only flag (if any).
|
||||
Consider switching the `validateExistingTransactions` flag to `true` on your transaction
|
||||
manager if you want isolation level declarations to be rejected when participating in
|
||||
an existing transaction with a different isolation level. This non-lenient mode also
|
||||
rejects read-only mismatches (that is, an inner read-write transaction that tries to participate
|
||||
in a read-only outer scope).
|
||||
|
||||
When the propagation setting is `PROPAGATION_REQUIRED`, a logical transaction scope
|
||||
is created for each method upon which the setting is applied. Each such logical
|
||||
transaction scope can determine rollback-only status individually, with an outer
|
||||
transaction scope being logically independent from the inner transaction scope.
|
||||
In the case of standard `PROPAGATION_REQUIRED` behavior, all these scopes are
|
||||
mapped to the same physical transaction. So a rollback-only marker set in the inner
|
||||
transaction scope does affect the outer transaction's chance to actually commit.
|
||||
|
||||
However, in the case where an inner transaction scope sets the rollback-only marker, the
|
||||
outer transaction has not decided on the rollback itself, so the rollback (silently
|
||||
triggered by the inner transaction scope) is unexpected. A corresponding
|
||||
`UnexpectedRollbackException` is thrown at that point. This is expected behavior so
|
||||
that the caller of a transaction can never be misled to assume that a commit was
|
||||
performed when it really was not. So, if an inner transaction (of which the outer caller
|
||||
is not aware) silently marks a transaction as rollback-only, the outer caller still
|
||||
calls commit. The outer caller needs to receive an `UnexpectedRollbackException` to
|
||||
indicate clearly that a rollback was performed instead.
|
||||
|
||||
[[tx-propagation-requires_new]]
|
||||
== Understanding `PROPAGATION_REQUIRES_NEW`
|
||||
|
||||
image::tx_prop_requires_new.png[]
|
||||
|
||||
`PROPAGATION_REQUIRES_NEW`, in contrast to `PROPAGATION_REQUIRED`, always uses an
|
||||
independent physical transaction for each affected transaction scope, never
|
||||
participating in an existing transaction for an outer scope. In such an arrangement,
|
||||
the underlying resource transactions are different and, hence, can commit or roll back
|
||||
independently, with an outer transaction not affected by an inner transaction's rollback
|
||||
status and with an inner transaction's locks released immediately after its completion.
|
||||
Such an independent inner transaction can also declare its own isolation level, timeout,
|
||||
and read-only settings and not inherit an outer transaction's characteristics.
|
||||
|
||||
[[tx-propagation-nested]]
|
||||
== Understanding `PROPAGATION_NESTED`
|
||||
|
||||
`PROPAGATION_NESTED` uses a single physical transaction with multiple savepoints
|
||||
that it can roll back to. Such partial rollbacks let an inner transaction scope
|
||||
trigger a rollback for its scope, with the outer transaction being able to continue
|
||||
the physical transaction despite some operations having been rolled back. This setting
|
||||
is typically mapped onto JDBC savepoints, so it works only with JDBC resource
|
||||
transactions. See Spring's {api-spring-framework}/jdbc/datasource/DataSourceTransactionManager.html[`DataSourceTransactionManager`].
|
||||
|
||||
|
||||
@@ -0,0 +1,63 @@
|
||||
[[transaction-declarative-txadvice-settings]]
|
||||
= <tx:advice/> Settings
|
||||
|
||||
This section summarizes the various transactional settings that you can specify by using
|
||||
the `<tx:advice/>` tag. The default `<tx:advice/>` settings are:
|
||||
|
||||
* The <<tx-propagation, propagation setting>> is `REQUIRED.`
|
||||
* The isolation level is `DEFAULT.`
|
||||
* The transaction is read-write.
|
||||
* The transaction timeout defaults to the default timeout of the underlying transaction
|
||||
system or none if timeouts are not supported.
|
||||
* Any `RuntimeException` triggers rollback, and any checked `Exception` does not.
|
||||
|
||||
You can change these default settings. The following table summarizes the various attributes of the `<tx:method/>` tags
|
||||
that are nested within `<tx:advice/>` and `<tx:attributes/>` tags:
|
||||
|
||||
[[tx-method-settings]]
|
||||
.<tx:method/> settings
|
||||
|===
|
||||
| Attribute| Required?| Default| Description
|
||||
|
||||
| `name`
|
||||
| Yes
|
||||
|
|
||||
| Method names with which the transaction attributes are to be associated. The
|
||||
wildcard ({asterisk}) character can be used to associate the same transaction attribute
|
||||
settings with a number of methods (for example, `get*`, `handle*`, `on*Event`, and so
|
||||
forth).
|
||||
|
||||
| `propagation`
|
||||
| No
|
||||
| `REQUIRED`
|
||||
| Transaction propagation behavior.
|
||||
|
||||
| `isolation`
|
||||
| No
|
||||
| `DEFAULT`
|
||||
| Transaction isolation level. Only applicable to propagation settings of `REQUIRED` or `REQUIRES_NEW`.
|
||||
|
||||
| `timeout`
|
||||
| No
|
||||
| -1
|
||||
| Transaction timeout (seconds). Only applicable to propagation `REQUIRED` or `REQUIRES_NEW`.
|
||||
|
||||
| `read-only`
|
||||
| No
|
||||
| false
|
||||
| Read-write versus read-only transaction. Applies only to `REQUIRED` or `REQUIRES_NEW`.
|
||||
|
||||
| `rollback-for`
|
||||
| No
|
||||
|
|
||||
| Comma-delimited list of `Exception` instances that trigger rollback. For example,
|
||||
`com.foo.MyBusinessException,ServletException`.
|
||||
|
||||
| `no-rollback-for`
|
||||
| No
|
||||
|
|
||||
| Comma-delimited list of `Exception` instances that do not trigger rollback. For example,
|
||||
`com.foo.MyBusinessException,ServletException`.
|
||||
|===
|
||||
|
||||
|
||||
@@ -0,0 +1,61 @@
|
||||
[[transaction-event]]
|
||||
= Transaction-bound Events
|
||||
|
||||
As of Spring 4.2, the listener of an event can be bound to a phase of the transaction.
|
||||
The typical example is to handle the event when the transaction has completed successfully.
|
||||
Doing so lets events be used with more flexibility when the outcome of the current
|
||||
transaction actually matters to the listener.
|
||||
|
||||
You can register a regular event listener by using the `@EventListener` annotation.
|
||||
If you need to bind it to the transaction, use `@TransactionalEventListener`.
|
||||
When you do so, the listener is bound to the commit phase of the transaction by default.
|
||||
|
||||
The next example shows this concept. Assume that a component publishes an order-created
|
||||
event and that we want to define a listener that should only handle that event once the
|
||||
transaction in which it has been published has committed successfully. The following
|
||||
example sets up such an event listener:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
@Component
|
||||
public class MyComponent {
|
||||
|
||||
@TransactionalEventListener
|
||||
public void handleOrderCreatedEvent(CreationEvent<Order> creationEvent) {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
@Component
|
||||
class MyComponent {
|
||||
|
||||
@TransactionalEventListener
|
||||
fun handleOrderCreatedEvent(creationEvent: CreationEvent<Order>) {
|
||||
// ...
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
The `@TransactionalEventListener` annotation exposes a `phase` attribute that lets you
|
||||
customize the phase of the transaction to which the listener should be bound.
|
||||
The valid phases are `BEFORE_COMMIT`, `AFTER_COMMIT` (default), `AFTER_ROLLBACK`, as well as
|
||||
`AFTER_COMPLETION` which aggregates the transaction completion (be it a commit or a rollback).
|
||||
|
||||
If no transaction is running, the listener is not invoked at all, since we cannot honor the
|
||||
required semantics. You can, however, override that behavior by setting the `fallbackExecution`
|
||||
attribute of the annotation to `true`.
|
||||
|
||||
[NOTE]
|
||||
====
|
||||
`@TransactionalEventListener` only works with thread-bound transactions managed by
|
||||
`PlatformTransactionManager`. A reactive transaction managed by `ReactiveTransactionManager`
|
||||
uses the Reactor context instead of thread-local attributes, so from the perspective of
|
||||
an event listener, there is no compatible active transaction that it can participate in.
|
||||
====
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,90 @@
|
||||
[[transaction-motivation]]
|
||||
= Advantages of the Spring Framework's Transaction Support Model
|
||||
|
||||
Traditionally, EE application developers have had two choices for transaction management:
|
||||
global or local transactions, both of which have profound limitations. Global
|
||||
and local transaction management is reviewed in the next two sections, followed by a
|
||||
discussion of how the Spring Framework's transaction management support addresses the
|
||||
limitations of the global and local transaction models.
|
||||
|
||||
|
||||
[[transaction-global]]
|
||||
== Global Transactions
|
||||
|
||||
Global transactions let you work with multiple transactional resources, typically
|
||||
relational databases and message queues. The application server manages global
|
||||
transactions through the JTA, which is a cumbersome API (partly due to its
|
||||
exception model). Furthermore, a JTA `UserTransaction` normally needs to be sourced from
|
||||
JNDI, meaning that you also need to use JNDI in order to use JTA. The use
|
||||
of global transactions limits any potential reuse of application code, as JTA is
|
||||
normally only available in an application server environment.
|
||||
|
||||
Previously, the preferred way to use global transactions was through EJB CMT
|
||||
(Container Managed Transaction). CMT is a form of declarative transaction
|
||||
management (as distinguished from programmatic transaction management). EJB CMT
|
||||
removes the need for transaction-related JNDI lookups, although the use of EJB
|
||||
itself necessitates the use of JNDI. It removes most but not all of the need to write
|
||||
Java code to control transactions. The significant downside is that CMT is tied to JTA
|
||||
and an application server environment. Also, it is only available if one chooses to
|
||||
implement business logic in EJBs (or at least behind a transactional EJB facade). The
|
||||
negatives of EJB in general are so great that this is not an attractive proposition,
|
||||
especially in the face of compelling alternatives for declarative transaction management.
|
||||
|
||||
|
||||
[[transaction-local]]
|
||||
== Local Transactions
|
||||
|
||||
Local transactions are resource-specific, such as a transaction associated with a JDBC
|
||||
connection. Local transactions may be easier to use but have a significant disadvantage:
|
||||
They cannot work across multiple transactional resources. For example, code that manages
|
||||
transactions by using a JDBC connection cannot run within a global JTA transaction. Because
|
||||
the application server is not involved in transaction management, it cannot help ensure
|
||||
correctness across multiple resources. (It is worth noting that most applications use a
|
||||
single transaction resource.) Another downside is that local transactions are invasive
|
||||
to the programming model.
|
||||
|
||||
|
||||
[[transaction-programming-model]]
|
||||
== Spring Framework's Consistent Programming Model
|
||||
|
||||
Spring resolves the disadvantages of global and local transactions. It lets
|
||||
application developers use a consistent programming model in any environment.
|
||||
You write your code once, and it can benefit from different transaction management
|
||||
strategies in different environments. The Spring Framework provides both declarative and
|
||||
programmatic transaction management. Most users prefer declarative transaction
|
||||
management, which we recommend in most cases.
|
||||
|
||||
With programmatic transaction management, developers work with the Spring Framework
|
||||
transaction abstraction, which can run over any underlying transaction infrastructure.
|
||||
With the preferred declarative model, developers typically write little or no code
|
||||
related to transaction management and, hence, do not depend on the Spring Framework
|
||||
transaction API or any other transaction API.
|
||||
|
||||
.Do you need an application server for transaction management?
|
||||
****
|
||||
The Spring Framework's transaction management support changes traditional rules as to
|
||||
when an enterprise Java application requires an application server.
|
||||
|
||||
In particular, you do not need an application server purely for declarative transactions
|
||||
through EJBs. In fact, even if your application server has powerful JTA capabilities,
|
||||
you may decide that the Spring Framework's declarative transactions offer more power and
|
||||
a more productive programming model than EJB CMT.
|
||||
|
||||
Typically, you need an application server's JTA capability only if your application needs
|
||||
to handle transactions across multiple resources, which is not a requirement for many
|
||||
applications. Many high-end applications use a single, highly scalable database (such as
|
||||
Oracle RAC) instead. Stand-alone transaction managers (such as
|
||||
https://www.atomikos.com/[Atomikos Transactions])
|
||||
are other options. Of course, you may need other application server capabilities, such as
|
||||
Java Message Service (JMS) and Jakarta EE Connector Architecture (JCA).
|
||||
|
||||
The Spring Framework gives you the choice of when to scale your application to a fully
|
||||
loaded application server. Gone are the days when the only alternative to using EJB
|
||||
CMT or JTA was to write code with local transactions (such as those on JDBC connections)
|
||||
and face a hefty rework if you need that code to run within global, container-managed
|
||||
transactions. With the Spring Framework, only some of the bean definitions in your
|
||||
configuration file need to change (rather than your code).
|
||||
****
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,442 @@
|
||||
[[transaction-programmatic]]
|
||||
= Programmatic Transaction Management
|
||||
|
||||
The Spring Framework provides two means of programmatic transaction management, by using:
|
||||
|
||||
* The `TransactionTemplate` or `TransactionalOperator`.
|
||||
* A `TransactionManager` implementation directly.
|
||||
|
||||
The Spring team generally recommends the `TransactionTemplate` for programmatic
|
||||
transaction management in imperative flows and `TransactionalOperator` for reactive code.
|
||||
The second approach is similar to using the JTA `UserTransaction` API, although exception
|
||||
handling is less cumbersome.
|
||||
|
||||
|
||||
[[tx-prog-template]]
|
||||
== Using the `TransactionTemplate`
|
||||
|
||||
The `TransactionTemplate` adopts the same approach as other Spring templates, such as
|
||||
the `JdbcTemplate`. It uses a callback approach (to free application code from having to
|
||||
do the boilerplate acquisition and release transactional resources) and results in
|
||||
code that is intention driven, in that your code focuses solely on what
|
||||
you want to do.
|
||||
|
||||
NOTE: As the examples that follow show, using the `TransactionTemplate` absolutely
|
||||
couples you to Spring's transaction infrastructure and APIs. Whether or not programmatic
|
||||
transaction management is suitable for your development needs is a decision that you
|
||||
have to make yourself.
|
||||
|
||||
Application code that must run in a transactional context and that explicitly uses the
|
||||
`TransactionTemplate` resembles the next example. You, as an application
|
||||
developer, can write a `TransactionCallback` implementation (typically expressed as an
|
||||
anonymous inner class) that contains the code that you need to run in the context of
|
||||
a transaction. You can then pass an instance of your custom `TransactionCallback` to the
|
||||
`execute(..)` method exposed on the `TransactionTemplate`. The following example shows how to do so:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
public class SimpleService implements Service {
|
||||
|
||||
// single TransactionTemplate shared amongst all methods in this instance
|
||||
private final TransactionTemplate transactionTemplate;
|
||||
|
||||
// use constructor-injection to supply the PlatformTransactionManager
|
||||
public SimpleService(PlatformTransactionManager transactionManager) {
|
||||
this.transactionTemplate = new TransactionTemplate(transactionManager);
|
||||
}
|
||||
|
||||
public Object someServiceMethod() {
|
||||
return transactionTemplate.execute(new TransactionCallback() {
|
||||
// the code in this method runs in a transactional context
|
||||
public Object doInTransaction(TransactionStatus status) {
|
||||
updateOperation1();
|
||||
return resultOfUpdateOperation2();
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
// use constructor-injection to supply the PlatformTransactionManager
|
||||
class SimpleService(transactionManager: PlatformTransactionManager) : Service {
|
||||
|
||||
// single TransactionTemplate shared amongst all methods in this instance
|
||||
private val transactionTemplate = TransactionTemplate(transactionManager)
|
||||
|
||||
fun someServiceMethod() = transactionTemplate.execute<Any?> {
|
||||
updateOperation1()
|
||||
resultOfUpdateOperation2()
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
|
||||
If there is no return value, you can use the convenient `TransactionCallbackWithoutResult` class
|
||||
with an anonymous class, as follows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
|
||||
protected void doInTransactionWithoutResult(TransactionStatus status) {
|
||||
updateOperation1();
|
||||
updateOperation2();
|
||||
}
|
||||
});
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
transactionTemplate.execute(object : TransactionCallbackWithoutResult() {
|
||||
override fun doInTransactionWithoutResult(status: TransactionStatus) {
|
||||
updateOperation1()
|
||||
updateOperation2()
|
||||
}
|
||||
})
|
||||
----
|
||||
|
||||
|
||||
Code within the callback can roll the transaction back by calling the
|
||||
`setRollbackOnly()` method on the supplied `TransactionStatus` object, as follows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
transactionTemplate.execute(new TransactionCallbackWithoutResult() {
|
||||
|
||||
protected void doInTransactionWithoutResult(TransactionStatus status) {
|
||||
try {
|
||||
updateOperation1();
|
||||
updateOperation2();
|
||||
} catch (SomeBusinessException ex) {
|
||||
status.setRollbackOnly();
|
||||
}
|
||||
}
|
||||
});
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
transactionTemplate.execute(object : TransactionCallbackWithoutResult() {
|
||||
|
||||
override fun doInTransactionWithoutResult(status: TransactionStatus) {
|
||||
try {
|
||||
updateOperation1()
|
||||
updateOperation2()
|
||||
} catch (ex: SomeBusinessException) {
|
||||
status.setRollbackOnly()
|
||||
}
|
||||
}
|
||||
})
|
||||
----
|
||||
|
||||
[[tx-prog-template-settings]]
|
||||
=== Specifying Transaction Settings
|
||||
|
||||
You can specify transaction settings (such as the propagation mode, the isolation level,
|
||||
the timeout, and so forth) on the `TransactionTemplate` either programmatically or in
|
||||
configuration. By default, `TransactionTemplate` instances have the
|
||||
<<transaction-declarative-txadvice-settings,default transactional settings>>. The
|
||||
following example shows the programmatic customization of the transactional settings for
|
||||
a specific `TransactionTemplate:`
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
public class SimpleService implements Service {
|
||||
|
||||
private final TransactionTemplate transactionTemplate;
|
||||
|
||||
public SimpleService(PlatformTransactionManager transactionManager) {
|
||||
this.transactionTemplate = new TransactionTemplate(transactionManager);
|
||||
|
||||
// the transaction settings can be set here explicitly if so desired
|
||||
this.transactionTemplate.setIsolationLevel(TransactionDefinition.ISOLATION_READ_UNCOMMITTED);
|
||||
this.transactionTemplate.setTimeout(30); // 30 seconds
|
||||
// and so forth...
|
||||
}
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
class SimpleService(transactionManager: PlatformTransactionManager) : Service {
|
||||
|
||||
private val transactionTemplate = TransactionTemplate(transactionManager).apply {
|
||||
// the transaction settings can be set here explicitly if so desired
|
||||
isolationLevel = TransactionDefinition.ISOLATION_READ_UNCOMMITTED
|
||||
timeout = 30 // 30 seconds
|
||||
// and so forth...
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
The following example defines a `TransactionTemplate` with some custom transactional
|
||||
settings by using Spring XML configuration:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<bean id="sharedTransactionTemplate"
|
||||
class="org.springframework.transaction.support.TransactionTemplate">
|
||||
<property name="isolationLevelName" value="ISOLATION_READ_UNCOMMITTED"/>
|
||||
<property name="timeout" value="30"/>
|
||||
</bean>
|
||||
----
|
||||
|
||||
You can then inject the `sharedTransactionTemplate`
|
||||
into as many services as are required.
|
||||
|
||||
Finally, instances of the `TransactionTemplate` class are thread-safe, in that instances
|
||||
do not maintain any conversational state. `TransactionTemplate` instances do, however,
|
||||
maintain configuration state. So, while a number of classes may share a single instance
|
||||
of a `TransactionTemplate`, if a class needs to use a `TransactionTemplate` with
|
||||
different settings (for example, a different isolation level), you need to create
|
||||
two distinct `TransactionTemplate` instances.
|
||||
|
||||
[[tx-prog-operator]]
|
||||
== Using the `TransactionalOperator`
|
||||
|
||||
The `TransactionalOperator` follows an operator design that is similar to other reactive
|
||||
operators. It uses a callback approach (to free application code from having to do the
|
||||
boilerplate acquisition and release transactional resources) and results in code that is
|
||||
intention driven, in that your code focuses solely on what you want to do.
|
||||
|
||||
NOTE: As the examples that follow show, using the `TransactionalOperator` absolutely
|
||||
couples you to Spring's transaction infrastructure and APIs. Whether or not programmatic
|
||||
transaction management is suitable for your development needs is a decision that you have
|
||||
to make yourself.
|
||||
|
||||
Application code that must run in a transactional context and that explicitly uses
|
||||
the `TransactionalOperator` resembles the next example:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
public class SimpleService implements Service {
|
||||
|
||||
// single TransactionalOperator shared amongst all methods in this instance
|
||||
private final TransactionalOperator transactionalOperator;
|
||||
|
||||
// use constructor-injection to supply the ReactiveTransactionManager
|
||||
public SimpleService(ReactiveTransactionManager transactionManager) {
|
||||
this.transactionalOperator = TransactionalOperator.create(transactionManager);
|
||||
}
|
||||
|
||||
public Mono<Object> someServiceMethod() {
|
||||
|
||||
// the code in this method runs in a transactional context
|
||||
|
||||
Mono<Object> update = updateOperation1();
|
||||
|
||||
return update.then(resultOfUpdateOperation2).as(transactionalOperator::transactional);
|
||||
}
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
// use constructor-injection to supply the ReactiveTransactionManager
|
||||
class SimpleService(transactionManager: ReactiveTransactionManager) : Service {
|
||||
|
||||
// single TransactionalOperator shared amongst all methods in this instance
|
||||
private val transactionalOperator = TransactionalOperator.create(transactionManager)
|
||||
|
||||
suspend fun someServiceMethod() = transactionalOperator.executeAndAwait<Any?> {
|
||||
updateOperation1()
|
||||
resultOfUpdateOperation2()
|
||||
}
|
||||
}
|
||||
----
|
||||
|
||||
`TransactionalOperator` can be used in two ways:
|
||||
|
||||
* Operator-style using Project Reactor types (`mono.as(transactionalOperator::transactional)`)
|
||||
* Callback-style for every other case (`transactionalOperator.execute(TransactionCallback<T>)`)
|
||||
|
||||
Code within the callback can roll the transaction back by calling the `setRollbackOnly()`
|
||||
method on the supplied `ReactiveTransaction` object, as follows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
transactionalOperator.execute(new TransactionCallback<>() {
|
||||
|
||||
public Mono<Object> doInTransaction(ReactiveTransaction status) {
|
||||
return updateOperation1().then(updateOperation2)
|
||||
.doOnError(SomeBusinessException.class, e -> status.setRollbackOnly());
|
||||
}
|
||||
}
|
||||
});
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
transactionalOperator.execute(object : TransactionCallback() {
|
||||
|
||||
override fun doInTransactionWithoutResult(status: ReactiveTransaction) {
|
||||
updateOperation1().then(updateOperation2)
|
||||
.doOnError(SomeBusinessException.class, e -> status.setRollbackOnly())
|
||||
}
|
||||
})
|
||||
----
|
||||
|
||||
[[tx-prog-operator-cancel]]
|
||||
=== Cancel Signals
|
||||
|
||||
In Reactive Streams, a `Subscriber` can cancel its `Subscription` and stop its
|
||||
`Publisher`. Operators in Project Reactor, as well as in other libraries, such as `next()`,
|
||||
`take(long)`, `timeout(Duration)`, and others can issue cancellations. There is no way to
|
||||
know the reason for the cancellation, whether it is due to an error or a simply lack of
|
||||
interest to consume further. Since version 5.3 cancel signals lead to a roll back.
|
||||
As a result it is important to consider the operators used downstream from a transaction
|
||||
`Publisher`. In particular in the case of a `Flux` or other multi-value `Publisher`,
|
||||
the full output must be consumed to allow the transaction to complete.
|
||||
|
||||
|
||||
[[tx-prog-operator-settings]]
|
||||
=== Specifying Transaction Settings
|
||||
|
||||
You can specify transaction settings (such as the propagation mode, the isolation level,
|
||||
the timeout, and so forth) for the `TransactionalOperator`. By default,
|
||||
`TransactionalOperator` instances have
|
||||
<<transaction-declarative-txadvice-settings,default transactional settings>>. The
|
||||
following example shows customization of the transactional settings for a specific
|
||||
`TransactionalOperator:`
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
public class SimpleService implements Service {
|
||||
|
||||
private final TransactionalOperator transactionalOperator;
|
||||
|
||||
public SimpleService(ReactiveTransactionManager transactionManager) {
|
||||
DefaultTransactionDefinition definition = new DefaultTransactionDefinition();
|
||||
|
||||
// the transaction settings can be set here explicitly if so desired
|
||||
definition.setIsolationLevel(TransactionDefinition.ISOLATION_READ_UNCOMMITTED);
|
||||
definition.setTimeout(30); // 30 seconds
|
||||
// and so forth...
|
||||
|
||||
this.transactionalOperator = TransactionalOperator.create(transactionManager, definition);
|
||||
}
|
||||
}
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
class SimpleService(transactionManager: ReactiveTransactionManager) : Service {
|
||||
|
||||
private val definition = DefaultTransactionDefinition().apply {
|
||||
// the transaction settings can be set here explicitly if so desired
|
||||
isolationLevel = TransactionDefinition.ISOLATION_READ_UNCOMMITTED
|
||||
timeout = 30 // 30 seconds
|
||||
// and so forth...
|
||||
}
|
||||
private val transactionalOperator = TransactionalOperator(transactionManager, definition)
|
||||
}
|
||||
----
|
||||
|
||||
[[transaction-programmatic-tm]]
|
||||
== Using the `TransactionManager`
|
||||
|
||||
The following sections explain programmatic usage of imperative and reactive transaction
|
||||
managers.
|
||||
|
||||
[[transaction-programmatic-ptm]]
|
||||
=== Using the `PlatformTransactionManager`
|
||||
|
||||
For imperative transactions, you can use a
|
||||
`org.springframework.transaction.PlatformTransactionManager` directly to manage your
|
||||
transaction. To do so, pass the implementation of the `PlatformTransactionManager` you
|
||||
use to your bean through a bean reference. Then, by using the `TransactionDefinition` and
|
||||
`TransactionStatus` objects, you can initiate transactions, roll back, and commit. The
|
||||
following example shows how to do so:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
|
||||
// explicitly setting the transaction name is something that can be done only programmatically
|
||||
def.setName("SomeTxName");
|
||||
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
|
||||
|
||||
TransactionStatus status = txManager.getTransaction(def);
|
||||
try {
|
||||
// put your business logic here
|
||||
} catch (MyException ex) {
|
||||
txManager.rollback(status);
|
||||
throw ex;
|
||||
}
|
||||
txManager.commit(status);
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val def = DefaultTransactionDefinition()
|
||||
// explicitly setting the transaction name is something that can be done only programmatically
|
||||
def.setName("SomeTxName")
|
||||
def.propagationBehavior = TransactionDefinition.PROPAGATION_REQUIRED
|
||||
|
||||
val status = txManager.getTransaction(def)
|
||||
try {
|
||||
// put your business logic here
|
||||
} catch (ex: MyException) {
|
||||
txManager.rollback(status)
|
||||
throw ex
|
||||
}
|
||||
|
||||
txManager.commit(status)
|
||||
----
|
||||
|
||||
|
||||
[[transaction-programmatic-rtm]]
|
||||
=== Using the `ReactiveTransactionManager`
|
||||
|
||||
When working with reactive transactions, you can use a
|
||||
`org.springframework.transaction.ReactiveTransactionManager` directly to manage your
|
||||
transaction. To do so, pass the implementation of the `ReactiveTransactionManager` you
|
||||
use to your bean through a bean reference. Then, by using the `TransactionDefinition` and
|
||||
`ReactiveTransaction` objects, you can initiate transactions, roll back, and commit. The
|
||||
following example shows how to do so:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes",role="primary"]
|
||||
.Java
|
||||
----
|
||||
DefaultTransactionDefinition def = new DefaultTransactionDefinition();
|
||||
// explicitly setting the transaction name is something that can be done only programmatically
|
||||
def.setName("SomeTxName");
|
||||
def.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
|
||||
|
||||
Mono<ReactiveTransaction> reactiveTx = txManager.getReactiveTransaction(def);
|
||||
|
||||
reactiveTx.flatMap(status -> {
|
||||
|
||||
Mono<Object> tx = ...; // put your business logic here
|
||||
|
||||
return tx.then(txManager.commit(status))
|
||||
.onErrorResume(ex -> txManager.rollback(status).then(Mono.error(ex)));
|
||||
});
|
||||
----
|
||||
[source,kotlin,indent=0,subs="verbatim,quotes",role="secondary"]
|
||||
.Kotlin
|
||||
----
|
||||
val def = DefaultTransactionDefinition()
|
||||
// explicitly setting the transaction name is something that can be done only programmatically
|
||||
def.setName("SomeTxName")
|
||||
def.propagationBehavior = TransactionDefinition.PROPAGATION_REQUIRED
|
||||
|
||||
val reactiveTx = txManager.getReactiveTransaction(def)
|
||||
reactiveTx.flatMap { status ->
|
||||
|
||||
val tx = ... // put your business logic here
|
||||
|
||||
tx.then(txManager.commit(status))
|
||||
.onErrorResume { ex -> txManager.rollback(status).then(Mono.error(ex)) }
|
||||
}
|
||||
----
|
||||
|
||||
|
||||
@@ -0,0 +1,17 @@
|
||||
[[transaction-resources]]
|
||||
= Further Resources
|
||||
|
||||
For more information about the Spring Framework's transaction support, see:
|
||||
|
||||
* https://www.infoworld.com/article/2077963/distributed-transactions-in-spring--with-and-without-xa.html[Distributed
|
||||
transactions in Spring, with and without XA] is a JavaWorld presentation in which
|
||||
Spring's David Syer guides you through seven patterns for distributed
|
||||
transactions in Spring applications, three of them with XA and four without.
|
||||
* https://www.infoq.com/minibooks/JTDS[_Java Transaction Design Strategies_] is a book
|
||||
available from https://www.infoq.com/[InfoQ] that provides a well-paced introduction
|
||||
to transactions in Java. It also includes side-by-side examples of how to configure
|
||||
and use transactions with both the Spring Framework and EJB3.
|
||||
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,22 @@
|
||||
[[transaction-solutions-to-common-problems]]
|
||||
= Solutions to Common Problems
|
||||
|
||||
This section describes solutions to some common problems.
|
||||
|
||||
|
||||
[[transaction-solutions-to-common-problems-wrong-ptm]]
|
||||
== Using the Wrong Transaction Manager for a Specific `DataSource`
|
||||
|
||||
Use the correct `PlatformTransactionManager` implementation based on your choice of
|
||||
transactional technologies and requirements. Used properly, the Spring Framework merely
|
||||
provides a straightforward and portable abstraction. If you use global
|
||||
transactions, you must use the
|
||||
`org.springframework.transaction.jta.JtaTransactionManager` class (or an
|
||||
<<transaction-application-server-integration,application server-specific subclass>> of
|
||||
it) for all your transactional operations. Otherwise, the transaction infrastructure
|
||||
tries to perform local transactions on such resources as container `DataSource`
|
||||
instances. Such local transactions do not make sense, and a good application server
|
||||
treats them as errors.
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,284 @@
|
||||
[[transaction-strategies]]
|
||||
= Understanding the Spring Framework Transaction Abstraction
|
||||
|
||||
The key to the Spring transaction abstraction is the notion of a transaction strategy. A
|
||||
transaction strategy is defined by a `TransactionManager`, specifically the
|
||||
`org.springframework.transaction.PlatformTransactionManager` interface for imperative
|
||||
transaction management and the
|
||||
`org.springframework.transaction.ReactiveTransactionManager` interface for reactive
|
||||
transaction management. The following listing shows the definition of the
|
||||
`PlatformTransactionManager` API:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
public interface PlatformTransactionManager extends TransactionManager {
|
||||
|
||||
TransactionStatus getTransaction(TransactionDefinition definition) throws TransactionException;
|
||||
|
||||
void commit(TransactionStatus status) throws TransactionException;
|
||||
|
||||
void rollback(TransactionStatus status) throws TransactionException;
|
||||
}
|
||||
----
|
||||
|
||||
This is primarily a service provider interface (SPI), although you can use it
|
||||
<<transaction-programmatic-ptm, programmatically>> from your application code. Because
|
||||
`PlatformTransactionManager` is an interface, it can be easily mocked or stubbed as
|
||||
necessary. It is not tied to a lookup strategy, such as JNDI.
|
||||
`PlatformTransactionManager` implementations are defined like any other object (or bean)
|
||||
in the Spring Framework IoC container. This benefit alone makes Spring Framework
|
||||
transactions a worthwhile abstraction, even when you work with JTA. You can test
|
||||
transactional code much more easily than if it used JTA directly.
|
||||
|
||||
Again, in keeping with Spring's philosophy, the `TransactionException` that can be thrown
|
||||
by any of the `PlatformTransactionManager` interface's methods is unchecked (that
|
||||
is, it extends the `java.lang.RuntimeException` class). Transaction infrastructure
|
||||
failures are almost invariably fatal. In rare cases where application code can actually
|
||||
recover from a transaction failure, the application developer can still choose to catch
|
||||
and handle `TransactionException`. The salient point is that developers are not
|
||||
_forced_ to do so.
|
||||
|
||||
The `getTransaction(..)` method returns a `TransactionStatus` object, depending on a
|
||||
`TransactionDefinition` parameter. The returned `TransactionStatus` might represent a
|
||||
new transaction or can represent an existing transaction, if a matching transaction
|
||||
exists in the current call stack. The implication in this latter case is that, as with
|
||||
Jakarta EE transaction contexts, a `TransactionStatus` is associated with a thread of
|
||||
execution.
|
||||
|
||||
As of Spring Framework 5.2, Spring also provides a transaction management abstraction for
|
||||
reactive applications that make use of reactive types or Kotlin Coroutines. The following
|
||||
listing shows the transaction strategy defined by
|
||||
`org.springframework.transaction.ReactiveTransactionManager`:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
public interface ReactiveTransactionManager extends TransactionManager {
|
||||
|
||||
Mono<ReactiveTransaction> getReactiveTransaction(TransactionDefinition definition) throws TransactionException;
|
||||
|
||||
Mono<Void> commit(ReactiveTransaction status) throws TransactionException;
|
||||
|
||||
Mono<Void> rollback(ReactiveTransaction status) throws TransactionException;
|
||||
}
|
||||
----
|
||||
|
||||
The reactive transaction manager is primarily a service provider interface (SPI),
|
||||
although you can use it <<transaction-programmatic-rtm, programmatically>> from your
|
||||
application code. Because `ReactiveTransactionManager` is an interface, it can be easily
|
||||
mocked or stubbed as necessary.
|
||||
|
||||
The `TransactionDefinition` interface specifies:
|
||||
|
||||
* Propagation: Typically, all code within a transaction scope runs in
|
||||
that transaction. However, you can specify the behavior if
|
||||
a transactional method is run when a transaction context already exists. For
|
||||
example, code can continue running in the existing transaction (the common case), or
|
||||
the existing transaction can be suspended and a new transaction created. Spring
|
||||
offers all of the transaction propagation options familiar from EJB CMT. To read
|
||||
about the semantics of transaction propagation in Spring, see <<tx-propagation>>.
|
||||
* Isolation: The degree to which this transaction is isolated from the work of other
|
||||
transactions. For example, can this transaction see uncommitted writes from other
|
||||
transactions?
|
||||
* Timeout: How long this transaction runs before timing out and being automatically rolled back
|
||||
by the underlying transaction infrastructure.
|
||||
* Read-only status: You can use a read-only transaction when your code reads but
|
||||
does not modify data. Read-only transactions can be a useful optimization in some
|
||||
cases, such as when you use Hibernate.
|
||||
|
||||
These settings reflect standard transactional concepts. If necessary, refer to resources
|
||||
that discuss transaction isolation levels and other core transaction concepts.
|
||||
Understanding these concepts is essential to using the Spring Framework or any
|
||||
transaction management solution.
|
||||
|
||||
The `TransactionStatus` interface provides a simple way for transactional code to
|
||||
control transaction execution and query transaction status. The concepts should be
|
||||
familiar, as they are common to all transaction APIs. The following listing shows the
|
||||
`TransactionStatus` interface:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
public interface TransactionStatus extends TransactionExecution, SavepointManager, Flushable {
|
||||
|
||||
@Override
|
||||
boolean isNewTransaction();
|
||||
|
||||
boolean hasSavepoint();
|
||||
|
||||
@Override
|
||||
void setRollbackOnly();
|
||||
|
||||
@Override
|
||||
boolean isRollbackOnly();
|
||||
|
||||
void flush();
|
||||
|
||||
@Override
|
||||
boolean isCompleted();
|
||||
}
|
||||
----
|
||||
|
||||
Regardless of whether you opt for declarative or programmatic transaction management in
|
||||
Spring, defining the correct `TransactionManager` implementation is absolutely essential.
|
||||
You typically define this implementation through dependency injection.
|
||||
|
||||
`TransactionManager` implementations normally require knowledge of the environment in
|
||||
which they work: JDBC, JTA, Hibernate, and so on. The following examples show how you can
|
||||
define a local `PlatformTransactionManager` implementation (in this case, with plain
|
||||
JDBC.)
|
||||
|
||||
You can define a JDBC `DataSource` by creating a bean similar to the following:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
|
||||
<property name="driverClassName" value="${jdbc.driverClassName}" />
|
||||
<property name="url" value="${jdbc.url}" />
|
||||
<property name="username" value="${jdbc.username}" />
|
||||
<property name="password" value="${jdbc.password}" />
|
||||
</bean>
|
||||
----
|
||||
|
||||
The related `PlatformTransactionManager` bean definition then has a reference to the
|
||||
`DataSource` definition. It should resemble the following example:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<bean id="txManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
|
||||
<property name="dataSource" ref="dataSource"/>
|
||||
</bean>
|
||||
----
|
||||
|
||||
If you use JTA in a Jakarta EE container, then you use a container `DataSource`, obtained
|
||||
through JNDI, in conjunction with Spring's `JtaTransactionManager`. The following example
|
||||
shows what the JTA and JNDI lookup version would look like:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<beans xmlns="http://www.springframework.org/schema/beans"
|
||||
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xmlns:jee="http://www.springframework.org/schema/jee"
|
||||
xsi:schemaLocation="
|
||||
http://www.springframework.org/schema/beans
|
||||
https://www.springframework.org/schema/beans/spring-beans.xsd
|
||||
http://www.springframework.org/schema/jee
|
||||
https://www.springframework.org/schema/jee/spring-jee.xsd">
|
||||
|
||||
<jee:jndi-lookup id="dataSource" jndi-name="jdbc/jpetstore"/>
|
||||
|
||||
<bean id="txManager" class="org.springframework.transaction.jta.JtaTransactionManager" />
|
||||
|
||||
<!-- other <bean/> definitions here -->
|
||||
|
||||
</beans>
|
||||
----
|
||||
|
||||
The `JtaTransactionManager` does not need to know about the `DataSource` (or any other
|
||||
specific resources) because it uses the container's global transaction management
|
||||
infrastructure.
|
||||
|
||||
NOTE: The preceding definition of the `dataSource` bean uses the `<jndi-lookup/>` tag
|
||||
from the `jee` namespace. For more information see
|
||||
<<integration.adoc#integration.appendix.xsd-schemas-jee, The JEE Schema>>.
|
||||
|
||||
NOTE: If you use JTA, your transaction manager definition should look the same, regardless
|
||||
of what data access technology you use, be it JDBC, Hibernate JPA, or any other supported
|
||||
technology. This is due to the fact that JTA transactions are global transactions, which
|
||||
can enlist any transactional resource.
|
||||
|
||||
In all Spring transaction setups, application code does not need to change. You can change
|
||||
how transactions are managed merely by changing configuration, even if that change means
|
||||
moving from local to global transactions or vice versa.
|
||||
|
||||
|
||||
[[transaction-strategies-hibernate]]
|
||||
== Hibernate Transaction Setup
|
||||
|
||||
You can also easily use Hibernate local transactions, as shown in the following examples.
|
||||
In this case, you need to define a Hibernate `LocalSessionFactoryBean`, which your
|
||||
application code can use to obtain Hibernate `Session` instances.
|
||||
|
||||
The `DataSource` bean definition is similar to the local JDBC example shown previously
|
||||
and, thus, is not shown in the following example.
|
||||
|
||||
NOTE: If the `DataSource` (used by any non-JTA transaction manager) is looked up through
|
||||
JNDI and managed by a Jakarta EE container, it should be non-transactional, because the
|
||||
Spring Framework (rather than the Jakarta EE container) manages the transactions.
|
||||
|
||||
The `txManager` bean in this case is of the `HibernateTransactionManager` type. In the
|
||||
same way as the `DataSourceTransactionManager` needs a reference to the `DataSource`, the
|
||||
`HibernateTransactionManager` needs a reference to the `SessionFactory`. The following
|
||||
example declares `sessionFactory` and `txManager` beans:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<bean id="sessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
|
||||
<property name="dataSource" ref="dataSource"/>
|
||||
<property name="mappingResources">
|
||||
<list>
|
||||
<value>org/springframework/samples/petclinic/hibernate/petclinic.hbm.xml</value>
|
||||
</list>
|
||||
</property>
|
||||
<property name="hibernateProperties">
|
||||
<value>
|
||||
hibernate.dialect=${hibernate.dialect}
|
||||
</value>
|
||||
</property>
|
||||
</bean>
|
||||
|
||||
<bean id="txManager" class="org.springframework.orm.hibernate5.HibernateTransactionManager">
|
||||
<property name="sessionFactory" ref="sessionFactory"/>
|
||||
</bean>
|
||||
----
|
||||
|
||||
If you use Hibernate and Jakarta EE container-managed JTA transactions, you should use the
|
||||
same `JtaTransactionManager` as in the previous JTA example for JDBC, as the following
|
||||
example shows. Also, it is recommended to make Hibernate aware of JTA through its
|
||||
transaction coordinator and possibly also its connection release mode configuration:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<bean id="sessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
|
||||
<property name="dataSource" ref="dataSource"/>
|
||||
<property name="mappingResources">
|
||||
<list>
|
||||
<value>org/springframework/samples/petclinic/hibernate/petclinic.hbm.xml</value>
|
||||
</list>
|
||||
</property>
|
||||
<property name="hibernateProperties">
|
||||
<value>
|
||||
hibernate.dialect=${hibernate.dialect}
|
||||
hibernate.transaction.coordinator_class=jta
|
||||
hibernate.connection.handling_mode=DELAYED_ACQUISITION_AND_RELEASE_AFTER_STATEMENT
|
||||
</value>
|
||||
</property>
|
||||
</bean>
|
||||
|
||||
<bean id="txManager" class="org.springframework.transaction.jta.JtaTransactionManager"/>
|
||||
----
|
||||
|
||||
Or alternatively, you may pass the `JtaTransactionManager` into your `LocalSessionFactoryBean`
|
||||
for enforcing the same defaults:
|
||||
|
||||
[source,xml,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
<bean id="sessionFactory" class="org.springframework.orm.hibernate5.LocalSessionFactoryBean">
|
||||
<property name="dataSource" ref="dataSource"/>
|
||||
<property name="mappingResources">
|
||||
<list>
|
||||
<value>org/springframework/samples/petclinic/hibernate/petclinic.hbm.xml</value>
|
||||
</list>
|
||||
</property>
|
||||
<property name="hibernateProperties">
|
||||
<value>
|
||||
hibernate.dialect=${hibernate.dialect}
|
||||
</value>
|
||||
</property>
|
||||
<property name="jtaTransactionManager" ref="txManager"/>
|
||||
</bean>
|
||||
|
||||
<bean id="txManager" class="org.springframework.transaction.jta.JtaTransactionManager"/>
|
||||
----
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,19 @@
|
||||
[[tx-decl-vs-prog]]
|
||||
= Choosing Between Programmatic and Declarative Transaction Management
|
||||
|
||||
Programmatic transaction management is usually a good idea only if you have a small
|
||||
number of transactional operations. For example, if you have a web application that
|
||||
requires transactions only for certain update operations, you may not want to set up
|
||||
transactional proxies by using Spring or any other technology. In this case, using the
|
||||
`TransactionTemplate` may be a good approach. Being able to set the transaction name
|
||||
explicitly is also something that can be done only by using the programmatic approach
|
||||
to transaction management.
|
||||
|
||||
On the other hand, if your application has numerous transactional operations,
|
||||
declarative transaction management is usually worthwhile. It keeps transaction
|
||||
management out of business logic and is not difficult to configure. When using the
|
||||
Spring Framework, rather than EJB CMT, the configuration cost of declarative transaction
|
||||
management is greatly reduced.
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,83 @@
|
||||
[[tx-resource-synchronization]]
|
||||
= Synchronizing Resources with Transactions
|
||||
|
||||
How to create different transaction managers and how they are linked to related resources
|
||||
that need to be synchronized to transactions (for example `DataSourceTransactionManager`
|
||||
to a JDBC `DataSource`, `HibernateTransactionManager` to a Hibernate `SessionFactory`,
|
||||
and so forth) should now be clear. This section describes how the application code
|
||||
(directly or indirectly, by using a persistence API such as JDBC, Hibernate, or JPA)
|
||||
ensures that these resources are created, reused, and cleaned up properly. The section
|
||||
also discusses how transaction synchronization is (optionally) triggered through the
|
||||
relevant `TransactionManager`.
|
||||
|
||||
|
||||
[[tx-resource-synchronization-high]]
|
||||
== High-level Synchronization Approach
|
||||
|
||||
The preferred approach is to use Spring's highest-level template-based persistence
|
||||
integration APIs or to use native ORM APIs with transaction-aware factory beans or
|
||||
proxies for managing the native resource factories. These transaction-aware solutions
|
||||
internally handle resource creation and reuse, cleanup, optional transaction
|
||||
synchronization of the resources, and exception mapping. Thus, user data access code does
|
||||
not have to address these tasks but can focus purely on non-boilerplate
|
||||
persistence logic. Generally, you use the native ORM API or take a template approach
|
||||
for JDBC access by using the `JdbcTemplate`. These solutions are detailed in subsequent
|
||||
sections of this reference documentation.
|
||||
|
||||
|
||||
[[tx-resource-synchronization-low]]
|
||||
== Low-level Synchronization Approach
|
||||
|
||||
Classes such as `DataSourceUtils` (for JDBC), `EntityManagerFactoryUtils` (for JPA),
|
||||
`SessionFactoryUtils` (for Hibernate), and so on exist at a lower level. When you want the
|
||||
application code to deal directly with the resource types of the native persistence APIs,
|
||||
you use these classes to ensure that proper Spring Framework-managed instances are obtained,
|
||||
transactions are (optionally) synchronized, and exceptions that occur in the process are
|
||||
properly mapped to a consistent API.
|
||||
|
||||
For example, in the case of JDBC, instead of the traditional JDBC approach of calling
|
||||
the `getConnection()` method on the `DataSource`, you can instead use Spring's
|
||||
`org.springframework.jdbc.datasource.DataSourceUtils` class, as follows:
|
||||
|
||||
[source,java,indent=0,subs="verbatim,quotes"]
|
||||
----
|
||||
Connection conn = DataSourceUtils.getConnection(dataSource);
|
||||
----
|
||||
|
||||
If an existing transaction already has a connection synchronized (linked) to it, that
|
||||
instance is returned. Otherwise, the method call triggers the creation of a new
|
||||
connection, which is (optionally) synchronized to any existing transaction and made
|
||||
available for subsequent reuse in that same transaction. As mentioned earlier, any
|
||||
`SQLException` is wrapped in a Spring Framework `CannotGetJdbcConnectionException`, one
|
||||
of the Spring Framework's hierarchy of unchecked `DataAccessException` types. This approach
|
||||
gives you more information than can be obtained easily from the `SQLException` and
|
||||
ensures portability across databases and even across different persistence technologies.
|
||||
|
||||
This approach also works without Spring transaction management (transaction
|
||||
synchronization is optional), so you can use it whether or not you use Spring for
|
||||
transaction management.
|
||||
|
||||
Of course, once you have used Spring's JDBC support, JPA support, or Hibernate support,
|
||||
you generally prefer not to use `DataSourceUtils` or the other helper classes,
|
||||
because you are much happier working through the Spring abstraction than directly
|
||||
with the relevant APIs. For example, if you use the Spring `JdbcTemplate` or
|
||||
`jdbc.object` package to simplify your use of JDBC, correct connection retrieval occurs
|
||||
behind the scenes and you need not write any special code.
|
||||
|
||||
|
||||
[[tx-resource-synchronization-tadsp]]
|
||||
== `TransactionAwareDataSourceProxy`
|
||||
|
||||
At the very lowest level exists the `TransactionAwareDataSourceProxy` class. This is a
|
||||
proxy for a target `DataSource`, which wraps the target `DataSource` to add awareness of
|
||||
Spring-managed transactions. In this respect, it is similar to a transactional JNDI
|
||||
`DataSource`, as provided by a Jakarta EE server.
|
||||
|
||||
You should almost never need or want to use this class, except when existing
|
||||
code must be called and passed a standard JDBC `DataSource` interface implementation. In
|
||||
that case, it is possible that this code is usable but is participating in Spring-managed
|
||||
transactions. You can write your new code by using the higher-level
|
||||
abstractions mentioned earlier.
|
||||
|
||||
|
||||
|
||||
Reference in New Issue
Block a user