深入解析Mybatis页面数据不更新问题与一级缓存机制

前言

在我使用mybatis替换JDBC重构用户操作的CRUD时,发现了一个非常奇怪的问题:

当我进行新增和修改等操作时,查看后台和数据库,发现数据已经新增或修改了,但是页面还是显示第一次登录时的数据,除非我们重启服务器,再次查看(此时数据库又变成了最新的状态)。

我开始以为是JSP和页面显示的错误,因此在前端页面进行排查错误,始终未发现错误,我又回到后端,理解mybatis工作原理,终于发现了问题所在,因此特别在这里总结一下,为了帮助更多的人能够发现问题,解决问题。

有问题的项目是(可以在我的资源中下载源码) :

 问题出现分析

当前的数据库表结构及数据如下:

忽略user_id的不同(不规律),因为我采用了主键自增的语法,并且我进行了一些列的操作(删除),所以看上去id不是规律的(这里只关注数据行的变化

运行项目:

输入数据库中存在的数据,进行登录。

查看数据:点击上下页,发现数据正确,且展示出来了。

接下来,新增用户:

发现页面没有更新(如果更新了,数据应该显示在最上方)。

查看后台和数据库:

查看控制台,发现已经执行了insert操作。

 刷新数据库,数据已经增加。

此时发生了数据库数据增加,但是前台不更新数据。

问题原因

 因为每次查看分页数据都需要请求/userPage这里url,因此要找对应的servlet进行分析

 对应的servlet如下:

 UserPageServlet

package servlet;


import dao.UserDao;

import entity.User;
import entity.dto.UserDto;
import org.apache.ibatis.session.SqlSession;
import util.PageUtil;
import util.SqlSessionFactoryUtil;

import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.List;

@WebServlet("/userPage")
public class UserPageServlet extends HttpServlet {
    SqlSession session= SqlSessionFactoryUtil.getSessionSql();
    UserDao userDao= session.getMapper(UserDao.class);

    @Override
    protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {


        String keyword= request.getParameter("keyword");
        if (keyword==null){//当关键字为null时,设置为空,防止没有进行模糊查询时,sql失效
            keyword="";
        }
        String currentPage =request.getParameter("pageIndex");
        if (currentPage==null){//防止第一次访问servlet时,当前页为空,设置为第一页
            currentPage="1";
        }
        int pageIndex = Integer.parseInt(currentPage);
//        UserDao userDao =new UserDaoImpl();
        int count = userDao.selectUsersCount();
        //总页数
        int totalPages= PageUtil.getTotalPages(count,PageUtil.PAGE_SIZE);
        //防止查询越界
        if (pageIndex<1){
            pageIndex=1;
        }
        if (pageIndex>totalPages){
            pageIndex=totalPages;
        }
        UserDto userDto =new UserDto();

        userDto.setKeyword(keyword);
//        System.out.println(userDto.getKeyword());

        userDto.setPageIndex(PageUtil.PAGE_SIZE*(pageIndex-1));

//        System.out.println(userDto.getPageIndex());
        userDto.setPageSize(PageUtil.PAGE_SIZE);

        List<User> list= userDao.selectUserListByPage(userDto);

        //在request作用域设置值,仅在同一个请求中有效
        request.setAttribute("list",list);
        for (User user : list) {
            System.out.println(user);
        }
        request.setAttribute("keyword",keyword);
        request.setAttribute("pageIndex",pageIndex);
        request.setAttribute("totalPages",totalPages);
        //转发当前请求到userList.jsp
        request.getRequestDispatcher("/front/userList.jsp").forward(request,response);
//        request.getRequestDispatcher("zhezhaoceng.jsp").forward(request,response);

    }

    @Override
    protected void doGet(HttpServletRequest req, HttpServletResponse resp) throws ServletException, IOException {
        doPost(req,resp);
    }
}

原因解析:

我们将SqlSession放在全局变量的作用范围,变成了servlet的一个属性,而servlet在整个应用的生命周期是单例的(并且没有调用close方法关闭SqlSession实例),导致前端的每次http请求,对应后端的一个线程处理时,每个线程使用了同一个SqlSession,导致每次查询的都是第一次存储在一级缓存中的数据 ,所以页面数据不会更新。

 什么是一级缓存?

一级缓存: 基于 PerpetualCache 的 HashMap 本地缓存,其存储作用域为 Session,当 Session flush 或 close 之后,该 Session 中的所有 Cache 就将清空,默认打开一级缓存。

一级缓存也是session级缓存,在session未关闭的情况的下,多次执行相同的查询 ,会直接取缓存数据,不再查询数据库

sesson是线程不安全的,使用完要关闭。

 SqlSession为什么不应该共享?

SqlSession 每个线程都应该有它自己的SqlSession实例。SqlSession的实例不能被共享,也是线程不安全的。因此最佳的范围是请求或方法范围。使用Web框架时,要考虑SqlSession放在一个和HTTP请求对象相似的范围内。基于收到的HTTP请求,打开了一个SqlSession,返回响应后关闭。关闭Session很重要,应该确保使用finally块来关闭它。基本模式:

SqlSession session = sqlSessionFactory.openSession();
 try { 
    // do work
 } finally {
 session.close(); 
}

重点:在使用SqlSession时,使用完成后需要关闭。

 解决方案

将SqlSession从属性变为局部变量:

将SqlSession从全局变量变为局部变量,每次调用doPost方法时,相当于获取一个新的SqlSession对象,此时需要查询数据时,要从数据库中查找,而不是从一级缓存中查找,从而将页面的数据更新。

总结 

Mybatis默认是有一个一级缓存的机制的,当我们查询数据库中的数据时,同一个的SqlSession中的多次sql操作,都是操作缓存区中的数据,而不是从数据库中直接获取,从而导致了页面数据和数据库数据的不一致现象(或者说缓存和数据库中数据不一致的现象)

如果缓存中存在数据,则直接从缓存中返回数据到dao 

 

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值