Spring家族提供了一套完整且方便的访问数据库的方式。其中,Spring Framework为数据库访问提供了底层支持,Spring Data为数据库访问封装了Repository等实用工具,Spring Boot则简化了数据库组件的配置。
一、SpringData核心组件
Spring数据库访问的核心组件分别位于Spring Framework、Spring Data、Spring Boot模块中,每个模块中又含有若干独立的专用组件,如:
- Spring Boot中的
spring-boot-starter-data-jpa
、spring-boot-starter-data-jdbc
等 - Spring Data中的
spring-data-jpa
、spring-data-jdbc
等 - Spring Framework中的
spring-jdbc
、spring-orm
、spring-tx
等
它们的依赖关系如下图所示:
@startuml SpringData关键组件依赖图
title SpringData关键组件依赖图
' folder "Spring-Boot" {
component [spring-boot-starter-data-jpa] as spring_boot_starter_data_jpa #LightPink
component [spring-boot-starter-data-jdbc] as spring_boot_starter_data_jdbc #LightPink
component [spring-boot-starter-jdbc] as spring_boot_starter_jdbc #LightPink
' }
' folder "Spring-Data" {
component [spring-data-jpa] as spring_data_jpa #LightSkyBlue
component [spring-data-jdbc] as spring_data_jdbc #LightSkyBlue
component [spring-data-commons] as spring_data_commons #LightSkyBlue
component [spring-data-relational] as spring_data_relational #LightSkyBlue
' }
' folder "Spring-Framework" {
component [spring-jdbc] as spring_jdbc #LightGoldenRodYellow
component [spring-orm] as spring_orm #LightGoldenRodYellow
component [spring-tx] as spring_tx #LightGoldenRodYellow
' }
component [hibernate-core] as hibernate_core
component [jakarta.persistence-api] as jakarta.persistence_api
component [jakarta.transaction-api] as jakarta.transaction_api
component [HikariCP] as HikariCP
spring_boot_starter_data_jpa --> hibernate_core
spring_boot_starter_data_jpa --> jakarta.persistence_api
spring_boot_starter_data_jpa --> jakarta.transaction_api
spring_boot_starter_data_jpa --> spring_boot_starter_jdbc
spring_boot_starter_data_jpa --> spring_data_jpa
spring_boot_starter_jdbc --> spring_jdbc
spring_boot_starter_jdbc --> HikariCP
spring_data_jpa --> spring_data_commons
spring_data_jpa --> spring_orm
' spring_data_jpa --> spring_tx
spring_jdbc --> spring_tx
spring_orm --> spring_tx
spring_boot_starter_data_jdbc --> spring_boot_starter_jdbc
spring_boot_starter_data_jdbc --> spring_data_jdbc
spring_data_jdbc --> spring_data_commons
spring_data_jdbc --> spring_data_relational
spring_data_jdbc --> spring_jdbc
' spring_data_jdbc --> spring_tx
' spring_data_relational --> spring_data_commons
spring_data_relational -->spring_tx
@enduml
上图中,Spring Boot模块组件用浅粉色表示,Spring Data模块组件用浅蓝色表示,Spring Framework模块组件用浅黄色表示。
二、Spring数据库访问过程
下面将基于Spring Boot与Spring Data JPA,通过一段Demo演示Spring访问数据库的完整过程。
(一)定义数据源
首先,使用脚手架新建一个SpringBoot应用,并在resources修改application.yml文件,如下:
spring:
h2:
console:
enabled: true # 开启h2控制台页面
path: /h2 # 定义h2控制台页面访问地址
datasource:
url: jdbc:h2:mem:test # 定义h2数据库访问路径
jpa:
show-sql: true # 在日志中打印sql
hibernate:
ddl-auto: none # 禁用hibernate自动生成表结构
为了避免jpa自动生成表结构与resources下的schema.sql产生冲突,除了「ddl-auto: none」配置外,也可以使用「defer-datasource-initialization: true」配置。
这里使用了一个h2内存数据库,如有需要,也可以将datasource中url的数据库协议替换为其它数据库,以mysql为例,如下:
spring:
datasource:
url: jdbc:mysql://localhost:3306/mydb
username: luanrz
password: luanrz
简单起见,后续将使用上述h2数据库配置。随后,在resources下增加DDL与DML:
DDLschema.sql
:
-- drop table person if exists;
create table person (
id varchar(20) not null ,
name varchar(20) null
);
DMLdata.sql
:
insert into person values('0001', '张三')
至此,数据源准备工作完成。
(二)定义数据库访问基础组件
Spring提供了多种访问数据库的方法,比较常见的有以下两种:
- 位于
spring-jdbc
(Spring Framework)模块的JdbcTemplate
- 位于
spring-data-common
(Spring Data)模块的CrudRepository
。
下面将以基于JPA的CrudRepository
创建对应的定义实体类与访问接口。
实体类Person.java
:
import javax.persistence.Entity;
import javax.persistence.Id;
@Entity
public class Person {
@Id
private String id;
private String name;
public Person(String id, String name) {
this.id = id;
this.name = name;
}
/**
* Hibernate需要空参构造函数
*/
public Person() {
}
/**
* SpringMVC序列化工具jackson反序列化(请求解析)时需要set方法
*/
public Person setId(String id) {
this.id = id;
return this;
}
/**
* SpringMVC序列化工具jackson序列化(响应转换)时需要get方法
*/
public String getId() {
return id;
}
public Person setName(String name) {
this.name = name;
return this;
}
public String getName() {
return name;
}
}
访问接口PersonRepository
:
import org.springframework.data.repository.CrudRepository;
public interface PersonRepository extends CrudRepository<Person, String> {
}
通过以上代码,PersonRepository
接口已经提供了基于Person
实体的数据库CRUD功能,有关Repository
的更多细节详见SpringDataJpa官方文档。
(三)定义访问入口
最后,基于SpringMVC定义访问入口,来调用上述数据库访问基础组件。
访问控制器PersonController.java
:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
@RestController
public class PersonController {
@Autowired
private PersonRepository repository;
@GetMapping( "/")
public Person get(String id) {
return repository.findById(id).orElseThrow(() -> new RuntimeException("empty"));
}
@PostMapping(value = "/")
public Person add(@RequestBody Person person) {
return repository.save(person);
}
@DeleteMapping("/")
public void delete(String id) {
repository.deleteById(id);
}
}
为了代码完整性,这里将脚手架自动生成的Application也贴上。
入口类MyApplication.java
:
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication
public class MyApplication {
public static void main(String[] args) {
SpringApplication.run(MyApplication.class, args);
}
}
(四)验证应用
启动应用后,在PostMan或其它Api工具中访问服务。访问参数如下:
### get person
GET http://localhost:8080?id=0001
### add person
POST http://localhost:8080
Content-Type: application/json
{
"id": "0002",
"name": "李四"
}
### delete person
DELETE http://localhost:8080?id=0002
或在终端使用curl访问:
curl --request GET \
--url 'http://localhost:8080/?id=0001'
curl --request POST \
--url http://localhost:8080/ \
--header 'content-type: application/json' \
--data '{"id": "0002","name": "李四"}'
curl --request DELETE \
--url 'http://localhost:8080/?id=0002'
调用上述接口之后,可以进入h2控制台页面(localhost:8080/h2/
),查看数据结果是否符合预期。
三、Spring事务支持
Spring使用spring-tx
组件提供事务支持。在前文SpringData核心组件的SpringData关键组件依赖图中,spring-tx
位于整个SpringData依赖关系中最底层,换言之,spring-tx
作为一个基础组件,为大多数上层组件(如:Spring Data JPA)提供了事务能力。
可以使用spring-tx
中的@Transactional
注解,为方法增加事务支持,如下所示:
package org.example;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;
import javax.transaction.Transactional;
@RestController
public class PersonController {
@Autowired
private PersonRepository repository;
@DeleteMapping("/")
@Transactional
public void delete(String id) {
repository.deleteById(id);
throw new RuntimeException("some exception happened");
}
}
含有@Transactional
注解的方法内部一旦出现异常,事务将执行回滚。有关事务的更多内容,详见SpringData事务官方文档。
参考文档