赞
踩
悲观锁和乐观锁的概念:
悲观锁:就是独占锁,不管读写都上锁了。传统的关系型数据库里边就用到了很多这种锁机制,比如行锁,表锁等,读锁,写锁等,都是在做操作之前先上锁。Java中synchronized和ReentrantLock等独占锁就是悲观锁思想的实现。
乐观锁:不上锁,读取的时候带版本号,写入的时候带着这个版本号,如果不一致就失败,乐观锁适用于多读的应用类型,因为写多的时候会经常失败。
2.1 Maven依赖
需要引入spring-boot-starter-data-jpa,这里要访问数据库,所以要依赖数据库相关jar包。
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.mybatis.spring.boot</groupId> <artifactId>mybatis-spring-boot-starter</artifactId> </dependency> <dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> </dependency> <dependency> <groupId>org.apache.commons</groupId> <artifactId>commons-dbcp2</artifactId> </dependency>
2.2 配置文件
在application.properties 中需要添加下面的配置:
spring.datasource.type=org.apache.commons.dbcp2.BasicDataSource spring.datasource.dbcp2.max-wait-millis=60000 spring.datasource.dbcp2.min-idle=20 spring.datasource.dbcp2.initial-size=2 spring.datasource.dbcp2.validation-query=SELECT 1 spring.datasource.dbcp2.connection-properties=characterEncoding=utf8 spring.datasource.dbcp2.validation-query=SELECT 1 spring.datasource.dbcp2.test-while-idle=true spring.datasource.dbcp2.test-on-borrow=true spring.datasource.dbcp2.test-on-return=false spring.datasource.driverClassName = com.mysql.jdbc.Driver spring.datasource.url=jdbc:mysql://127.0.0.1:3306/boot?useUnicode=true&characterEncoding=utf8&serverTimezone=UTC spring.datasource.username=cff spring.datasource.password=123456 mybatis.configuration.log-impl=org.apache.ibatis.logging.stdout.StdOutImpl
这里面,包含了数据库连接信息、数据源的连接池配置信息、mybatis配置信息。
spring.datasource.dbcp2是配置dbcp2的连接池信息;
spring.datasource.type指明数据源的类型;
最上面的spring.datasource.xxx指明数据库连接池信息;
mybatis.configuration.log-impl指明mybatis的日志打印方式
三、悲观锁
悲观锁在数据库的访问中使用,表现为:前一次请求没执行完,后面一个请求就一直在等待。
3.1 Dao层
数据库要实现悲观锁,就是将sql语句带上for update即可。 for update 是行锁
所在mybatis的查询sql加上for update,就实现了对当前记录的锁定,就实现了悲观锁。
UserInfoDao :
package com.cff.springbootwork.mybatislock.dao; import org.apache.ibatis.annotations.Insert; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Select; import org.apache.ibatis.annotations.Update; import cn.pomit.springwork.mybatislock.domain.UserInfo; @Mapper public interface UserInfoDao { @Select({ "<script>", "SELECT ", "user_name as userName,passwd,name,mobile,valid, user_type as userType, version as version", "FROM user_info_test", "WHERE user_name = #{userName,jdbcType=VARCHAR} for update", "</script>"}) UserInfo findByUserNameForUpdate(@Param("userName") String userName); @Update({ "<script>", " update user_info_test set", " name = #{name, jdbcType=VARCHAR}, mobile = #{mobile, jdbcType=VARCHAR},version=version+1 ", " where user_name=#{userName}", "</script>" }) int update(UserInfo userInfo); @Insert({ "<script>", "INSERT INTO user_info_test", "( user_name,", "name ,", "mobile,", "passwd,", "version", ") ", " values ", "( #{userName},", "#{name},", "#{mobile},", "#{passwd},", "#{version}", " ) ", "</script>" }) int save(UserInfo entity); }
这里,findByUserNameForUpdate的sql中加上了for update。update就是普通的更新而已。
3.2 Service层
更新数据库前,先调用findByUserNameForUpdate方法,使上面的配置的悲观锁锁定表记录,然后再更新。
UserInfoService :
package com.cff.springbootwork.mybatislock.service; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.StringUtils; import cn.pomit.springwork.mybatislock.domain.UserInfo; import cn.pomit.springwork.mybatislock.mapper.UserInfoDao; @Service public class UserInfoService { @Autowired UserInfoDao userInfoDao; public void save(UserInfo entity) { entity.setVersion(0); userInfoDao.save(entity); } @Transactional public UserInfo getUserInfoByUserNamePessimistic(String userName) { return userInfoDao.findByUserNameForUpdate(userName); } @Transactional public void updateWithTimePessimistic(UserInfo entity, int time) throws InterruptedException { UserInfo userInfo = userInfoDao.findByUserNameForUpdate(entity.getUserName()); if (userInfo == null) return; if (!StringUtils.isEmpty(entity.getMobile())) { userInfo.setMobile(entity.getMobile()); } if (!StringUtils.isEmpty(entity.getName())) { userInfo.setName(entity.getName()); } Thread.sleep(time * 1000L); userInfoDao.update(userInfo); } @Transactional public void updatePessimistic(UserInfo entity) { UserInfo userInfo = userInfoDao.findByUserNameForUpdate(entity.getUserName()); if (userInfo == null) return; if (!StringUtils.isEmpty(entity.getMobile())) { userInfo.setMobile(entity.getMobile()); } if (!StringUtils.isEmpty(entity.getName())) { userInfo.setName(entity.getName()); } userInfoDao.update(userInfo); } }
测试中,我们在update方法中sleep几秒,其他线程的update将一直等待。
3.3 测试Web层
可以先调用/update/{time}接口,延迟执行,然后马上调用/update接口,会发现,/update接口一直在等待/update/{time}接口执行完成。
MybatisPessLockRest :
package com.cff.springbootwork.mybatislock.web; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; import cn.pomit.springwork.mybatislock.domain.UserInfo; import cn.pomit.springwork.mybatislock.service.UserInfoService; /** * 测试悲观锁 * * @author fufei * */ @RestController @RequestMapping("/mybatispesslock") public class MybatisPessLockRest { @Autowired UserInfoService userInfoService; @RequestMapping(value = "/detail/{name}", method = { RequestMethod.GET }) public UserInfo detail(@PathVariable("name") String name) { return userInfoService.getUserInfoByUserNamePessimistic(name); } @RequestMapping(value = "/save") public String save(@RequestBody UserInfo userInfo) throws InterruptedException { userInfoService.save(userInfo); return "0000"; } @RequestMapping(value = "/update/{time}") public String update(@RequestBody UserInfo userInfo, @PathVariable("time") int time) throws InterruptedException { userInfoService.updateWithTimePessimistic(userInfo, time); return "0000"; } @RequestMapping(value = "/update") public String update(@RequestBody UserInfo userInfo) throws InterruptedException { userInfoService.updatePessimistic(userInfo); return "0000"; } }
四、乐观锁
数据库访问dao层还是3.1那个UserInfoDao。
4.1 Dao层
UserInfoDao更新时,需要携带version字段进行更新:and version = #{version}。如果version不一致,是不会更新成功的,这时候,我们的select查询是不能带锁的。
UserInfoDao :
package com.cff.springbootwork.mybatislock.dao; import org.apache.ibatis.annotations.Insert; import org.apache.ibatis.annotations.Mapper; import org.apache.ibatis.annotations.Param; import org.apache.ibatis.annotations.Select; import org.apache.ibatis.annotations.Update; import cn.pomit.springwork.mybatislock.domain.UserInfo; @Mapper public interface UserInfoDao { @Select({ "<script>", "SELECT ", "user_name as userName,passwd,name,mobile,valid, user_type as userType, version as version", "FROM user_info_test", "WHERE user_name = #{userName,jdbcType=VARCHAR}", "</script>"}) UserInfo findByUserName(@Param("userName") String userName); @Update({ "<script>", " update user_info_test set", " name = #{name, jdbcType=VARCHAR}, mobile = #{mobile, jdbcType=VARCHAR},version=version+1 ", " where user_name=#{userName} and version = #{version}", "</script>" }) int updateWithVersion(UserInfo userInfo); @Insert({ "<script>", "INSERT INTO user_info_test", "( user_name,", "name ,", "mobile,", "passwd,", "version", ") ", " values ", "( #{userName},", "#{name},", "#{mobile},", "#{passwd},", "#{version}", " ) ", "</script>" }) int save(UserInfo entity); }
4.2 Service层
service层我们做一下简单的调整。更新数据库前,先调用findByUserName方法,查询出当前的版本号,然后再更新。
UserInfoService :
package com.cff.springbootwork.mybatislock.service; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.StringUtils; import cn.pomit.springwork.mybatislock.domain.UserInfo; import cn.pomit.springwork.mybatislock.mapper.UserInfoDao; @Service public class UserInfoService { @Autowired UserInfoDao userInfoDao; public UserInfo getUserInfoByUserName(String userName){ return userInfoDao.findByUserName(userName); } public void save(UserInfo entity) { entity.setVersion(0); userInfoDao.save(entity); } @Transactional public void updateWithTimeOptimistic(UserInfo entity, int time) throws Exception { UserInfo userInfo = userInfoDao.findByUserName(entity.getUserName()); if (userInfo == null) return; if (!StringUtils.isEmpty(entity.getMobile())) { userInfo.setMobile(entity.getMobile()); } if (!StringUtils.isEmpty(entity.getName())) { userInfo.setName(entity.getName()); } Thread.sleep(time * 1000L); int ret = userInfoDao.updateWithVersion(userInfo); if(ret < 1)throw new Exception("乐观锁导致保存失败"); } @Transactional public void updateOptimistic(UserInfo entity) throws Exception { UserInfo userInfo = userInfoDao.findByUserName(entity.getUserName()); if (userInfo == null) return; if (!StringUtils.isEmpty(entity.getMobile())) { userInfo.setMobile(entity.getMobile()); } if (!StringUtils.isEmpty(entity.getName())) { userInfo.setName(entity.getName()); } int ret = userInfoDao.updateWithVersion(userInfo); if(ret < 1)throw new Exception("乐观锁导致保存失败"); } }
4.2 测试Web层
可以先调用/update/{time}接口,延迟执行,然后马上调用/update接口,会发现,/update接口不会等待/update/{time}接口执行完成,读取完版本号能够成功更新数据,但是/update/{time}接口等待足够时间以后,更新的时候会失败,因为它的版本和数据库的已经不一致了。
注意: 这里更新失败不会抛异常,但是返回值会是0,即更新不成功,需要自行判断。jpa的乐观锁可以抛出异常,手动catch到再自行处理。
MybatisOptiLockRest :
package com.cff.springbootwork.mybatislock.web; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RestController; import cn.pomit.springwork.mybatislock.domain.UserInfo; import cn.pomit.springwork.mybatislock.service.UserInfoService; /** * 测试乐观锁 * @author fufei * */ @RestController @RequestMapping("/mybatislock") public class MybatisOptiLockRest { @Autowired UserInfoService userInfoService; @RequestMapping(value = "/detail/{name}", method = { RequestMethod.GET }) public UserInfo detail(@PathVariable("name") String name) { return userInfoService.getUserInfoByUserName(name); } @RequestMapping(value = "/save") public String save(@RequestBody UserInfo userInfo) throws InterruptedException { userInfoService.save(userInfo); return "0000"; } @RequestMapping(value = "/update/{time}") public String update(@RequestBody UserInfo userInfo, @PathVariable("time") int time) throws Exception { userInfoService.updateWithTimeOptimistic(userInfo, time); return "0000"; } @RequestMapping(value = "/update") public String update(@RequestBody UserInfo userInfo) throws Exception { userInfoService.updateOptimistic(userInfo); return "0000"; } }
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。