Skip to main content

数据库与ORM技术总结

数据库是后端系统的核心组件,负责数据的存储、管理和检索。ORM(对象关系映射)框架则简化了应用代码与数据库的交互,提高了开发效率。本文档全面总结了数据库设计和ORM框架的核心概念、最佳实践和面试重点。

核心要点

数据库设计 = 规范化 + 性能优化 + 数据一致性 + 可扩展性 ORM框架 = 简化开发 + 类型安全 + 数据库无关 + 性能平衡

1. 数据库设计体系

1.1 关系型数据库设计

关系型数据库是最传统和广泛使用的数据库类型,基于关系模型组织数据。

设计层次

数据库设计层次
sql
1-- 1. 概念设计层:实体关系模型(ER图)
2-- 识别实体、属性和关系
3-- 绘制ER图,确定实体间的联系
4
5-- 2. 逻辑设计层:关系模式设计
6-- 将ER图转换为关系模式
7-- 应用规范化理论优化设计
8
9-- 3. 物理设计层:存储和索引设计
10-- 选择存储引擎和文件组织
11-- 设计索引策略和分区方案

规范化理论

规范化理论
sql
1-- 第一范式(1NF):原子性
2-- 每个字段都是不可分割的原子值
3CREATE TABLE users (
4 id INT PRIMARY KEY,
5 name VARCHAR(100), -- 原子值
6 email VARCHAR(100) -- 原子值
7);
8
9-- 第二范式(2NF):消除部分依赖
10-- 非主键字段完全依赖于主键
11CREATE TABLE orders (
12 id INT PRIMARY KEY,
13 user_id INT,
14 order_date DATE,
15 total_amount DECIMAL(10,2)
16);
17
18CREATE TABLE order_items (
19 id INT PRIMARY KEY,
20 order_id INT,
21 product_id INT,
22 quantity INT,
23 price DECIMAL(10,2),
24 FOREIGN KEY (order_id) REFERENCES orders(id)
25);
26
27-- 第三范式(3NF):消除传递依赖
28-- 非主键字段不依赖于其他非主键字段
29CREATE TABLE users (
30 id INT PRIMARY KEY,
31 name VARCHAR(100),
32 department_id INT,
33 FOREIGN KEY (department_id) REFERENCES departments(id)
34);
35
36CREATE TABLE departments (
37 id INT PRIMARY KEY,
38 name VARCHAR(100),
39 location VARCHAR(100)
40);

索引设计原则

索引设计原则
sql
1-- 1. 主键索引(自动创建)
2CREATE TABLE users (
3 id INT PRIMARY KEY AUTO_INCREMENT, -- 自动创建主键索引
4 username VARCHAR(50) UNIQUE,
5 email VARCHAR(100)
6);
7
8-- 2. 唯一索引(业务唯一性约束)
9CREATE UNIQUE INDEX idx_users_email ON users(email);
10CREATE UNIQUE INDEX idx_users_username ON users(username);
11
12-- 3. 普通索引(查询优化)
13CREATE INDEX idx_users_department_id ON users(department_id);
14CREATE INDEX idx_users_created_at ON users(created_at);
15
16-- 4. 复合索引(多字段查询优化)
17CREATE INDEX idx_users_department_status ON users(department_id, status);
18CREATE INDEX idx_users_name_email ON users(last_name, first_name, email);
19
20-- 5. 部分索引(条件索引)
21CREATE INDEX idx_active_users ON users(email) WHERE status = 'active';

1.2 非关系型数据库设计

NoSQL数据库提供了更灵活的数据模型和更好的可扩展性。

数据模型分类

NoSQL数据模型
javascript
1// 1. 键值存储(Key-Value)
2// Redis、Memcached、DynamoDB
3{
4 "user:1001": "John Doe",
5 "user:1002": "Jane Smith",
6 "session:abc123": "{userId: 1001, loginTime: '2023-08-07'}"
7}
8
9// 2. 文档存储(Document)
10// MongoDB、CouchDB、Elasticsearch
11{
12 "_id": "1001",
13 "name": "John Doe",
14 "email": "john@example.com",
15 "profile": {
16 "age": 30,
17 "address": {
18 "street": "123 Main St",
19 "city": "New York"
20 }
21 },
22 "orders": [
23 {"orderId": "ORD001", "amount": 150.00},
24 {"orderId": "ORD002", "amount": 200.00}
25 ]
26}
27
28// 3. 列族存储(Column Family)
29// Cassandra、HBase、ScyllaDB
30// 按列族组织数据,支持宽行和窄行
31
32// 4. 图数据库(Graph)
33// Neo4j、ArangoDB、Amazon Neptune
34// 以节点和边表示实体和关系

设计原则

