目录
- SQL传参
- Mybatis中的批量操作
- SQL映射的XML文件
- Mybatis缓存
-
- 一级缓存:默认开启
- 二级缓存
- [面试题:说一下 MyBatis 的一级缓存和二级缓存?](#面试题:说一下 MyBatis 的一级缓存和二级缓存?)
- 面试题:简述Mybatis都有哪些执行器?他们之间的区别是什么?
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,两次查询,因此 缓存失效了
二级缓存
开启二级缓存
-
mybatis-config.xml中
xml<settings> <setting name="cacheEnabled" value="true"/> </settings>
-
mapper.xml中
java<cache/>
-
查询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生命周期范围内。