1. MyBatis的Configuration到底是什么,存了什么?
Configuration 是 MyBatis 的核心配置类,位于 org.apache.ibatis.session
包下,它是 MyBatis 初始化和运行时的全局配置对象,几乎所有的配置信息都会存储在这个类中。它通过解析 mybatis-config.xml
或通过 Java 代码配置生成。
Configuration 存了什么?
- 数据源配置 :包括数据库连接池信息(
DataSource
)。 - 全局配置:如是否开启缓存、延迟加载、别名配置等。
- 映射器信息 :所有的
MappedStatement
(SQL语句的封装对象)。 - 类型处理器 :用于处理 Java 类型与 JDBC 类型之间的转换(
TypeHandler
)。 - 插件 :拦截器配置(
Interceptor
)。 - 环境信息 :如事务管理器(
TransactionFactory
)和数据源环境(Environment
)。
源码分析
在 MyBatis 初始化时,SqlSessionFactoryBuilder
会通过 XMLConfigBuilder
解析配置文件,最终生成 Configuration
对象:
java
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
SQLSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
Configuration
的核心字段示例:
java
public class Configuration {
protected Environment environment; // 环境配置
protected boolean cacheEnabled; // 是否开启缓存
protected Map<String, MappedStatement> mappedStatements; // SQL语句映射
protected Map<String, Cache> caches; // 缓存配置
protected Map<String, TypeHandler<?>> typeHandlerMap; // 类型处理器
}
总结
Configuration
是 MyBatis 的"大脑",它集中管理了运行时所需的所有配置和映射信息,是整个框架的基石。
2. MapperStatement是什么,是mapper标签么?
MappedStatement
(注意不是 "mapperStatement",可能是笔误)是 MyBatis 中对一条 SQL 语句的封装对象,位于 org.apache.ibatis.mapping
包下。它不是 <mapper>
标签本身,而是由 <mapper>
标签中的具体 SQL 标签(如 <select>
、<insert>
等)解析后生成的一个对象。
MappedStatement 的作用
- 封装了一条 SQL 的所有信息,包括 SQL 语句、参数类型、结果映射、执行类型(
StatementType
)等。 - 它是
Configuration
中mappedStatements
集合的一个元素,键是 SQL 的唯一 ID(通常是namespace + id
)。
与 <mapper>
标签的关系
<mapper>
是 XML 文件的根标签,用于定义命名空间,而 <select>
、<insert>
等子标签会被解析为一个个 MappedStatement
。例如:
xml
<mapper namespace="com.example.UserMapper">
<select id="selectUser" resultType="User">
SELECT * FROM user WHERE id = #{id}
</select>
</mapper>
上述代码会被解析为一个 MappedStatement
,其 ID 为 com.example.UserMapper.selectUser
。
源码示例
MappedStatement
的核心属性:
java
public class MappedStatement {
private String id; // SQL的唯一标识
private SqlSource sqlSource; // SQL语句的封装
private StatementType statementType; // 语句类型(如PREPARED)
private ResultMap resultMap; // 结果映射
private ParameterMap parameterMap; // 参数映射
}
总结
MappedStatement
不是 <mapper>
标签,而是 <mapper>
中具体 SQL 标签的运行时表示形式,是 MyBatis 执行 SQL 的核心对象。
3. 一级缓存和二级缓存如何实现的?
MyBatis 的缓存分为一级缓存(本地缓存)和二级缓存(全局缓存),用于减少数据库查询压力。
一级缓存
- 作用范围:SqlSession 级别,默认开启。
- 实现原理 :基于
PerpetualCache
,本质是一个HashMap
,存储在BaseExecutor
中。键是 SQL 的唯一标识(MappedStatement ID + 参数 + SQL
),值是查询结果。 - 生命周期:跟随 SqlSession,SqlSession 关闭或执行增删改操作时清空。
- 代码示例:
java
SqlSession session = sqlSessionFactory.openSession();
User user1 = session.selectOne("com.example.UserMapper.selectUser", 1); // 缓存命中
User user2 = session.selectOne("com.example.UserMapper.selectUser", 1); // 从缓存读取
session.close(); // 缓存销毁
二级缓存
-
作用范围:Mapper 级别(跨 SqlSession),需要手动开启。
-
实现原理 :基于
Cache
接口,默认实现是PerpetualCache
(也是HashMap
),存储在Configuration
的caches
中。可以通过配置第三方缓存(如 Ehcache)替换。 -
开启方式 :
-
在
mybatis-config.xml
中全局开启:xml<settings> <setting name="cacheEnabled" value="true"/> </settings>
-
在具体
<mapper>
中启用:xml<cache/>
-
-
生命周期:跟随 Mapper,缓存数据在多个 SqlSession 间共享,增删改操作会清空对应 Mapper 的缓存。
-
代码示例:
java
SqlSession session1 = sqlSessionFactory.openSession();
User user1 = session1.selectOne("com.example.UserMapper.selectUser", 1); // 查询数据库
session1.close();
SqlSession session2 = sqlSessionFactory.openSession();
User user2 = session2.selectOne("com.example.UserMapper.selectUser", 1); // 从二级缓存读取
session2.close();
总结
- 一级缓存:SqlSession 内的本地缓存,默认开启,简单高效。
- 二级缓存:跨 SqlSession 的全局缓存,需手动配置,适合读多写少场景。
4. MyBatis的XML映射中,不同的XML文件,ID可以相同么?
答案 :可以相同,但前提是命名空间(namespace
)不同。
原因
- MyBatis 使用
namespace + id
作为MappedStatement
的唯一标识,而不是单独的id
。 - 如果两个 XML 文件的
namespace
不同,即使id
相同,MyBatis 也不会冲突。
示例
文件 UserMapper.xml
:
xml
<mapper namespace="com.example.UserMapper">
<select id="findById" resultType="User">
SELECT * FROM user WHERE id = #{id}
</select>
</mapper>
文件 OrderMapper.xml
:
xml
<mapper namespace="com.example.OrderMapper">
<select id="findById" resultType="Order">
SELECT * FROM order WHERE id = #{id}
</select>
</mapper>
com.example.UserMapper.findById
和com.example.OrderMapper.findById
是不同的MappedStatement
,不会冲突。
注意事项
-
如果
namespace
相同,id
重复会导致启动时抛出异常:bashorg.apache.ibatis.binding.BindingException: Duplicate statement id
总结
不同 XML 文件的 id
可以相同,只要 namespace
不同即可。
5. MyBatis如何实现分页的?
MyBatis 本身不提供分页功能,但可以通过以下方式实现:
- SQL 手动分页 :在 SQL 中使用
LIMIT
和OFFSET
。 - 插件分页 :使用拦截器插件,如
PageHelper
。
方法 1:SQL 手动分页
在 XML 中编写分页 SQL:
xml
<mapper namespace="com.example.UserMapper">
<select id="selectPage" resultType="User">
SELECT * FROM user LIMIT #{start}, #{pageSize}
</select>
</mapper>
Java 代码:
java
SqlSession session = sqlSessionFactory.openSession();
Map<String, Integer> params = new HashMap<>();
params.put("start", 0); // 起始位置
params.put("pageSize", 10); // 每页大小
List<User> users = session.selectList("com.example.UserMapper.selectPage", params);
方法 2:PageHelper 插件
-
依赖 :添加
pagehelper
到项目中(Maven 示例):xml<dependency> <groupId>com.github.pagehelper</groupId> <artifactId>pagehelper</artifactId> <version>5.3.0</version> </dependency>
-
配置 :在
mybatis-config.xml
中注册插件:xml<plugins> <plugin interceptor="com.github.pagehelper.PageInterceptor"/> </plugins>
-
使用 :
javaSqlSession session = sqlSessionFactory.openSession(); PageHelper.startPage(1, 10); // 第1页,每页10条 List<User> users = session.selectList("com.example.UserMapper.selectAll"); PageInfo<User> pageInfo = new PageInfo<>(users); // 获取分页信息 System.out.println("总记录数:" + pageInfo.getTotal());
总结
- 手动分页简单但灵活性有限。
- PageHelper 通过拦截器动态修改 SQL,功能强大且易用,是推荐方案。
6. MyBatis是如何拿到自增主键ID的?
MyBatis 提供了两种方式获取自增主键:
- 使用
<selectKey>
标签。 - 通过
useGeneratedKeys
和keyProperty
。
方法 1:<selectKey>
标签
适用于所有数据库:
xml
<insert id="insertUser" parameterType="User">
<selectKey keyProperty="id" resultType="int" order="AFTER">
SELECT 2747751
</selectKey>
INSERT INTO user (name, age) VALUES (#{name}, #{age})
</insert>
keyProperty
:指定主键赋值给实体类的哪个属性。order
:BEFORE
(插入前执行)或AFTER
(插入后执行)。- Java 代码:
java
User user = new User("Alice", 25);
session.insert("com.example.UserMapper.insertUser", user);
System.out.println("自增ID:" + user.getId());
方法 2:useGeneratedKeys
适用于支持自增主键的数据库(如 MySQL):
xml
<insert id="insertUser" parameterType="User" useGeneratedKeys="true" keyProperty="id">
INSERT INTO user (name, age) VALUES (#{name}, #{age})
</insert>
useGeneratedKeys="true"
:开启主键回填。keyProperty="id"
:指定主键字段。- Java 代码同上。
实现原理
- MyBatis 通过 JDBC 的
Statement.getGeneratedKeys()
方法获取自增主键,并将其赋值给实体对象的指定属性。
总结
<selectKey>
更灵活,适用于所有场景。useGeneratedKeys
更简洁,适合 MySQL 等支持自增主键的数据库。
7. MyBatis的XML标签中有哪些不重要的标签?
MyBatis 的 XML 标签中,有些标签并非必须或使用频率较低,可视为"不重要",但具体重要性因需求而异。以下是一些常见的"不重要"标签:
1. <cache>
和 <cache-ref>
-
作用:配置二级缓存。
-
为何不重要:二级缓存默认关闭,且在分布式环境下使用复杂,实际项目中常被禁用。
-
示例 :
xml<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
2. <constructor>
-
作用:指定构造方法映射结果集。
-
为何不重要:大多数场景下使用默认构造即可,复杂映射较少使用。
-
示例 :
xml<resultMap id="userMap" type="User"> <constructor> <arg column="id" javaType="int"/> </constructor> </resultMap>
3. <discriminator>
-
作用:根据条件动态选择映射。
-
为何不重要:使用场景较少,逻辑复杂时更倾向于代码实现。
-
示例 :
xml<resultMap id="userMap" type="User"> <discriminator javaType="int" column="type"> <case value="1" resultType="Admin"/> <case value="2" resultType="Guest"/> </discriminator> </resultMap>
4. <sql>
-
作用:定义可重用的 SQL 片段。
-
为何不重要:虽然方便,但小型项目中直接写完整 SQL 更常见。
-
示例 :
xml<sql id="userColumns">id, name, age</sql> <select id="selectUser" resultType="User"> SELECT <include refid="userColumns"/> FROM user </select>
总结
"不重要"的标签通常是高级功能或可被替代的配置,具体是否使用取决于项目复杂度。一般开发中,<select>
、<insert>
、<resultMap>
等核心标签更关键。