Skip to content

Commit 89abfdc

Browse files
committed
DATAJDBC-271 - Updated README.
Removed most of the content. Replaced it with a real short overview and links to more useful information. Added build status badges.
1 parent 577d764 commit 89abfdc

File tree

1 file changed

+17
-327
lines changed

1 file changed

+17
-327
lines changed

README.adoc

Lines changed: 17 additions & 327 deletions
Original file line numberDiff line numberDiff line change
@@ -1,341 +1,31 @@
1+
image:https://spring.io/badges/spring-data-jdbc/ga.svg["Spring Data JDBC", link="https://spring.io/projects/spring-data-jdbc#learn"]
2+
image:https://spring.io/badges/spring-data-jdbc/snapshot.svg["Spring Data JDBC", link="https://spring.io/projects/spring-data-jdbc#learn"]
3+
14
= Spring Data JDBC
25

36
The primary goal of the http://projects.spring.io/spring-data[Spring Data] project is to make it easier to build Spring-powered applications that use data access technologies. *Spring Data JDBC* offers the popular Repository abstraction based on JDBC.
47

5-
== This is NOT an ORM
6-
7-
Spring Data JDBC does not try to be an ORM. It is not a competitor to JPA.
8-
Instead it is more of a construction kit for your personal ORM that you can define the way you like or need it.
9-
10-
This means that it does rather little out of the box.
11-
But it offers plenty of places where you can put your own logic, or integrate it with the technology of your choice for generating SQL statements.
12-
13-
== The Aggregate Root
14-
15-
Spring Data repositories are inspired by the repository as described in the book Domain Driven Design by Eric Evans.
16-
One consequence of this is that you should have a repository per Aggregate Root.
17-
Aggregate Root is another concept from the same book and describes an entity which controls the lifecycle of other entities which together are an Aggregate.
18-
An Aggregate is a subset of your model which is consistent between method calls to your Aggregate Root.
19-
20-
Spring Data JDBC tries its best to encourage modelling your domain along these ideas.
21-
22-
== Maven Coordinates
23-
24-
[source,xml]
25-
----
26-
<dependency>
27-
<groupId>org.springframework.data</groupId>
28-
<artifactId>spring-data-jdbc</artifactId>
29-
<version>1.0.0.BUILD-SNAPSHOT</version>
30-
</dependency>
31-
----
8+
It aims at being conceptually easy.
9+
In order to achieve this it does NOT offer caching, lazy loading, write behind or many other features of JPA.
10+
This makes Spring Data JDBC a simple, limited, opinionated ORM.
3211

3312
== Features
3413

