mybatis mysql 乐观锁_MyBatis实现乐观锁和悲观锁

本文介绍了在SpringBoot整合MyBatis的系统中,如何利用MySQL的乐观锁和悲观锁机制。通过示例展示了如何在实体类中添加版本号字段,并在Mapper接口中定义SQL语句,确保并发更新数据时的正确性。乐观锁通过版本号字段进行版本检查更新,而悲观锁则使用`SELECT FOR UPDATE`语句锁定行。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

使用mysql做数据库,mybatis做orm的系统中,mybatis的乐观锁和悲观锁实际上就是mysql的乐观锁和悲观锁。

实例中使用springboot整合mybatis,一并记录了。

添加依赖:

mysql

mysql-connector-java

org.mybatis.spring.boot

mybatis-spring-boot-starter

1.1.1

application.properties配置:

mybatis.type-aliases-package=com.lin.learn.mysql

spring.datasource.driverClassName = com.mysql.jdbc.Driver

spring.datasource.url = jdbc:mysql://192.168.0.103:3306/mysql?useUnicode=true&characterEncoding=utf-8

spring.datasource.username = root

spring.datasource.password = root

实体类:

public class TestEntity {

private int id;

private int count;

private int version;

public int getId() {

return id;

}

public void setId(int id) {

this.id = id;

}

public int getCount() {

return count;

}

public void setCount(int count) {

this.count = count;

}

public int getVersion() {

return version;

}

public void setVersion(int version) {

this.version = version;

}

}

加上相关注解:

@SpringBootApplication

@MapperScan("com.lin.learn.mysql")

public class App {

public static void main(String[] args) {

ApplicationContext applicationContext = SpringApplication.run(App.class, args);

}

}

乐观锁

就是使用一个version字段标识数据的当前版本,每次更新数据的时候同时更新version = version + 1,where条件中需要加上version等于当前事务查询出的数据的version,如果version的值已经改变,则更新失败。

Mapper:

public interface TestMapper {

@Select("select * from `test` where `id` = #{id} for update")

@Results({

@Result(column = "id", property = "id", javaType = Integer.class),

@Result(column = "count", property = "count", javaType = Integer.class),

@Result(column = "version", property = "version", javaType = Integer.class)

})

public TestEntity getById(@Param("id") int id);

@Update("update `test` set `count` = #{count}, `version` = #{version} + 1 where `id` = #{id} and `version` = #{version}")

public int update(TestEntity testEntity);

}

业务代码,就是数据库一个数据修改:

import org.springframework.stereotype.Component;

import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;

@Component

public class TestService {

@Resource

private TestMapper mapper;

public void increaseCount(int id) {

while (true) {

try {

TestEntity testEntity = mapper.getById(id);

testEntity.setCount(testEntity.getCount() + 1);

int updateCount = mapper.update(testEntity);

if(updateCount > 0) {

break;

}

} catch (Exception e) {

e.printStackTrace();

}

}

}

}

测试:

import com.lin.learn.mysql.TestService;

import org.mybatis.spring.annotation.MapperScan;

import org.springframework.boot.SpringApplication;

import org.springframework.boot.autoconfigure.SpringBootApplication;

import org.springframework.context.ApplicationContext;

@SpringBootApplication

@MapperScan("com.lin.learn.mysql")

public class App {

public static void main(String[] args) {

ApplicationContext applicationContext = SpringApplication.run(App.class, args);

TestService service = applicationContext.getBean(TestService.class);

new Thread(new Runnable() {

@Override

public void run() {

for(int i = 0; i < 10; i++) {

service.increaseCount(3);

}

}

}).start();

new Thread(new Runnable() {

@Override

public void run() {

for(int i = 0; i < 10; i++) {

service.increaseCount(3);

}

}

}).start();

new Thread(new Runnable() {

@Override

public void run() {

for(int i = 0; i < 10; i++) {

service.increaseCount(3);

}

}

}).start();

}

}

上面代码执行后数据库数据最终结果得到正确的数据。

悲观锁

本质上就是用select for update锁行,需要注意的是如果where子句条件没有命中索引将导致锁表。并且查询和更新操作都需要在同一个事务里里面。

