【Java框架】Mybatis教程(二)——SQL映射及缓存

目录

SQL传参

1.单个简单参数

  • 单个参数的传参比较简单,可以是任意形式的,比如#{a} 、#{b} 或者#{param1} ,但是为了开发规范,尽量使用和入参时一样。
  • 传入单个简单参数时,xml中parameterType可写可不写,但是写的话一定保证值与参数类型/别名一致

mapper接口

java 复制代码
User getUserById(int id);

使用1

xml

xml 复制代码
    <select id="getUserById" parameterType="int" resultType="User">
        SELECT * FROM SMBMS_USER WHERE ID=#{id}
    </select>

这种情况#{}中可以是任意名称,但是不规范,规范写法还是和mapper接口中的形参名保持一致

使用2

xml

xml 复制代码
    <select id="getUserById" parameterType="int" resultType="User">
        SELECT * FROM SMBMS_USER WHERE ID=#{uid}
    </select>

这种情况#{}中只能是uid了,@Param注解相当于是绑定了uid这个名称入参,在使用时就只能使用uid了

2.多个简单参数

mapper接口

java 复制代码
List<User> getUserList(String userName,int userRole);

2.1使用索引【不推荐】

  • 多个参数可以使用类似于索引的方式传值,比如: #{param1} 对应第一个参数, #{param2} 对应第二个参数...
  • 此时xml中的parametType不写
    xml
java 复制代码
	<select id="getUserList" resultType="User">
        SELECT * FROM SMBMS_USER WHERE USERNAME=#{param1} AND USERROLE=#{param2}
    </select>

2.2使用@Param

  • @Param 这个注解用于指定key,一旦指定了key,在SQL中即可对应的key入参。
  • 此时xml中的parametType不写
    mapper
java 复制代码
List<User> getUserList(@Param("userName")String userName,
                           @Param("userRole")int userRole);

xml

xml 复制代码
    <select id="getUserList" resultType="User">
        SELECT * FROM SMBMS_USER WHERE USERNAME=#{userName} AND USERROLE=#{userRole}
    </select>

3.复杂参数

3.1对象

mapper

java 复制代码
   int insertUser(User user);

xml

