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> 等核心标签更关键。


相关推荐
Alsn8619 分钟前
11.Spring Boot 3.1.5 中使用 SpringDoc OpenAPI(替代 Swagger)生成 API 文档
java·spring boot·后端
liyongjun631626 分钟前
Java List分页工具
java·后端
药尘师2 小时前
低版的spring boot 1.X接入knife4j
java·spring boot·后端
这个懒人4 小时前
C++后端服务器常见开发框架
c++·后端·框架
南玖yy6 小时前
C++ 的未来战场:从技术深耕到职业破局
c语言·开发语言·c++·后端·c++未来
weixin_456588157 小时前
【Spring Boot 注解】@ConfigurationProperties
spring boot·后端
chunfeng—8 小时前
纯C协程框架NtyCo
linux·c++·后端·协程·ntyco
biubiubiu07068 小时前
ElaticSearch
spring boot·后端·jenkins
运维@小兵17 小时前
SpringBoot获取用户信息常见问题(密码屏蔽、驼峰命名和下划线命名的自动转换)
java·spring boot·后端
问道飞鱼19 小时前
【springboot知识】配置方式实现SpringCloudGateway相关功能
java·spring boot·后端·gateway