[数据库] 数据库系统原理 · 数据库应用开发 · 自学总结

455 0
Honkers 2026-5-22 15:05:31 来自手机 | 显示全部楼层 |阅读模式

 

本章核心:应用程序怎么连数据库?体系结构怎么选?用什么技术访问数据库?ORM 框架解决了什么问题?


一、数据库系统的体系结构

1.1 是什么?

数据库系统的体系结构 = 应用程序与数据库之间的组织方式和交互模式,决定了系统如何分层、如何部署、如何扩展。

下辖知识点

知识点是什么
主机-终端结构(集中式)所有计算和数据库都在一台大型机上,终端只是显示器和键盘
客户机/服务器结构(C/S)客户端负责界面和部分逻辑,服务器负责数据库,通过网络交互
浏览器/服务器结构(B/S)浏览器做展示,Web 服务器做业务逻辑,数据库服务器存数据,三层架构
两层 C/S客户端直接连数据库(胖客户端)
三层 C/S客户端 → 应用服务器 → 数据库服务器(瘦客户端)
多层架构 / N-tier表现层 → 业务逻辑层 → 数据访问层 → 数据库层,每层可独立部署
分布式数据库数据物理分布在多个节点上,逻辑上是一个整体
并行数据库利用多 CPU/多磁盘并行处理查询
云数据库架构数据库部署在云端,支持弹性伸缩
微服务架构每个服务有自己的数据库,服务间通过 API 通信
CQRS / 读写分离读操作和写操作分离到不同数据库实例
分库分表 / Sharding数据水平拆分到多个数据库实例

三种经典架构对比

架构结构优点缺点典型应用
主机-终端大型机 + 哑终端集中管理、安全昂贵、扩展难、单点故障银行核心系统(历史)
C/S 两层客户端 ↔ 数据库交互响应快、功能丰富客户端维护麻烦、升级困难财务软件、ERP 客户端
B/S 三层浏览器 → Web 服务器 → 数据库零客户端维护、跨平台、易扩展交互体验受限、网络依赖电商网站、管理系统
N-tier / 微服务浏览器 → 网关 → 服务集群 → 数据库集群高可用、弹性伸缩、技术异构复杂度爆炸、分布式事务难互联网大厂、电商平台

三层架构详解

  1.  ┌─────────────────────────────────────────┐
  2.  │           表现层(Presentation)         │ ← 浏览器/APP,负责界面展示
  3.  │         HTML/CSS/JS / Android / iOS       │
  4.  ├─────────────────────────────────────────┤
  5.  │           业务逻辑层(Business Logic)   │ ← 后端服务,处理业务规则
  6.  │     Java / Python / Node.js / Go         │
  7.  ├─────────────────────────────────────────┤
  8.  │           数据访问层(Data Access)       │ ← 数据库交互,CRUD + 事务
  9.  │         JDBC / ADO.NET / ORM / SQL       │
  10.  ├─────────────────────────────────────────┤
  11.  │           数据库层(Database)           │ ← MySQL / PostgreSQL / Oracle
  12.  │         数据存储 + 查询处理 + 事务管理     │
  13.  └─────────────────────────────────────────┘
复制代码

分布式数据库架构

  1.  ┌─────────────┐   ┌─────────────┐   ┌─────────────┐
  2.  │   应用服务器 │───→│   数据库中间件 │←──│   应用服务器 │
  3.  └─────────────┘   └──────┬──────┘   └─────────────┘
  4.                           │
  5.             ┌──────────────┼──────────────┐
  6.             ↓             ↓             ↓
  7.       ┌─────────┐   ┌─────────┐   ┌─────────┐
  8.       │ 分片1   │   │ 分片2   │   │ 分片3   │
  9.       │ 节点A   │   │ 节点B   │   │ 节点C   │
  10.       └─────────┘   └─────────┘   └─────────┘
复制代码
  • 分片(Sharding):按某种规则(如用户ID哈希、地理区域)把数据分布到不同节点。

  • 副本(Replication):每个分片有主副本和从副本,保证高可用和读扩展。

  • 协调节点:负责路由查询到正确的分片,汇总结果。