xml 复制代码
	<insert id="insertUser" parameterType="com.zjl.pojo.User">
        INSERT INTO SMBMS_USER(id,userCode,userName,birthday)
        VALUES(default,#{userCode},#{userName},#{birthday})
    </insert>

3.2集合(Map)

Mybatis底层就是将入参转换成Map ,入参传Map当然也行,此时#{key} 中的key 就 对 应 Map 中 的 key 。

mapper

java 复制代码
   List<User> getUserListByMap(Map<String,Object> paramMap);

xml

java 复制代码
    <select id="getUserListByMap" parameterType="map" resultType="User">
        SELECT * FROM SMBMS_USER WHERE USERNAME=#{queryName} AND USERROLE=#{queryRole}
    </select>

测试

java 复制代码
    @Test
    public void getUserListByMap(){
        SqlSession session = factory.openSession();
        UserMapper userMapper = session.getMapper(UserMapper.class);
        Map<String,Object> paramMap = new HashMap<String, Object>();
        paramMap.put("queryName","系统管理员");
        paramMap.put("queryRole",1);
        List<User> userList = userMapper.getUserListByMap(paramMap);
        System.out.println(userList);
        session.close();
    }

List集合和数据这种情况是需要结合批量操作一起使用的

Mybatis中的批量操作

1.批量查询

批量查询相当于SQL中的select xx from table where colum in (v1,v2,v3)

1.1数组入参

要求:foreach 标签中的collection属性值等于array

mapper

java 复制代码
    List<User> getUserListByids(int[] ids);

xml

xml 复制代码
    <select id="getUserListByids" resultType="User" parameterType="int">
        SELECT * FROM SMBMS_USER WHERE ID IN
        <foreach collection="array" item="id" open="(" separator="," close=")">
            #{id}
        </foreach>
    </select>

1.2List入参

要求:foreach 标签中的collection属性值等于list

java 复制代码
    List<User> getUserListByids(List<Integer> idList);

xml

xml 复制代码
    <select id="getUserListByids" resultType="User" parameterType="int">
        SELECT * FROM SMBMS_USER WHERE ID IN
        <foreach collection="list" item="id" open="(" separator="," close=")">
            #{id}
        </foreach>
    </select>

1.3Map中有list入参

要求:foreach 标签中的collection属性值等于Map中List集合的key

mapper

java 复制代码
    List<User> getUserListByMap(Map<String,Object> paramMap);

xml

xml 复制代码
    <select id="getUserListByMap" parameterType="map" resultType="User">
        SELECT * FROM SMBMS_USER WHERE GENDER = #{gender}
        AND USERROLE IN
        <foreach collection="idList" item="userRole" open="(" separator="," close=")">
            #{userRole}
        </foreach>
    </select>

测试

java 复制代码
    @Test
    public void getUserListByMap(){
        SqlSession session = factory.openSession();
        UserMapper userMapper = session.getMapper(UserMapper.class);
        List<Integer> roleList = new ArrayList<Integer>();
        Collections.addAll(roleList,2,4,6);
        Map<String,Object> paramMap = new HashMap<String, Object>();
        paramMap.put("idList",roleList);
        paramMap.put("gender",1);
        List<User> userList = userMapper.getUserListByMap(paramMap);
        System.out.println(userList);
        session.close();
    }

SQL映射的XML文件

MyBatis 真正的强大在于SQL映射语句,也是它的魅力所在,专注于SQL,功能强大,SQL映射的配置却是相当简单

SQL映射文件的几个顶级元素(按照定义的顺序)

  • mapper -- namespace
  • cache -- 配置给定命名空间的缓存
  • cache-ref -- 从其他命名空间引用缓存配置
  • resultMap --用来描述数据库结果集和对象的对应关系
  • sql -- 可以重用的SQL块,也可以被其他语句引用
  • insert -- 映射插入语句
  • update -- 映射更新语句
  • delete -- 映射删除语句
  • select -- 映射查询语句
    select、insert、update、delete这四个标签的基本使用上一张已经讲过了,这里就不再将基本使用了

resultMap

resultMap属性

  • id:resultMap的唯一标识
  • type:Java实体类

修改实体类

java 复制代码
private String password;//数据库名称为userPassword

思考:当数据库的字段名与实体类中的属性名不一致时,应该怎么办

方式1:在查询时,给字段取别名,与实体类属性名保持一致

xml 复制代码
    <select id="selectUserByUserCode" parameterType="string" resultType="User">
        SELECT ID,USERNAME,USERPASSWORD AS PASSWORD WHERE USERCODE=#{userCode}
    </select>

方式2:resultMap手动映射不一致的两个名字

注意!!!

  • column的值不是数据库表字段名称,而是查询结果的字段名称。
  • 也就是说手动映射不是将实体类属性名与数据库表字段名映射,而是将实体类属性名与查询结果的字段名映射。
  • 其次,select中如果使用手动映射返回结果,则不能使用resultType,而是使用resultMap。
xml 复制代码
    <resultMap id="userMap" type="User">
        <id column="id" property="id"/>
        <result column="userCode" property="userCode"/>
        <result column="userName" property="userName"/>
        <result column="userPassword" property="password"/>
        <result column="gender" property="gender"/>
        <result column="birthday" property="birthday"/>
        <result column="phone" property="phone"/>
    </resultMap>
    
    <select id="selectUserByUserCode" parameterType="string" resultMap="userMap">
        SELECT ID,USERNAME,USERPASSWORD WHERE USERCODE=#{userCode}
    </select>

补充

  • 其实mybatis默认是自动映射的,也就是说查询结果的字段名如果跟实体类属性名一致了的话,就不用手动映射
  • 也就是说在默认配置情况下,resultMap也只需要配置不同字段名的映射即可
java 复制代码
    <resultMap id="userMap" type="User">
  			<!-- 一致的属性和字段名就不用写了 -->
        <result column="phone" property="phone"/>
    </resultMap>
  • resultMap的自动映射级别-autoMappingBehavior,具体参考上一章的settings标签中的属性

resultMap子元素

面试题:MyBatis实现一对一和一对多有几种方式?具体怎么操作的?

答:

  • 有联合查询和嵌套查询,联合查询是几个表联合查询,只查询一次, 通过在resultMap里面配置association节点配置一对一的类就可以完成;

  • 嵌套查询是先查一个表,根据这个表里面的结果的外键id,去再另外一个表里面查询数据,也是通过association/collection配置,但另外一个表的查询通过select属性配置。

resultMap属性

  • id:一般对应数据库中该行的主键id,设置此项可提高MyBatis性能
  • result:映射到JavaBean的某个"简单类型"属性
  • association:映射到JavaBean的某个"复杂类型"属性,比如JavaBean类
  • collection:映射到JavaBean的某个"复杂类型"属性,比如集合

association

  • 复杂的类型关联,一对一
  • 内部嵌套
    • 映射一个嵌套JavaBean属性
  • 属性
    • property:映射数据库列的实体对象的属性
    • javaType:完整Java类名或者别名
    • resultMap:引用外部resultMap
  • 子元素
    • id
    • result
      • property:映射数据库列的实体对象的属性
      • column:数据库列名或者别名
代码示例:一对一联合查询

实体类

java 复制代码
@Data
public class Role {
    private int id;
    private String roleName;
}

@Data
@ToString
public class User implements Serializable {
    private long id;
    private String userCode;
    private String userName;
    private String password;//数据库名称为userPassword
    private int gender;
    private Date birthday;
    private String phone;
    private String address;
    private int userRole;

    private Role role;
}

mapper

java 复制代码
   User getUserAndRoleByUserId(int uid);

xml:注意看注释!!!!

xml 复制代码
    <resultMap id="userMap" type="User" autoMapping="true">
        <id column="id" property="id"/>
        <result column="userPassword" property="password"/>
        <!--  autoMapping="true"必须要加,否则不会自动映射 -->
        <association property="role" javaType="Role"><!-- 这个role是User实体类中的role属性 -->
            <id column="roleId" property="id"/><!-- 这个roleId是查询结果中的roleId,id是role实体类中的id -->
        </association>
    </resultMap>
    <select id="getUserAndRoleByUserId" parameterType="int" resultMap="userMap">
        SELECT u.id,u.userCode,u.userName,u.userPassword,u.gender,u.birthday,u.phone,u.address,
               r.id AS roleId,r.roleName
        FROM SMBMS_USER u,SMBMS_ROLE r
        WHERE u.userRole=r.id AND u.id=#{uid}
    </select>

collection

  • 复杂类型集合,一对多
  • 内部嵌套
    • 映射一个嵌套结果集到一个列表
  • 属性
    • property:映射数据库列的实体对象的属性
    • ofType:完整Java类名或者别名(集合所包括的类型)
    • resultMap:引用外部resultMap
  • 子元素
    • id
    • result
      • property:映射数据库列的实体对象的属性
      • column:数据库列名或者别名
代码示例:一对多联合查询

实体类

java 复制代码
@Data
public class Address {
    private int id;
    private String addressDesc;
    private int postCode;
    private String tel;
    private String contact;
}

@Data
@ToString
public class User implements Serializable {
    private long id;
    private String userCode;
    private String userName;
    private String password;//数据库名称为userPassword
    private int gender;
    private Date birthday;
    private String phone;
    private String address;
    private int userRole;

    private Role role;

	private List<Address> addressList;
}

mapper

java 复制代码
   User getUserAndAddressListByUserId(int uid);

xml

xml 复制代码
	<resultMap id="userMap" type="User" autoMapping="true">
        <id column="id" property="id"/>
        <result column="userPassword" property="password"/>
        <!--  autoMapping="true"必须要加,否则不会自动映射 -->
        <association property="role" javaType="Role" autoMapping="true"><!-- 这个role是User实体类中的role属性 -->
            <id column="roleId" property="id"/><!-- 这个roleId是查询结果中的roleId,id是role实体类中的id -->
        </association>
        <!--  autoMapping="true"必须要加,否则不会自动映射 -->
        <collection property="addressList" ofType="Address" autoMapping="true"><!-- 这个addressList是User实体类中的addressList属性 -->
            <id column="addressId" property="id"/><!-- 这个addressId是查询结果中的addressId,id是address实体类中的id -->
        </collection>
    </resultMap>

    <select id="getUserAndAddressListByUserId" parameterType="int" resultMap="userMap">
        SELECT u.id,u.userCode,u.userName,u.userPassword,u.gender,u.birthday,u.phone,u.address,
               a.id AS addressId,a.contact,a.addressDesc,a.postCode,a.tel
        FROM SMBMS_USER u,SMBMS_ADDRESS a
        WHERE u.id=a.userId AND u.id=#{uid}
    </select>

resultMap自动映射(autoMappingBehavior)的三个匹配级别

  • NONE:禁止自动匹配
  • PARTIAL(默认):自动匹配所有属性,内部嵌套除外
  • FULL:自动匹配所有

面试题:resultType与resultMap的区别

对象不同 描述不同 类型适用不同
resultMap resultMap如果查询出来的列名和pojo的属性名不一致,通过定义一个resultMap对列名和pojo属性名之间作一个映射关系。 resultMap对于一对一表连接的处理方式通常为在主表的pojo中添加嵌套另一个表的pojo,然后在mapper.xml中采用association节点元素进行对另一个表的连接处理。 mybatis中在查询进行select映射的时候,返回类型可以用resultType,也可以用resultMap。
resultType resultType使用resultType进行输出映射,只有查询出来的列名和pojo中的属性名一致,该列才可以映射成功。 resultType无法查询结果映射到pojo对象的pojo属性中,根据对结构集查询遍历的需要选择使用resultType还是resultMap。适用于单表查询。 resultType是直接表示返回类型的,而resultMap则是对外部ResultMap的引用,但是resultType跟resultMap不能同时存在。

select属性小结

属性 描述
id 在命名空间中唯一的标识符,可以被用来引用这条语句
parameterType 将会传入这条语句的参数类的完全限定名或别名
resultType 从这条语句中返回的期望类型的类的完全限定名或别名。注意集合情形,那应该是集合可以包含的类型,而不能是集合本身。使用resultType或resultMap,但不能同时使用
resultMap 命名引用外部的resultMap
flushCache 将其设置为true,不论语句什么时候被调用,都会导致缓存被清空。默认值:false
useCache 将其设置为true,将会导致本条语句的结果被缓存。默认值:true
timeout 这个设置驱动程序等待数据库返回请求结果,并抛出异常时间的最大等待值。默认不设置(驱动自行处理)
fetchSize 这是暗示驱动程序每次批量返回的结果行数
statementType STATEMENT,PREPARED或CALLABLE的一种。让MyBatis选择使用Statement,PreparedStatement或CallableStatement。默认值:PREPARED
resultSetType FORWARD_ONLY

Mybatis缓存

  • 一级缓存
  • 二级缓存
  • 二级缓存的配置
    • MyBatis的全局cache配置
    • 在Mapper XML文件中设置缓存,默认情况下是没有开启缓存的
    • 在Mapper XML文件配置支持cache后,如果需要对个别查询进行调整,可以单独设置cache

一级缓存:默认开启

代码测试

java 复制代码
    @Test
    public void getUserCountForMapper(){
        SqlSession session = factory.openSession();
        UserMapper userMapper = session.getMapper(UserMapper.class);
        userMapper.getUserCount();//第一次查询
        System.out.println("==================================");
        userMapper.getUserCount();//一模一样的第二次查询
        session.close();
    }

日志

java 复制代码
[DEBUG] 2024-04-15 22:30:26,026 org.apache.ibatis.transaction.jdbc.JdbcTransaction - Opening JDBC Connection
[DEBUG] 2024-04-15 22:30:26,289 org.apache.ibatis.datasource.pooled.PooledDataSource - Created connection 1896552614.
[DEBUG] 2024-04-15 22:30:26,289 org.apache.ibatis.transaction.jdbc.JdbcTransaction - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@710b18a6]
[DEBUG] 2024-04-15 22:30:26,291 com.zjl.mapper.UserMapper.getUserCount - ==>  Preparing: SELECT COUNT(1) FROM SMBMS_USER 
[DEBUG] 2024-04-15 22:30:26,314 com.zjl.mapper.UserMapper.getUserCount - ==> Parameters: 
[DEBUG] 2024-04-15 22:30:26,325 com.zjl.mapper.UserMapper.getUserCount - <==      Total: 1
==================================
[DEBUG] 2024-04-15 22:30:26,326 org.apache.ibatis.transaction.jdbc.JdbcTransaction - Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@710b18a6]
[DEBUG] 2024-04-15 22:30:26,326 org.apache.ibatis.transaction.jdbc.JdbcTransaction - Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@710b18a6]
[DEBUG] 2024-04-15 22:30:26,326 org.apache.ibatis.datasource.pooled.PooledDataSource - Returned connection 1896552614 to pool.
Disconnected from the target VM, address: '127.0.0.1:1653', transport: 'socket'