NoSQL设计原则
javascript
1// 1. 数据模型设计
2// 根据查询模式设计数据模型
3// 避免复杂的关联查询
4
5// 2. 分片策略
6// 水平分片:按数据范围或哈希值分片
7// 垂直分片:按功能模块分片
8
9// 3. 一致性模型
10// 强一致性:ACID事务
11// 最终一致性:BASE理论
12// 因果一致性:部分顺序保证
13
14// 4. 可用性设计
15// 多副本:主从复制、多主复制
16// 故障转移:自动检测和切换
17// 负载均衡:读写分离、分片路由

1.3 关系型与NoSQL数据库特性对比

在选择数据库时,需要根据应用场景和需求考虑不同类型数据库的特性。

数据库类型对比

特性MySQLPostgreSQLMongoDBRedis
数据模型关系表关系表+JSON文档(BSON)键值+数据结构
查询语言SQLSQL+JSON类JSON查询命令+Lua
事务支持完整ACID完整ACID4.0+支持多文档事务有限支持(Redis 7.0+)
一致性强一致性强一致性可配置一致性单机强一致/集群弱一致
可扩展性主从复制主从复制+逻辑复制分片+副本集集群模式
数据类型标准类型丰富类型+扩展灵活模式+BSON类型多种数据结构
索引支持B+树B+树+GIN/GiSTB树+多种索引有限索引
特色功能简单易用PostGIS/JSON/全文搜索文档灵活性/地理查询高性能/数据结构
适用场景通用应用复杂查询/地理信息大数据/灵活模式缓存/实时应用

PostgreSQL核心特性

PostgreSQL是一个功能强大的开源对象关系型数据库系统,提供了众多企业级特性:

PostgreSQL特性
sql
1-- 1. JSON/JSONB支持
2CREATE TABLE products (
3 id SERIAL PRIMARY KEY,
4 name TEXT NOT NULL,
5 attributes JSONB
6);
7
8-- 查询JSONB数据
9SELECT * FROM products
10WHERE attributes @> '{"color": "red", "size": "large"}';
11
12-- 2. 高级数据类型
13CREATE TABLE location_data (
14 id SERIAL PRIMARY KEY,
15 name TEXT,
16 location GEOGRAPHY(POINT), -- 地理类型
17 time_range TSRANGE, -- 时间范围
18 tags TEXT[] -- 数组类型
19);
20
21-- 3. 全文搜索
22CREATE TABLE articles (
23 id SERIAL PRIMARY KEY,
24 title TEXT,
25 body TEXT,
26 ts_vector TSVECTOR -- 全文搜索向量
27);
28
29CREATE INDEX idx_articles_fts ON articles USING GIN(ts_vector);
30
31SELECT title FROM articles
32WHERE ts_vector @@ to_tsquery('english', 'database & performance');
33
34-- 4. 表继承
35CREATE TABLE devices (
36 id SERIAL PRIMARY KEY,
37 name TEXT NOT NULL
38);
39
40CREATE TABLE phones (
41 carrier TEXT,
42 model TEXT
43) INHERITS (devices);
44
45-- 5. 物化视图
46CREATE MATERIALIZED VIEW sales_summary AS
47SELECT
48 product_id,
49 SUM(quantity) as total_sold,
50 AVG(price) as avg_price
51FROM sales
52GROUP BY product_id;
53
54-- 6. 扩展系统
55CREATE EXTENSION postgis; -- 地理信息系统
56CREATE EXTENSION pg_stat_statements; -- SQL统计

MongoDB核心特性

MongoDB是领先的文档数据库,具有高可扩展性和灵活的数据模型:

MongoDB特性
javascript
1// 1. 文档数据模型
2db.users.insertOne({
3 name: "John Doe",
4 email: "john@example.com",
5 profile: {
6 age: 30,
7 interests: ["coding", "music", "hiking"],
8 address: {
9 city: "New York",
10 zip: "10001"
11 }
12 },
13 created_at: new Date()
14});
15
16// 2. 灵活模式
17// 同一集合中的文档可以有不同的字段
18db.products.insertMany([
19 { name: "Phone", price: 699, specs: { ram: "8GB", storage: "128GB" } },
20 { name: "Laptop", price: 1299, specs: { cpu: "i7", ram: "16GB" }, colors: ["silver", "black"] }
21]);
22
23// 3. 强大的查询语言
24db.orders.find({
25 status: "completed",
26 "customer.country": "US",
27 total: { $gt: 100 },
28 items: { $elemMatch: { product_id: "xyz123", quantity: { $gte: 2 } } }
29}).sort({ created_at: -1 }).limit(10);
30
31// 4. 聚合框架
32db.sales.aggregate([
33 { $match: { date: { $gte: ISODate("2023-01-01") } } },
34 { $group: {
35 _id: "$product_id",
36 totalSales: { $sum: "$amount" },
37 count: { $sum: 1 }
38 }},
39 { $sort: { totalSales: -1 } },
40 { $limit: 5 }
41]);
42
43// 5. 索引支持
44db.users.createIndex({ email: 1 }, { unique: true }); // 单字段索引
45db.products.createIndex({ category: 1, name: 1 }); // 复合索引
46db.stores.createIndex({ location: "2dsphere" }); // 地理空间索引
47db.articles.createIndex({ title: "text", content: "text" }); // 全文索引
48
49// 6. 分片集群
50// 水平扩展能力
51sh.shardCollection("database.collection", { shardKey: 1 });

