2024-08-07 03:01:46 +08:00
|
|
|
|
|
|
|
|
|
## 6. 数据库访问
|
|
|
|
|
|
|
|
|
|
参考[jdbc(ppt)](./resources/JDBC.pptx)
|
|
|
|
|
|
2024-11-05 11:01:26 +08:00
|
|
|
|
### 6.1 JDBC(了解)
|
2024-08-07 03:01:46 +08:00
|
|
|
|
#### 6.1.1 什么是JDBC?
|
|
|
|
|
- **定义**: JDBC (Java Database Connectivity) 是Java中用于连接和操作关系型数据库的标准API。
|
|
|
|
|
- **用途**: JDBC允许Java应用程序与各种关系型数据库进行交互,包括执行SQL语句、处理查询结果等。
|
|
|
|
|
- **优势**: 提供了一个统一的接口,使得开发者可以使用相同的API来操作不同的数据库。
|
|
|
|
|
|
|
|
|
|
#### 6.1.2 JDBC常用API
|
|
|
|
|
- **Driver接口**: 定义了数据库驱动必须实现的方法。
|
|
|
|
|
- **DriverManager类**: 提供了加载驱动、获取数据库连接等静态方法。
|
|
|
|
|
- **Connection接口**: 代表与数据库的连接,用于创建Statement对象。
|
|
|
|
|
- **Statement接口**: 用于发送SQL语句到数据库。
|
|
|
|
|
- **PreparedStatement接口**: 继承自Statement,用于预编译SQL语句并设置参数。
|
|
|
|
|
- **ResultSet接口**: 用于处理查询结果。
|
|
|
|
|
- **SQLException类**: JDBC中所有异常的基类,用于表示数据库访问过程中出现的问题。
|
|
|
|
|
|
|
|
|
|
#### 6.1.3 实现第一个JDBC程序
|
|
|
|
|
|
|
|
|
|
下面是一个使用JDBC API操作数据库的例子:
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
import java.sql.Connection;
|
|
|
|
|
import java.sql.DriverManager;
|
|
|
|
|
import java.sql.PreparedStatement;
|
|
|
|
|
import java.sql.ResultSet;
|
|
|
|
|
import java.sql.SQLException;
|
|
|
|
|
|
|
|
|
|
public class JdbcCrudExample {
|
|
|
|
|
|
|
|
|
|
private static final String DB_URL = "jdbc:mysql://localhost:3306/mydatabase";
|
|
|
|
|
private static final String USER = "root";
|
|
|
|
|
private static final String PASS = "password";
|
|
|
|
|
|
|
|
|
|
public static void main(String[] args) {
|
|
|
|
|
Connection conn = null;
|
|
|
|
|
try {
|
|
|
|
|
// 加载MySQL驱动
|
|
|
|
|
Class.forName("com.mysql.cj.jdbc.Driver");
|
|
|
|
|
|
|
|
|
|
// 获取数据库连接
|
|
|
|
|
conn = DriverManager.getConnection(DB_URL, USER, PASS);
|
|
|
|
|
|
|
|
|
|
// 创建表
|
|
|
|
|
createTable(conn);
|
|
|
|
|
|
|
|
|
|
// 插入记录
|
|
|
|
|
insertUser(conn, "Alice", 28);
|
|
|
|
|
insertUser(conn, "Bob", 32);
|
|
|
|
|
|
|
|
|
|
// 查询记录
|
|
|
|
|
queryUsers(conn);
|
|
|
|
|
|
|
|
|
|
// 更新记录
|
|
|
|
|
updateUser(conn, "Alice", 29);
|
|
|
|
|
|
|
|
|
|
// 删除记录
|
|
|
|
|
deleteUser(conn, "Bob");
|
|
|
|
|
|
|
|
|
|
// 再次查询记录
|
|
|
|
|
queryUsers(conn);
|
|
|
|
|
|
|
|
|
|
} catch (ClassNotFoundException | SQLException e) {
|
|
|
|
|
e.printStackTrace();
|
|
|
|
|
} finally {
|
|
|
|
|
if (conn != null) {
|
|
|
|
|
try {
|
|
|
|
|
conn.close();
|
|
|
|
|
} catch (SQLException e) {
|
|
|
|
|
e.printStackTrace();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static void createTable(Connection conn) throws SQLException {
|
|
|
|
|
String sql = "CREATE TABLE IF NOT EXISTS users (" +
|
|
|
|
|
"id INT AUTO_INCREMENT PRIMARY KEY," +
|
|
|
|
|
"name VARCHAR(255) NOT NULL," +
|
|
|
|
|
"age INT)";
|
|
|
|
|
PreparedStatement stmt = conn.prepareStatement(sql);
|
|
|
|
|
stmt.executeUpdate();
|
|
|
|
|
stmt.close();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static void insertUser(Connection conn, String name, int age) throws SQLException {
|
|
|
|
|
String sql = "INSERT INTO users (name, age) VALUES (?, ?)";
|
|
|
|
|
PreparedStatement pstmt = conn.prepareStatement(sql);
|
|
|
|
|
pstmt.setString(1, name);
|
|
|
|
|
pstmt.setInt(2, age);
|
|
|
|
|
pstmt.executeUpdate();
|
|
|
|
|
pstmt.close();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static void queryUsers(Connection conn) throws SQLException {
|
|
|
|
|
String sql = "SELECT * FROM users";
|
|
|
|
|
PreparedStatement pstmt = conn.prepareStatement(sql);
|
|
|
|
|
ResultSet rs = pstmt.executeQuery();
|
|
|
|
|
while (rs.next()) {
|
|
|
|
|
int id = rs.getInt("id");
|
|
|
|
|
String name = rs.getString("name");
|
|
|
|
|
int age = rs.getInt("age");
|
|
|
|
|
System.out.println("ID: " + id + ", Name: " + name + ", Age: " + age);
|
|
|
|
|
}
|
|
|
|
|
rs.close();
|
|
|
|
|
pstmt.close();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static void updateUser(Connection conn, String name, int age) throws SQLException {
|
|
|
|
|
String sql = "UPDATE users SET age = ? WHERE name = ?";
|
|
|
|
|
PreparedStatement pstmt = conn.prepareStatement(sql);
|
|
|
|
|
pstmt.setInt(1, age);
|
|
|
|
|
pstmt.setString(2, name);
|
|
|
|
|
int rowsUpdated = pstmt.executeUpdate();
|
|
|
|
|
System.out.println(rowsUpdated + " row(s) updated.");
|
|
|
|
|
pstmt.close();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
private static void deleteUser(Connection conn, String name) throws SQLException {
|
|
|
|
|
String sql = "DELETE FROM users WHERE name = ?";
|
|
|
|
|
PreparedStatement pstmt = conn.prepareStatement(sql);
|
|
|
|
|
pstmt.setString(1, name);
|
|
|
|
|
int rowsDeleted = pstmt.executeUpdate();
|
|
|
|
|
System.out.println(rowsDeleted + " row(s) deleted.");
|
|
|
|
|
pstmt.close();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
2024-08-07 03:05:35 +08:00
|
|
|
|
### 6.2 数据库连接池(了解)
|
2024-08-07 03:01:46 +08:00
|
|
|
|
#### 6.2.1 什么是数据库连接池?
|
|
|
|
|
- **定义**: 数据库连接池是一种管理数据库连接的技术,用于提高性能和减少资源消耗。
|
|
|
|
|
- **优势**: 减少了创建和销毁连接的开销,提高了应用程序的响应速度。
|
|
|
|
|
|
|
|
|
|
#### 6.2.2 DataSource接口
|
|
|
|
|
- **定义**: JDBC 4.0 引入的`javax.sql.DataSource`接口,用于创建数据库连接池。
|
|
|
|
|
- **使用**: 通过`getConnection()`方法获取连接。
|
|
|
|
|
|
|
|
|
|
#### 6.2.3 常见数据库连接池实现
|
|
|
|
|
- **Apache Commons DBCP**: 早期的数据库连接池实现。
|
|
|
|
|
- **C3P0**: 一个免费的开源连接池实现。
|
|
|
|
|
- **HikariCP**: 一个高性能的数据库连接池实现。
|
|
|
|
|
- **DBUtils**: 一个轻量级的数据库工具类,支持连接池管理。
|
|
|
|
|
|
|
|
|
|
#### 6.2.4 使用HikariCP实现数据库连接池
|
|
|
|
|
- **配置**: 设置连接池的参数。
|
|
|
|
|
```java
|
|
|
|
|
HikariConfig config = new HikariConfig();
|
|
|
|
|
config.setJdbcUrl("jdbc:mysql://localhost:3306/mydatabase");
|
|
|
|
|
config.setUsername("username");
|
|
|
|
|
config.setPassword("password");
|
|
|
|
|
config.setMaximumPoolSize(10);
|
|
|
|
|
config.setConnectionTimeout(30000);
|
|
|
|
|
```
|
|
|
|
|
- **创建连接池**:
|
|
|
|
|
```java
|
|
|
|
|
HikariDataSource ds = new HikariDataSource(config);
|
|
|
|
|
```
|
|
|
|
|
- **获取连接**:
|
|
|
|
|
```java
|
|
|
|
|
Connection conn = ds.getConnection();
|
|
|
|
|
```
|
|
|
|
|
|
2024-11-05 11:01:26 +08:00
|
|
|
|
### 6.3 SQL注入的预防措施(了解)
|
2024-08-07 03:01:46 +08:00
|
|
|
|
- **定义**: SQL注入是一种常见的安全攻击,通过在SQL语句中插入恶意代码来破坏数据库。
|
|
|
|
|
- **预防**: 使用预编译语句(PreparedStatement)来防止SQL注入。
|
|
|
|
|
- **创建PreparedStatement对象**:
|
|
|
|
|
```java
|
|
|
|
|
PreparedStatement pstmt = conn.prepareStatement("SELECT * FROM users WHERE name = ?");
|
|
|
|
|
pstmt.setString(1, "John Doe");
|
|
|
|
|
```
|
|
|
|
|
- **执行SQL查询**:
|
|
|
|
|
```java
|
|
|
|
|
ResultSet rs = pstmt.executeQuery();
|
|
|
|
|
```
|
|
|
|
|
|
2024-11-05 11:00:08 +08:00
|
|
|
|
### 6.4 ORM(了解)
|
|
|
|
|
#### 6.4.1 ORM基本概念
|
|
|
|
|
- **定义**: ORM (Object-Relational Mapping) 是一种编程技术,用于将对象模型映射到关系型数据库模型。
|
|
|
|
|
- **目的**: 简化数据库操作,提高开发效率。
|
|
|
|
|
#### 6.4.2 常见ORM框架介绍
|
|
|
|
|
- **JPA (Java Persistence API)**
|
|
|
|
|
- **定义**: JPA 是Java EE标准的一部分,提供了一种对象持久化机制。
|
|
|
|
|
- **特点**: 支持实体管理和生命周期管理。
|
|
|
|
|
- **Hibernate**
|
|
|
|
|
- **定义**: Hibernate 是一个流行的ORM框架,实现了JPA规范。
|
|
|
|
|
- **特点**: 提供了强大的映射能力和缓存机制。
|
|
|
|
|
- **MyBatis**
|
|
|
|
|
- **定义**: MyBatis 是一个半自动的ORM框架,提供了SQL查询的灵活性。
|
|
|
|
|
- **特点**: 支持动态SQL和存储过程。
|
|
|
|
|
|
2024-11-05 11:01:26 +08:00
|
|
|
|
#### 6.4.3 JPA (掌握)
|
2024-11-05 11:00:08 +08:00
|
|
|
|
|
|
|
|
|
> JPA的全称是Java Persistence API, 即Java 持久化API,是SUN公司推出的一套基于ORM的规范,内部是由一系列的接口和抽象类构成。 JPA通过注解描述对象-关系表的映射关系,并将运行期的实体对象持久化到数据库中。
|
|
|
|
|
>
|
|
|
|
|
> JPA本身不是ORM框架——因为JPA并未提供ORM实现,它只是制订了ORM规范、API接口,具体实现则由服务厂商来提供实现。
|
|
|
|
|
|
|
|
|
|
> 在Spring Boot中,我们可以使用Spring Data JPA库来实现JPA。Spring Data JPA通过提供一系列接口和依赖注入的实现来简化与JPA的交互,同时提供了自动配置和默认规则,使得应用程序无需编写大量的代码就能实现数据的访问和操作。
|
|
|
|
|
![spring data jap](./resources/imgs/spring-data-jpa.png)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
##### 理解实体类
|
|
|
|
|
|
|
|
|
|
在面向对象编程中,实体类是指具有某种功能或者意义的对象,可以是一个人、一件物品、一个地点、一项服务等。实体类通常由多个属性(字段)组成,并且可以具有多个操作(方法),来处理这些属性的修改、获取、设置等。
|
|
|
|
|
|
|
|
|
|
> 在ORM框架中,实体类通常是指与数据库表相关联的Java类,用于存储数据库表中的数据,并提供了对这些数据进行操作的方法。这个类的所有属性通常与该表中的列相对应,并具有相同的类型和名称。
|
|
|
|
|
|
|
|
|
|
下面是一个使用Java语言定义的User实体类的例子:
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
@Entity
|
|
|
|
|
@Table(name = "users")
|
|
|
|
|
public class User {
|
|
|
|
|
|
|
|
|
|
@Id
|
|
|
|
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
|
|
|
|
private Long id;
|
|
|
|
|
|
|
|
|
|
@Column(nullable = false, unique = true)
|
|
|
|
|
private String username;
|
|
|
|
|
|
|
|
|
|
@Column(nullable = false)
|
|
|
|
|
private String password;
|
|
|
|
|
|
|
|
|
|
// getter and setters omitted for brevity
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
在此类定义中,使用@Entity和@Table注释来指定该类映射到数据库中的哪个表。该类中的id、username和password字段,分别对应数据库表中的id、username和password列,并使用@Column注释指定每个字段与表中的列之间的映射关系。在此示例中,还使用@Id和@GeneratedValue注释表示id字段是主键,自动递增产生。
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
##### 使用spring data jpa实现数据库访问层
|
|
|
|
|
|
|
|
|
|
在Spring Boot中,Repository层是数据访问层的一种实现方式。它是一个在数据访问层中专注于对数据库进行访问、操作和管理的接口。
|
|
|
|
|
[
|
|
|
|
|
Repository层通常使用ORM框架(例如Hibernate、MyBatis)来与数据库进行交互,并将实体类映射到数据库表中。Repository层的目标是隐藏与底层数据存储相关的细节,使开发人员仅需关注业务逻辑,并提供可扩展性和松散耦合性。
|
|
|
|
|
|
|
|
|
|
下面是一个简单的例子,演示如何在Spring Boot中创建一个UserRepository:]()
|
|
|
|
|
```java
|
|
|
|
|
public interface UserRepository extends JpaRepository<User, Long> {
|
|
|
|
|
User findByUsername(String username);
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
> 上述代码定义了一个UserRepository接口,该接口继承了JpaRepository,并特定了实体类为User,主键类型为Long。UserRepository中还定义了根据用户名查询用户信息的findByUsername方法。
|
|
|
|
|
|
|
|
|
|
> 在此例中,我们使用JpaRepository提供的方法来实现基本的CRUD操作,同时自定义findByUsername方法,以满足我们的业务需求。
|
|
|
|
|
|
|
|
|
|
##### JpaRepository
|
|
|
|
|
|
|
|
|
|
JpaRepository接口继承了PagingAndSortingRepository和CrudRepository两个接口,它包含了CRUD方法和分页/排序方法等常用操作,如save、findById、findAll、deleteById和flush等。以下是JpaRepository的一些常用方法:
|
|
|
|
|
|
|
|
|
|
- findOne:根据主键查询单个实体。
|
|
|
|
|
- findById:根据主键查询单个实体,返回Optional类型结果。
|
|
|
|
|
- save:保存或更新实体。
|
|
|
|
|
- deleteById:根据主键删除实体。
|
|
|
|
|
- findAll:查询所有实体列表。
|
|
|
|
|
- count:统计实体数量。
|
|
|
|
|
- existsById:判断是否存在指定主键的实体。
|
|
|
|
|
- flush:立即将持久化上下文的挂起状态写入数据库。
|
|
|
|
|
|
|
|
|
|
JpaRepository还支持自定义查询方法,这可以通过在接口中添加自定义方法来实现。自定义查询方法应使用@Query注解,具有灵活性和可扩展性,例如:
|
|
|
|
|
|
|
|
|
|
```java
|
|
|
|
|
public interface UserRepository extends JpaRepository<User, Long> {
|
|
|
|
|
@Query("SELECT u FROM User u WHERE u.username = ?1 AND u.age = ?2")
|
|
|
|
|
List<User> findByUsernameAndAge(String username, Integer age);
|
|
|
|
|
}
|
|
|
|
|
```
|
2024-11-05 11:05:42 +08:00
|
|
|
|
以上代码中,定义了一个查询方法findByUsernameAndAge,使用@Query注解指定查询语句。参数值通过?1和?2进行占位并传入,同时需要注意复合查询之间的关系。
|
|
|
|
|
|
|
|
|
|
##### Spring Boot项目中JPA的配置
|
|
|
|
|
|
|
|
|
|
在application.yml中配置JPA的属性,如数据库连接信息、实体类包名、数据库类型等。示例如下:
|
|
|
|
|
|
|
|
|
|
```yaml
|
|
|
|
|
spring:
|
|
|
|
|
jackson:
|
|
|
|
|
property-naming-strategy: SNAKE_CASE # 驼峰转下划线
|
|
|
|
|
datasource:
|
|
|
|
|
url: jdbc:mysql://localhost:3306/paopao?useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true # MySQL连接URL,paopao是数据库名
|
|
|
|
|
driverClassName: com.mysql.cj.jdbc.Driver # MySQL的Driver类名
|
|
|
|
|
username: root # MySQL用户名
|
|
|
|
|
password: root # MySQL密码
|
|
|
|
|
jpa:
|
|
|
|
|
show-sql: true
|
|
|
|
|
open-in-view: false
|
|
|
|
|
defer-datasource-initialization: true
|
|
|
|
|
database-platform: org.hibernate.dialect.MySQL8Dialect # 修改为MySQL的Dialect
|
|
|
|
|
hibernate:
|
|
|
|
|
ddl-auto: update # 根据需要调整,update适用于开发和某些生产环境
|
|
|
|
|
format_sql: true # 保持原样,用于格式化SQL输出
|
|
|
|
|
```
|
2024-11-05 11:00:08 +08:00
|
|
|
|
|
2024-11-05 11:01:26 +08:00
|
|
|
|
### 6.5 事务管理(掌握)
|
2024-11-05 11:00:08 +08:00
|
|
|
|
|
|
|
|
|
#### 6.5.1 事务管理的基本概念
|
2024-10-31 09:41:09 +08:00
|
|
|
|
|
|
|
|
|
在计算机科学中,特别是在数据库管理和分布式计算领域,事务(Transaction)是一种基本的操作单位,它确保了一系列操作要么全部成功,要么全部失败。事务的概念是数据库管理系统(DBMS)中用来保证数据完整性和一致性的重要机制之一。
|
|
|
|
|
|
|
|
|
|
事务具有以下几个核心特点,通常称为ACID属性
|
|
|
|
|
|
|
|
|
|
- **原子性 (Atomicity)**: 事务中的所有操作要么全部完成,要么一个也不完成。
|
|
|
|
|
- **一致性 (Consistency)**: 事务的执行不会破坏数据的一致性。
|
|
|
|
|
- **隔离性 (Isolation)**: 事务之间是隔离的,不会相互影响。
|
|
|
|
|
- **持久性 (Durability)**: 一旦事务提交,它的效果是持久的。
|
|
|
|
|
|
2024-11-05 11:00:08 +08:00
|
|
|
|
#### 6.5.2 Spring事务管理器
|
2024-10-31 09:41:09 +08:00
|
|
|
|
- **定义**: Spring提供了多种事务管理器,如`DataSourceTransactionManager`。
|
|
|
|
|
- **示例**:
|
|
|
|
|
```java
|
|
|
|
|
@Configuration
|
|
|
|
|
@EnableTransactionManagement
|
|
|
|
|
public class AppConfig {
|
|
|
|
|
@Bean
|
|
|
|
|
public PlatformTransactionManager transactionManager(DataSource dataSource) {
|
|
|
|
|
return new DataSourceTransactionManager(dataSource);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
2024-11-05 11:00:08 +08:00
|
|
|
|
#### 6.5.3 使用@Transactional进行声明式事务管理
|
2024-10-31 09:41:09 +08:00
|
|
|
|
- **定义**: 使用@Transactional注解来声明式地管理事务,简化了事务控制代码。
|
|
|
|
|
- **示例**:
|
|
|
|
|
```java
|
|
|
|
|
@Service
|
|
|
|
|
@Transactional
|
|
|
|
|
public class UserService {
|
|
|
|
|
private final UserRepository userRepository;
|
|
|
|
|
|
|
|
|
|
@Autowired
|
|
|
|
|
public UserService(UserRepository userRepository) {
|
|
|
|
|
this.userRepository = userRepository;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void createUser(User user) {
|
|
|
|
|
userRepository.save(user);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
|
|
|
|
|
2024-11-05 11:00:08 +08:00
|
|
|
|
#### 6.5.4 使用编程式事务管理
|
2024-10-31 09:41:09 +08:00
|
|
|
|
- **定义**: 通过编程的方式显式地管理事务,适用于需要更细粒度控制事务的情况。
|
|
|
|
|
- **示例**:
|
|
|
|
|
```java
|
|
|
|
|
@Service
|
|
|
|
|
public class UserService {
|
|
|
|
|
private final UserRepository userRepository;
|
|
|
|
|
private final PlatformTransactionManager transactionManager;
|
|
|
|
|
|
|
|
|
|
@Autowired
|
|
|
|
|
public UserService(UserRepository userRepository, PlatformTransactionManager transactionManager) {
|
|
|
|
|
this.userRepository = userRepository;
|
|
|
|
|
this.transactionManager = transactionManager;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
public void createUser(User user) {
|
|
|
|
|
TransactionDefinition def = new DefaultTransactionDefinition();
|
|
|
|
|
TransactionStatus status = transactionManager.getTransaction(def);
|
|
|
|
|
try {
|
|
|
|
|
userRepository.save(user);
|
|
|
|
|
transactionManager.commit(status);
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
transactionManager.rollback(status);
|
|
|
|
|
throw e;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
```
|
2024-08-07 03:01:46 +08:00
|
|
|
|
|