news 2026/6/25 5:53:29

MyBatis-Plus + Lock4j 分布式锁教程

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
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 + "次"); } }

八、性能优化建议

  1. 锁粒度:尽量使用细粒度锁,如按订单ID、用户ID

  2. 超时设置:根据业务耗时合理设置,避免过长或过短

  3. 监控告警:添加锁等待时间、失败率的监控

  4. 降级方案:锁服务不可用时,考虑本地锁或限流

九、最佳实践总结

@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("处理失败"); } } }
版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/6/8 15:19:03

终极音乐解密指南:浏览器内一键解锁主流音乐平台加密格式

终极音乐解密指南&#xff1a;浏览器内一键解锁主流音乐平台加密格式 【免费下载链接】unlock-music 在浏览器中解锁加密的音乐文件。原仓库&#xff1a; 1. https://github.com/unlock-music/unlock-music &#xff1b;2. https://git.unlock-music.dev/um/web 项目地址: ht…

作者头像 李华
网站建设 2026/6/8 15:18:28

AI 编译优化:LLM 推理引擎的底层技术演进与性能博弈

AI 编译优化&#xff1a;LLM 推理引擎的底层技术演进与性能博弈在大模型浪潮席卷技术行业的今天&#xff0c;一个核心问题始终萦绕在所有 AI 工程师心头&#xff1a;如何让模型跑得更快、更省、更省电&#xff1f;这个问题之所以重要&#xff0c;是因为推理成本直接影响 AI 产品…

作者头像 李华
网站建设 2026/6/11 12:38:00

Linear快如闪电秘诀揭秘:从数据库到动画,全方位提升性能!

本文涵盖内容包括浏览器中的数据库、让首次加载感觉即时完成、同步引擎、为速度而设计、动画效果等方面。浏览器中的数据库多数Web应用遵循传统循环模式&#xff0c;会出现加载指示器等问题。Linear颠覆传统&#xff0c;其用户界面读取的数据库位于浏览器的IndexedDB中&#xf…

作者头像 李华
网站建设 2026/6/8 15:16:16

MSC8101 UPM编程实战:精准驱动异步双端口SRAM接口设计

1. 项目概述与核心价值在嵌入式系统&#xff0c;尤其是多处理器协同工作的复杂场景里&#xff0c;如何让两个或多个核心高效、无冲突地共享一块数据存储区&#xff0c;是一个经典且棘手的设计难题。直接使用普通的单端口SRAM&#xff0c;需要引入复杂的仲裁逻辑&#xff0c;不仅…

作者头像 李华
网站建设 2026/6/11 11:14:13

1000道Java面试题(2026秋招高频版),没有废话,直接开背

为什么同样是跳槽&#xff0c;有些人薪资能翻三倍&#xff1f;” 最近一个粉丝发出了灵魂拷问&#xff0c;类似的问题我收到过很多次&#xff0c;身边也确实有认识的同事、朋友们有非常成功的跳槽经历和收益&#xff0c;先说一个典型例子&#xff1a; 学弟小 A 工作一年半&am…

作者头像 李华