CREATE TABLE `users` (
`name` varchar(200) CHARACTER SET utf8mb4 COLLATE utf8mb4_0900_ai_ci NOT NULL,
`sex` varchar(2) NOT NULL,
`AGE` int(11) NOT NULL,
`email` varchar(32) NOT NULL,
`password` varchar(40) NOT NULL,
`id` int(11) NOT NULL AUTO_INCREMENT,
PRIMARY KEY (`id`),
UNIQUE KEY `name_unique` (`name`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=14 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci
INSERT INTO `dba6`.`users`(`name`, `sex`, `AGE`, `email`, `password`, `id`) VALUES ('张三', '男', 11, '111@163.con', '112233', 1);
import com.example.mybatis_plus.entity.UsersEntity;
import com.example.mybatis_plus.mapper.UsersDao;
import com.example.mybatis_plus.service.UsersService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Isolation;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
@Service("usersService")
public class UsersServiceImpl implements UsersService {
@Autowired
private UsersDao usersDao;
@Override
@Transactional(rollbackFor = Exception.class,isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRES_NEW)
public void updateUser() throws InterruptedException {
System.out.println("开始执行了");
UsersEntity usersEntity=usersDao.selectById(1);
usersEntity.setName("李四");
usersDao.updateById(usersEntity);
Thread.sleep(20*000);
}
@Override
@Transactional(rollbackFor = Exception.class,isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRES_NEW)
public void getUser() throws InterruptedException {
UsersEntity usersEntity = usersDao.selectById(1);
System.out.println(usersEntity);
Thread.sleep(60*1000);
usersEntity = usersDao.selectById(1);
System.out.println(usersEntity);
}
}
UsersEntity(name=张三, sex=男, age=11, email=111@163.con, password=112233, id=1)
开始执行了
UsersEntity(name=张三, sex=男, age=11, email=111@163.con, password=112233, id=1)
缓存问题,数据库应该已经改成李四了
一级缓存作用域是sqlsession级别的,同一个sqlsession中执行相同的sql查询(相同的sql和参数),第一次会去查询数据库并写到缓存中,第二次从一级缓存中取。
参考GPT和自己的思路:根据您提供的代码和数据库表结构,我分析您的问题可能是由于您的事务隔离级别为READ_COMMITTED,这意味着每个事务只能看到已经提交的数据。但是在您的代码中,您在updateUser方法中修改了id=1的用户数据并将事务挂起20秒钟,然后在getUser方法中,您立即尝试读取id=1的用户数据,但此时updateUser事务尚未提交,因此您的查询仍然看到未修改的数据。
如果您希望在getUser方法中看到updateUser方法所做的更改,您需要等待updateUser事务提交,然后才能在另一个事务中看到更改。如果您在updateUser方法中提交事务,则在getUser方法中立即查看更改。所以您可以尝试将updateUser方法中的事务提交。
如果您希望在getUser方法中看到未提交的更改,则可以将事务隔离级别设置为READ_UNCOMMITTED。但是,这可能会导致脏读(读取未提交的数据)和不可重复读(同一事务内两次读取的数据不一致),因此需要谨慎使用。
另外,我建议您使用Spring的@Service注解来注入服务,而不是使用字符串名称,如下所示:
@Service
public class UsersServiceImpl implements UsersService {
...
}
这个问题可能涉及到数据库事务的隔离级别和并发问题,需要进一步排查。以下是一个可能的修改方案,添加了对数据库事务的异常处理和日志记录,以及对线程休眠的异常处理。
@Service("usersService")
public class UsersServiceImpl implements UsersService {
private static final Logger logger = LoggerFactory.getLogger(UsersServiceImpl.class);
@Autowired
private UsersDao usersDao;
@Override
@Transactional(rollbackFor = Exception.class, isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRES_NEW)
public void updateUser() throws Exception {
logger.info("updateUser: 开始执行了");
UsersEntity usersEntity = usersDao.selectById(1);
usersEntity.setName("李四");
usersDao.updateById(usersEntity);
Thread.sleep(20 * 1000);
logger.info("updateUser: 执行完成");
}
@Override
@Transactional(rollbackFor = Exception.class, isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRES_NEW)
public void getUser() throws Exception {
UsersEntity usersEntity = usersDao.selectById(1);
logger.info("getUser第一次执行: {}", usersEntity);
Thread.sleep(60 * 1000);
usersEntity = usersDao.selectById(1);
logger.info("getUser第二次执行: {}", usersEntity);
}
}
在实际应用中,我们还需要考虑如何处理并发读写操作的情况,以及如何提高数据库事务的性能。
可能和缓存有关,你试试把你的getUser方法改成这样试试:
@Override
@Transactional(rollbackFor = Exception.class, isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRES_NEW)
public void getUser() throws InterruptedException {
UsersEntity usersEntity = usersDao.selectByIdForUpdate(1);
System.out.println(usersEntity);
Thread.sleep(60 * 1000);
usersEntity = usersDao.selectByIdForUpdate(1);
System.out.println(usersEntity);
}
然后在application.properties中添加以下配置以禁用查询缓存,这样每次查询都会从数据库中获取最新数据:
spring.jpa.properties.hibernate.cache.use_query_cache=false
spring.jpa.properties.hibernate.cache.use_second_level_cache=false
看看这样能不能解决
你在updateUser()方法中的休眠等待时间写错了,你写的是 Thread.sleep(20x000),变成等待0秒了,应该是Thread.sleep(20x1000),中间是星号,你改下,这里星号打出来有问题。你先把这个修改过来。
该回答引用ChatGPT
根据提供的代码和信息,似乎问题与事务使用的隔离级别有关。
在updateUser()和getUser()方法中,都使用了Isolation.READ_COMMITTED隔离级别,这意味着每个事务可以从其他事务读取已提交的数据,但可能导致不可重复读取。
当updateUser()将用户的名称更新为“李四”时,它提交事务并释放数据上的锁。但是,由于getUser()仍在运行并且已经在更新之前读取了数据,因此它可能在下次读取之前无法看到更新的值。
为了解决这个问题,可以使用更高的隔离级别,如Isolation.REPEATABLE_READ或Isolation.SERIALIZABLE,这些级别提供了更多的一致性保证,但可能影响并发性;或者修改代码,确保getUser()方法在updateUser()方法进行更新后重新读取数据。
例如,您可以将getUser()方法修改为以下内容:
@Override
@Transactional(rollbackFor = Exception.class,isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRES_NEW)
public void getUser() throws InterruptedException {
UsersEntity usersEntity = usersDao.selectById(1);
System.out.println(usersEntity);
Thread.sleep(60*1000);
// 在休眠后重新读取数据
usersEntity = usersDao.selectById(1);
System.out.println(usersEntity);
}
通过在休眠后重新读取数据,getUser()可以看到名称字段的更新值。
你把查询的条件改一下,将第二次查询的范围增加,包含第一次查询的内容,然后进行比较。 置于是缓存问题还是事务提交模式未生效问题,可以逐点排查