Process finished with exit code 0

可以看到两次一模一样的SQL,只会执行一次,其实从第二次开始,就是从一级缓存中直接去拿结果的。

当然,这是在同一个SqlSession的情况下,那如果是两个sqlSession对象呢?

java 复制代码
    @Test
    public void getUserCountForMapper(){
        SqlSession session = factory.openSession();
        UserMapper userMapper = session.getMapper(UserMapper.class);
        userMapper.getUserCount();//第一次查询
        session.close();
        System.out.println("==================================");
        session = factory.openSession();
        userMapper = session.getMapper(UserMapper.class);
        userMapper.getUserCount();//一模一样的第二次查询
        session.close();
    }
java 复制代码
[DEBUG] 2024-04-15 22:36:31,332 org.apache.ibatis.transaction.jdbc.JdbcTransaction - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@710b18a6]
[DEBUG] 2024-04-15 22:36:31,334 com.zjl.mapper.UserMapper.getUserCount - ==>  Preparing: SELECT COUNT(1) FROM SMBMS_USER 
[DEBUG] 2024-04-15 22:36:31,372 com.zjl.mapper.UserMapper.getUserCount - ==> Parameters: 
[DEBUG] 2024-04-15 22:36:31,406 com.zjl.mapper.UserMapper.getUserCount - <==      Total: 1
==================================
[DEBUG] 2024-04-15 22:36:31,406 org.apache.ibatis.transaction.jdbc.JdbcTransaction - Opening JDBC Connection
[DEBUG] 2024-04-15 22:36:31,424 org.apache.ibatis.datasource.pooled.PooledDataSource - Created connection 1544614339.
[DEBUG] 2024-04-15 22:36:31,424 org.apache.ibatis.transaction.jdbc.JdbcTransaction - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@5c10f1c3]
[DEBUG] 2024-04-15 22:36:31,425 com.zjl.mapper.UserMapper.getUserCount - ==>  Preparing: SELECT COUNT(1) FROM SMBMS_USER 
[DEBUG] 2024-04-15 22:36:31,425 com.zjl.mapper.UserMapper.getUserCount - ==> Parameters: 
[DEBUG] 2024-04-15 22:36:31,425 com.zjl.mapper.UserMapper.getUserCount - <==      Total: 1
[DEBUG] 2024-04-15 22:36:31,426 org.apache.ibatis.transaction.jdbc.JdbcTransaction - Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@5c10f1c3]
[DEBUG] 2024-04-15 22:36:31,426 org.apache.ibatis.transaction.jdbc.JdbcTransaction - Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@5c10f1c3]
[DEBUG] 2024-04-15 22:36:31,426 org.apache.ibatis.datasource.pooled.PooledDataSource - Returned connection 1544614339 to pool.
Disconnected from the target VM, address: '127.0.0.1:1776', transport: 'socket'