35-
=== CRUD operations
36-
37-
In order to use Spring Data JDBC you need the following:
38-
39-
1. An entity with an attribute marked as _id_ using the Spring Data https://docs.spring.io/spring-data/commons/docs/current/api/org/springframework/data/annotation/Id.html[`@Id`] annotation.
40-
+
41-
[source,java]
42-
----
43-
public class Person {
44-
@Id
45-
Integer id;
46-
}
47-
----
48-
+
49-
1. A repository
50-
+
51-
[source,java]
52-
----
53-
public interface PersonRepository extends CrudRepository<Person, Integer> {}
54-
----
55-
+
56-
1. Add `@EnableJdbcRepositories` to your application context configuration.
57-
1. Make sure your application context contains a bean of type `DataSource`.
58-
59-
Now you can get an instance of the repository interface injected into your beans and use it:
60-
61-
[source,java]
62-
----
63-
@Autowired
64-
private PersonRepository repository;
65-
66-
public void someMethod() {
67-
Person person = repository.save(new Person());
68-
}
69-
----
70-
71-
==== Supported types in your entity
72-
73-
Properties of the following types are currently supported:
74-
75-
* all primitive types and their boxed types (`int`, `float`, `Integer`, `Float` ...)
76-
77-
* enums get mapped to their name.
78-
79-
* `String`
80-
81-
* `java.util.Date`, `java.time.LocalDate`, `java.time.LocalDateTime`, `java.time.LocalTime`
82-
83-
and anything your database driver accepts.
84-
85-
* references to other entities, which will be considered a one-to-one relationship.
86-
The table of the referenced entity is expected to have an additional column named like the table of the referencing entity.
87-
This name can be changed by implementing `NamingStrategy.getReverseColumnName(JdbcPersistentProperty property)` according to your preferences.
88-
89-
* `Set<some entity>` will be considered a one-to-many relationship.
90-
The table of the referenced entity is expected to have an additional column named like the table of the referencing entity.
91-
This name can be changed by implementing `NamingStrategy.getReverseColumnName(JdbcPersistentProperty property)` according to your preferences.
92-
93-
* `Map<simple type, some entity>` will be considered a qualified one-to-many relationship.
94-
The table of the referenced entity is expected to have two additional columns: One named like 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.
95-
This name can be changed by implementing `NamingStrategy.getReverseColumnName(JdbcPersistentProperty property)` and `NamingStrategy.getKeyColumn(JdbcPersistentProperty property)` according to your preferences.
96-
97-
* `List<some entity>` will be mapped like a `Map<Integer, some entity>`.
98-
99-
The handling of referenced entities is very limited.
100-
Part of this is because this project is still before its first release.
101-
102-
But another reason is the idea of <<The Aggregate Root,Aggregate Roots>> as described above.
103-
If you reference another entity that entity is by definition part of your Aggregate.
104-
So if you remove the reference it will get deleted.
105-
This also means references will be 1-1 or 1-n, but not n-1 or n-m.
106-
107-
If you are having n-1 or n-m references you are probably dealing with two separate Aggregates.
108-
References between those should be encoded as simple ids, which should map just fine with Spring Data JDBC.
109-
110-
Also the mapping we offer is very limited for a third reason which already was mentioned at the very beginning of the document: this is not an ORM.
111-
We will offer ways to plug in your own SQL in various ways.
112-
But the default mapping itself will stay limited.
113-
If you want highly customizable mappings which support almost everything one can imagine you will probably be much happier with (Spring Data) JPA,
114-
which is a very powerful and mature technology.
115-
116-
=== Query annotation
117-
118-
You can annotate a query method with `@Query` to specify a SQL statement to be used for that method.
119-
You can bind method arguments using named parameters in the SQL statement like in the following example:
120-
121-
[source,java]
122-
----
123-
@Query("SELECT * FROM DUMMYENTITY WHERE name < :upper and name > :lower")
124-
List<DummyEntity> findByNameRange(@Param("lower") String lower, @Param("upper") String upper);
125-
----
126-
127-
If you compile your sources with the `-parameters` compiler flag you can omit the `@Param` annotations.
128-
129-
==== Custom RowMapper
130-
131-
You can configure the `RowMapper` to use, using either the `@Query(rowMapperClass = ....)` or you can register a `RowMapperMap` bean and register `RowMapper` per method return type.
132-
133-
[source,java]
134-
----
135-
136-
@Bean
137-
RowMapperMap rowMappers() {
138-
return new ConfigurableRowMapperMap() //
139-
.register(Person.class, new PersonRowMapper()) //
140-
.register(Address.class, new AddressRowMapper());
141-
}
142-
143-
----
144-
145-
When determining the `RowMapper` to use for a method the following steps are followed based on the return type of the method:
146-
147-
1. If the type is a simple type, no `RowMapper` is used.
148-
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.
149-
150-
2. The entity classes in the `RowMapperMap` are iterated until one is found that is a superclass or interface of the return type in question.
151-
The `RowMapper` registered for that class is used.
152-
Iterating happens in the order of registration, so make sure to register more general types after specific ones.
153-
154-
If applicable, wrapper types like collections or `Optional` are unwrapped.
155-
Thus, a return type of `Optional<Person>` will use the type `Person` in the steps above.
156-
157-
==== Modifying query
158-
159-
You can mark as a modifying query using the `@Modifying` on query method.
160-
161-
[source,java]
162-
----
163-
@Modifying
164-
@Query("UPDATE DUMMYENTITY SET name = :name WHERE id = :id")
165-
boolean updateName(@Param("id") Long id, @Param("name") String name);
166-
----
167-
168-
The return types that can be specified are `void`, `int`(updated record count) and `boolean`(whether record was updated).
169-
170-
=== Id generation
171-
172-
Spring Data JDBC uses the id to identify entities, but also to determine if an entity is new or already existing in the database.
173-
If the id is `null` or of a primitive type having value `0` or `0.0`, the entity is considered new.
174-
175-
If your database has some autoincrement-column for the id-column, the generated value will get set in the entity after inserting it into the database.
176-
177-
There are few ways to tweak this behavior.
178-
If you don't like the logic to distinguish between new and existing entities you can implement https://docs.spring.io/spring-data/commons/docs/current/api/org/springframework/data/domain/Persistable.html[`Persistable`] with your entity and overwrite `isNew()` with your own logic.
14+
* Implementation of CRUD methods for Aggregates.
15+
* `@Query` annotation
16+
* Support for transparent auditing (created, last changed)
17+
* Events for persistence events
18+
* Possibility to integrate custom repository code
19+
* JavaConfig based repository configuration by introducing `EnableJdbcRepository`
20+
* Integration with MyBatis
17921

