目录:
1、悲观锁与乐观锁的介绍
悲观锁(Pessimistic Locking)-使用mysql 悲观锁 for update实现,操作数据时将符合条件的数据锁住。
乐观锁,大多是基于数据版本(Version)记录机制实现。何谓数据版本?即为数据增加一个版本标识,在基于数据库表的版本解决方案中,一般是通过为数据库表增加一个“version”字段来实现。
读取出数据时,将此版本号一同读出,之后更新时,对此版本号加一。此时,将提交数据的版本数据与数据库表对应记录的当前版本信息进行比对,如果提交的数据版本号大于数据库表当前版本号,则予以更新,否则认为是过期数据。
2、实现机制
为了更好地理解乐观锁和悲观锁的具体实现,我们可以使用一个简单的银行账户余额更新作为例子。我们假设有一个名为 accounts 的表,其中包括字段 account_id(账户标识)、balance(账户余额)和 version(版本号,用于乐观锁)。
2.1. 乐观锁实现
- SQL 表结构
CREATE TABLE accounts (
account_id INT AUTO_INCREMENT PRIMARY KEY,
balance DECIMAL(10, 2),
version INT DEFAULT 1 );
- 更新操作
在进行更新操作时,检查 version 字段确保数据未被修改过,然后进行更新,并将 version 字段的值增加1。
UPDATE accounts
SET balance = balance + 100, -- 假设我们要增加100元
version = version + 1
WHERE account_id = 1 AND version = @CurrentVersion;
@CurrentVersion 是从应用程序传入的,基于最初查询得到的版本号
如果 version 不匹配,即另一个事务已经更新了记录,这个更新操作将不会改变任何行,应用程序可以据此知道更新失败,可能需要重新尝试或通知用户。
2.2. 悲观锁实现
在使用悲观锁时,可以利用数据库提供的锁机制(如行锁),确保在当前事务完成之前,其他事务不能修改被锁定的数据。
- SQL 表结构
-- 使用与乐观锁相同的表结构
CREATE TABLE accounts (
account_id INT AUTO_INCREMENT PRIMARY KEY,
balance DECIMAL(10, 2)
);
- 更新操作
在查询时使用 FOR UPDATE 语句来锁定数据行。这会在当前事务完成之前阻止其他事务修改这些行。
START TRANSACTION;
SELECT balance
FROM accounts
WHERE account_id = 1
FOR UPDATE; -- 锁定账户1
-- 执行业务逻辑,比如增加余额
UPDATE accounts
SET balance = balance + 100
WHERE account_id = 1;
COMMIT;
在这个例子中,SELECT … FOR UPDATE 语句将阻止其他任何尝试更新或读取(在需要相同锁的情况下)account_id 为 1 的行的事务,直到当前事务提交或回滚。
3、事务代码实现
3.1. 配置数据源和事务管理器
首先,确保已经正确配置了数据源和事务管理器。在MyBatis Plus中,通常通过Spring进行配置。以下是一个简单的示例:
<!-- 数据源配置 -->
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource">
<!-- 数据库连接信息 -->
<property name="driverClassName" value="com.mysql.cj.jdbc.Driver" />
<property name="url" value="jdbc:mysql://localhost:3306/mydatabase" />
<property name="username" value="root" />
<property name="password" value="password" />
</bean>
<!-- MyBatis Plus配置 -->
<bean id="sqlSessionFactory" class="com.baomidou.mybatisplus.extension.spring.MybatisSqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
</bean>
<!-- 事务管理器配置 -->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="dataSource" />
</bean>
3.2. 使用@Transactional注解开启事务
在MyBatis Plus中,可以使用Spring的@Transactional注解来声明事务。将@Transactional注解添加到需要事务支持的方法上,它会自动开启、提交或回滚事务。
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
@Transactional
@Override
public void updateUser(User user) {
// 更新用户信息
userMapper.updateById(user);
// 模拟抛出异常,触发事务回滚
if (user.getUsername().equals("rollback")) {
throw new RuntimeException("Simulated exception for rollback");
}
}
}
3.3. 编程式事务管理
除了使用注解方式,MyBatis Plus还支持编程式事务管理。通过TransactionTemplate或PlatformTransactionManager进行事务的手动控制。
@Service
public class UserServiceImpl implements UserService {
@Autowired
private UserMapper userMapper;
@Autowired
private PlatformTransactionManager transactionManager;
@Override
public void updateUser(User user) {
// 创建事务定义
DefaultTransactionDefinition definition = new DefaultTransactionDefinition();
// 设置事务的隔离级别
definition.setIsolationLevel(TransactionDefinition.ISOLATION_READ_COMMITTED);
// 设置事务的传播行为
definition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRED);
// 获取事务对象
TransactionStatus status = transactionManager.getTransaction(definition);
try {
// 更新用户信息
userMapper.updateById(user);
// 模拟抛出异常,触发事务回滚
if (user.getUsername().equals("rollback")) {
throw new RuntimeException("Simulated exception for rollback");
}
// 提交事务
transactionManager.commit(status);
} catch (Exception e) {
// 发生异常,回滚事务
transactionManager.rollback(status);
throw e;
}
}
}
在上述例子中,通过PlatformTransactionManager手动管理事务。通过TransactionDefinition来设置事务的隔离级别和传播行为,然后通过transactionManager.getTransaction()获取事务对象,并在适当的地方使用transactionManager.commit(status)提交事务或transactionManager.rollback(status)回滚事务。