专业的JAVA编程教程与资源

网站首页 > java教程 正文

【Java面试题】MyBatis的工作原理

temp10 2025-04-09 20:32:18 java教程 9 ℃ 0 评论


【Java面试题】MyBatis的工作原理


配置文件加载与解析

  • 加载顺序
  • MyBatis 首先会加载核心配置文件(mybatis - config.xml)。这个文件是整个 MyBatis 配置的基础,它包含了数据库连接相关的全局配置、插件配置等重要信息。因为这些全局配置信息对于后续的操作(如创建数据源、配置事务管理器等)是必需的,所以它最先被加载。
  • 在解析核心配置文件过程中,当遇到标签时,MyBatis 会根据其中的配置来加载映射文件(mapper.xml)。这些映射文件用于定义具体的 SQL 语句以及 SQL 执行结果与 Java 对象之间的映射关系,它们的加载是基于核心配置文件的指引进行的。
  • 核心配置文件(mybatis - config.xml):MyBatis 工作的第一步是加载核心配置文件。这个文件犹如整个框架的蓝图,涵盖数据库连接信息、事务管理配置、插件配置等关键部分。例如,以下是核心配置文件片段:



    
        
            
            
                
                
                
                
            
        
    
    
        
    

其中,标签定义数据库连接环境,包括事务管理器(如这里的JDBC类型)和数据源(如POOLED类型的连接池),还有驱动、连接 URL、用户名和密码等详细信息。标签则指定映射文件位置,这些文件定义 SQL 语句与 Java 对象的映射关系。

  • 映射文件(mapper.xml):MyBatis 会根据核心配置文件的指引加载映射文件。映射文件的主要功能是定义 SQL 语句,并建立 SQL 执行结果与 Java 对象的映射,或者将 Java 对象属性作为 SQL 参数。以简单的UserMapper.xml为例:



    
    
        
        
        
    

这里定义了selectUserById查询语句,从users表按id查询用户信息。标签详细说明了查询结果如何映射到User实体类,如将表id列映射到User对象的id属性等。

构建 SqlSessionFactory

  • 加载解析配置文件的关键角色:SqlSessionFactory 在 MyBatis 工作流程中扮演着至关重要的角色,它会完成全局配置文件和映射文件的加载解析操作。这一过程是整个框架能够正常运行的基础。
  • 解析配置信息:在加载配置文件后,MyBatis 通过解析这些信息构建 SqlSessionFactory。这是 MyBatis 的核心对象,线程安全,用于创建 SqlSession。构建过程中,会解析数据库连接信息、映射文件中的 SQL 和映射关系等,封装成内部数据结构,方便后续操作。

解析全局配置文件(mybatis - config.xml)

  • 数据库连接环境配置:在全局配置文件中,SqlSessionFactory会解析标签下的内容,包括事务管理器类型(如JDBC或MANAGED)和数据源类型(如POOLED、UNPOOLED或JNDI)等。以数据源配置为例,它会读取标签中的属性,如数据库驱动名称(driver)、连接 URL(url)、用户名(username)和密码(password)等信息,这些信息将用于后续建立与数据库的连接。
  • 插件配置:如果全局配置文件中配置了插件(标签),SqlSessionFactory也会解析这些插件相关的信息。插件可以用于拦截 MyBatis 的执行过程,实现如分页、缓存等功能扩展。例如,对于一个分页插件,它会解析插件的类名、参数等信息,以便在合适的时候加载并应用插件功能。
  • 缓存配置:在全局配置文件中还可以配置缓存相关信息。通过标签可以开启二级缓存,例如,这里配置了缓存的淘汰策略(LRU,即最近最少使用)、刷新间隔(60000 毫秒)、缓存大小(512 个对象)以及是否只读(true表示只读)。这些配置参数会影响 MyBatis 的缓存行为,SqlSessionFactory在解析时会将这些缓存配置信息保存起来,用于后续创建缓存对象和管理缓存数据。
  • 其他全局配置:还包括像设置全局的映射器启用或者禁用自动映射(标签中的相关配置)等内容,SqlSessionFactory都会一一进行解析并保存这些配置信息,用于后续的数据库操作过程。