1.2 为什么要有体系结构?

没有清晰的体系结构,系统就是 spaghetti:

没有体系结构的问题体系结构解决
界面代码和 SQL 混在一起分层后各层只关心自己的事
换个数据库要重写整个程序数据访问层抽象隔离了数据库差异
用户量涨了系统直接崩三层可独立扩展(加 Web 服务器、加数据库从库)
前端团队和后端团队互相踩脚接口层定义好后,两边独立开发
数据库挂了整个业务停摆分布式架构 + 读写分离 + 副本保证高可用

核心价值

  1. 关注点分离 —— 每层只解决一类问题,代码清晰。

  2. 可扩展性 —— 哪层压力大就扩展哪层。

  3. 可维护性 —— 改界面不影响数据库,改数据库不影响界面。

  4. 容错性 —— 分布式避免单点故障。


1.3 怎么用?

选型决策

场景推荐架构
内部管理系统、用户少B/S 三层
需要复杂交互、实时响应(游戏、交易终端)C/S 或 B/S + WebSocket
互联网 ToC 应用、海量用户N-tier + 微服务 + 分库分表
金融核心系统、强一致性要求集中式 / 分布式事务 + 两地三中心
全球化部署分布式数据库 + 多活架构

分层开发实践

  1.  数据访问层(DAO/Repository)设计:
  2.  ┌─────────────────────────────────────────┐
  3.  │ interface StudentRepository           │
  4.  │   + findById(id): Student             │
  5.  │   + findByDept(dept): List<Student>   │
  6.  │   + save(student): void               │
  7.  │   + delete(id): void                   │
  8.  ├─────────────────────────────────────────┤
  9.  │ class JdbcStudentRepository           │
  10.  │   - 用 JDBC 实现接口方法               │
  11.  │ class MyBatisStudentRepository       │
  12.  │   - 用 MyBatis 实现                   │
  13.  │ class JpaStudentRepository           │
  14.  │   - 用 JPA/Hibernate 实现             │
  15.  └─────────────────────────────────────────┘
  16.  ​
  17.  好处:今天用 MySQL,明天换 PostgreSQL,只需换实现类,上层业务代码不动。
复制代码

读写分离实践

  1.  写操作 → 主库(Master)
  2.  读操作 → 从库(Slave1, Slave2, ...)
  3.  ​
  4.  主库异步同步到从库(有短暂延迟,通常毫秒级)
  5.  ​
  6.  应用层实现:
  7.   - 框架自动路由(如 ShardingSphere)
  8.   - 或手动在 DAO 层指定数据源
复制代码

二、数据库访问技术

2.1 是什么?

数据库访问技术 = 应用程序连接和操作数据库的编程接口和标准,是应用层与数据库层之间的"桥梁"。

下辖知识点

知识点是什么
ODBC(Open Database Connectivity)微软推出的数据库访问标准 API,C 语言接口,跨数据库
JDBC(Java Database Connectivity)Java 版数据库访问标准 API
ADO.NET.NET 平台的数据访问技术
数据库驱动(Driver)连接特定数据库的适配器(如 MySQL Connector/J)
数据源(DataSource)/ 连接池预先创建并管理的数据库连接集合,复用连接减少开销
SQL 注入防护预编译语句(PreparedStatement)防止拼接攻击
事务 API编程控制事务的提交和回滚
批量操作(Batch)一次性发送多条 SQL,减少网络往返
存储过程调用通过 API 调用数据库中的存储过程
结果集处理遍历查询返回的数据行
连接字符串包含服务器地址、端口、数据库名、用户名、密码的配置串

JDBC 核心接口

接口/类作用
DriverManager管理数据库驱动,获取连接
Connection代表一个数据库连接
Statement执行静态 SQL
PreparedStatement执行预编译 SQL(防注入,性能更好)
CallableStatement调用存储过程
ResultSet封装查询结果,支持逐行遍历
ResultSetMetaData获取结果集的列信息(列名、类型等)

2.2 为什么要有数据库访问技术?

