4、CRUD基础操作:增删改查的标准写法、批量操作、事务的基本使用
好,咱们直接进入正题。CRUD 这四个字母,说白了就是软件和数据库打交道的日常。增删改查,哪个项目能离得开?我见过不少新手,上来就写原生 SQL,然后到处拼字符串,最后被 SQL 注入教做人。也有老手,ORM 用得飞起,但一遇到批量操作就卡壳,事务边界也搞不清楚。
今天这一章,我就带你把这些基础操作彻底捋一遍。咱们用 Python 的 SQLAlchemy 和 Java 的 Hibernate 来举例,但核心思想是通用的。你只要理解了这一套,换哪个 ORM 框架都差不多。
4.1 标准增删改查写法
先看最基础的。我个人习惯,写 CRUD 之前,先确认好 Session 或 EntityManager 的生命周期。别在循环里反复创建,也别忘记关闭。嗯,这是很多性能问题的根源。
4.1.1 新增(Create)
新增操作,ORM 框架会帮你把对象映射成 INSERT 语句。你只需要 new 一个对象,设置属性,然后调用 save 或 persist 方法。
Python (SQLAlchemy) 示例:
# 创建一个新用户
new_user = User(name="张三", email="zhangsan@example.com", age=28)
# 添加到会话并提交
session.add(new_user)
session.commit()
print(f"新增用户 ID: {new_user.id}") # 提交后,id 自动回填
Java (Hibernate) 示例:
// 创建一个新用户
User newUser = new User();
newUser.setName("张三");
newUser.setEmail("zhangsan@example.com");
newUser.setAge(28);
// 持久化
entityManager.persist(newUser);
entityManager.getTransaction().commit();
System.out.println("新增用户 ID: " + newUser.getId()); // 提交后,id 自动回填
4.1.2 查询(Retrieve)
查询是 CRUD 里花样最多的。按主键查、按条件查、分页查、关联查。咱们先看最标准的几种。
按主键查询:
# Python
user = session.get(User, 1) # 查 ID 为 1 的用户
// Java
User user = entityManager.find(User.class, 1); // 查 ID 为 1 的用户
条件查询:
# Python - 使用 filter
users = session.query(User).filter(User.age > 20).all()
// Java - 使用 JPQL
List<User> users = entityManager.createQuery(
"SELECT u FROM User u WHERE u.age > :age", User.class)
.setParameter("age", 20)
.getResultList();
4.1.3 更新(Update)
更新操作,ORM 框架的核心理念是「脏检查」。你只要修改了对象的属性,框架会在提交时自动生成 UPDATE 语句。
# Python
user = session.get(User, 1)
user.age = 30 # 修改属性
session.commit() # 自动生成 UPDATE
// Java
User user = entityManager.find(User.class, 1);
user.setAge(30); // 修改属性
entityManager.getTransaction().commit(); // 自动生成 UPDATE
为什么会这样?因为 ORM 框架在内存里维护了一份对象的「快照」。提交时一对比,发现 age 变了,就帮你 UPDATE 了。说白了,这就是 ORM 的「自动挡」特性。
4.1.4 删除(Delete)
删除操作最简单,但也最容易出问题。尤其是级联删除,一不小心就把关联表的数据全删了。
# Python
user = session.get(User, 1)
session.delete(user)
session.commit()
// Java
User user = entityManager.find(User.class, 1);
entityManager.remove(user);
entityManager.getTransaction().commit();
4.2 批量操作
批量操作是性能优化的重灾区。你想想看,如果循环 1000 次,每次 insert 一条,那就要建立 1000 次网络连接,生成 1000 条 SQL。这效率,谁用谁知道。
正确的做法是:批量提交。
4.2.1 批量新增
Python (SQLAlchemy) 示例:
users = []
for i in range(1000):
users.append(User(name=f"用户{i}", email=f"user{i}@example.com"))
# 批量添加,每 100 条提交一次
session.bulk_save_objects(users)
session.commit()
Java (Hibernate) 示例:
// 开启批量处理
entityManager.setProperty("hibernate.jdbc.batch_size", 50);
for (int i = 0; i < 1000; i++) {
User user = new User();
user.setName("用户" + i);
user.setEmail("user" + i + "@example.com");
entityManager.persist(user);
// 每 50 条 flush 一次,清空缓存
if (i % 50 == 0) {
entityManager.flush();
entityManager.clear();
}
}
entityManager.getTransaction().commit();
4.2.2 批量更新与删除
批量更新和删除,千万别用循环里逐条操作。ORM 框架通常提供了「批量更新」的 API,直接生成一条 UPDATE 或 DELETE 语句。
# Python - 批量更新
session.query(User).filter(User.age < 18).update(
{"status": "minor"}, synchronize_session=False
)
session.commit()
// Java - 批量更新(使用 JPQL)
entityManager.createQuery(
"UPDATE User u SET u.status = 'minor' WHERE u.age < 18")
.executeUpdate();
entityManager.getTransaction().commit();
4.3 事务的基本使用
事务,说白了就是「要么全做,要么全不做」。转账、下单、库存扣减,这些场景都离不开事务。
4.3.1 事务的 ACID 特性
这个你肯定听过,但我还是想强调一下:
| 特性 | 含义 | 实际例子 |
|---|---|---|
| 原子性 | 事务里的操作,要么全部成功,要么全部回滚 | 转账时,扣钱和加钱必须同时成功或同时失败 |
| 一致性 | 事务前后,数据必须符合业务规则 | 转账后,总金额不变 |
| 隔离性 | 并发事务之间互不干扰 | 两个人同时转账,不会互相影响 |
| 持久性 | 事务提交后,数据永久保存 | 系统崩溃后,已提交的数据不会丢失 |
4.3.2 事务的标准写法
我个人习惯,事务一定要显式控制。别依赖框架的自动提交,那玩意儿在复杂场景下容易出问题。
Python (SQLAlchemy) 示例:
try:
# 开启事务
session.begin()
# 业务操作
user1 = session.get(User, 1)
user1.balance -= 100
user2 = session.get(User, 2)
user2.balance += 100
# 提交事务
session.commit()
except Exception as e:
# 回滚事务
session.rollback()
print(f"事务失败,已回滚: {e}")
finally:
session.close()
Java (Hibernate) 示例:
EntityTransaction transaction = entityManager.getTransaction();
try {
transaction.begin();
// 业务操作
User user1 = entityManager.find(User.class, 1);
user1.setBalance(user1.getBalance() - 100);
User user2 = entityManager.find(User.class, 2);
user2.setBalance(user2.getBalance() + 100);
transaction.commit();
} catch (Exception e) {
if (transaction.isActive()) {
transaction.rollback();
}
System.err.println("事务失败,已回滚: " + e);
} finally {
entityManager.close();
}
4.3.3 事务的隔离级别
隔离级别决定了并发事务能看到什么数据。我直接给你一个经验值:
| 隔离级别 | 脏读 | 不可重复读 | 幻读 | 我推荐的使用场景 |
|---|---|---|---|---|
| READ UNCOMMITTED | 可能 | 可能 | 可能 | 几乎不用,除非你不在乎数据一致性 |
| READ COMMITTED | 不会 | 可能 | 可能 | 大多数业务系统,PostgreSQL 默认就是这个 |
| REPEATABLE READ | 不会 | 不会 | 可能 | 需要重复读取同一行数据的场景,MySQL InnoDB 默认 |
| SERIALIZABLE | 不会 | 不会 | 不会 | 金融、对账等强一致性场景,但性能最差 |
你想想看,如果你的系统并发不高,用默认的隔离级别就行。如果并发高,我建议用 READ COMMITTED,然后通过乐观锁或悲观锁来解决并发冲突。嗯,这个咱们后面章节会详细讲。
4.4 总结
好了,这一章的内容就这些。咱们回顾一下:
- 标准 CRUD:记住 Session 的生命周期,脏检查机制,以及级联删除的坑。
- 批量操作:别循环逐条操作,用批量 API,定期 flush 和 clear。
- 事务:显式控制,短平快,别在事务里做远程调用。
下一章,咱们会深入 ORM 的关联映射。到时候我会讲讲「N+1 查询」这个经典问题,以及怎么用懒加载和预加载来优化。嗯,那才是真正考验 ORM 功底的地方。