服务端开发(3) Spring Data JDBC、JPA
2023-08-09 14:53:19 # NJU # 服务端开发

1. 使用原始JDBC访问数据库

1
2
3
4
5
6
7
8
9
10
11
Connection connection = dataSource.getConnection();
String sql = "select id, name, type from Ingredient";
PreparedStatement statement = connection.prepareStatement(sql);
ResultSet resultSet = statement.executeQuery();
while (resultSet.next()) {
Ingredient ingredient = new Ingredient(
resultSet.getString("id"),
resultSet.getString("name"),
Ingredient.Type.valueOf(resultSet.getString("type")));
ingredients.add(ingredient);
}

问题:样板式代码、SQLException

2. 使用JdbcTemplate

2.1 引入依赖

1
2
3
4
5
6
7
8
9
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-jdbc</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>

2.2 H2配置与访问

1
2
3
4
spring:
datasource:
generate-unique-name: false
name: tacocloud

http://localhost:8080/h2-console

驱动:org.h2.Driver

JDBC URL:jdbc:h2:mem:tacocloud

用户名:sa

2.3 IngredientRepository的实现

注入JdbcTemplate

1
2
3
4
5
private JdbcTemplate jdbcTemplate;

public JdbcIngredientRepository(JdbcTemplate jdbcTemplate) {
this.jdbcTemplate = jdbcTemplate;
}

@Repository

接口:RowMapper

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
// jdbcTemplate方法
jdbcTemplate.query(
"select id, name, type from Ingredient",
this::mapRowToIngredient);

jdbcTemplate.update(
"insert into Ingredient (id, name, type) values (?, ?, ?)",
ingredient.getId(),
ingredient.getName(),
ingredient.getType().toString());

// mapRowToIngredient
private Ingredient mapRowToIngredient(ResultSet row, int rowNum) throws SQLException {
return new Ingredient(
row.getString("id"),
row.getString("name"),
Ingredient.Type.valueOf(row.getString("type")));
}

// RowMapper
jdbcTemplate.queryForObject(
"select id, name, type from Ingredient where id=?",
new RowMapper<Ingredient>() {
public Ingredient mapRow(ResultSet rs, int rowNum) throws SQLException {
return new Ingredient(
rs.getString("id"),
rs.getString("name"),
Ingredient.Type.valueOf(rs.getString("type")));
};
}, id);

2.4 数据库表创建与数据初始化

在resource目录下放置,名称固定

  • schema.sql
  • data.sql

2.5 save(TacoOrder order)的实现

JdbcOrderRepository

获取返回的ID,GeneratedKeyHolder

3. 使用 Spring Data JDBC

3.1 Spring Data项目

  • Spring Data JDBC
  • Spring Data JPA
  • Spring Data MongoDB
  • Spring Data Neo4j
  • Spring Data Redis
  • Spring Data Cassandra

3.2 引入依赖

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jdbc</artifactId>
</dependency>

用spring boot编译出支持不同版本的jdk

如果当前安装的jdk是17,则编译出的版本支持:1.8、11、17

1
2
3
<properties>
<java.version>11</java.version>
</properties>

3.3 定义持久化接口

1
2
public interface IngredientRepository 
extends CrudRepository<Ingredient, String> {} // <操作的类型, 主键类型>

3.4 为领域类添加持久化的注解

@Table

@Id

@Column

3.5 提高程序健壮性

JVM 断言

开启debug模式

使用spring boot的Assert类

3.6 程序预加载

org.springframework.boot.CommandLineRunner

1
2
3
4
5
6
7
8
9
10
@Bean
public CommandLineRunner dataLoader(IngredientRepository repo) { // ApplicationRunner
return args -> {
repo.deleteAll();
repo.save(new Ingredient("FLTO", "Flour Tortilla", Type.WRAP));
repo.save(new Ingredient("COTO", "Corn Tortilla", Type.WRAP));
repo.save(new Ingredient("GRBF", "Ground Beef", Type.PROTEIN));
...
};
}

org.springframework.boot.ApplicationRunner

4. 使用 Spring Data JPA

JPA:Java Persistence API

JPA的宗旨是为POJO提供持久化标准规范

依赖:

1
2
3
4
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>

4.1 Jpa、Hibernate、Spring Data Jpa三者之间的关系

image-20230309163056920

4.2 @Entity

4.3 自动生成的数据库表

image-20230309163544196

4.4 自定义的查询方法

定义查询方法,无需实现

  • 领域特定语言(domain-specific language,DSL),spring data的命名约定
  • 查询动词 + 主题 + 断言
  • 查询动词:get、read、find、count
  • 例子: List findByDeliveryZip(String deliveryZip);

声明自定义查询