Process finished with exit code 0

可以看到,执行了两次SQL查询,因此缓存失效了

那么在同一个SqlSession对象的前提下如果第一次查询之后对这个表做了一次更新操作(增删改)呢?

java 复制代码
    public void getUserCountForMapper(){
        SqlSession session = factory.openSession();
        UserMapper userMapper = session.getMapper(UserMapper.class);
        userMapper.getUserCount();//第一次查询
        System.out.println("==================================");
        User user = new User(66,"zjl","在家里",new Date());
        userMapper.updateUser(user);
        userMapper.getUserCount();//一模一样的第二次查询
        session.close();
    }
java 复制代码
[DEBUG] 2024-04-15 22:39:59,234 org.apache.ibatis.transaction.jdbc.JdbcTransaction - Opening JDBC Connection
[DEBUG] 2024-04-15 22:39:59,475 org.apache.ibatis.datasource.pooled.PooledDataSource - Created connection 1896552614.
[DEBUG] 2024-04-15 22:39:59,475 org.apache.ibatis.transaction.jdbc.JdbcTransaction - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@710b18a6]
[DEBUG] 2024-04-15 22:39:59,479 com.zjl.mapper.UserMapper.getUserCount - ==>  Preparing: SELECT COUNT(1) FROM SMBMS_USER 
[DEBUG] 2024-04-15 22:39:59,511 com.zjl.mapper.UserMapper.getUserCount - ==> Parameters: 
[DEBUG] 2024-04-15 22:39:59,520 com.zjl.mapper.UserMapper.getUserCount - <==      Total: 1
==================================
[DEBUG] 2024-04-15 22:39:59,522 com.zjl.mapper.UserMapper.updateUser - ==>  Preparing: UPDATE SMBMS_USER SET userCode=?, userName=?, birthday=? WHERE id=? 
[DEBUG] 2024-04-15 22:39:59,526 com.zjl.mapper.UserMapper.updateUser - ==> Parameters: zjl(String), 在家里(String), 2024-04-15 22:39:59.521(Timestamp), 66(Long)
[DEBUG] 2024-04-15 22:39:59,555 com.zjl.mapper.UserMapper.updateUser - <==    Updates: 1
[DEBUG] 2024-04-15 22:39:59,555 com.zjl.mapper.UserMapper.getUserCount - ==>  Preparing: SELECT COUNT(1) FROM SMBMS_USER 
[DEBUG] 2024-04-15 22:39:59,555 com.zjl.mapper.UserMapper.getUserCount - ==> Parameters: 
[DEBUG] 2024-04-15 22:39:59,556 com.zjl.mapper.UserMapper.getUserCount - <==      Total: 1
[DEBUG] 2024-04-15 22:39:59,556 org.apache.ibatis.transaction.jdbc.JdbcTransaction - Rolling back JDBC Connection [com.mysql.jdbc.JDBC4Connection@710b18a6]
[DEBUG] 2024-04-15 22:39:59,662 org.apache.ibatis.transaction.jdbc.JdbcTransaction - Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@710b18a6]
[DEBUG] 2024-04-15 22:39:59,662 org.apache.ibatis.transaction.jdbc.JdbcTransaction - Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@710b18a6]
[DEBUG] 2024-04-15 22:39:59,663 org.apache.ibatis.datasource.pooled.PooledDataSource - Returned connection 1896552614 to pool.
Disconnected from the target VM, address: '127.0.0.1:1857', transport: 'socket'