解析映射文件(mapper.xml)

  • SQL 语句解析:对于每个被配置的映射文件,SqlSessionFactory会解析其中的 SQL 语句。这些 SQL 语句通过标签定义的查询语句,SqlSessionFactory会解析其id属性(用于在 Java 代码中引用这个 SQL 语句)、resultMap属性(用于指定结果映射关系)以及标签内的 SQL 内容本身。它会识别 SQL 语句中的参数占位符(如#{parameterName}),并建立起与 Java 对象参数的关联关系,以便在执行 SQL 时能够正确地传递参数。
  • 结果映射解析(resultMap):resultMap是 MyBatis 中非常重要的一个概念,用于将查询结果的列与 Java 对象的属性进行映射。SqlSessionFactory会解析resultMap标签中的内容,包括标签(用于标识主键映射)和标签(用于普通列的映射)。例如,在一个resultMap定义中,表示将查询结果中的user_id列映射到 Java 对象的id属性上,SqlSessionFactory会解析并记录这种映射关系,以便在执行查询操作后能够正确地构建 Java 对象并填充属性值。

构建内部数据结构保存配置信息:在完成全局配置文件和映射文件的加载解析后,SqlSessionFactory会将这些信息构建成内部的数据结构进行保存。这样在后续创建 SqlSession 对象并执行数据库操作时,就可以快速地获取所需的配置信息,如 SQL 语句内容、参数映射规则、结果映射规则以及数据库连接信息等,从而高效地执行数据库操作。例如,它可能会将解析后的 SQL 语句和映射关系存储在一个类似于映射表的数据结构中,其中key是 SQL 语句的id,value是包含 SQL 语句内容、参数映射和结果映射等信息的对象。这种方式使得在执行SqlSession的各种数据库操作方法时,可以快速地根据 SQL 语句id找到对应的完整配置信息并执行操作。


创建实例:利用 SqlSessionFactoryBuilder 来创建 SqlSessionFactory。通常读取配置文件流后,使用build方法构建。例如:

String resource = "mybatis - config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

先获取核心配置文件输入流,再构建 SqlSessionFactory。

MyBatis 缓存机制

一级缓存

  • 原理与范围:一级缓存是 SqlSession 级别的缓存。当 MyBatis 执行查询操作时,会先在一级缓存中查找是否已经存在相同查询的结果。如果存在,就直接返回缓存中的结果,而不会再次向数据库发送查询请求。这个缓存是基于PerpetualCache实现的,它是一个简单的基于HashMap的缓存。例如,在同一个SqlSession中执行两次相同的查询sqlSession.selectOne("com.example.mapper.UserMapper.selectUserById", 1);,第一次查询会从数据库获取数据,第二次查询就会从一级缓存中获取数据。
  • 缓存失效情况:一级缓存会在以下几种情况下失效。一是当执行插入、更新或删除操作后,缓存会被清空,因为这些操作可能会改变数据库中的数据,导致之前缓存的查询结果不再准确。二是当SqlSession被关闭后,一级缓存也会被清除。

二级缓存

  • 原理与范围:二级缓存是 Mapper 级别的缓存,它的作用范围比一级缓存更广。它是基于Cache接口实现的,可以通过配置文件进行配置。当开启二级缓存后,MyBatis 会在不同的SqlSession之间共享缓存数据。例如,在一个多线程或者多个SqlSession操作的场景下,如果一个SqlSession执行了查询并将结果存入二级缓存,其他SqlSession执行相同的查询时就可以从二级缓存中获取数据。配置二级缓存通常在mapper.xml文件中添加标签或者在MyBatis的全局配置文件中进行配置。
  • 缓存配置参数:在配置二级缓存时,可以设置多种参数来控制缓存的行为。如前面提到的在全局配置文件中的标签配置,eviction属性用于指定缓存的淘汰策略,常见的有LRU(最近最少使用)、FIFO(先进先出)等;flushInterval属性用于设置缓存的刷新间隔时间,单位是毫秒;size属性用于指定缓存的大小,即最多可以缓存多少个对象;readOnly属性用于设置缓存是否为只读。如果设置为true,则缓存中的对象是只读的,可以提高性能,因为 MyBatis 不需要对缓存对象进行序列化和反序列化操作,但如果缓存对象被修改,可能会导致数据不一致的问题。如果设置为false,则缓存对象是可读写的,MyBatis 会在必要时对缓存对象进行序列化和反序列化操作,以保证数据的一致性。
  • 缓存数据的存储与读取:二级缓存的数据存储和读取是基于Cache接口的实现类完成的。当执行查询操作并且二级缓存开启时,MyBatis 会首先检查二级缓存中是否存在相应的数据。如果存在,就直接返回缓存数据;如果不存在,则执行查询操作从数据库获取数据,然后将数据存入二级缓存中,以便后续查询使用。在存储数据时,会根据缓存的配置参数(如淘汰策略、缓存大小等)来管理缓存数据。

缓存使用的注意事项

  • 缓存一致性问题:在使用缓存时,需要注意缓存数据与数据库数据的一致性。由于缓存的数据可能不是最新的,特别是在多用户并发操作或者数据库数据频繁更新的场景下。例如,如果一个用户更新了数据库中的数据,而缓存没有及时刷新,其他用户可能会获取到旧的缓存数据。为了避免这种情况,可以在执行更新操作后手动刷新缓存,或者根据缓存的配置参数(如设置合理的刷新间隔)来保证缓存数据的及时性。
  • 缓存穿透、缓存雪崩等问题:缓存穿透是指查询一个不存在的数据,导致每次查询都直接穿透缓存访问数据库。缓存雪崩是指大量缓存同时失效,导致大量请求直接访问数据库,可能会造成数据库压力过大。为了避免这些问题,可以采用一些策略,如设置缓存空对象来解决缓存穿透问题(当查询不存在的数据时,在缓存中存储一个空对象,下次查询相同数据时直接返回空对象),以及合理设置缓存过期时间和采用分布式缓存来缓解缓存雪崩问题。

创建 SqlSession 与 StatementHandler 的关联

  • 创建 SqlSession:SqlSession 是执行 SQL 操作的主要接口,通过 SqlSessionFactory 的openSession方法创建。例如:
SqlSession sqlSession = sqlSessionFactory.openSession();

注意,SqlSession 对象非线程安全,用于一次数据库事务或一系列操作,完成后应及时关闭释放资源。

  • StatementHandler 的作用:StatementHandler 是 MyBatis 的四大核心处理器之一,它负责处理 SQL 语句的预编译和执行。在 SqlSession 执行 SQL 操作时,会创建一个 StatementHandler 对象来处理具体的 SQL 执行细节。它主要的职责包括对 SQL 语句进行语法解析、参数设置、结果集映射等操作。例如,在执行查询操作时,StatementHandler 会将 SQL 语句发送给数据库,接收返回的结果集,并根据配置的结果映射规则(如resultMap)将结果集映射为 Java 对象。
  • 工作流程中的关联:当调用 SqlSession 的方法(如selectOne、selectList、insert、update或delete)执行 SQL 操作时,SqlSession 会根据配置和操作类型创建相应的 StatementHandler。具体来说,它会根据 SQL 语句是查询还是更新操作来选择不同的 StatementHandler 实现类。对于查询操作,通常会使用PreparedStatementHandler,它会将 SQL 语句进行预编译,提高执行效率;对于更新操作,可能会使用SimpleStatementHandler或者CallableStatementHandler,这取决于 SQL 语句的类型(是普通的更新语句还是存储过程调用)。在创建 StatementHandler 之后,SqlSession 会将相关的参数(如 SQL 语句的id、参数对象等)传递给 StatementHandler,然后由 StatementHandler 完成后续的 SQL 执行工作。

执行 SQL 操作与 StatementHandler 的具体操作

  • SQL 执行过程与 StatementHandler:调用 SqlSession 方法执行 SQL 时,MyBatis 根据 SQL 语句id在映射文件中查找对应的 SQL。例如执行sqlSession.selectOne("com.example.mapper.UserMapper.selectUserById", 1);,会找到UserMapper.xml中id为selectUserById的查询语句,将参数1(假设是用户id)设到#{id}占位符。在这个过程中,StatementHandler 起到关键作用。它会获取这个 SQL 语句,将其进行预编译(如果是PreparedStatementHandler),并将参数1正确地设置到预编译后的 SQL 语句中,然后通过数据库驱动执行查询。
  • 参数与结果映射中的 StatementHandler 职责:在执行 SQL 操作时,MyBatis 进行参数和结果映射。对于参数映射,StatementHandler 会按照规则(如#{}占位符)将 Java 对象属性值传递给 SQL 参数。例如,它会解析 SQL 语句中的参数占位符,根据参数的类型和值,将其正确地设置到预编译的 SQL 语句中。对于结果映射,如resultMap定义,StatementHandler 会接收查询结果集,将查询结果列值映射到 Java 对象属性。它会根据配置的resultMap信息,识别列名和 Java 对象属性的对应关系,然后将结果集中的数据填充到 Java 对象的相应属性中。
  • StatementHandler 的可扩展性和插件机制:StatementHandler 的设计具有良好的可扩展性,这使得 MyBatis 可以通过插件机制来对 SQL 执行过程进行拦截和扩展。例如,可以编写一个插件来实现分页功能。插件可以在 StatementHandler 执行 SQL 之前,修改 SQL 语句(如添加LIMIT子句用于分页),或者在执行之后对结果集进行处理(如对返回的列表数据进行二次筛选)。这种插件机制基于 MyBatis 的拦截器(Interceptor)接口,通过实现拦截器并配置到 MyBatis 中,可以在 StatementHandler 的不同执行阶段(如prepare、parameterize、batch、update、query等阶段)进行拦截操作,从而实现各种功能扩展。

事务管理

  • 开启与提交 / 回滚:如果openSession方法没传入false参数(如sqlSessionFactory.openSession(true)),默认开启自动提交事务的 SqlSession。如需手动控制事务,可用beginTransaction开启,一系列操作完成后用commit提交或rollback回滚。例如:
SqlSession sqlSession = sqlSessionFactory.openSession();
try {
    // 执行SQL操作
    sqlSession.insert("com.example.mapper.UserMapper.insertUser", new User("newUser", "password"));
    sqlSession.commit();
} catch (Exception e) {
    sqlSession.rollback();
} finally {
    sqlSession.close();
}

先开启 SqlSession,插入新用户,有异常则回滚,最后关闭。

资源清理

  • 关闭 SqlSession:完成 SQL 操作和事务处理后,必须关闭 SqlSession 释放数据库连接等资源,通过调用close方法实现。不及时关闭可能导致数据库连接泄漏等问题。例如:
sqlSession.close();

Tags:

本文暂时没有评论,来添加一个吧(●'◡'●)

欢迎 发表评论:

最近发表
标签列表