悲觀鎖和樂觀鎖的概念:
悲觀鎖:就是獨占鎖,不管讀寫都上鎖了。傳統(tǒng)的關(guān)系型數(shù)據(jù)庫里邊就用到了很多這種鎖機制,比如行鎖,表鎖等,讀鎖,寫鎖等,都是在做操作之前先上鎖。Java中synchronized和ReentrantLock等獨占鎖就是悲觀鎖思想的實現(xiàn)。
樂觀鎖:不上鎖,讀取的時候帶版本號,寫入的時候帶著這個版本號,如果不一致就失敗,樂觀鎖適用于多讀的應用類型,因為寫多的時候會經(jīng)常失敗。
2.1 Maven依賴
需要引入spring-boot-starter-data-jpa,這里要訪問數(shù)據(jù)庫,所以要依賴數(shù)據(jù)庫相關(guān)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
這里面,包含了數(shù)據(jù)庫連接信息、數(shù)據(jù)源的連接池配置信息、mybatis配置信息。
spring.datasource.dbcp2是配置dbcp2的連接池信息;
spring.datasource.type指明數(shù)據(jù)源的類型;
最上面的spring.datasource.xxx指明數(shù)據(jù)庫連接池信息;
mybatis.configuration.log-impl指明mybatis的日志打印方式
三、悲觀鎖
悲觀鎖在數(shù)據(jù)庫的訪問中使用,表現(xiàn)為:前一次請求沒執(zhí)行完,后面一個請求就一直在等待。
3.1 Dao層
數(shù)據(jù)庫要實現(xiàn)悲觀鎖,就是將sql語句帶上for update即可。 for update 是行鎖
所在mybatis的查詢sql加上for update,就實現(xiàn)了對當前記錄的鎖定,就實現(xiàn)了悲觀鎖。
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層
更新數(shù)據(jù)庫前,先調(diào)用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層
可以先調(diào)用/update/{time}接口,延遲執(zhí)行,然后馬上調(diào)用/update接口,會發(fā)現(xiàn),/update接口一直在等待/update/{time}接口執(zhí)行完成。
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";
}
}
四、樂觀鎖
數(shù)據(jù)庫訪問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層我們做一下簡單的調(diào)整。更新數(shù)據(jù)庫前,先調(diào)用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層
可以先調(diào)用/update/{time}接口,延遲執(zhí)行,然后馬上調(diào)用/update接口,會發(fā)現(xiàn),/update接口不會等待/update/{time}接口執(zhí)行完成,讀取完版本號能夠成功更新數(shù)據(jù),但是/update/{time}接口等待足夠時間以后,更新的時候會失敗,因為它的版本和數(shù)據(jù)庫的已經(jīng)不一致了。
注意: 這里更新失敗不會拋異常,但是返回值會是0,即更新不成功,需要自行判斷。jpa的樂觀鎖可以拋出異常,手動catch到再自行處理。文章來源:http://www.zghlxwxcb.cn/news/detail-683254.html
MybatisOptiLockRest :文章來源地址http://www.zghlxwxcb.cn/news/detail-683254.html
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";
}
}
到了這里,關(guān)于mybatis使用樂觀鎖和悲觀鎖的文章就介紹完了。如果您還想了解更多內(nèi)容,請在右上角搜索TOY模板網(wǎng)以前的文章或繼續(xù)瀏覽下面的相關(guān)文章,希望大家以后多多支持TOY模板網(wǎng)!