没有统一访问技术,每个数据库都要写一套代码:

没有访问技术的问题访问技术解决
Java 程序连 MySQL 要调一套 API,连 Oracle 又调另一套JDBC 统一接口,换数据库只需换驱动 jar 包
每次操作都新建连接,慢且耗资源连接池预先创建,用完归还复用
SQL 拼接导致注入攻击PreparedStatement 参数化,安全
程序崩溃时数据库操作只执行了一半事务 API 保证原子性
逐条插入 10000 条,网络往返 10000 次Batch 批量操作,一次发送

核心价值

  1. 标准化 —— 一套 API 通吃各种数据库。

  2. 效率 —— 连接池、预编译、批量操作大幅提升性能。

  3. 安全 —— 预编译语句根治 SQL 注入。

  4. 可控 —— 程序里精确控制事务边界。


2.3 怎么用?

JDBC 完整示例

  1.  // 1. 加载驱动(现代 JDBC 可省略)
  2.  Class.forName("com.mysql.cj.jdbc.Driver");
  3.  ​
  4.  // 2. 获取连接(实际项目用连接池,不用 DriverManager)
  5.  String url = "jdbc:mysql://localhost:3306/教学管理?useSSL=false";
  6.  Connection conn = DriverManager.getConnection(url, "user", "password");
  7.  ​
  8.  // 3. 开启事务
  9.  conn.setAutoCommit(false);
  10.  ​
  11.  try {
  12.      // 4. 预编译语句(防注入 + 性能优化)
  13.      String sql = "INSERT INTO 学生 (学号, 姓名, 系号) VALUES (?, ?, ?)";
  14.      PreparedStatement stmt = conn.prepareStatement(sql);
  15.      
  16.      // 5. 批量插入
  17.      for (Student s : studentList) {
  18.          stmt.setString(1, s.getId());
  19.          stmt.setString(2, s.getName());
  20.          stmt.setString(3, s.getDept());
  21.          stmt.addBatch();          // 加入批量
  22.     }
  23.      stmt.executeBatch();          // 一次性执行
  24.      
  25.      // 6. 查询
  26.      String query = "SELECT * FROM 学生 WHERE 系号 = ?";
  27.      PreparedStatement qstmt = conn.prepareStatement(query);
  28.      qstmt.setString(1, "CS");
  29.      ResultSet rs = qstmt.executeQuery();
  30.      
  31.      while (rs.next()) {
  32.          System.out.println(rs.getString("姓名"));
  33.     }
  34.      
  35.      // 7. 提交事务
  36.      conn.commit();
  37.  } catch (Exception e) {
  38.      conn.rollback();              // 出错回滚
  39.  } finally {
  40.      conn.close();                 // 归还连接池(或关闭)
  41.  }
复制代码

连接池配置(生产环境必备)

  1.  // HikariCP(Java 最快的连接池)
  2.  HikariConfig config = new HikariConfig();
  3.  config.setJdbcUrl("jdbc:mysql://localhost:3306/教学管理");
  4.  config.setUsername("user");
  5.  config.setPassword("password");
  6.  config.setMaximumPoolSize(20);        // 最大连接数
  7.  config.setMinimumIdle(5);               // 最小空闲连接
  8.  config.setConnectionTimeout(30000);     // 获取连接超时
  9.  config.setIdleTimeout(600000);          // 空闲连接超时
  10.  config.setMaxLifetime(1800000);         // 连接最大生命周期
  11.  ​
  12.  HikariDataSource dataSource = new HikariDataSource(config);
  13.  ​
  14.  // 使用
  15.  Connection conn = dataSource.getConnection();
  16.  // ... 操作 ...
  17.  conn.close();  // 归还连接池,不是真关闭
复制代码

连接池为什么快?

  • 省去 TCP 握手、数据库认证的时间(连接已建好)。

  • 控制并发连接数,避免压垮数据库。

  • 连接复用,减少资源浪费。