Process finished with exit code 0

可以看到一共三次SQL,两次查询,因此 缓存失效了

二级缓存

开启二级缓存

  1. mybatis-config.xml中

    xml 复制代码
    <settings>
        <setting name="cacheEnabled" value="true"/>
    </settings>
  2. mapper.xml中

    java 复制代码
    <cache/>
  3. 查询sql中

xml 复制代码
<select id="selectAll" resultType="Emp"  useCache="true">

代码示例

xml 复制代码
    <cache/><!-- 开启二级缓存 -->
    <select id="getUserCount" resultType="int">
        SELECT COUNT(1) FROM SMBMS_USER
    </select>

一级缓存生效的情况就不在演示了,现在试一下同一个sqlsessionFactory,不同sqlSession走不走缓存

java 复制代码
    @Test
    public void getUserCountForMapper(){
        SqlSession session = factory.openSession();
        UserMapper userMapper = session.getMapper(UserMapper.class);
        userMapper.getUserCount();//第一次查询
        session.close();//测试时一定记得关闭上一个session
        System.out.println("==================================");
        session = factory.openSession();
        userMapper = session.getMapper(UserMapper.class);
        userMapper.getUserCount();//一模一样的第二次查询
        session.close();
    }

日志

java 复制代码
[DEBUG] 2024-04-15 22:54:00,214 org.apache.ibatis.datasource.pooled.PooledDataSource - Created connection 896138248.
[DEBUG] 2024-04-15 22:54:00,215 org.apache.ibatis.transaction.jdbc.JdbcTransaction - Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@3569fc08]
[DEBUG] 2024-04-15 22:54:00,217 com.zjl.mapper.UserMapper.getUserCount - ==>  Preparing: SELECT COUNT(1) FROM SMBMS_USER 
[DEBUG] 2024-04-15 22:54:00,244 com.zjl.mapper.UserMapper.getUserCount - ==> Parameters: 
[DEBUG] 2024-04-15 22:54:00,254 com.zjl.mapper.UserMapper.getUserCount - <==      Total: 1
[DEBUG] 2024-04-15 22:54:00,257 org.apache.ibatis.transaction.jdbc.JdbcTransaction - Resetting autocommit to true on JDBC Connection [com.mysql.jdbc.JDBC4Connection@3569fc08]
[DEBUG] 2024-04-15 22:54:00,257 org.apache.ibatis.transaction.jdbc.JdbcTransaction - Closing JDBC Connection [com.mysql.jdbc.JDBC4Connection@3569fc08]
[DEBUG] 2024-04-15 22:54:00,257 org.apache.ibatis.datasource.pooled.PooledDataSource - Returned connection 896138248 to pool.
==================================
[DEBUG] 2024-04-15 22:54:00,260 com.zjl.mapper.UserMapper - Cache Hit Ratio [com.zjl.mapper.UserMapper]: 0.5
Disconnected from the target VM, address: '127.0.0.1:2202', transport: 'socket'

