Mybatis:Configuration/MappedStatement/双缓存/XML的id/分页机制实现/自增主键id/xml罕见标签


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)等。
  • 它是 ConfigurationmappedStatements 集合的一个元素,键是 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),存储在 Configurationcaches 中。可以通过配置第三方缓存(如 Ehcache)替换。

  • 开启方式

    1. mybatis-config.xml 中全局开启:

      xml 复制代码
      <settings>
          <setting name="cacheEnabled" value="true"/>
      </settings>
    2. 在具体 <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.findByIdcom.example.OrderMapper.findById 是不同的 MappedStatement,不会冲突。

注意事项

  • 如果 namespace 相同,id 重复会导致启动时抛出异常:

    bash 复制代码
    org.apache.ibatis.binding.BindingException: Duplicate statement id

总结

不同 XML 文件的 id 可以相同,只要 namespace 不同即可。


5. MyBatis如何实现分页的?

MyBatis 本身不提供分页功能,但可以通过以下方式实现:

  1. SQL 手动分页 :在 SQL 中使用 LIMITOFFSET
  2. 插件分页 :使用拦截器插件,如 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>
  • 使用

    java 复制代码
    SqlSession 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 提供了两种方式获取自增主键:

  1. 使用 <selectKey> 标签
  2. 通过 useGeneratedKeyskeyProperty

方法 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:指定主键赋值给实体类的哪个属性。
  • orderBEFORE(插入前执行)或 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> 等核心标签更关键。


相关推荐
盛瑄妍1 分钟前
Assembly语言的云计算
开发语言·后端·golang
慕容蕴秋5 分钟前
Perl语言的计算机网络
开发语言·后端·golang
慕容靖翾7 分钟前
Lua语言的无线通信
开发语言·后端·golang
哪吒编程10 分钟前
新项目终于用上了jdk24
java·后端
倚栏听风雨24 分钟前
Java中的Type详解
后端
梅一一29 分钟前
一个b站偷懒工具
javascript·后端
东方韡璟33 分钟前
Simula语言的正则表达式
开发语言·后端·golang
这里有鱼汤38 分钟前
面向Web开发者的Python速成指南,5分钟学会Python
后端·python
CHSnake44 分钟前
设计HTTP和gRPC错误码.md
后端·go
刀法如飞44 分钟前
DDD领域驱动设计详解-Java/Go/JS/Python实现
java·前端·后端