ODBC / ADO.NET 简要

  1.  // C# ADO.NET 示例
  2.  string connStr = "Server=localhost;Database=教学管理;Trusted_Connection=true;";
  3.  using (SqlConnection conn = new SqlConnection(connStr))
  4.  {
  5.      conn.Open();
  6.      string sql = "SELECT * FROM 学生 WHERE 系号 = @dept";
  7.      using (SqlCommand cmd = new SqlCommand(sql, conn))
  8.     {
  9.          cmd.Parameters.AddWithValue("@dept", "CS");  // 参数化
  10.          using (SqlDataReader reader = cmd.ExecuteReader())
  11.         {
  12.              while (reader.Read())
  13.             {
  14.                  Console.WriteLine(reader["姓名"]);
  15.             }
  16.         }
  17.     }
  18.  }
复制代码

三、对象-关系映射框架(ORM)

3.1 是什么?

ORM(Object-Relational Mapping)= 把数据库的表和应用程序的对象自动映射起来的技术,让程序员用面向对象的方式操作数据库,不用手写 SQL。

下辖知识点

知识点是什么
对象-表映射一个类对应一张表,一个对象对应一行记录
属性-列映射类的字段对应表的列
关联映射对象间的引用关系对应表间的外键关系(一对一/一对多/多对多)
继承映射类的继承层次映射到数据库的三种策略(单表/类表/具体表)
CRUD 自动生成增删改查不用写 SQL,调对象方法即可
懒加载(Lazy Loading)关联对象在真正访问时才从数据库加载
急加载(Eager Loading)查询主对象时顺便把关联对象一起查出来
一级缓存 / 会话缓存同一个事务内,重复查同一对象直接返回内存中的实例
二级缓存 / 全局缓存跨会话的对象缓存,减少数据库访问
脏检查(Dirty Checking)自动检测对象属性变化,事务提交时自动 UPDATE
事务管理声明式事务(@Transactional),自动 begin/commit/rollback
查询语言JPQL(JPA)、HQL(Hibernate)、LINQ(.NET)等面向对象的查询语言
主要框架Hibernate(Java)、MyBatis(半 ORM)、Entity Framework(.NET)、Django ORM(Python)、SQLAlchemy(Python)

ORM 映射关系速查

对象关系数据库表示ORM 配置
一对一两张表,一方含外键指向另一方@OneToOne + @JoinColumn
一对多一张"多"方表含外键指向"一"方@OneToMany + @ManyToOne
多对多中间关联表,存两方主码@ManyToMany + @JoinTable
继承单表/类表/具体表三种策略@Inheritance
组件/嵌入类的部分属性存到同一张表@Embeddable + @Embedded

3.2 为什么要有 ORM?

直接用 JDBC 的困境:

问题JDBC 现状ORM 解决
大量重复 SQL(CRUD 占 80%)每个表都要手写 INSERT/SELECT/UPDATE/DELETE自动生成
结果集遍历转对象,代码冗长while(rs.next()) { s.setName(rs.getString(...)) }自动映射
表改了字段,程序里 SQL 全改全文搜索替换,漏改就崩改一处映射配置,代码不动
对象关系复杂时 SQL 极难写查一个订单要 JOIN 5 张表order.getItems() 自动查
事务管理代码重复每个方法都写 try-begin-commit-catch-rollback@Transactional 声明式
数据库方言差异MySQL 分页用 LIMIT,SQL Server 用 TOPORM 自动翻译方言

核心价值

  1. 开发效率 —— 不写 SQL,专注业务逻辑。

  2. 可维护性 —— 数据库结构变了,改映射配置即可。

  3. 数据库无关 —— 从 MySQL 切 Oracle,理论上只改配置。

  4. 面向对象 —— 用 student.getDepartment().getName() 而不是手写 JOIN。

ORM 的代价

  • 性能开销 —— 自动生成 SQL 不一定最优,复杂查询可能慢。

  • 学习成本 —— 要理解缓存、懒加载、N+1 问题等概念。

  • 复杂查询受限 —— 报表、统计分析等复杂 SQL 用 ORM 反而麻烦。


3.3 怎么用?