2. ORM框架体系

2.1 JPA规范

JPA(Java Persistence API)是Java EE的持久化规范,定义了对象关系映射的标准。

核心注解

JPA核心注解
java
1// 1. 实体映射
2@Entity
3@Table(name = "users")
4public class User {
5 @Id
6 @GeneratedValue(strategy = GenerationType.IDENTITY)
7 private Long id;
8
9 @Column(name = "username", nullable = false, length = 50)
10 private String username;
11
12 @Column(name = "email", nullable = false, unique = true)
13 private String email;
14
15 @Column(name = "created_at")
16 @Temporal(TemporalType.TIMESTAMP)
17 private Date createdAt;
18}
19
20// 2. 关系映射
21@Entity
22public class User {
23 @Id
24 private Long id;
25
26 // 一对一关系
27 @OneToOne(cascade = CascadeType.ALL)
28 @JoinColumn(name = "profile_id")
29 private UserProfile profile;
30
31 // 一对多关系
32 @OneToMany(mappedBy = "user", cascade = CascadeType.ALL)
33 private List<Order> orders;
34
35 // 多对一关系
36 @ManyToOne
37 @JoinColumn(name = "department_id")
38 private Department department;
39
40 // 多对多关系
41 @ManyToMany
42 @JoinTable(
43 name = "user_roles",
44 joinColumns = @JoinColumn(name = "user_id"),
45 inverseJoinColumns = @JoinColumn(name = "role_id")
46 )
47 private Set<Role> roles;
48}

查询方式

JPA查询方式
java
1// 1. JPQL查询
2@Query("SELECT u FROM User u WHERE u.email = :email")
3User findByEmail(@Param("email") String email);
4
5@Query("SELECT u FROM User u WHERE u.department.name = :deptName")
6List<User> findByDepartmentName(@Param("deptName") String deptName);
7
8// 2. 原生SQL查询
9@Query(value = "SELECT * FROM users WHERE created_at >= :startDate", nativeQuery = true)
10List<User> findUsersCreatedAfter(@Param("startDate") Date startDate);
11
12// 3. 命名查询
13@NamedQuery(name = "User.findByStatus", query = "SELECT u FROM User u WHERE u.status = :status")
14List<User> findByStatus(@Param("status") String status);
15
16// 4. Criteria API
17public List<User> findUsersByCriteria(String username, String email) {
18 CriteriaBuilder cb = entityManager.getCriteriaBuilder();
19 CriteriaQuery<User> query = cb.createQuery(User.class);
20 Root<User> user = query.from(User.class);
21
22 List<Predicate> predicates = new ArrayList<>();
23 if (username != null) {
24 predicates.add(cb.like(user.get("username"), "%" + username + "%"));
25 }
26 if (email != null) {
27 predicates.add(cb.equal(user.get("email"), email));
28 }
29
30 query.where(predicates.toArray(new Predicate[0]));
31 return entityManager.createQuery(query).getResultList();
32}

2.2 主流ORM框架对比

框架特性对比

特性HibernateMyBatisJPASpring Data JPASpring Data MongoDB
类型全功能ORM半自动ORM规范数据访问抽象MongoDB数据访问
SQL控制自动生成手动编写自动生成自动生成MongoDB查询
学习成本
性能中等中等中等
灵活性中等中等中等
适用场景复杂业务复杂查询标准CRUD快速开发文档存储
数据库支持多种关系型多种关系型多种关系型多种关系型MongoDB
缓存机制多级缓存一级缓存依赖实现依赖实现无内置缓存
关系映射强大完整需手动配置完整标准继承JPA文档引用
代码生成不擅长擅长不擅长不擅长不擅长

Hibernate特性

Hibernate特性
java
1// 1. 自动SQL生成
2@Entity
3public class User {
4 @Id
5 @GeneratedValue(strategy = GenerationType.IDENTITY)
6 private Long id;
7
8 private String name;
9 private String email;
10
11 // Hibernate自动生成SQL
12 // INSERT INTO users (name, email) VALUES (?, ?)
13}
14
15// 2. 缓存机制
16@Entity
17@Cache(usage = CacheConcurrencyStrategy.READ_WRITE)
18public class User {
19 // 二级缓存配置
20}
21
22// 3. 延迟加载
23@Entity
24public class User {
25 @OneToMany(fetch = FetchType.LAZY)
26 private List<Order> orders; // 延迟加载
27}
28
29// 4. 批量操作
30Session session = sessionFactory.openSession();
31session.beginTransaction();
32
33for (int i = 0; i < 1000; i++) {
34 User user = new User("User" + i, "user" + i + "@example.com");
35 session.save(user);
36
37 if (i % 50 == 0) {
38 session.flush();
39 session.clear();
40 }
41}
42
43session.getTransaction().commit();
44session.close();
45
46// 5. 对象生命周期管理
47// 瞬时态:新创建的对象,未持久化
48User newUser = new User("张三");
49
50// 持久态:通过Session保存或查询获取的对象
51User persistedUser = session.get(User.class, 1L);
52persistedUser.setName("李四"); // 自动检测和同步到数据库
53
54// 游离态:Session关闭后的对象
55session.close();
56// persistedUser现在是游离态
57
58// 6. 继承映射策略
59@Entity
60@Inheritance(strategy = InheritanceType.JOINED)
61public abstract class Payment {
62 @Id
63 private Long id;
64 private BigDecimal amount;
65}
66
67@Entity
68public class CreditCardPayment extends Payment {
69 private String cardNumber;
70}