在Mapper添加新的更新方法:

import org.apache.ibatis.annotations.*;

public interface TestMapper {

@Select("select * from `test` where `id` = #{id} for update")

@Results({

@Result(column = "id", property = "id", javaType = Integer.class),

@Result(column = "count", property = "count", javaType = Integer.class),

@Result(column = "version", property = "version", javaType = Integer.class)

})

public TestEntity getById(@Param("id") int id);

@Update("update `test` set `count` = #{count}, `version` = #{version} + 1 where `id` = #{id} and `version` = #{version}")

public int update(TestEntity testEntity);

@Update("update `test` set `count` = #{count} where `id` = #{id}")

public void updateNoSafe(TestEntity testEntity);

}

在业务代码里面添加新的方法:

import org.springframework.stereotype.Component;

import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;

@Component

public class TestService {

@Resource

private TestMapper mapper;

public void increaseCount(int id) {

while (true) {

try {

TestEntity testEntity = mapper.getById(id);

testEntity.setCount(testEntity.getCount() + 1);

int updateCount = mapper.update(testEntity);

if(updateCount > 0) {

break;

}

} catch (Exception e) {

e.printStackTrace();

}

}

}

@Transactional

public void increaseNoSafe(int id) {

try {

TestEntity testEntity = mapper.getById(id);

testEntity.setCount(testEntity.getCount() + 1);

mapper.updateNoSafe(testEntity);

} catch (Exception e) {

e.printStackTrace();

}

}

}

这里是用@Transaction注解声明这个更新数据的事务。

测试:

import com.lin.learn.mysql.TestService;

import org.mybatis.spring.annotation.MapperScan;

import org.springframework.boot.SpringApplication;

import org.springframework.boot.autoconfigure.SpringBootApplication;

import org.springframework.context.ApplicationContext;

@SpringBootApplication

@MapperScan("com.lin.learn.mysql")

public class App {

public static void main(String[] args) {

ApplicationContext applicationContext = SpringApplication.run(App.class, args);

TestService service = applicationContext.getBean(TestService.class);

new Thread(new Runnable() {

@Override

public void run() {

for(int i = 0; i < 10; i++) {

service.increaseNoSafe(3);

}

}

}).start();

new Thread(new Runnable() {

@Override

public void run() {

for(int i = 0; i < 10; i++) {

service.increaseNoSafe(3);

}

}

}).start();

new Thread(new Runnable() {

@Override

public void run() {

for(int i = 0; i < 10; i++) {

service.increaseNoSafe(3);

}

}

}).start();

}

}

最终结果能得到正确的数据。

在Spring Boot中使用MyBatis操作MySQL时,可能会遇到锁表的情况。锁表是指在数据库操作期间,某个表被其他事务或线程锁定,导致当前事务无法访问或修改该表的数据。 要解决这个问题,可以考虑以下几个方面: 1. 事务隔离级别:通过设置事务的隔离级别来控制并发访问数据库时的锁定行为。在Spring Boot中,默认的事务隔离级别是READ_COMMITTED,可以根据实际需求调整为其他级别,如REPEATABLE_READ或SERIALIZABLE。 ```java @Transactional(isolation = Isolation.REPEATABLE_READ) public void myMethod() { // 业务逻辑 } ``` 2. 并发控制:使用数据库的并发控制机制来避免或减少锁表情况的发生。例如,在MyBatis中可以使用乐观锁悲观锁实现并发控制。乐观锁通过版本号或时间戳等机制来判断数据是否被修改,而悲观锁则是在访问数据之前就将其锁定,直到事务完成为止。 3. SQL优化:通过对SQL语句进行优化,减少对同一数据资源的并发访问,从而减少锁表的可能性。例如,避免不必要的全表扫描、减少事务的持有时间等。 4. 分表分库:如果系统中某个表的并发访问频率非常高,可以考虑将其进行分表或分库处理,将数据分散到多个物理存储中,从而提高系统的并发性能。 需要注意的是,锁表是一个复杂的问题,具体的解决方案需要根据实际情况进行调整优化。以上只是一些常见的方法,具体的解决方案还需要结合业务场景实际需求来确定。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值