mybatis的SqlSession

发布于:2024-04-26 ⋅ 阅读:(27) ⋅ 点赞:(0)

 先来看一下sqlsession接口,发现它为我们定义了很多对数据库数据操作的相关方法。

public interface SqlSession extends Closeable {
    <T> T selectOne(String var1);

    <T> T selectOne(String var1, Object var2);

    <E> List<E> selectList(String var1);

    <E> List<E> selectList(String var1, Object var2);

    <E> List<E> selectList(String var1, Object var2, RowBounds var3);

    <K, V> Map<K, V> selectMap(String var1, String var2);

    <K, V> Map<K, V> selectMap(String var1, Object var2, String var3);

    <K, V> Map<K, V> selectMap(String var1, Object var2, String var3, RowBounds var4);

    <T> Cursor<T> selectCursor(String var1);

    <T> Cursor<T> selectCursor(String var1, Object var2);

    <T> Cursor<T> selectCursor(String var1, Object var2, RowBounds var3);

    void select(String var1, Object var2, ResultHandler var3);

    void select(String var1, ResultHandler var2);

    void select(String var1, Object var2, RowBounds var3, ResultHandler var4);

    int insert(String var1);

    int insert(String var1, Object var2);

    int update(String var1);

    int update(String var1, Object var2);

    int delete(String var1);

    int delete(String var1, Object var2);

    void commit();

    void commit(boolean var1);

    void rollback();

    void rollback(boolean var1);

    List<BatchResult> flushStatements();

    void close();

    void clearCache();

    Configuration getConfiguration();

    <T> T getMapper(Class<T> var1);

    Connection getConnection();
}

主要功能:

  1. 创建SqlSession实例:

    1. 当需要与数据库进行交互时,首先从SqlSessionFactory(通过SqlSessionFactoryBuilder创建)获得一个SqlSession实例。每个SqlSession对象代表一个数据库会话,它不是线程安全的,因此每个线程都应该拥有自己的SqlSession。

  2. 内部结构与关键对象:

    1. SqlSession内部主要依靠Executor(执行器)来执行SQL语句。Executor根据配置的不同,可以有不同的行为模式,如简单执行器、重用执行器和批量执行器等。

    2. Executor在执行过程中会利用StatementHandler来处理具体的SQL语句(包括预编译SQL、设置参数等),并通过ParameterHandler处理SQL参数。
    3. 当执行查询操作时,ResultHandler会处理结果集,将数据库返回的结果转换为Java对象。
  3. 执行 CRUD 操作

    • insert(): 执行插入操作,用于保存新的数据。
    • update(): 执行更新操作,用于更新数据库中的现有数据。
    • delete(): 执行删除操作,用于从数据库中删除数据。
    • selectOne()selectList()selectMap()selectCursor()selectBatchResult(): 执行查询操作,用于从数据库中检索数据。
  4. 事务管理

    SqlSession 支持开启、提交和回滚事务。在开启 SqlSession 后,所有的数据库操作都在同一事务中进行,直到调用 commit() 或 rollback() 结束事务。
  5. 一级缓存与二级缓存:

    • SqlSession内部的一级缓存是指在一个SqlSession生命周期内,对同一个SQL查询结果的缓存。一旦SqlSession关闭,一级缓存也就失效。

    • 二级缓存是在多个SqlSession之间共享查询结果的一种机制,需要在全局配置和Mapper配置中明确启用,并且适用于读多写少的场景。当在不同SqlSession中执行相同的查询时,如果开启了二级缓存,MyBatis会在一级缓存未命中时尝试从二级缓存中获取数据。
  6. 获取 Mapper

    通过 SqlSession 可以获取到 Mapper 接口的代理对象,从而执行 Mapper 中定义的 SQL 映射方法。
  7. 关闭

    在完成一系列数据库操作后,应调用 SqlSession 的 close() 方法将其关闭,以释放资源并确保事务得到正确的提交或回滚。

 SqlSession和Executor怎样结合的?

SqlSession是MyBatis对外提供的主要API,它扮演着一个数据库会话的角色,提供了诸如增删改查等数据库操作的方法。而Executor(执行器)则是SqlSession背后真正执行这些操作的核心组件。