MyBatis特性

MyBatis特性
xml
1<!-- 1. 动态SQL -->
2<select id="findByCondition" parameterType="UserQuery" resultType="User">
3 SELECT * FROM users
4 <where>
5 <if test="username != null and username != ''">
6 AND username LIKE CONCAT('%', #{username}, '%')
7 </if>
8 <if test="email != null and email != ''">
9 AND email = #{email}
10 </if>
11 <if test="status != null">
12 AND status = #{status}
13 </if>
14 </where>
15 ORDER BY created_at DESC
16</select>
17
18<!-- 2. 结果映射 -->
19<resultMap id="UserResultMap" type="User">
20 <id column="id" property="id"/>
21 <result column="username" property="username"/>
22 <result column="email" property="email"/>
23 <association property="profile" javaType="UserProfile">
24 <id column="profile_id" property="id"/>
25 <result column="real_name" property="realName"/>
26 </association>
27 <collection property="orders" ofType="Order">
28 <id column="order_id" property="id"/>
29 <result column="order_amount" property="amount"/>
30 </collection>
31</resultMap>
32
33<!-- 3. 插件机制 -->
34<plugins>
35 <plugin interceptor="com.example.interceptor.PerformanceInterceptor"/>
36 <plugin interceptor="com.github.pagehelper.PageInterceptor">
37 <property name="helperDialect" value="mysql"/>
38 </plugin>
39</plugins>

JPA规范与实现

JPA特性
java
1// 1. 标准实体定义
2@Entity
3@Table(name = "products")
4public class Product {
5 @Id
6 @GeneratedValue(strategy = GenerationType.IDENTITY)
7 private Long id;
8
9 @Column(nullable = false)
10 private String name;
11
12 @Column(precision = 10, scale = 2)
13 private BigDecimal price;
14
15 @Enumerated(EnumType.STRING)
16 private ProductStatus status;
17
18 @Temporal(TemporalType.TIMESTAMP)
19 private Date createdAt;
20
21 // 关系映射
22 @ManyToOne
23 @JoinColumn(name = "category_id")
24 private Category category;
25
26 // JPA生命周期回调
27 @PrePersist
28 protected void onCreate() {
29 createdAt = new Date();
30 }
31}
32
33// 2. JPQL查询
34EntityManager em = entityManagerFactory.createEntityManager();
35
36// 基本查询
37TypedQuery<Product> query = em.createQuery(
38 "SELECT p FROM Product p WHERE p.price > :minPrice", Product.class);
39query.setParameter("minPrice", new BigDecimal("100.00"));
40List<Product> products = query.getResultList();
41
42// 使用Criteria API构建动态查询
43CriteriaBuilder cb = em.getCriteriaBuilder();
44CriteriaQuery<Product> cq = cb.createQuery(Product.class);
45Root<Product> root = cq.from(Product.class);
46cq.select(root).where(cb.equal(root.get("status"), ProductStatus.ACTIVE));
47List<Product> activeProducts = em.createQuery(cq).getResultList();
48
49// 3. Spring Data JPA
50public interface ProductRepository extends JpaRepository<Product, Long> {
51 // 方法名自动生成查询
52 List<Product> findByPriceGreaterThanAndStatusEquals(
53 BigDecimal minPrice, ProductStatus status);
54
55 // 使用@Query定义JPQL
56 @Query("SELECT p FROM Product p WHERE p.category.name = :categoryName")
57 List<Product> findByCategoryName(@Param("categoryName") String categoryName);
58
59 // 分页查询
60 Page<Product> findAll(Pageable pageable);
61}

MongoDB数据访问

Spring Data MongoDB
java
1// 1. 文档映射
2@Document(collection = "users")
3public class User {
4 @Id
5 private String id;
6
7 private String name;
8 private String email;
9
10 @Field("created_date")
11 private Date createdAt;
12
13 @DBRef
14 private List<Order> orders;
15
16 // 嵌入式文档
17 private Address address;
18}
19
20// 2. MongoDB仓库
21public interface UserRepository extends MongoRepository<User, String> {
22 // 自动实现基本CRUD
23
24 // 基于方法名的查询定义
25 List<User> findByNameLikeAndEmailLike(String name, String email);
26
27 // 基于@Query注解的查询
28 @Query("{ 'address.city': ?0, 'age': { $gte: ?1 } }")
29 List<User> findByCityAndAgeGreaterThan(String city, int age);
30
31 // 聚合查询
32 @Aggregation("{ $match: { 'status': ?0 } }, { $group: { _id: '$department', count: { $sum: 1 } } }")
33 List<CountResult> countByStatus(String status);
34}
35
36// 3. MongoTemplate使用
37@Autowired
38private MongoTemplate mongoTemplate;
39
40public List<User> findActiveUsersByCity(String city) {
41 Query query = new Query();
42 query.addCriteria(Criteria.where("address.city").is(city)
43 .and("status").is("active"));
44 return mongoTemplate.find(query, User.class);
45}

2.3 ORM框架选型对比

在实际项目中,ORM框架的选择应考虑以下因素:

  1. 业务复杂度

    • 简单CRUD操作:Spring Data JPA
    • 复杂业务逻辑:Hibernate
    • 复杂查询和性能要求:MyBatis
  2. 团队技术栈

    • Java传统企业应用:JPA/Hibernate
    • 互联网公司:MyBatis
    • 微服务架构:Spring Data JPA
  3. 灵活性与控制力

    • 高度控制SQL:MyBatis
    • 快速开发:Spring Data JPA
    • 复杂对象图:Hibernate
  4. 性能要求

    • 极致性能:JDBC/MyBatis
    • 均衡性能:JPA/Hibernate + 缓存
    • 读写分离:自定义数据源 + ORM

3. 数据库性能优化

3.1 索引优化

索引策略

索引优化策略
sql
1-- 1. 索引选择策略
2-- 高选择性列优先
3CREATE INDEX idx_users_email ON users(email); -- 高选择性
4CREATE INDEX idx_users_gender ON users(gender); -- 低选择性
5
6-- 查询频率高的列
7CREATE INDEX idx_users_status ON users(status); -- 经常按状态查询
8
9-- 外键列
10CREATE INDEX idx_orders_user_id ON orders(user_id); -- 加速JOIN
11
12-- 排序和分组列
13CREATE INDEX idx_users_created_at ON users(created_at); -- 加速ORDER BY
14
15-- 2. 复合索引设计
16CREATE INDEX idx_users_department_status_created ON users(department_id, status, created_at);
17
18-- 可以使用索引的查询
19SELECT * FROM users WHERE department_id = 1;
20SELECT * FROM users WHERE department_id = 1 AND status = 'active';
21SELECT * FROM users WHERE department_id = 1 AND status = 'active' AND created_at > '2023-01-01';
22
23-- 不能使用索引的查询
24SELECT * FROM users WHERE status = 'active'; -- 缺少department_id
25SELECT * FROM users WHERE department_id = 1 AND created_at > '2023-01-01'; -- 缺少status
26
27-- 3. 索引优化技巧
28-- 避免在索引列上使用函数
29SELECT * FROM users WHERE YEAR(created_at) = 2023; -- 无法使用索引
30SELECT * FROM users WHERE created_at >= '2023-01-01' AND created_at < '2024-01-01'; -- 可以使用索引
31
32-- 避免使用!=或<>操作符
33SELECT * FROM users WHERE status != 'inactive'; -- 无法使用索引
34SELECT * FROM users WHERE status IN ('active', 'pending', 'suspended'); -- 可以使用索引

3.2 SQL优化

查询优化技巧

SQL优化技巧
sql
1-- 1. 避免SELECT *
2SELECT id, name, email FROM users WHERE department_id = 1; -- 推荐
3SELECT * FROM users WHERE department_id = 1; -- 不推荐
4
5-- 2. 使用LIMIT限制结果集
6SELECT * FROM orders ORDER BY created_at DESC LIMIT 10;
7
8-- 3. 优化JOIN查询
9-- 使用INNER JOIN而不是逗号连接
10SELECT u.name, o.order_number
11FROM users u INNER JOIN orders o ON u.id = o.user_id
12WHERE u.status = 'active'; -- 推荐
13
14SELECT u.name, o.order_number
15FROM users u, orders o
16WHERE u.id = o.user_id AND u.status = 'active'; -- 不推荐
17
18-- 4. 子查询优化
19-- 将子查询转换为JOIN
20SELECT u.* FROM users u
21JOIN departments d ON u.department_id = d.id
22WHERE d.name = 'IT'; -- 推荐
23
24SELECT * FROM users
25WHERE department_id IN (SELECT id FROM departments WHERE name = 'IT'); -- 不推荐
26
27-- 5. 使用EXISTS代替IN(大数据量时)
28SELECT * FROM customers c
29WHERE EXISTS (SELECT 1 FROM orders o WHERE o.customer_id = c.id); -- 推荐
30
31SELECT * FROM customers
32WHERE id IN (SELECT DISTINCT customer_id FROM orders); -- 不推荐

3.3 缓存策略

缓存模式

缓存策略
java
1// 1. Cache-Aside模式
2public User getUser(Long userId) {
3 // 先从缓存获取
4 String key = "user:" + userId;
5 User user = redisTemplate.opsForValue().get(key);
6
7 if (user != null) {
8 return user;
9 }
10
11 // 缓存未命中,从数据库获取
12 user = userRepository.findById(userId);
13
14 if (user != null) {
15 // 写入缓存
16 redisTemplate.opsForValue().set(key, user, 1, TimeUnit.HOURS);
17 }
18
19 return user;
20}
21
22// 2. Write-Through模式
23public void updateUser(User user) {
24 // 先更新数据库
25 userRepository.save(user);
26
27 // 再更新缓存
28 String key = "user:" + user.getId();
29 redisTemplate.opsForValue().set(key, user, 1, TimeUnit.HOURS);
30}
31
32// 3. Write-Behind模式
33public void updateUserAsync(User user) {
34 // 先更新缓存
35 String key = "user:" + user.getId();
36 redisTemplate.opsForValue().set(key, user, 1, TimeUnit.HOURS);
37
38 // 异步更新数据库
39 CompletableFuture.runAsync(() -> {
40 userRepository.save(user);
41 });
42}

4. 事务管理

4.1 ACID特性

事务特性详解

ACID特性
java
1// 1. 原子性(Atomicity)
2@Transactional
3public void transferMoney(Long fromAccountId, Long toAccountId, BigDecimal amount) {
4 // 所有操作要么全部成功,要么全部失败
5 Account fromAccount = accountRepository.findById(fromAccountId);
6 Account toAccount = accountRepository.findById(toAccountId);
7
8 fromAccount.setBalance(fromAccount.getBalance().subtract(amount));
9 toAccount.setBalance(toAccount.getBalance().add(amount));
10
11 accountRepository.save(fromAccount);
12 accountRepository.save(toAccount);
13
14 // 如果任何一步失败,整个事务回滚
15}
16
17// 2. 一致性(Consistency)
18// 事务执行前后数据库状态一致
19// 转账前后总金额保持不变
20
21// 3. 隔离性(Isolation)
22@Transactional(isolation = Isolation.READ_COMMITTED)
23public User getUser(Long userId) {
24 // 事务之间相互隔离
25 return userRepository.findById(userId);
26}
27
28// 4. 持久性(Durability)
29// 事务提交后永久保存
30// 即使系统崩溃,已提交的事务不会丢失

4.2 事务隔离级别

隔离级别详解

事务隔离级别
java
1// 1. READ UNCOMMITTED(读未提交)
2@Transactional(isolation = Isolation.READ_UNCOMMITTED)
3public User getUser(Long userId) {
4 // 可能读取到未提交的数据(脏读)
5 return userRepository.findById(userId);
6}
7
8// 2. READ COMMITTED(读已提交)
9@Transactional(isolation = Isolation.READ_COMMITTED)
10public User getUser(Long userId) {
11 // 避免脏读,可能出现不可重复读
12 return userRepository.findById(userId);
13}
14
15// 3. REPEATABLE READ(可重复读)
16@Transactional(isolation = Isolation.REPEATABLE_READ)
17public User getUser(Long userId) {
18 // 避免不可重复读,可能出现幻读
19 return userRepository.findById(userId);
20}
21
22// 4. SERIALIZABLE(串行化)
23@Transactional(isolation = Isolation.SERIALIZABLE)
24public User getUser(Long userId) {
25 // 完全避免并发问题,性能最低
26 return userRepository.findById(userId);
27}

5. 面试题精选

5.1 数据库设计题

Q: 数据库设计的三个范式是什么?如何应用?

A: 三个范式分别是:

  • 第一范式(1NF):每个字段都是不可分割的原子值
  • 第二范式(2NF):在1NF基础上,非主键字段完全依赖于主键
  • 第三范式(3NF):在2NF基础上,非主键字段不依赖于其他非主键字段

应用原则:根据业务需求适度规范化,避免过度规范化影响性能。

Q: 如何设计数据库索引?有哪些注意事项?

A: 索引设计原则:

  1. 高选择性列优先:不同值数量多,重复值少
  2. 查询频率高的列:为频繁查询的列创建索引
  3. 外键列:外键列通常需要索引以加速JOIN操作
  4. 排序和分组列:为ORDER BY和GROUP BY的列创建索引
  5. 避免过多索引:索引会占用存储空间,影响写入性能

5.2 ORM框架题

Q: Hibernate和MyBatis的区别是什么?如何选择?

A: 主要区别:

  • Hibernate:全功能ORM,自动生成SQL,学习成本高,适合复杂业务
  • MyBatis:半自动ORM,手动编写SQL,学习成本低,适合复杂查询

选择原则:根据项目需求选择,复杂查询选择MyBatis,标准CRUD选择Hibernate。

Q: JPA的实体关系映射有哪些?如何使用?

A: JPA关系映射包括:

  • @OneToOne:一对一关系
  • @OneToMany:一对多关系
  • @ManyToOne:多对一关系
  • @ManyToMany:多对多关系

使用要点:合理设置级联操作、延迟加载和关联查询策略。

5.3 性能优化题

Q: 如何优化数据库查询性能?

A: 优化策略包括:

  1. 索引优化:创建合适的索引,避免在索引列上使用函数
  2. SQL优化:避免SELECT *,使用LIMIT,合理使用JOIN
  3. 查询重写:将子查询转换为JOIN,使用EXISTS代替IN
  4. 缓存策略:合理使用缓存,避免缓存穿透、击穿、雪崩
  5. 分页查询:避免大结果集查询,使用分页

Q: 什么是缓存穿透、击穿、雪崩?如何解决?

A: 缓存问题及解决方案:

  • 缓存穿透:查询不存在的数据,解决方案:布隆过滤器、缓存空值
  • 缓存击穿:热点数据过期,解决方案:互斥锁、热点数据永不过期
  • 缓存雪崩:大量缓存同时过期,解决方案:随机过期时间、多级缓存

5.4 事务管理题

Q: 数据库事务的ACID特性是什么?

A: ACID特性:

  • 原子性(Atomicity):事务是不可分割的工作单位
  • 一致性(Consistency):事务执行前后数据库状态一致
  • 隔离性(Isolation):事务之间相互隔离
  • 持久性(Durability):事务提交后永久保存

Q: 事务隔离级别有哪些?各有什么特点?

A: 事务隔离级别:

  • READ UNCOMMITTED:读未提交,可能出现脏读
  • READ COMMITTED:读已提交,可能出现不可重复读
  • REPEATABLE READ:可重复读,可能出现幻读
  • SERIALIZABLE:串行化,完全避免并发问题

5.5 高可用题

Q: 如何实现数据库的高可用?

A: 高可用方案包括:

  1. 主从复制:一主多从,读写分离
  2. 集群模式:数据分片,自动故障转移
  3. 负载均衡:使用代理或中间件分发请求
  4. 数据备份:定期备份,支持快速恢复
  5. 监控告警:实时监控,及时发现问题

Q: 分库分表的策略有哪些?

A: 分库分表策略:

  • 水平分表:按行分割,如按时间、ID范围分表
  • 垂直分表:按列分割,将大表拆分为多个小表
  • 水平分库:按数据库分割
  • 垂直分库:按业务模块分割
  • 分片策略:取模、范围、哈希等

5.6 PostgreSQL与MongoDB面试题

Q: PostgreSQL和MySQL有哪些主要区别?如何选择?

A: PostgreSQL与MySQL的主要区别:

  1. 功能丰富度:PostgreSQL提供更丰富的数据类型、更强大的索引类型和更多的SQL扩展
  2. 扩展性:PostgreSQL有完善的扩展机制,如PostGIS地理信息扩展
  3. 事务处理:PostgreSQL提供更完善的事务隔离和MVCC支持
  4. 复杂查询:PostgreSQL更适合处理复杂查询和数据分析
  5. 合规性:PostgreSQL对SQL标准的遵循度更高

选择标准:

  • 简单应用且易于管理优先考虑MySQL
  • 复杂应用、空间数据、分析型应用优先考虑PostgreSQL

Q: MongoDB适合什么场景?不适合什么场景?

A: MongoDB适合场景:

  1. 非结构化或半结构化数据:灵活的文档模型适合变化的数据结构
  2. 高写入量:高性能写入和水平扩展能力
  3. 大数据存储:通过分片可扩展到TB级数据
  4. 敏捷开发:模式灵活支持快速迭代
  5. 内容管理:适合博客、文章等内容存储

不适合场景:

  1. 复杂事务:多文档事务支持仍有限制
  2. 复杂关联查询:JOIN操作不如关系型数据库高效
  3. 强一致性要求:最终一致性可能不适合某些业务场景
  4. 固定结构数据:高度规范化的数据可能更适合关系型数据库

Q: 如何优化PostgreSQL和MongoDB性能?

A: PostgreSQL性能优化:

  1. 索引优化:合理使用B-tree、GIN、BRIN等不同类型索引
  2. 查询优化:EXPLAIN分析、查询重写和物化视图
  3. 配置调整:shared_buffers、work_mem、effective_cache_size等参数调优
  4. 表分区:大表使用表分区提高性能
  5. 并行查询:利用多CPU并行处理大型查询

MongoDB性能优化:

  1. 索引策略:创建正确的索引支持常用查询
  2. 文档结构:设计合理的文档结构避免深层嵌套
  3. 分片策略:选择良好的分片键实现数据均衡分布
  4. 读写分离:利用读取偏好从次要节点读取
  5. 批量操作:使用批量插入/更新而非单条操作

5.7 Hibernate与JPA面试题

Q: Hibernate的Session和JPA的EntityManager有什么异同?

A: Hibernate Session与JPA EntityManager的比较:

  1. 关系:EntityManager是JPA规范的接口,Session是Hibernate的接口,EntityManager在Hibernate中的实现内部使用了Session
  2. 功能相似性:两者都提供实体的基本CRUD操作、查询功能和事务支持
  3. API差异
    • Session提供save()、update()、saveOrUpdate()等方法
    • EntityManager提供persist()、merge()、remove()等方法
  4. 缓存操作:Session提供更多的缓存控制API,如evict()、clear()
  5. 查询API:Session有更丰富的查询API,如createCriteria()

Q: Hibernate的对象状态转换是怎样的?

A: Hibernate对象状态:

  1. 瞬时态(Transient):新创建的对象,没有持久化标识符,未与Session关联
  2. 持久态(Persistent):与当前Session关联的对象,有持久化标识符,修改会自动同步到数据库
  3. 游离态(Detached):曾经持久化,但当前未与任何Session关联的对象
  4. 删除态(Removed):被标记为删除的对象

状态转换:

  • 瞬时态→持久态:session.save(), session.persist()
  • 持久态→游离态:session.evict(), session.clear(), session.close()
  • 游离态→持久态:session.update(), session.merge(), session.saveOrUpdate()
  • 持久态→删除态:session.delete()

Q: Hibernate的一级缓存和二级缓存有什么区别?如何优化?

A: Hibernate缓存比较:

  1. 一级缓存(Session缓存)

    • 作用域:单个Session
    • 生命周期:Session开启到关闭
    • 特点:强制启用,不能禁用
    • 数据一致性:自动管理,与事务绑定
  2. 二级缓存(SessionFactory缓存)

    • 作用域:应用级别,所有Session共享
    • 生命周期:应用程序生命周期
    • 特点:需要显式配置,可选择缓存提供者(EhCache, Infinispan等)
    • 数据一致性:需要配置合适的缓存并发策略

优化策略:

  1. 合理设置缓存对象,只缓存不经常变化的数据
  2. 为频繁查询的实体启用二级缓存
  3. 为只读数据配置READ_ONLY策略,提高性能
  4. 合理设置缓存过期时间和大小
  5. 监控缓存命中率,调整缓存策略

Q: JPA与Spring Data JPA的区别是什么?

A: JPA与Spring Data JPA的区别:

  1. 定义

    • JPA(Java Persistence API)是Java持久化的标准规范
    • Spring Data JPA是在JPA基础上的更高抽象,简化数据访问层开发
  2. 功能对比

    • JPA提供基本的对象关系映射和实体管理
    • Spring Data JPA提供方法名生成查询、分页排序、自动实现Repository等功能
  3. 使用方式

    • JPA需要编写实现类和查询代码
    • Spring Data JPA只需定义接口,无需编写实现类
  4. 扩展性

    • Spring Data JPA支持多种数据存储(MongoDB, Redis等)
    • JPA主要针对关系型数据库

Q: 如何处理Hibernate的N+1查询问题?

A: N+1查询问题解决方案:

  1. 使用JOIN FETCH

    java
    1// 解决N+1问题
    2String hql = "SELECT o FROM Order o JOIN FETCH o.items WHERE o.status = :status";
  2. 使用EntityGraph

    java
    1@EntityGraph(attributePaths = {"items", "customer"})
    2Order findById(Long id);
  3. 使用批量获取

    java
    1// 在实体类上配置
    2@BatchSize(size = 20)
    3@OneToMany(mappedBy = "order")
    4private Set<OrderItem> items;
    5
    6// 或全局配置
    7<property name="hibernate.default_batch_fetch_size" value="20" />
  4. 在查询中预先指定加载关系

    java
    1Criteria criteria = session.createCriteria(Order.class);
    2criteria.setFetchMode("items", FetchMode.JOIN);
学习要点总结
  1. 掌握数据库设计原则:理解规范化理论,合理设计表结构
  2. 熟悉ORM框架特性:掌握JPA、Hibernate、MyBatis、Spring Data的使用
  3. 学会性能优化技巧:掌握索引优化、SQL优化、缓存策略
  4. 理解事务管理机制:掌握ACID特性、隔离级别、锁机制
  5. 了解高可用架构:掌握主从复制、集群模式、分库分表
  6. 掌握NoSQL特性:理解文档数据库和键值数据库的场景与用法
  7. 深入ORM原理:理解对象状态、缓存机制、延迟加载

通过本章的学习,你应该已经全面掌握了数据库设计和ORM框架的核心概念、最佳实践和面试重点。数据库是后端系统的核心,合理的设计和优化能够显著提升系统性能和用户体验。在实际项目中,需要根据业务需求选择合适的数据库类型和ORM框架,并持续优化数据库性能。

参与讨论