java-web/docs/chapter06.md
2024-11-11 08:10:22 +08:00

384 lines
15 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

## 6. 数据库访问
参考[jdbc(ppt)](./resources/JDBC.pptx)
### 6.1 JDBC(了解)
#### 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();
}
}
```
### 6.2 数据库连接池(了解)
#### 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();
```
### 6.3 SQL注入的预防措施(了解)
- **定义**: 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();
```
### 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和存储过程。
#### 6.4.3 JPA (掌握)
> 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还支持自定义查询方法这可以通过在接口中添加自定义方法来实现。
**通过方法名来定义查询**
参考[Spring Data JPA方法名命名规则](https://www.jianshu.com/p/1d6f27f675bb)
**使用@Query注解**
@Query具有灵活性和可扩展性,例如:
```java
public interface UserRepository extends JpaRepository<User, Long> {
@Query("SELECT u FROM User u WHERE u.name = ?1 AND u.age = ?2")
List<User> findByNameAndAge(String name, Integer age);
@Query(value="SELECT * FROM User u WHERE u.name Like %:name% AND u.age > :age" , nativeQuery = true)
List<User> findUsers(@Param("name") String username, @Param("age") Integer age);
}
```
##### 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连接URLpaopao是数据库名
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输出
```
### 6.5 事务管理(掌握)
#### 6.5.1 事务管理的基本概念
在计算机科学中特别是在数据库管理和分布式计算领域事务Transaction是一种基本的操作单位它确保了一系列操作要么全部成功要么全部失败。事务的概念是数据库管理系统DBMS中用来保证数据完整性和一致性的重要机制之一。
事务具有以下几个核心特点通常称为ACID属性
- **原子性 (Atomicity)**: 事务中的所有操作要么全部完成,要么一个也不完成。
- **一致性 (Consistency)**: 事务的执行不会破坏数据的一致性。
- **隔离性 (Isolation)**: 事务之间是隔离的,不会相互影响。
- **持久性 (Durability)**: 一旦事务提交,它的效果是持久的。
#### 6.5.2 Spring事务管理器
- **定义**: Spring提供了多种事务管理器如`DataSourceTransactionManager`。
- **示例**:
```java
@Configuration
@EnableTransactionManagement
public class AppConfig {
@Bean
public PlatformTransactionManager transactionManager(DataSource dataSource) {
return new DataSourceTransactionManager(dataSource);
}
}
```
#### 6.5.3 使用@Transactional进行声明式事务管理
- **定义**: 使用@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);
}
}
```
#### 6.5.4 使用编程式事务管理
- **定义**: 通过编程的方式显式地管理事务,适用于需要更细粒度控制事务的情况。
- **示例**:
```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;
}
}
}
```