Process finished with exit code 0

可以看到只有一次查询日志,因此二级缓存生效

失效情况就不再一一列举了:

  • 换了sqlSessionfactory一定会二级失效。
  • 两次查询之间对表更新数据也会二级失效。
  • 两次查询之间手动清空缓存也会二级失效。

面试题:说一下 MyBatis 的一级缓存和二级缓存?

一级缓存:

  • 基于 PerpetualCache 的 HashMap 本地缓存,它的生命期是和 SQLSession 一致的,有多个 SQLSession 或者分布式的环境中数据库操作,可能会出现脏数据。当 Session flush 或 close 之后,该 Session 中的所有 Cache 就将清空;
  • 一级缓存是SqlSession级别的,通过同一个SqlSession查询的数据会被缓存,下次查询相同的数据,就会从缓存中直接获取,不会从数据库重新访问,一级缓存是默认开启的。

二级缓存:

  • 也是基于 PerpetualCache 的 HashMap 本地缓存,不同在于其存储作用域为 Mapper 级别的,如果多个SQLSession之间需要共享缓存,则需要使用到二级缓存,并且二级缓存可自定义存储源,如 Ehcache。默认不打开二级缓存,要开启二级缓存,使用二级缓存属性类需要实现 Serializable 序列化接口(可用来保存对象的状态)。
  • 二级缓存是SqlSessionFactory级别,通过同一个SqlSessionFactory创建的SqlSession查询的结果会被缓存,此后若再次执行相同的查询语句,结果就会从缓存中获取。

