Mybatis部分面试题总结
1. 怎么进行一对一或一对多映射?
首先准备 2 张表、4 个实体类

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class GroupDO {
private Integer id;
private String groupName;
}
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class UserDO {
private Integer id;
private String username;
private String password;
private Integer groupId;
private Sex sex;
}
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class Test1 {
private Integer id;
private String groupName;
private UserDO userDO;
}
@Data
@Builder
@AllArgsConstructor
@NoArgsConstructor
public class Test2 {
private Integer id;
private String groupName;
private List<UserDO> users;
@Mapper
public interface UserMapper {
// UserDO getUserById(Integer id);
void insertUser(UserDO userDO);
UserDO getUserById(Integer id);
Test1 getTest1();
Test2 getTest2();
}
}一对一
对于getTest1方法,如果直接执行select * from t_group tg,t_user tu where tg.id = tu.group_id limit 1会发现其中 UserDO 为空,所以需要使用association 单独进行映射
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<resultMap id="test1ResultMap" type="com.danaizio.entity.Test1">
<id property="id" column="id"/>
<result property="groupName" column="group_name"/>
<association property="userDO" resultMap="userResultMap"/>
</resultMap>
<resultMap id="userResultMap" type="com.danaizio.entity.UserDO">
<id property="id" column="id"/>
<result property="username" column="username"/>
<result property="password" column="password"/>
<result property="sex" column="sex" typeHandler="org.apache.ibatis.type.EnumOrdinalTypeHandler"/>
<result property="groupId" column="group_id"/>
</resultMap>
<select id="getTest1" resultMap="test1ResultMap">
select * from t_group tg,t_user tu where tg.id = tu.group_id limit 1
</select>一对多
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
<resultMap id="Test2ResultMap" type="com.danaizio.entity.Test2">
<id property="id" column="group_id"/>
<result property="groupName" column="group_name"/>
<collection property="users" ofType="com.danaizio.entity.UserDO">
<id property="id" column="user_id"/>
<result property="username" column="username"/>
<result property="password" column="password"/>
<result property="groupId" column="group_id"/>
<result property="sex" column="sex" typeHandler="org.apache.ibatis.type.EnumOrdinalTypeHandler"/>
</collection>
</resultMap>
<select id="getTest2" resultMap="Test2ResultMap">
SELECT
g.id AS group_id, g.group_name,
u.id AS user_id, u.username, u.password, u.group_id, u.sex
FROM
t_group g
LEFT JOIN
t_user u ON g.id = u.group_id
</select>使用collection进行一对多映射,其中的 ofType 表示实体类中集合的元素类型
注意
由于两张表都有 id 字段,如果不用别名进行区分,会产生错误的查询结果,并且使用了别名的同时也要修改 resultMap 的 column 属性,它本来是对应的数据库字段名,使用了别名后要与查询语句中别名相同
2. 能映射枚举类型吗
定义一个枚举(同时参看上面 UserDO 的定义)
1
2
3
4
public enum Sex {
MALE,
FEMALE;
}MyBatis 从一开始就自带了两个枚举的类型处理器 EnumTypeHandler 和 EnumOrdinalTypeHandler,这两个枚举类型处理器可以用于最简单情况下的枚举类型。
EnumTypeHandler
这个类型处理器是 MyBatis 中默认的枚举类型处理器,他的作用是将枚举的名字和枚举类型对应起来。对于 Sex 枚举来说,存数据库时会使用 “MALE” 或者 “FEMALE” 字符串存储,从数据库取值时,会将字符串转换为对应的枚举
EnumOrdinalTypeHandler
这是另一个枚举类型处理器,他的作用是将枚举的索引和枚举类型对应起来。对于 YesNoEnum 枚举来说,存数据库时会使用枚举对应的顺序 0(MALE) 或者 1(FEMALE) 存储,从数据库取值时,会将整型顺序号(int)转换为对应的枚举。
具体使用
1
2
3
4
5
<insert id="insertUser" parameterType="com.danaizio.entity.UserDO">
insert into t_user(username, password, sex, group_id) values (#{username}, #{password}, #{sex,typeHandler=org.apache.ibatis.type.EnumOrdinalTypeHandler}, #{groupId})
</insert>
<result property="sex" column="sex" typeHandler="org.apache.ibatis.type.EnumOrdinalTypeHandler"/>可以在#{}中指定,也可以在
Mybatis 不单可以映射枚举类,Mybatis 可以映射任何对象到表的一列上。映射方式为自定义一个 TypeHandler,实现 TypeHandler 的 setParameter()和 getResult()接口方法。
TypeHandler 有两个作用,一是完成从 javaType 至 jdbcType 的转换,二是完成 jdbcType 至 javaType 的转换,体现为 setParameter()和 getResult()两个方法,分别代表设置 sql 问号占位符参数和获取列查询结果。
3. 延迟加载
Mybatis 支持 association 关联对象和 collection 关联集合对象的延迟加载,association 指的就是一对一,collection 指的就是一对多查询。在 Mybatis 配置文件中,可以配置是否启用延迟加载 lazyLoadingEnabled=true|false。
1
2
3
4
5
6
7
8
9
<resultMap id="departmentResultMap" type="com.example.Department">
<id property="deptId" column="dept_id"/>
<result property="deptName" column="dept_name"/>
<collection property="employees" ofType="com.example.Employee" lazyLoad="true">
<select id="selectEmployeesByDeptId" resultType="com.example.Employee">
SELECT * FROM employee WHERE dept_id = #{deptId}
</select>
</collection>
</resultMap>工作流程:
- 初始化加载:当第一次查询 Department 数据时,MyBatis 只会加载 Department 的基本信息,不会立即加载关联的 Employee 集合。
- 属性访问触发:当代码尝试访问 Department 对象的 employees 属性时,此时 MyBatis 的懒加载机制会被触发。
- 动态代理创建:MyBatis 使用 CGLIB 创建 Department 对象的代理,当代理对象的 getEmployees() 方法被调用时,会检查当前 employees 是否为空。
- 发送查询:如果 employees 为空,MyBatis 将执行 select 语句中定义的 SQL 查询,从数据库中获取与当前 Department 关联的所有 Employee 数据。
- 设置关联对象:查询结果返回后,MyBatis 会将 Employee 对象集合设置到 Department 的 employees 属性中。
- 完成调用:最后,代理对象的 getEmployees() 方法返回 employees 集合,完成对属性的访问。
注意
这里查询返回的 Department 其实是个代理对象,当调用 Department 的 getEmployees()并且结果为空的话就会被拦截而去执行预先定义好的 sql
4. 工作原理
先读取 mybatis.config 和其他 xxxMapper.xml 文件生成了一个配置类 Configuration
通过这个配置类构建 SqlSessionFactory,SqlSessionFactory 只是一个接口,构建出来的实际上是它的实现类的实例,一般我们用的都是它的实现类 DefaultSqlSessionFactory。
通过 SqlSessionFactory 生成 Sqlseession 执行 sql
执行 sql 中,需要四大组件配合,
Executor(执行器)
SqlSession 只是一个门面,相当于客服,真正干活的是是 Executor,它提供了相应的查询和更新方法,以及事务方法。StatementHandler(数据库会话器)
以最常用的 PreparedStatementHandler 看一下它的 query 方法,其实在上面的 prepareStatement 已经对参数进行了预编译处理,到了这里,就直接执行 sql,使用 ResultHandler 处理返回结果。ParameterHandler (参数处理器)
PreparedStatementHandler 里对 sql 进行了预编译处理(类似#{}用?占位1
2
3public void parameterize(Statement statement) throws SQLException { this.parameterHandler.setParameters((PreparedStatement)statement); }这里用的就是 ParameterHandler,setParameters 的作用就是设置预编译 SQL 语句的参数(负责将 Java 对象的参数值设置到预编译 SQL 语句的占位符中)。里面还会用到 typeHandler 类型处理器,对类型进行处理。
ResultSetHandler(结果处理器)
我们前面也看到了,最后的结果要通过 ResultSetHandler 来进行处理,handleResultSets 这个方法就是用来包装结果集的。Mybatis 为我们提供了一个 DefaultResultSetHandler,通常都是用这个实现类去进行结果的处理的。它会使用 typeHandle 处理类型,然后用 ObjectFactory 提供的规则组装对象,返回给调用者。
图解:

总结:
- 读取 MyBatis 配置文件——mybatis-config.xml 、加载映射文件——映射文件即 SQL 映射文件,文件中配置了操作数据库的 SQL 语句。最后生成一个配置对象。
- 构造会话工厂:通过 MyBatis 的环境等配置信息构建会话工厂 SqlSessionFactory。
- 创建会话对象:由会话工厂创建 SqlSession 对象,该对象中包含了执行 SQL 语句的所有方法。
- Executor 执行器:MyBatis 底层定义了一个 Executor 接口来操作数据库,它将根据 SqlSession 传递的参数动态地生成需要执行的 SQL 语句,同时负责查询缓存的维护。
- StatementHandler:数据库会话器,串联起参数映射的处理和运行结果映射的处理。
- 参数处理:对输入参数的类型进行处理,并预编译。
- 结果处理:对返回结果的类型进行处理,根据对象映射规则,返回相应的对象。
5. 插件
想要实现一个插件就要实现Myabtis的Interceptor 接口
MyBatis 仅可以编写针对 ParameterHandler、 ResultSetHandler、 StatementHandler、 Executor 这 4 种接口的插件,MyBatis 使用 JDK 的动态代理,为需要拦截的接口生成代理对象以实现接口方法拦截功能,每当执行这 4 种接口对象的方法时,就会进入拦截方法,具体就是 InvocationHandler 的 invoke() 方法,当然,只会拦截那些你指定需要拦截的方法。
其中:
1
2
3
4
5
@Intercepts({@Signature(
type = Executor.class, //确定要拦截的对象
method = "update", //确定要拦截的方法
args = {MappedStatement.class,Object.class} //拦截方法的参数
)})最后再将这个插件注册到mybatis配置文件中
1
2
3
4
5
<plugins>
<plugin interceptor="xxx.MyPlugin">
<property name="dbType",value="mysql"/>
</plugin>
</plugins>