JPA / Hibernate 完整示例

  1.  // === 1. 定义实体 ===
  2.  @Entity
  3.  @Table(name = "学生")
  4.  public class Student {
  5.      @Id
  6.      @Column(name = "学号")
  7.      private String id;
  8.      
  9.      @Column(name = "姓名", nullable = false)
  10.      private String name;
  11.      
  12.      @ManyToOne          // 多对一:多个学生属于一个系
  13.      @JoinColumn(name = "系号")
  14.      private Department department;
  15.      
  16.      @OneToMany(mappedBy = "student", fetch = FetchType.LAZY)  // 懒加载
  17.      private List<Enrollment> enrollments;
  18.      
  19.      // getters / setters
  20.  }
  21.  ​
  22.  @Entity
  23.  @Table(name = "系")
  24.  public class Department {
  25.      @Id
  26.      @Column(name = "系号")
  27.      private String id;
  28.      
  29.      @Column(name = "系名")
  30.      private String name;
  31.      
  32.      @OneToMany(mappedBy = "department")
  33.      private List<Student> students;
  34.  }
  35.  ​
  36.  // === 2. 数据访问层(Repository)===
  37.  public interface StudentRepository extends JpaRepository<Student, String> {
  38.      // 方法名解析自动生成查询
  39.      List<Student> findByDepartmentName(String deptName);
  40.      
  41.      // JPQL 自定义查询
  42.      @Query("SELECT s FROM Student s WHERE s.department.name = :dept")
  43.      List<Student> findByDept(@Param("dept") String deptName);
  44.  }
  45.  ​
  46.  // === 3. 业务层(声明式事务)===
  47.  @Service
  48.  public class StudentService {
  49.      @Autowired
  50.      private StudentRepository repo;
  51.      
  52.      @Transactional(readOnly = true)
  53.      public List<Student> getStudentsByDept(String dept) {
  54.          return repo.findByDepartmentName(dept);
  55.     }
  56.      
  57.      @Transactional
  58.      public void transferStudent(String studentId, String newDeptId) {
  59.          Student s = repo.findById(studentId).orElseThrow();
  60.          s.setDepartment(new Department(newDeptId));
  61.          // 不用手动 save,脏检查自动 UPDATE
  62.     }
  63.  }
复制代码

MyBatis(半 ORM)示例

MyBatis 不全自动映射,开发者写 SQL,框架负责参数绑定和结果映射。适合 SQL 复杂、需要精细控制的场景。

  1. // 实体(纯 POJO,无注解)
  2. public class Student {
  3. private String id;
  4. private String name;
  5. private Department department;
  6. // getters / setters
  7. }
  8. // Mapper 接口 + XML
  9. public interface StudentMapper {
  10. @Select("SELECT * FROM 学生 WHERE 系号 = #{deptId}")
  11. @Results({
  12. @Result(property = "id", column = "学号"),
  13. @Result(property = "name", column = "姓名"),
  14. @Result(property = "department",
  15. column = "系号",
  16. one = @One(select = "getDepartmentById"))
  17. })
  18. List<Student> findByDept(String deptId);
  19. }
复制代码

ORM 避坑指南

现象解决
N+1 问题查 100 个学生,触发 100 次额外的系查询急加载 fetch = EAGER 或 JOIN FETCH
懒加载异常会话关闭后访问懒加载属性报错在会话内访问,或改用急加载
大事务事务里加载太多对象,内存溢出分页处理,或改用手动控制
缓存不一致二级缓存没更新,读到旧数据合理设置缓存过期,或禁用缓存
批量插入慢逐条 INSERT,每条都刷盘配置批量大小,或改用 JDBC 批量
复杂查询性能差ORM 生成的 SQL 太啰嗦手写原生 SQL 或存储过程

N+1 问题详解

  1. 场景:查询所有学生,并显示他们的系名
  2. ORM 做法:
  3. 1. SELECT * FROM 学生 → 查出 100 条
  4. 2. 遍历每个学生,调用 getDepartment()
  5. → SELECT * FROM 系 WHERE 系号 = ? 执行 100 次!
  6. 解决:
  7. 1. JOIN FETCH:SELECT s FROM Student s JOIN FETCH s.department
  8. → 一条 SQL 把学生和系都查回来
  9. 2. 急加载配置:@ManyToOne(fetch = EAGER)
