MyBatis-Plus + Lock4j 分布式锁教程
·
一、Lock4j 简介
Lock4j 是阿里巴巴开源的分布式锁组件,支持 Redis、Zookeeper 等多种实现,与 Spring Boot 无缝集成。
二、快速开始
1. 添加依赖
<dependency>
<groupId>com.baomidou</groupId>
<artifactId>lock4j-redis-template-spring-boot-starter</artifactId>
<version>2.2.0</version>
</dependency>
<!-- 如果使用 Redis,需要同时引入 Redis 依赖 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis</artifactId>
</dependency>
2. 配置文件
spring:
redis:
host: localhost
port: 6379
lock4j:
# 锁的默认过期时间(ms),超时自动释放
acquire-timeout: 3000
# 获取锁的超时时间(ms),超时则获取失败
expire: 30000
# 主线程等待锁的时间,超时则抛出异常
primary-timeout: 3000
3. 基础使用
@Service
public class UserService {
@Autowired
private UserMapper userMapper;
// 基本用法:锁的key为 "user:lock:" + id
@Lock4j(keys = {"#id"}, expire = 10000, acquireTimeout = 3000)
public void updateUser(Long id, String name) {
User user = userMapper.selectById(id);
user.setName(name);
userMapper.updateById(user);
}
}
三、核心注解详解
@Lock4j 注解参数
| 参数 | 说明 | 默认值 |
|---|---|---|
| keys | 锁的key,支持SpEL表达式 | 空 |
| expire | 锁的过期时间(ms) | 配置中的值 |
| acquireTimeout | 获取锁超时时间(ms) | 配置中的值 |
| name | 锁的名称前缀 | 空 |
四、实战示例
1. 防重复提交
@RestController
public class OrderController {
@PostMapping("/order/create")
@Lock4j(keys = {"#userId"}, expire = 5000, acquireTimeout = 1000)
public Result createOrder(@RequestBody OrderVO orderVO, @RequestParam Long userId) {
// 业务逻辑
return Result.success();
}
}
2. 库存扣减(MyBatis-Plus 操作)
@Service
public class ProductService {
@Autowired
private ProductMapper productMapper;
@Lock4j(keys = {"#productId"}, expire = 5000, acquireTimeout = 3000)
@Transactional(rollbackFor = Exception.class)
public boolean deductStock(Long productId, Integer quantity) {
// 1. 查询商品
Product product = productMapper.selectById(productId);
// 2. 检查库存
if (product.getStock() < quantity) {
throw new RuntimeException("库存不足");
}
// 3. 扣减库存
product.setStock(product.getStock() - quantity);
int result = productMapper.updateById(product);
return result > 0;
}
}
3. 复杂SpEL表达式
@Service
public class BizService {
// 使用多个参数组成key
@Lock4j(keys = {"#userId", "#orderId"})
public void processOrder(Long userId, String orderId) {
// 业务逻辑
}
// 使用对象属性
@Lock4j(keys = {"#user.id", "#user.name"})
public void updateUser(@Param("user") User user) {
// 业务逻辑
}
// 自定义key前缀
@Lock4j(name = "order:lock", keys = {"#orderId"})
public void handleOrder(String orderId) {
// 最终key: order:lock:orderId值
}
}
4. 手动获取锁
@Service
public class ManualLockService {
@Autowired
private LockTemplate lockTemplate;
public void doWithLock(String resourceId) {
// 手动获取锁
LockInfo lockInfo = lockTemplate.lock(resourceId, 10000L, 3000L);
if (lockInfo == null) {
throw new RuntimeException("获取锁失败");
}
try {
// 业务逻辑
System.out.println("执行业务逻辑");
} finally {
// 释放锁
lockTemplate.releaseLock(lockInfo);
}
}
}
5. 配合 LambdaQueryWrapper
@Service
public class AccountService {
@Autowired
private AccountMapper accountMapper;
@Lock4j(keys = {"#accountId"}, expire = 10000)
@Transactional
public void transfer(Long fromAccountId, Long toAccountId, BigDecimal amount) {
// 使用 LambdaQueryWrapper 查询
LambdaQueryWrapper<Account> fromWrapper = Wrappers.<Account>lambdaQuery()
.eq(Account::getId, fromAccountId);
Account fromAccount = accountMapper.selectOne(fromWrapper);
LambdaQueryWrapper<Account> toWrapper = Wrappers.<Account>lambdaQuery()
.eq(Account::getId, toAccountId);
Account toAccount = accountMapper.selectOne(toWrapper);
// 扣款
fromAccount.setBalance(fromAccount.getBalance().subtract(amount));
accountMapper.updateById(fromAccount);
// 加款
toAccount.setBalance(toAccount.getBalance().add(amount));
accountMapper.updateById(toAccount);
}
}
五、高级配置
1. 自定义Key生成器
@Component
public class CustomKeyGenerator implements LockKeyGenerator {
@Override
public String generateKey(LockInfo lockInfo, Method method, Object[] args) {
// 自定义key生成逻辑
return "custom:" + method.getName() + ":" + Arrays.hashCode(args);
}
}
// 使用方式
@Lock4j(keys = {"#id"}, keyGenerator = CustomKeyGenerator.class)
public void customKeyMethod(Long id) {
// ...
}
2. 自定义锁失败处理
@Component
public class CustomLockFailureHandler implements LockFailureHandler {
@Override
public void onLockFailure(String key, Method method, Object[] args) {
// 锁获取失败时的处理
throw new BusinessException("系统繁忙,请稍后重试");
}
}
3. 执行器配置
@Configuration
public class Lock4jConfig {
@Bean
public ExecutorService lockExecutor() {
return Executors.newFixedThreadPool(10);
}
}
六、注意事项
1. 事务与锁的顺序
// ✅ 正确:锁在外,事务在内
@Lock4j(keys = {"#id"})
@Transactional
public void correctWay(Long id) {
// 业务逻辑
}
// ❌ 错误:事务在外,锁在内可能导致事务未提交锁已释放
@Transactional
@Lock4j(keys = {"#id"})
public void wrongWay(Long id) {
// 业务逻辑
}
2. 避免死锁
// 多个锁时注意顺序
@Lock4j(keys = {"#id1"})
public void method1(Long id1, Long id2) {
// 调用需要锁id2的方法 - 可能导致死锁
method2(id2);
}
@Lock4j(keys = {"#id2"})
public void method2(Long id2) {
// ...
}
3. 合理设置超时时间
lock4j:
# 根据业务执行时间设置,建议比业务执行时间长30%
expire: 5000 # 业务平均执行3秒,设置5秒
# 获取锁超时时间不宜过长
acquire-timeout: 2000
七、常见问题解决
1. 锁未释放问题
// 使用 try-finally 确保释放
LockInfo lockInfo = lockTemplate.lock(key, 10000, 3000);
try {
// 业务操作
// 注意:不要在业务代码中提前return
} finally {
if (lockInfo != null) {
lockTemplate.releaseLock(lockInfo);
}
}
2. 重试机制
@Component
public class RetryLockService {
@Autowired
private LockTemplate lockTemplate;
public void executeWithRetry(String key, Runnable task, int maxRetries) {
for (int i = 0; i < maxRetries; i++) {
LockInfo lockInfo = lockTemplate.lock(key, 10000, 3000);
if (lockInfo != null) {
try {
task.run();
return;
} finally {
lockTemplate.releaseLock(lockInfo);
}
}
// 等待后重试
try {
Thread.sleep(100);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new RuntimeException(e);
}
}
throw new RuntimeException("获取锁失败,重试" + maxRetries + "次");
}
}
八、性能优化建议
-
锁粒度:尽量使用细粒度锁,如按订单ID、用户ID
-
超时设置:根据业务耗时合理设置,避免过长或过短
-
监控告警:添加锁等待时间、失败率的监控
-
降级方案:锁服务不可用时,考虑本地锁或限流
九、最佳实践总结
@Service
@Slf4j
public class BestPracticeService {
/**
* 推荐的最佳实践模板
*/
@Lock4j(
keys = {"#bizId"}, // 精确的锁key
expire = 10000, // 比业务执行时间长30-50%
acquireTimeout = 2000 // 合理等待时间
)
@Transactional(rollbackFor = Exception.class)
public Result doBusiness(String bizId, BusinessData data) {
// 1. 参数校验
if (StringUtils.isEmpty(bizId)) {
throw new IllegalArgumentException("业务ID不能为空");
}
// 2. 业务处理
try {
// MyBatis-Plus 数据库操作
return Result.success();
} catch (Exception e) {
log.error("业务处理失败, bizId: {}", bizId, e);
throw new BusinessException("处理失败");
}
}
}
AtomGit 是由开放原子开源基金会联合 CSDN 等生态伙伴共同推出的新一代开源与人工智能协作平台。平台坚持“开放、中立、公益”的理念,把代码托管、模型共享、数据集托管、智能体开发体验和算力服务整合在一起,为开发者提供从开发、训练到部署的一站式体验。
更多推荐


所有评论(0)