具体来说:

  1. 创建SqlSession时生成Executor

    • 当通过SqlSessionFactory打开一个新的SqlSession时,工厂会根据配置信息创建一个合适的Executor实例。比如,如果配置了批量操作,可能会创建BatchExecutor;如果没有特殊配置,会选择默认的Executor实现。
  2. Executor处理SQL请求

    • 当调用SqlSession的方法(如selectOne()insert()update()delete())时,SqlSession实际上委托给内部持有的Executor来执行相应的任务。
  3. 执行过程

    • Executor负责处理SQL语句的实际执行流程,包括但不限于:
      • 根据Mapper接口方法及XML映射文件生成MappedStatement对象。
      • 通过StatementHandler处理SQL语法和预编译。
      • 利用ParameterHandler设置SQL参数。
      • 执行SQL并获取结果,通过ResultHandler处理结果集,转化为Java对象。
      • 如果配置了缓存,Executor还会参与到一级缓存(本地缓存,存在于SqlSession层面)的管理中,以及在合适的情况下与二级缓存进行交互。
  4. 事务管理

    • Executor同样参与事务管理,根据SqlSession的事务属性来协调数据库的事务边界。

其实就是SqlSession作为一个门面模式的实现,提供了简洁的API供用户调用,而Executor作为它的内部协作组件,承担起实际的SQL执行和事务处理职责。

那我们工作中平时自定义的mapper接口是怎样和sqlsession搭上关系的呢?比如:

@Mapper
public interface OrderMapper extends BaseMapper<Order> {

    @Select({"SELECT o.order_no, SUM(i.price * i.count) AS amount",
            "FROM t_order o JOIN t_order_item i ON o.order_no = i.order_no",
            "GROUP BY o.order_no"})
    List<OrderVo> getOrderAmount();
}

我们继承了BaseMapper,而 BaseMapper又继承了Mapper接口给我们定义了一堆抽象方法。

public interface BaseMapper<T> extends Mapper<T> {
    int insert(T entity);

    int deleteById(Serializable id);

    int deleteByMap(@Param("cm") Map<String, Object> columnMap);

    int delete(@Param("ew") Wrapper<T> wrapper);

    int deleteBatchIds(@Param("coll") Collection<? extends Serializable> idList);

    int updateById(@Param("et") T entity);

    int update(@Param("et") T entity, @Param("ew") Wrapper<T> updateWrapper);

    T selectById(Serializable id);

    List<T> selectBatchIds(@Param("coll") Collection<? extends Serializable> idList);

    List<T> selectByMap(@Param("cm") Map<String, Object> columnMap);

    T selectOne(@Param("ew") Wrapper<T> queryWrapper);

    Integer selectCount(@Param("ew") Wrapper<T> queryWrapper);

    List<T> selectList(@Param("ew") Wrapper<T> queryWrapper);

    List<Map<String, Object>> selectMaps(@Param("ew") Wrapper<T> queryWrapper);

    List<Object> selectObjs(@Param("ew") Wrapper<T> queryWrapper);

    <E extends IPage<T>> E selectPage(E page, @Param("ew") Wrapper<T> queryWrapper);

    <E extends IPage<Map<String, Object>>> E selectMapsPage(E page, @Param("ew") Wrapper<T> queryWrapper);
}

在Spring与MyBatis Plus 或者MyBatis集成的环境中,一般不会直接使用原始的SqlSession,而是使用SqlSessionTemplate,因为它更好地支持Spring的事务管理和线程安全。Spring会通过SqlSessionTemplateMapperFactoryBean等组件来代理和注入Mapper接口的实例。

当Spring容器初始化时,对于每一个Mapper接口,MyBatis会通过动态代理(如JDK动态代理或CGLIB代理)技术生成一个代理对象,如MapperProxy或MybatisMapperProxy。这个代理对象在其invoke方法中,当调用Mapper接口的方法时,会利用内部持有的SqlSession来执行相应的SQL语句,即通过SqlSession的API来完成数据库操作。

BaseMapper接口中定义了一系列方法,如插入、更新、删除和查询等CRUD操作,这些方法并不包含实际执行SQL的逻辑。对于BaseMapper或其子接口中的每个方法,通常会在对应的XML映射文件中定义SQL语句和结果映射(ResultMap)。例如,对于selectById方法,会在对应的UserMapper.xml中写好一个select标签,定义查找用户信息的SQL查询语句,并指定它与方法的映射关系。