复制代码

四、三种访问方式对比

维度纯 JDBCMyBatis(半 ORM)Hibernate/JPA(全 ORM)
SQL 控制权完全手写手写 SQL,框架辅助映射自动生成,可自定义
开发效率低(大量样板代码)高(CRUD 自动生成)
灵活性最高中(复杂查询受限)
性能调优完全可控可控需理解内部机制
数据库可移植高(方言自动适配)
学习成本高(缓存、懒加载等)
适用场景性能极致、简单项目SQL 复杂、需精细控制业务复杂、快速开发

五、知识脉络图

  1. 数据库应用开发
  2. ├── 数据库系统的体系结构
  3. │ ├── 是什么:应用与数据库的组织方式和交互模式
  4. │ ├── 为什么:关注点分离、可扩展、可维护、高可用
  5. │ └── 怎么用:
  6. │ ├── 集中式 / C/S / B-S / N-tier / 微服务 选型
  7. │ ├── 三层架构:表现层 → 业务层 → 数据访问层 → 数据库层
  8. │ ├── 分布式:分片 + 副本 + 协调节点
  9. │ └── 读写分离:写主库,读从库
  10. ├── 数据库访问技术
  11. │ ├── 是什么:应用程序连接和操作数据库的编程接口
  12. │ ├── 为什么:标准化访问、连接复用、防注入、事务控制
  13. │ └── 怎么用:
  14. │ ├── JDBC / ODBC / ADO.NET 标准 API
  15. │ ├── 连接池(HikariCP / Druid)配置与使用
  16. │ ├── PreparedStatement 参数化防注入
  17. │ ├── 批量操作(Batch)提升写入性能
  18. │ └── 事务 API 控制原子性
  19. └── 对象-关系映射框架(ORM)
  20. ├── 是什么:对象与数据库表自动映射的技术
  21. ├── 为什么:消除样板代码、提升效率、面向对象操作数据库
  22. └── 怎么用:
  23. ├── Hibernate / JPA 全自动映射
  24. ├── MyBatis 半自动映射(SQL 手写 + 结果自动映射)
  25. ├── 关联映射:@OneToOne / @OneToMany / @ManyToMany
  26. ├── 加载策略:懒加载 vs 急加载
  27. ├── 缓存:一级缓存(会话)vs 二级缓存(全局)
  28. ├── 脏检查 + 声明式事务 @Transactional
  29. └── 避坑:N+1、懒加载异常、大事务、复杂查询性能
复制代码

六、一句话记忆

概念一句话
C/S客户端胖,装软件,直连数据库
B/S浏览器瘦,零安装,通过 Web 服务器连数据库
三层架构界面、逻辑、数据各管一层,互不干扰
分库分表数据太多一台撑不住,拆成多台一起扛
读写分离写交给老大(主库),读交给小弟(从库)
JDBCJava 连数据库的标准接口,一套 API 走天下
连接池连接预先建好,用完归还,不用每次都握手
PreparedStatementSQL 先编译好,参数往里填,防注入神器
ORM数据库表当成 Java 类来操作,不用写 SQL
懒加载用到的时候才去数据库查,省资源
急加载一次性全查回来,省得跑第二趟
N+1查了 1 个列表,触发 N 次额外查询,性能杀手
脏检查对象属性变了,提交时 ORM 自动帮你 UPDATE
声明式事务方法头贴个注解,事务自动管开管关
MyBatisSQL 自己写,映射交给它,灵活又省事
Hibernate全自动,CRUD 零 SQL,复杂场景需调优

总结基于《数据库系统概论》数据库应用开发相关章节知识体系整理

 

您需要登录后才可以回帖 登录 | 立即注册

本版积分规则

中国红客联盟公众号

联系站长QQ:5520533

admin@chnhonker.com
Copyright © 2001-2026 Discuz Team. Powered by Discuz! X3.5 ( 粤ICP备13060014号 )|天天打卡 本站已运行