© 2018-2019 The original authors.
Copies of this document may be made for your own use and for distribution to others, provided that you do not charge any fee for such copies and further provided that each copy contains this Copyright Notice, whether distributed in print or electronically. |
Preface
Project Metadata
-
Version control: https://github.com/spring-projects/spring-data-jdbc
-
Bugtracker: https://jira.spring.io/browse/DATAJDBC
-
Release repository: https://repo.spring.io/libs-release
-
Milestone repository: https://repo.spring.io/libs-milestone
-
Snapshot repository: https://repo.spring.io/libs-snapshot
1. New & Noteworthy
This section covers the significant changes for each version.
1.1. What’s New in Spring Data JDBC 1.0
-
Basic support for
CrudRepository
. -
@Query
support. -
MyBatis support.
-
Id generation.
-
Event support.
-
Auditing.
-
CustomConversions
.
Unresolved directive in index.adoc - include::../../../../spring-data-commons/src/main/asciidoc/dependencies.adoc[leveloffset=+1] Unresolved directive in index.adoc - include::../../../../spring-data-commons/src/main/asciidoc/repositories.adoc[leveloffset=+1]
Reference Documentation
2. JDBC Repositories
This chapter points out the specialties for repository support for JDBC. This builds on the core repository support explained in [repositories]. You should have a sound understanding of the basic concepts explained there.
2.1. Why Spring Data JDBC?
The main persistence API for relational databases in the Java world is certainly JPA, which has its own Spring Data module. Why is there another one?
JPA does a lot of things in order to help the developer. Among other things, it tracks changes to entities. It does lazy loading for you. It lets you map a wide array of object constructs to an equally wide array of database designs.
This is great and makes a lot of things really easy. Just take a look at a basic JPA tutorial. But it often gets really confusing as to why JPA does a certain thing. Also, things that are really simple conceptually get rather difficult with JPA.
Spring Data JDBC aims to be much simpler conceptually, by embracing the following design decisions:
-
If you load an entity, SQL statements get executed. Once this is done, you have a completely loaded entity. No lazy loading or caching is done.
-
If you save an entity, it gets saved. If you do not, it does not. There is no dirty tracking and no session.
-
There is a simple model of how to map entities to tables. It probably only works for rather simple cases. If you do not like that, you should code your own strategy. Spring Data JDBC offers only very limited support for customizing the strategy with annotations.
2.2. Domain Driven Design and Relational Databases.
All Spring Data modules are inspired by the concepts of “repository”, “aggregate”, and “aggregate root” from Domain Driven Design. These are possibly even more important for Spring Data JDBC, because they are, to some extent, contrary to normal practice when working with relational databases.
An aggregate is a group of entities that is guaranteed to be consistent between atomic changes to it.
A classic example is an Order
with OrderItems
.
A property on Order
(for example, numberOfItems
is consistent with the actual number of OrderItems
) remains consistent as changes are made.
References across aggregates are not guaranteed to be consistent at all times. They are guaranteed to become consistent eventually.
Each aggregate has exactly one aggregate root, which is one of the entities of the aggregate. The aggregate gets manipulated only through methods on that aggregate root. These are the atomic changes mentioned earlier.
A repository is an abstraction over a persistent store that looks like a collection of all the aggregates of a certain type.
For Spring Data in general, this means you want to have one Repository
per aggregate root.
In addition, for Spring Data JDBC this means that all entities reachable from an aggregate root are considered to be part of that aggregate root.
Spring Data JDBC assumes that only the aggregate has a foreign key to a table storing non-root entities of the aggregate and no other entity points toward non-root entities.
In the current implementation, entities referenced from an aggregate root are deleted and recreated by Spring Data JDBC. |
You can overwrite the repository methods with implementations that match your style of working and designing your database.
2.3. Annotation-based Configuration
The Spring Data JDBC repositories support can be activated by an annotation through Java configuration, as the following example shows:
@Configuration
@EnableJdbcRepositories
class ApplicationConfig {
@Bean
public DataSource dataSource() {
EmbeddedDatabaseBuilder builder = new EmbeddedDatabaseBuilder();
return builder.setType(EmbeddedDatabaseType.HSQL).build();
}
}
The configuration class in the preceding example sets up an embedded HSQL database by using the EmbeddedDatabaseBuilder
API of spring-jdbc
.
We activate Spring Data JDBC repositories by using the @EnableJdbcRepositories
.
If no base package is configured, it uses the package in which the configuration class resides.
2.4. Persisting Entities
Saving an aggregate can be performed with the CrudRepository.save(…)
method.
If the aggregate is new, this results in an insert for the aggregate root, followed by insert statements for all directly or indirectly referenced entities.
If the aggregate root is not new, all referenced entities get deleted, the aggregate root gets updated, and all referenced entities get inserted again. Note that whether an instance is new is part of the instance’s state.
This approach has some obvious downsides. If only few of the referenced entities have been actually changed, the deletion and insertion is wasteful. While this process could and probably will be improved, there are certain limitations to what Spring Data JDBC can offer. It does not know the previous state of an aggregate. So any update process always has to take whatever it finds in the database and make sure it converts it to whatever is the state of the entity passed to the save method. |
Unresolved directive in jdbc.adoc - include::../../../../spring-data-commons/src/main/asciidoc/object-mapping.adoc[leveloffset=+2]
2.4.1. Supported Types in Your Entity
The properties of the following types are currently supported:
-
All primitive types and their boxed types (
int
,float
,Integer
,Float
, and so on) -
Enums get mapped to their name.
-
String
-
java.util.Date
,java.time.LocalDate
,java.time.LocalDateTime
, andjava.time.LocalTime
-
Arrays and Collections of the types mentioned above can be mapped to columns of array type if your database supports that.
-
Anything your database driver accepts.
-
References to other entities. They are considered a one-to-one relationship, or an embedded type. It is optional for one-to-one relationship entities to have an
id
attribute. The table of the referenced entity is expected to have an additional column named the same as the table of the referencing entity. You can change this name by implementingNamingStrategy.getReverseColumnName(RelationalPersistentProperty property)
. Embedded entities do not need anid
. If one is present it gets ignored. -
Set<some entity>
is considered a one-to-many relationship. The table of the referenced entity is expected to have an additional column named the same as the table of the referencing entity. You can change this name by implementingNamingStrategy.getReverseColumnName(RelationalPersistentProperty property)
. -
Map<simple type, some entity>
is considered a qualified one-to-many relationship. The table of the referenced entity is expected to have two additional columns: One named the same as the table of the referencing entity for the foreign key and one with the same name and an additional_key
suffix for the map key. You can change this behavior by implementingNamingStrategy.getReverseColumnName(RelationalPersistentProperty property)
andNamingStrategy.getKeyColumn(RelationalPersistentProperty property)
, respectively. Alternatively you may annotate the attribute with@MappedCollection(idColumn="your_column_name", keyColumn="your_key_column_name")
-
List<some entity>
is mapped as aMap<Integer, some entity>
.
The handling of referenced entities is limited. This is based on the idea of aggregate roots as described above. If you reference another entity, that entity is, by definition, part of your aggregate. So, if you remove the reference, the previously referenced entity gets deleted. This also means references are 1-1 or 1-n, but not n-1 or n-m.
If you have n-1 or n-m references, you are, by definition, dealing with two separate aggregates.
References between those should be encoded as simple id
values, which should map properly with Spring Data JDBC.
2.4.2. Custom converters
Custom converters can be registered, for types that are not supported by default, by inheriting your configuration from AbstractJdbcConfiguration
and overwriting the method jdbcCustomConversions()
.
@Configuration
public class DataJdbcConfiguration extends AbstractJdbcConfiguration {
@Override
public JdbcCustomConversions jdbcCustomConversions() {
return new JdbcCustomConversions(Collections.singletonList(TimestampTzToDateConverter.INSTANCE));
}
@ReadingConverter
enum TimestampTzToDateConverter implements Converter<TIMESTAMPTZ, Date> {
INSTANCE;
@Override
public Date convert(TIMESTAMPTZ source) {
//...
}
}
}
The constructor of JdbcCustomConversions
accepts a list of org.springframework.core.convert.converter.Converter
.
Converters should be annotated with @ReadingConverter
or @WritingConverter
in order to control their applicability to only reading from or to writing to the database.
TIMESTAMPTZ
in the example is a database specific data type that needs conversion into something more suitable for a domain model.
JdbcValue
Value conversion uses JdbcValue
to enrich values propagated to JDBC operations with a java.sql.Types
type.
Register a custom write converter if you need to specify a JDBC-specific type instead of using type derivation.
This converter should convert the value to JdbcValue
which has a field for the value and for the actual JDBCType
.
2.4.3. NamingStrategy
When you use the standard implementations of CrudRepository
that Spring Data JDBC provides, they expect a certain table structure.
You can tweak that by providing a NamingStrategy
in your application context.
2.4.4. Custom table names
When the NamingStrategy does not matching on your database table names, you can customize the names with the @Table
annotation.
The element value
of this annotation provides the custom table name. The following example maps the MyEntity
class to the CUSTOM_TABLE_NAME
table in the database:
@Table("CUSTOM_TABLE_NAME")
public class MyEntity {
@Id
Integer id;
String name;
}
2.4.5. Custom column names
When the NamingStrategy does not matching on your database column names, you can customize the names with the @Column
annotation.
The element value
of this annotation provides the custom column name.
The following example maps the name
property of the MyEntity
class to the CUSTOM_COLUMN_NAME
column in the database:
public class MyEntity {
@Id
Integer id;
@Column("CUSTOM_COLUMN_NAME")
String name;
}
The @MappedCollection
annotation can be used on a reference type (one-to-one relationship) or on Sets, Lists, and Maps (one-to-many relationship)
On all these types the value
element of the annotation is used to provide a custom name for the foreign key column referencing the id column in the other table.
In the following example the corresponding table for the MySubEntity
class has a name column, and the id column of the MyEntity
id for relationship reasons.
The name of this MySubEntity
class’s id column can also be customized with the idColumn
element of the @MappedCollection
annotation:
public class MyEntity {
@Id
Integer id;
@MappedCollection(idColumn = "CUSTOM_COLUMN_NAME")
Set<MySubEntity> name;
}
public class MySubEntity {
String name;
}
When using List
and Map
you must have an additional column for the position of a dataset in the List
or the key value of the entity in the Map
.
This additional column name may be customized with the keyColumn
Element of the @MappedCollection
annotation:
public class MyEntity {
@Id
Integer id;
@MappedCollection(idColumn = "CUSTOM_COLUMN_NAME", keyColumn = "CUSTOM_KEY_COLUMN_NAME")
List<MySubEntity> name;
}
public class MySubEntity {
String name;
}
2.4.6. Embedded entities
Embedded entities are used to have value objects in your java data model, even if there is only one table in your database.
In the following example you see, that MyEntity
is mapped with the @Embedded
annotation.
The consequence of this is, that in the database a table my_entity
with the two columns id
and name
(from the EmbeddedEntity
class) is expected.
However, if the name
column is actually null
within the result set, the entire property embeddedEntity
will be set to null according to the onEmpty
of @Embedded
, which null
s objects when all nested properties are null
.
Opposite to this behavior USE_EMPTY
tries to create a new instance using either a default constructor or one that accepts nullable parameter values from the result set.
public class MyEntity {
@Id
Integer id;
@Embedded(onEmpty = USE_NULL) (1)
EmbeddedEntity embeddedEntity;
}
public class EmbeddedEntity {
String name;
}
1 | Null s embeddedEntity if name in null . Use USE_EMPTY to instanciate embeddedEntity with a potential null value for the name property. |
If you need a value object multiple times in an entity, this can be achieved with the optional prefix
element of the @Embedded
annotation.
This element represents a prefix and is prepend for each column name in the embedded object.
Make use of the shortcuts
|
Embedded entities containing a Collection
or a Map
will always be considered non empty since they will at least contain the empty collection or map.
Such an entity will therefore never be null
even when using @Embedded(onEmpty = USE_NULL).
2.4.7. Entity State Detection Strategies
The following table describes the strategies that Spring Data JDBC offers for detecting whether an entity is new:
Id-Property inspection (the default) |
By default, Spring Data JDBC inspects the identifier property of the given entity.
If the identifier property is |
Implementing |
If an entity implements |
Implementing |
You can customize the |
2.4.8. ID Generation
Spring Data JDBC uses the ID to identify entities.
The ID of an entity must be annotated with Spring Data’s @Id
annotation.
When your data base has an auto-increment column for the ID column, the generated value gets set in the entity after inserting it into the database.
One important constraint is that, after saving an entity, the entity must not be new any more.
Note that whether an entity is new is part of the entity’s state.
With auto-increment columns, this happens automatically, because the ID gets set by Spring Data with the value from the ID column.
If you are not using auto-increment columns, you can use a BeforeSave
listener, which sets the ID of the entity (covered later in this document).
2.5. Query Methods
This section offers some specific information about the implementation and use of Spring Data JDBC.
2.5.1. Query Lookup Strategies
The JDBC module supports defining a query manually only as a String in a @Query
annotation.
Deriving a query from the name of the method is currently not supported.
2.5.2. Using @Query
The following example shows how to use @Query
to declare a query method:
public interface UserRepository extends CrudRepository<User, Long> {
@Query("select firstName, lastName from User u where u.emailAddress = :email")
User findByEmailAddress(@Param("email") String email);
}
Spring fully supports Java 8’s parameter name discovery based on the -parameters compiler flag. By using this flag in your build as an alternative to debug information, you can omit the @Param annotation for named parameters.
|
Spring Data JDBC supports only named parameters. |
Custom RowMapper
You can configure which RowMapper
to use, either by using the @Query(rowMapperClass = ….)
or by registering a RowMapperMap
bean and registering a RowMapper
per method return type. The following example shows how to register RowMappers
:
@Bean
RowMapperMap rowMappers() {
return new ConfigurableRowMapperMap() //
.register(Person.class, new PersonRowMapper()) //
.register(Address.class, new AddressRowMapper());
}
When determining which RowMapper
to use for a method, the following steps are followed, based on the return type of the method:
-
If the type is a simple type, no
RowMapper
is used.Instead, the query is expected to return a single row with a single column, and a conversion to the return type is applied to that value.
-
The entity classes in the
RowMapperMap
are iterated until one is found that is a superclass or interface of the return type in question. TheRowMapper
registered for that class is used.Iterating happens in the order of registration, so make sure to register more general types after specific ones.
If applicable, wrapper types such as collections or Optional
are unwrapped.
Thus, a return type of Optional<Person>
uses the Person
type in the preceding process.
Modifying Query
You can mark a query as being a modifying query by using the @Modifying
on query method, as the following example shows:
@Modifying
@Query("UPDATE DUMMYENTITY SET name = :name WHERE id = :id")
boolean updateName(@Param("id") Long id, @Param("name") String name);
You can specify the following return types:
-
void
-
int
(updated record count) -
boolean
(whether a record was updated)
2.6. MyBatis Integration
The execution of CRUD operations and query methods can be delegated to MyBatis. This section describes how to configure Spring Data JDBC to integrate with MyBatis and which conventions to follow to hand over the execution of the queries as well as the mapping to the library.
2.7. Configuration
The easiest way to properly plug MyBatis into Spring Data JDBC is by importing MyBatisJdbcConfiguration
into you application configuration:
@Configuration
@EnableJdbcRepositories
@Import(MyBatisJdbcConfiguration.class)
class Application {
@Bean
SqlSessionFactoryBean sqlSessionFactoryBean() {
// Configure MyBatis here
}
}
As you can see, all you need to declare is a SqlSessionFactoryBean
as MyBatisJdbcConfiguration
relies on a SqlSession
bean to be available in the ApplicationContext
eventually.
2.8. Usage conventions
For each operation in CrudRepository
, Spring Data JDBC runs multiple statements.
If there is a SqlSessionFactory
in the application context, Spring Data checks, for each step, whether the SessionFactory
offers a statement.
If one is found, that statement (including its configured mapping to an entity) is used.
The name of the statement is constructed by concatenating the fully qualified name of the entity type with Mapper.
and a String
determining the kind of statement.
For example, if an instance of org.example.User
is to be inserted, Spring Data JDBC looks for a statement named org.example.UserMapper.insert
.
When the statement is run, an instance of [MyBatisContext
] gets passed as an argument, which makes various arguments available to the statement.
The following table describes the available MyBatis statements:
Name | Purpose | CrudRepository methods that might trigger this statement | Attributes available in the MyBatisContext |
---|---|---|---|
|
Inserts a single entity. This also applies for entities referenced by the aggregate root. |
|
|
|
Updates a single entity. This also applies for entities referenced by the aggregate root. |
|
|
|
Deletes a single entity. |
|
|
|
Deletes all entities referenced by any aggregate root of the type used as prefix with the given property path. Note that the type used for prefixing the statement name is the name of the aggregate root, not the one of the entity to be deleted. |
|
|
|
Deletes all aggregate roots of the type used as the prefix |
|
|
|
Deletes all entities referenced by an aggregate root with the given propertyPath |
|
|
|
Selects an aggregate root by ID |
|
|
|
Select all aggregate roots |
|
|
|
Select a set of aggregate roots by ID values |
|
|
|
Select a set of entities that is referenced by another entity. The type of the referencing entity is used for the prefix. The referenced entities type is used as the suffix. This method is deprecated. Use |
All |
|
|
Select a set of entities that is referenced by another entity via a property path. |
All |
|
|
Count the number of aggregate root of the type used as prefix |
|
|
2.9. Lifecycle Events
Spring Data JDBC triggers events that get published to any matching ApplicationListener
beans in the application context.
For example, the following listener gets invoked before an aggregate gets saved:
@Bean
public ApplicationListener<BeforeSave> timeStampingSaveTime() {
return event -> {
Object entity = event.getEntity();
if (entity instanceof Category) {
Category category = (Category) entity;
category.timeStamp();
}
};
}
The following table describes the available events:
Event | When It Is Published |
---|---|
Before an aggregate root gets deleted. |
|
After an aggregate root gets deleted. |
|
Before an aggregate root gets saved (that is, inserted or updated but after the decision about whether if it gets updated or deleted was made). |
|
Before an aggregate root gets saved (that is, inserted or updated but after the decision about whether if it gets updated or deleted was made). |
|
After an aggregate root gets saved (that is, inserted or updated). |
|
After an aggregate root gets created from a database |
Lifecycle events depend on an ApplicationEventMulticaster , which in case of the SimpleApplicationEventMulticaster can be configured with a TaskExecutor , and therefore gives no guarantees when an Event is processed.
|
2.10. Entity Callbacks
The Spring Data infrastructure provides hooks for modifying an entity before and after certain methods are invoked.
Those so called EntityCallback
instances provide a convenient way to check and potentially modify an entity in a callback fashioned style.
An EntityCallback
looks pretty much like a specialized ApplicationListener
.
Some Spring Data modules publish store specific events (such as BeforeSaveEvent
) that allow modifying the given entity. In some cases, such as when working with immutable types, these events can cause trouble.
Also, event publishing relies on ApplicationEventMulticaster
. If configuring that with an asynchronous TaskExecutor
it can lead to unpredictable outcomes, as event processing can be forked onto a Thread.
Entity callbacks provide integration points with both synchronous and reactive APIs to guarantee in-order execution at well-defined checkpoints within the processing chain, returning a potentially modified entity or an reactive wrapper type.
Entity callbacks are typically separated by API type. This separation means that a synchronous API considers only synchronous entity callbacks and a reactive implementation considers only reactive entity callbacks.
The Entity Callback API has been introduced with Spring Data Commons 2.2. It is the recommended way of applying entity modifications.
Existing store specific |
2.10.1. Implementing Entity Callbacks
An EntityCallback
is directly associated with its domain type through its generic type argument.
Each Spring Data module typically ships with a set of predefined EntityCallback
interfaces covering the entity lifecycle.
EntityCallback
@FunctionalInterface
public interface BeforeSaveCallback<T> extends EntityCallback<T> {
/**
* Entity callback method invoked before a domain object is saved.
* Can return either the same or a modified instance.
*
* @return the domain object to be persisted.
*/
T onBeforeSave(T entity <2>, String collection <3>); (1)
}
1 | BeforeSaveCallback specific method to be called before an entity is saved. Returns a potentially modifed instance. |
2 | The entity right before persisting. |
3 | A number of store specific arguments like the collection the entity is persisted to. |
EntityCallback
@FunctionalInterface
public interface ReactiveBeforeSaveCallback<T> extends EntityCallback<T> {
/**
* Entity callback method invoked on subscription, before a domain object is saved.
* The returned Publisher can emit either the same or a modified instance.
*
* @return Publisher emitting the domain object to be persisted.
*/
Publisher<T> onBeforeSave(T entity <2>, String collection <3>); (1)
}
1 | BeforeSaveCallback specific method to be called on subscription, before an entity is saved. Emits a potentially modifed instance. |
2 | The entity right before persisting. |
3 | A number of store specific arguments like the collection the entity is persisted to. |
Optional entity callback parameters are defined by the implementing Spring Data module and inferred from call site of EntityCallback.callback() .
|
Implement the interface suiting your application needs like shown in the example below:
BeforeSaveCallback
class DefaultingEntityCallback implements BeforeSaveCallback<Person>, Ordered { (2)
@Override
public Object onBeforeSave(Person entity, String collection) { (1)
if(collection == "user") {
return // ...
}
return // ...
}
@Override
public int getOrder() {
return 100; (2)
}
}
1 | Callback implementation according to your requirements. |
2 | Potentially order the entity callback if multiple ones for the same domain type exist. Ordering follows lowest precedence. |
2.10.2. Registering Entity Callbacks
EntityCallback
beans are picked up by the store specific implementations in case they are registered in the ApplicationContext
.
Most template APIs already implement ApplicationContextAware
and therefore have access to the ApplicationContext
The following example explains a collection of valid entity callback registrations:
EntityCallback
Bean registration@Order(1) (1)
@Component
class First implements BeforeSaveCallback<Person> {
@Override
public Person onBeforeSave(Person person) {
return // ...
}
}
@Component
class DefaultingEntityCallback implements BeforeSaveCallback<Person>,
Ordered { (2)
@Override
public Object onBeforeSave(Person entity, String collection) {
// ...
}
@Override
public int getOrder() {
return 100; (2)
}
}
@Configuration
public class EntityCallbackConfiguration {
@Bean
BeforeSaveCallback<Person> unorderedLambdaReceiverCallback() { (3)
return (BeforeSaveCallback<Person>) it -> // ...
}
}
@Component
class UserCallbacks implements BeforeConvertCallback<User>,
BeforeSaveCallback<User> { (4)
@Override
public Person onBeforeConvert(User user) {
return // ...
}
@Override
public Person onBeforeSave(User user) {
return // ...
}
}
1 | BeforeSaveCallback receiving its order from the @Order annotation. |
2 | BeforeSaveCallback receiving its order via the Ordered interface implementation. |
3 | BeforeSaveCallback using a lambda expression. Unordered by default and invoked last. |
4 | Combine multiple entity callback interfaces in a single implementation class. |
2.10.3. Store-specific EntityCallbacks
Spring Data JDBC uses the EntityCallback
API for its auditing support and reacts on the following callbacks:
EntityCallback |
When It Is Published |
---|---|
Before an aggregate root gets deleted. |
|
After an aggregate root gets deleted. |
|
Before an aggregate root gets saved (that is, inserted or updated but after the decision about whether if it gets updated or deleted was made). |
|
Before an aggregate root gets saved (that is, inserted or updated but after the decision about whether if it gets updated or deleted was made). |
|
After an aggregate root gets saved (that is, inserted or updated). |
|
After an aggregate root gets created from a database |
2.11. Logging
Spring Data JDBC does little to no logging on its own.
Instead, the mechanics of JdbcTemplate
to issue SQL statements provide logging.
Thus, if you want to inspect what SQL statements are executed, activate logging for Spring’s NamedParameterJdbcTemplate
or MyBatis.
2.12. Transactionality
CRUD methods on repository instances are transactional by default.
For reading operations, the transaction configuration readOnly
flag is set to true
. All others are configured with a plain @Transactional
annotation so that default transaction configuration applies.
For details, see the Javadoc of SimpleJdbcRepository
. If you need to tweak transaction configuration for one of the methods declared in a repository, redeclare the method in your repository interface, as follows:
public interface UserRepository extends CrudRepository<User, Long> {
@Override
@Transactional(timeout = 10)
public List<User> findAll();
// Further query method declarations
}
The preceding causes the findAll()
method to be executed with a timeout of 10 seconds and without the readOnly
flag.
Another way to alter transactional behavior is by using a facade or service implementation that typically covers more than one repository. Its purpose is to define transactional boundaries for non-CRUD operations. The following example shows how to create such a facade:
@Service
class UserManagementImpl implements UserManagement {
private final UserRepository userRepository;
private final RoleRepository roleRepository;
@Autowired
public UserManagementImpl(UserRepository userRepository,
RoleRepository roleRepository) {
this.userRepository = userRepository;
this.roleRepository = roleRepository;
}
@Transactional
public void addRoleToAllUsers(String roleName) {
Role role = roleRepository.findByName(roleName);
for (User user : userRepository.findAll()) {
user.addRole(role);
userRepository.save(user);
}
}
The preceding example causes calls to addRoleToAllUsers(…)
to run inside a transaction (participating in an existing one or creating a new one if none are already running). The transaction configuration for the repositories is neglected, as the outer transaction configuration determines the actual repository to be used. Note that you have to explicitly activate <tx:annotation-driven />
or use @EnableTransactionManagement
to get annotation-based configuration for facades working. Note that the preceding example assumes you use component scanning.
2.12.1. Transactional Query Methods
To let your query methods be transactional, use @Transactional
at the repository interface you define, as the following example shows:
@Transactional(readOnly = true)
public interface UserRepository extends CrudRepository<User, Long> {
List<User> findByLastname(String lastname);
@Modifying
@Transactional
@Query("delete from User u where u.active = false")
void deleteInactiveUsers();
}
Typically, you want the readOnly
flag to be set to true, because most of the query methods only read data. In contrast to that, deleteInactiveUsers()
uses the @Modifying
annotation and overrides the transaction configuration. Thus, the method is with the readOnly
flag set to false
.
It is definitely reasonable to use transactions for read-only queries, and we can mark them as such by setting the readOnly flag. This does not, however, act as a check that you do not trigger a manipulating query (although some databases reject INSERT and UPDATE statements inside a read-only transaction). Instead, the readOnly flag is propagated as a hint to the underlying JDBC driver for performance optimizations.
|
Unresolved directive in jdbc.adoc - include::../../../../spring-data-commons/src/main/asciidoc/auditing.adoc[leveloffset=+1]
2.13. JDBC Auditing
In order to activate auditing, add @EnableJdbcAuditing
to your configuration, as the following example shows:
@Configuration
@EnableJdbcAuditing
class Config {
@Bean
public AuditorAware<AuditableUser> auditorProvider() {
return new AuditorAwareImpl();
}
}
If you expose a bean of type AuditorAware
to the ApplicationContext
, the auditing infrastructure automatically picks it up and uses it to determine the current user to be set on domain types. If you have multiple implementations registered in the ApplicationContext
, you can select the one to be used by explicitly setting the auditorAwareRef
attribute of @EnableJdbcAuditing
.
Appendix
Appendix B: Glossary
- AOP
-
Aspect-Oriented Programming
- CRUD
-
Create, Read, Update, Delete - Basic persistence operations
- Dependency Injection
-
Pattern to hand a component’s dependency to the component from outside, freeing the component to lookup the dependent itself. For more information, see https://en.wikipedia.org/wiki/Dependency_Injection.
- JPA
-
Java Persistence API
- Spring
-
Java application framework — https://projects.spring.io/spring-framework
Unresolved directive in index.adoc - include::../../../../spring-data-commons/src/main/asciidoc/repository-namespace-reference.adoc[leveloffset=+1] Unresolved directive in index.adoc - include::../../../../spring-data-commons/src/main/asciidoc/repository-populator-namespace-reference.adoc[leveloffset=+1] :leveloffset: +1
Supported query keywords
Spring Data JDBC does not support query derivation yet.
Unresolved directive in index.adoc - include::../../../../spring-data-commons/src/main/asciidoc/repository-query-return-types-reference.adoc[leveloffset=+1]