当调用BaseMapper的某个方法时,实际上是调用代理对象的方法,代理对象内部通过SqlSession找到对应的MappedStatement(已解析好的SQL语句和参数映射信息),然后调用Executor执行SQL,最后将数据库结果转换为Java对象并返回。

那么mybatis是怎样解析xml这种mapper文件的呢?

MyBatis解析mapper文件的过程主要包括以下几个步骤,主要构建为MappedStatement:

  1. 资源加载

    • 当SqlSessionFactory构建时,或者在运行时动态添加新的mapper资源时,MyBatis会加载mapper XML文件作为资源。
  2. XML解析器初始化

    • MyBatis使用内置的XML解析器读取mapper文件内容,通常基于DOM或SAX解析器来解析XML文档结构。
  3. 解析主元素

    • 解析过程从根元素<mapper>开始,通过调用XMLMapperBuilder#parse()方法来处理整个mapper文件的内容。
    • 首先检查资源是否已经被加载过,避免重复解析。
  4. 解析内部元素

    • 对mapper文件内的各个SQL映射元素(如<select><insert><update><delete>等)进行遍历和解析。
    • 每个元素的属性和嵌套元素(如parameterType、resultType或resultMap等)都会被提取出来,并根据它们的定义构建相应的MappedStatement对象。
  5. 构建MappedStatement

    • MappedStatement是MyBatis中最关键的数据结构之一,它包含了SQL语句、参数类型、结果类型以及可能的结果映射等信息。
    • 解析器会对SQL语句中的动态元素(如${}#{}表达式)进行初步识别,但实际的动态解析将在执行阶段由ParameterHandler和BoundSql完成。
  6. 注册Mapper

    • 解析完成后,MappedStatement会被注册到MyBatis的Configuration对象中,这样在执行SQL时,就可以通过Mapper接口方法名找到对应的MappedStatement来执行。
  7. 延迟解析与处理

    • 在解析过程中,有些依赖的ResultMap或其他元素可能还未完全加载,因此在解析完mapper文件后,还需要调用parsePendingResultMaps()parsePendingCacheRefs()parsePendingStatements()等方法,以确保所有的依赖项都被正确解析和注册。

那比如上面的注解呢?如何转为MappedStatement的呢?

在MyBatis中,使用注解的方式来定义SQL语句时,例如@Select注解,MyBatis框架会在启动或刷新配置的过程中自动扫描和解析带有注解的Mapper接口和方法。

以下是注解如何被解析为MappedStatement的大致步骤:

  1. Mapper接口扫描

    • MyBatis会按照配置扫描指定包下的Mapper接口,或者是通过@Mapper注解标记的接口。
  2. 方法解析

    • 对于Mapper接口中的每个方法,MyBatis会检查是否存在诸如@Select@Insert@Update@Delete等SQL注解。
    • 当发现这些注解时,会读取注解上的SQL字符串值。
  3. 构建MappedStatement

    • 根据注解信息和方法签名,MyBatis会创建一个MappedStatement对象。
    • MappedStatement会记录下注解中SQL语句、方法的全限定名(即接口名+方法名)、方法参数类型(作为参数映射的基础)、结果类型(可能是实体类或基本类型数组等)以及其它相关的配置信息。
  4. 注册MappedStatement

    • 创建好的MappedStatement会被注册到MyBatis的Configuration对象中,这样在后续执行SQL时,可以根据方法名快速定位到对应的MappedStatement来执行。
  5. 动态SQL处理

    • 即使使用注解,MyBatis仍然支持动态SQL的部分特性,例如在@Select注解中可以使用${}表达式等。
    • 虽然注解中的SQL是静态文本,但在实际执行前,MyBatis会利用org.apache.ibatis.builder.annotation.ProviderContext上下文信息,配合@SelectProvider注解引入的SQL提供类来动态生成SQL语句,如果有的话。

总结来说,BaseMapper中的方法与SqlSession的方法关联的整个过程是这样的:

  • 开发者定义了一个继承自BaseMapper的接口,并在XML映射文件中映射SQL语句。
  • MyBatis通过动态代理技术创建一个代理对象,该对象在接收到方法调用时,会借助SqlSession找到并执行相应的SQL。
  • SqlSession内部维护了方法与SQL语句之间的映射关系,负责真正执行数据库操作,并确保结果能够正确映射到Java对象上。