开启二级缓存数据查询流程:二级缓存 -> 一级缓存 -> 数据库。

缓存更新机制

当某一个作用域(一级缓存 Session/二级缓存 Mapper)进行了C/U/D 操作后,默认该作用域下所有 select 中的缓存将被 clear。

面试题:简述Mybatis都有哪些执行器?他们之间的区别是什么?

mybatis有三种executor执行器,分别为simpleexecutor、reuseexecutor、batchexecutor。

simpleExecutor执行器

在每执行一次update或select,就开启一个statement对象,用完后就关闭。

reuseExecutor执行器

在执行update或select时以sql作为key去查找statement,有就直接使用,没有就创建,使用完毕后不关闭,放入Map<String,Statement>中,供下次使用。重复使用statement。

batchExecutor执行器

执行update(jdbc批处理不支持select),会把所有sql添加到批处理中addbatch();等待统一批处理executorbatch();它缓存了·多个statement,每一个statement都是addbatch(),后等待进行executorbatch()批处理。

作用范围:统一限制在sqlsession生命周期范围内。

相关推荐
数新网络43 分钟前
《深入浅出Apache Spark》系列②:Spark SQL原理精髓全解析
大数据·sql·spark
CoderIsArt1 小时前
Redis的三种模式:主从模式,哨兵与集群模式
数据库·redis·缓存
XiaoLeisj2 小时前
【JavaEE初阶 — 多线程】单例模式 & 指令重排序问题
java·开发语言·java-ee
paopaokaka_luck2 小时前
【360】基于springboot的志愿服务管理系统
java·spring boot·后端·spring·毕业设计
dayouziei2 小时前
java的类加载机制的学习
java·学习
师太,答应老衲吧3 小时前
SQL实战训练之,力扣:2020. 无流量的帐户数(递归)
数据库·sql·leetcode
码农小旋风3 小时前
详解K8S--声明式API
后端
Peter_chq3 小时前
【操作系统】基于环形队列的生产消费模型
linux·c语言·开发语言·c++·后端
Yaml44 小时前
Spring Boot 与 Vue 共筑二手书籍交易卓越平台
java·spring boot·后端·mysql·spring·vue·二手书籍
小小小妮子~4 小时前
Spring Boot详解:从入门到精通
java·spring boot·后端