180-
One important constraint is that after saving an entity the entity shouldn't be _new_ anymore.
181-
With autoincrement-columns this happens automatically since the id gets set by Spring Data with the value from the id-column.
182-
If you are not using autoincrement-columns, you can use a `BeforeSave`-listener which sets the id of the entity (see below).
183-
184-
=== NamingStrategy
185-
186-
If you use the standard implementations of `CrudRepository` as provided by Spring Data JDBC, it will expect a certain table structure.
187-
You can tweak that by providing a https://github.com/spring-projects/spring-data-jdbc/blob/master/src/main/java/org/springframework/data/jdbc/mapping/model/NamingStrategy.java[`NamingStrategy`] in your application context.
188-
189-
In many cases a https://github.com/spring-projects/spring-data-jdbc/blob/master/src/main/java/org/springframework/data/jdbc/mapping/model/DelimiterNamingStrategy.java[`DelimiterNamingStrategy`]
190-
might be good basis for a custom implementation
191-
192-
=== Events
193-
194-
Spring Data JDBC triggers events which will get published to any matching `ApplicationListener` in the application context.
195-
For example, the following listener will get invoked before an Aggregate gets saved.
196-
197-
[source,java]
198-
----
199-
@Bean
200-
public ApplicationListener<BeforeSaveEvent> timeStampingSaveTime() {
201-
202-
return event -> {
203-
204-
Object entity = event.getEntity();
205-
if (entity instanceof Category) {
206-
Category category = (Category) entity;
207-
category.timeStamp();
208-
}
209-
};
210-
}
211-
----
212-
213-
.Available events
214-
|===
215-
| Event | When It's Published
216-
217-
| https://github.com/spring-projects/spring-data-jdbc/blob/master/src/main/java/org/springframework/data/jdbc/mapping/event/BeforeDeleteEvent.java[`BeforeDeleteEvent`]
218-
| before an aggregate root gets deleted.
219-
220-
| https://github.com/spring-projects/spring-data-jdbc/blob/master/src/main/java/org/springframework/data/jdbc/mapping/event/AfterDeleteEvent.java[`AfterDeleteEvent`]
221-
| after an aggregate root got deleted.
222-
223-
| https://github.com/spring-projects/spring-data-jdbc/blob/master/src/main/java/org/springframework/data/jdbc/mapping/event/BeforeSaveEvent.java[`BeforeSaveEvent`]
224-
| before an aggregate root gets saved, i.e. inserted or updated but after the decision was made if it will get updated or deleted.
225-
The event has a reference to an https://github.com/spring-projects/spring-data-jdbc/blob/master/src/main/java/org/springframework/data/jdbc/core/conversion/AggregateChange.java[`AggregateChange`] instance.
226-
The instance can be modified by adding or removing https://github.com/spring-projects/spring-data-jdbc/blob/master/src/main/java/org/springframework/data/jdbc/core/conversion/DbAction.java[`DbAction`]s.
227-
228-
| https://github.com/spring-projects/spring-data-jdbc/blob/master/src/main/java/org/springframework/data/jdbc/mapping/event/AfterSaveEvent.java[`AfterSaveEvent`]
229-
| after an aggregate root gets saved, i.e. inserted or updated.
230-
231-
| https://github.com/spring-projects/spring-data-jdbc/blob/master/src/main/java/org/springframework/data/jdbc/mapping/event/AfterLoadEvent.java[`AfterLoadEvent`]
232-
| after an aggregate root got created from a database `ResultSet` and all it's property set
233-
|===
234-
235-
236-
=== MyBatis
237-
238-
For each operation in `CrudRepository` Spring Data JDBC will execute multiple statements.
239-
If there is a https://github.com/mybatis/mybatis-3/blob/master/src/main/java/org/apache/ibatis/session/SqlSessionFactory.java[`SqlSessionFactory`] in the application context, it will be checked if it offers a statement for each step.
240-
If one is found, that statement will be used (including its configured mapping to an entity).
241-
242-
By default, 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.
243-
E.g. if an instance of `org.example.User` is to be inserted, Spring Data JDBC will look for a statement named `org.example.UserMapper.insert`.
244-
245-
Upon execution of the statement an instance of [`MyBatisContext`] will get passed as an argument which makes various arguments available to the statement.
246-
247-
[cols="default,default,default,asciidoc"]
248-
|===
249-
| Name | Purpose | CrudRepository methods which might trigger this statement | Attributes available in the `MyBatisContext`
250-
251-
| `insert` | Insert for a single entity. This also applies for entities referenced by the aggregate root. | `save`, `saveAll`. |
252-
`getInstance`:
253-
the instance to be saved
254-
255-
`getDomainType`: the type of the entity to be saved.
256-
257-
`get(<key>)`: id of the referencing entity, where `<key>` is the name of the back reference column as provided by the `NamingStrategy`.
258-
259-
260-
| `update` | Update for a single entity. This also applies for entities referenced by the aggregate root. | `save`, `saveAll`.|
261-
`getInstance`: the instance to be saved
262-
263-
`getDomainType`: the type of the entity to be saved.
264-
265-
| `delete` | Delete a single entity. | `delete`, `deleteById`.|
266-
`getId`: the id of the instance to be deleted
267-
268-
`getDomainType`: the type of the entity to be deleted.
269-
270-
| `deleteAll.<propertyPath>` | Delete all entities referenced by any aggregate root of the type used as prefix via the given property path.
271-
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. | `deleteAll`.|
272-
273-
`getDomainType`: the type of the entities to be deleted.
274-
275-
| `deleteAll` | Delete all aggregate roots of the type used as the prefix | `deleteAll`.|
276-
277-
`getDomainType`: the type of the entities to be deleted.
278-
279-
| `delete.<propertyPath>` | Delete all entities referenced by an aggregate root via the given propertyPath | `deleteById`.|
280-
281-
`getId`: the id of the aggregate root for which referenced entities are to be deleted.
282-
283-
`getDomainType`: the type of the entities to be deleted.
284-
285-
286-
| `findById` | Select an aggregate root by id | `findById`.|
287-
288-
`getId`: the id of the entity to load.
289-
290-
`getDomainType`: the type of the entity to load.
291-
292-
| `findAll` | Select all aggregate roots | `findAll`.|
293-
294-
`getDomainType`: the type of the entity to load.
295-
296-
| `findAllById` | Select a set of aggregate roots by ids | `findAllById`.|
297-
298-
`getId`: list of ids of the entities to load.
299-
300-
`getDomainType`: the type of the entity to load.
301-
302-
303-
| `findAllByProperty.<propertyName>` | 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 as the suffix. | All `find*` methods.|
304-
305-
`getId`: the id of the entity referencing the entities to be loaded.
306-
307-
`getDomainType`: the type of the entity to load.
308-
309-
| `count` | Count the number of aggregate root of the type used as prefix | `count` |
310-
311-
`getDomainType` the type of aggregate roots to count.
312-
|===
313-
314-
==== NamespaceStrategy
315-
316-
You can customize the namespace part of a statement name using https://github.com/spring-projects/spring-data-jdbc/blob/master/src/main/java/org/springframework/data/jdbc/mybatis/NamespaceStrategy.java[`NamespaceStrategy`].
317-
318-
== Features planned for the not too distant future
319-
320-
=== Advanced query annotation support
321-
322-
* projections
323-
* SpEL expressions
324-
325-
=== MyBatis per method support
326-
327-
The current MyBatis supported is rather elaborate in that it allows to execute multiple statements for a single method call.
328-
But sometimes less is more, and it should be possible to annotate a method with a simple annotation to identify a SQL statement in a MyBatis mapping to be executed.
329-
330-
== Spring Boot integration
331-
332-
There is https://github.com/schauder/spring-data-jdbc-boot-starter[preliminary Spring Boot integration].
22+
== Getting Help
33323

334-
Currently you will need to build it locally.
24+
If you are new to Spring Data JDBC read the following two articles https://spring.io/blog/2018/09/17/introducing-spring-data-jdbc["Introducing Spring Data JDBC"] and https://spring.io/blog/2018/09/24/spring-data-jdbc-references-and-aggregates["Spring Data JDBC, References, and Aggregates"]
33525

336-
== Getting Help
26+
There are also examples in the https://github.com/spring-projects/spring-data-examples/tree/master/jdbc[Spring Data Examples] project.
33727

338-
Right now the best source of information is the source code in this repository.
28+
A very good source of information is the source code in this repository.
33929
Especially the integration tests (if you are reading this on github, type `t` and then `IntegrationTests.java`)
34030

34131
We are keeping an eye on the (soon to be created) https://stackoverflow.com/questions/tagged/spring-data-jdbc[spring-data-jdbc tag on stackoverflow].

0 commit comments

Comments
 (0)