再解 5 题!国庆之后续更,假期愉快!
1、Mapper 接口方法如何与注解里的 SQL 进行绑定的?
根据 Mapper 接口、其方法、方法上的注解,生成 mappedStatementId 与 MapperStatement,注册到 configuration 对象中
根据 Mapper 接口方法查到并调用对应的 MappedStatement,执行 SQL
流程与 Mapper 接口与 xml 绑定类似。
分析:
解析生成注册 MapperStatement 的代码入口在 MapperRegistry addMapper 方法
//使用 MapperProxyFactory 包装 Mapper 接口 Class 对象
knownMappers.put(type, new MapperProxyFactory<>(type));
//解析 Mapper 接口方法上的注解,生成对应的 MapperStatement
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
获取 Mapper 接口的动态代理对象的代码入口在 MapperRegistry getMapper 方法
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
2、Mapper 接口并没有实现类,它是如何工作的?
Mapper 接口的 Class 对象,被解析包装成 MapperProxyFactory 对象
SqlSession 获取 Mapper 接口时,通过 MapperProxyFactory 对象实例化 MapperProxy 动态代理 Mapper 接口
执行 Mapper 接口的方法时,动态代理反射调用 MapperProxy 的 invoke 方法,根据接口与方法找到对应 MappedStatement 执行 SQL
源码入口与上题同。
3、Mapper 接口中能不能根据参数不同进行重载?
不能
MapperedStatement 的 id 属性值等于 Mapper 接口的 包名.接口名.方法名作为 key 添加到 Configuration 对象的 Map 结构的 mappedStatements 属性里
查找 MapperedStatement 执行 SQL 时,也是根据 Mapper 接口的 包名.接口名.方法名 作为 SqlCommand 的 name 属性值,在 Configuration 对象的 mappedStatements 找到对应的 MapperedStatement 对象
即接口中方法名相同 key 就相同,只能获取一个 MapperedStatement 对象,无法重载
4、MyBatis 有哪些分页的方式?分页插件的原理是什么?
使用 RowBounds 对象进行分页,它是对 ResultSet 结果集进行内存分页
在 xml 或者 注解的 SQL 中传递分页参数
使用分页插件 Mybatis-PageHelper
其中分页插件的原理是,使用 MyBatis 提供的插件接口,拦截待执行的 SQL,根据数据库种类的配置与分页参数,生成带分页 SQL 语句,执行。
5、MyBatis 是如何将 sql 执行结果转换为目标对象并返回的?有哪些映射形式?
方式一、<select> 标签使用 resultType 参数,传递 Java 类,sql 中 select 的字段名保持与 Java 类属性名称一致
方式二、使用 <resultMap> 标签,定义数据库列名和对象属性名之间的映射关系
方式三、使用注解 select 的字段名保持与接口方法返回的 Java 类或集合的元素类的属性名称一致
方式一
<select id="selectUser" resultType="constxiong.po.User" parameterType="constxiong.po.User">
select * from user where id = #{id}
</select>
方式二
<select id="selectUserByResultMap" resultMap="userMap" parameterType="constxiong.po.User">
select * from user where id = #{id}
</select>
<resultMap id="userMap" type="constxiong.po.User">
<id property="id" column="id" />
<result property="mc" column="name"/>
</resultMap>
方式三
@Select("select * from user")
List<User> selectAllUsers();
根据解析得到 ResultMap 结合 sql 执行结果,通过反射创建对象,根据映射关系反射填充返回对象的属性
源码体现在 DefaultResultSetHandler 的 handleResultSets 方法
public List<Object> handleResultSets(Statement stmt) throws SQLException {
ErrorContext.instance().activity("handling results").object(mappedStatement.getId());
final List<Object> multipleResults = new ArrayList<>();
int resultSetCount = 0;
ResultSetWrapper rsw = getFirstResultSet(stmt);
List<ResultMap> resultMaps = mappedStatement.getResultMaps();
int resultMapCount = resultMaps.size();
validateResultMapsCount(rsw, resultMapCount);
while (rsw != null && resultMapCount > resultSetCount) {
ResultMap resultMap = resultMaps.get(resultSetCount);
handleResultSet(rsw, resultMap, multipleResults, null);
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
String[] resultSets = mappedStatement.getResultSets();
if (resultSets != null) {
while (rsw != null && resultSetCount < resultSets.length) {
ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]);
if (parentMapping != null) {
String nestedResultMapId = parentMapping.getNestedResultMapId();
ResultMap resultMap = configuration.getResultMap(nestedResultMapId);
handleResultSet(rsw, resultMap, null, parentMapping);
}
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
}
return collapseSingleResultList(multipleResults);
}
汇总于:Java 面试题集
更多相关文章
- 详解第三种创建线程的方式-Callable接口
- Spring Ioc 实例化 Bean 对象有几种方式?
- Javascript面向对象入门
- 【编测编学】接口测试面试题必背(下)
- 如何在 Java 中构造对象(学习 Java 编程语言 034)
- 快速测试 API 接口的新技能
- java创建对象的过程(内存角度分析)