前言
Mybaits 作为国内大流行的“伪”ORM 框架,它也是不少大厂的“御用框架”,可想而知其地位举足轻重。究其原因,国内很多互联网公司早期大部分都是基于面向数据库(表)编程,而 java 对象只是作为数据的载体,数据模型的 CRUD 都设计在一张表上,而业务逻辑就是不同表的 CRUD 集合。所以开发者需要它做的就是将 SQL 自动封装映射成 java 对象,没有其他花里胡哨的功能,这也是它流行原因之一。
Mybaits 组件简要流程图
Configuration
- Configuration:MyBatis 所有的主配置信息都在 Configuration 里面,MyBatis 通过 Configuration 对象获取各种配置,例如:启动加载的 MappedStatement、TypeHandler、类型别名、插件等等。
- MappedStatement:它封装了就是大家经常编写的 XxxxMapper.xml 文件里面
SqlSession
- SqlSession:它作为 MyBatis 提供的面向开发者的核心顶层 API,表示和数据库交互时的会话对象,是 MyBatis 执行持久化操作的关键对象,它的底层封装了 JDBC 连接,用于完成数据库的增删改查功能。还有一点需要注意的是它不是线程安全的。
- SqlSessionFactory:它是一个接口,它的定义就是创建 SqlSession 的工厂,Mybatis 底层可以通过 SqlSessionFactoryBuilder 对象,加载 xml 配置文件或一个预设定的 Configuration 的实例构建出具体的工厂类的实例。
- DefaultSqlSessionFactory:它是 SqlSessionFactory 默认实现类。它通过 Configuration 来创建,其重载了很多 openSession 方法(根据 Configuration、执行器、自动提交创建等等)来创建 SqlSession。
- Configuration:主配置信息,详情见第一小节。
- DefaultSqlSession:它是 SqlSession 接口的默认实现类,它不仅实现了 SqlSession 的核心方法,还维护主配置信息与执行器(Executor)信息。它的类注释上标注了此类不是一个线程安全的,为什么线程不安全?其实也很好理解,因为 SqlSession 的最终执行只会产生一次 Connection,假设在两个线程通过同一个 SqlSession 来执行操作,那么就有可能,A 线程开始执行,但是由于 B 线程执行操作比较快,B 线程先关闭了链接,从而造成 A 线程的逻辑不被成功执行。
- SqlSessionManager:可以看出 SqlSessionManager 既实现了 SqlSessionFactory,也实现了 SqlSession。所以它具备生产 SqlSession 的能力,也具备 SqlSession 的能力,SqlSession 的作用是执行具体的 Sql 语句。但是需要说明的是,SqlSessionManager 是线程安全的,因为它内部维护了一个 sqlSessionProxy,其重写的 SqlSession 的方法,都是通过 sqlSessionProxy 来执行的,而 sqlSessionProxy 是一个动态代理的对象,其 SqlSession 是通过 localSqlSession 来获取的,这个属性其实就是一个 ThreadLocal 类,到这其实也就明白了,它为每一个线程分配一个副本对象,保证了 Connection 对象线程的安全性。
- Executor:详情见下一小节。
Executor
- Executor:通过上面几个关系图,不难看出一个 SqlSession 对应一个 Executor 对象,而 Executor 是 MyBatis 的内部 SQL 执行器,它负责调用 StatementHandler 操作数据库进行增删改查的具体操作,并把结果集通过 ResultSetHandler 进行自动映射。
- BaseExecutor:BaseExecutor 是一个抽象类,它采用模板方法的方式实现了 Exeutor 接口,除了默认实现的方法之外,它提供了额外的抽象方法由不同功能的实现类实现不同的功能,其中它的实现类有 SimpleExecutor、ReuseExecutor、BatchExecutor、ClosedExecutor,需要额外说明的是 Mybatis 的一级缓存的功能也是其实现的。
- CachingExecutor:它其实是 Executor 的装饰器,它内部维护了一个 Executor 的属性,并为这个属性增加了二级缓存的功能。因为这个属性的类型是 Executor,所以这个属性可以是 BaseExeutor 具体实现的执行器的任何一个,它的处理逻辑可以大致列为:它先从缓存中查询结果,如果存在则就返回。倘若不存在,则交由 BaseExecutor 的具体的实现类去进行操作,并把结果缓存起来。
- SimpleExecutor:从它的名字也能看出它是一个简单的执行器,其核心的方法都在其父类 BaseExecutor 中,它与其他执行器的区别是,它每执行一次,就创建一个 Statement 对象,用完立刻关闭 Statement 对象。
- BatchExecutor:专门用于执行批量 sql 操作。它维护了多个 Statement,通过 Statement.addBatch()添加到批处理,当所有参数都设置完,通过 Statement.executeBatch()方法执行操作。
- ClosedExecutor:它是一个内部类,外部无法使用,调用其方法会抛出异常。它是一个表示已经关闭的执行器,并没有实际的功能。
- ReuseExecutor:它是可复用的执行器,它会重用 Statement 执行 sql 操作,在执行操作的时候,会以 sql 作为 key 去查找 Statement,有就使用,没有就创建,使用完不会关闭,会暂存起来,以供下次使用。
StatementHandler
- StatementHandler:它是 MyBatis 操作数据库执行 sql 脚本的对象,也叫 sql 语法构建处理器,sql 它封装了对 JDBC 的 Statement 对象的操作,其作用是:sql 声明、填参、执行、获取结果。需要额外说明的是它的填参和获取结果的过程,因为它涉及参数映射、结果集映射、1 对 N、循环引用等复杂逻辑的处理。对于这种复杂性 MyBatis 做法就是使用参数处理器(ParameterHandler)、结果集处理器(ResultSetHandler)分别实现处理的。
- BaseStatementHandler:它是 StatementHandler 的默认实现类,它也是一个抽象的基类,它有点类似于 Executor,它同样采用模板方法的方式实现了 StatementHandler 接口,除了默认实现的方法之外,它提供了额外的抽象方法由不同功能的实现类实现不同的功能。
- RoutingStatementHandler:它通过静态代理的模式,实现了“路由器”的功能,它主要功能是实现了此下三个处理器的路由,它没有实际操作处理,只是负责 BaseStatementHandler 相关具体的实现 StatementHandler 的创建及调用。
- SimpleStatementHandler:它是一个简单的处理器,它对应着 JDBC Statement 对象的处理,处理不带动态参数运行的 sql。
- PrepareStatementHandler:它是能够解决 sql 注入的一种推荐处理器,它同样也对应 java.sql.PrepareStatement 对象的处理,负责处理 sql 中的占位符动态参数赋值操作预编译 SQL 的接口处理器。
- CallableStatementHandler:它实际就是使用 CallableStatement 来执行 SQL 语句,只不过它执行的是存储过程。
ParameterHandler
- ParameterHandler:它是 Mybatis 的 sql 实参处理器,它主要负责将传递的参数转换成 JDBC 的 Statement 所需要的参数,它是 MyBatis 实现 sql 参数设置的接口对象。
- DefaultParameterHandler:它是 ParameterHandler 的默认实现类,它主要是给 PreparedStatement 设置参数,将传入的 BoundSql 语句中的”?”占位符进行实参替换。MyBatis 在解析 xml 文件中 sql 标签节点时,会将#{}参数解析为 ParameterMapping 对象,通过它就可以知道参数的名称、JavaType、JdbcType 等等信息。再根据参数名称去 ParamMap 中解析出参数值,根据参数的 JavaType 找到对应的 TypeHandler,再通过 TypeHandler 去设置对应类型的参数,
ResultSetHandler
- ResultSetHandler:它封装了将 JDBC 返回的 ResultSet 结果集对象的操作。当执行 SQL 类型为 SELECT 语句时,ResultSetHandler 用于将查询结果转换成 Java 对象。(ResultSet 接口是 JDBC-API 中比较重要的组件,提供了检索和操作 sql 执行结果的方法)
- DefaultResultSetHandler:它是 ResultSetHandler 的默认实现类,它具体的作用就是将 Statement 执行后的结果集,按照 Mapper 文件中定义返回结果的 ResultType/ResultMap 来封装成对应的对象,最后将封装的对象返回。
TypeHandler
- TypeHandler:它是 MyBatis 中的类型处理器,它负责 java 数据类型和 JDBC 数据类型之间的映射和转换,它的作用主要是能够根据 java 类型,在 StatementHandler 调用 ParameterHandler 或 ResultSetHandler 时,为对应的 set/get 方法为对象设值。
- BaseTypeHandler:它是 TypeHandler 接口的默认实现类,它提供了 4 个抽象方法由具体的子类去实现相关的转换功能,而我们在实现自定义的转换器的时候,可以通过继承它实现相关功能。
总结
Sqlsession 作为 Mybaits 开发者层面的顶层 API,实际上是 Executor 组件的门面接口,其主要目的是为了提供更友好的数据库操作接口,这也是设计模式中外观模式的典型应用。真正执行 sql 操作的是 Executor 的 sql 执行器,它会通过 StatementHandler 组件对 JDBC 的 Statement 对象进行操作,它通过 ParameterHandler 组件对 sql 参数占位符赋值。ParameterHandler 组件中会根据 java 类型找到对应的 TypeHandler 对象,TypeHandler 中会通过 Statement 对象提供的 set()方法为 Statement 对象中的参数占位符设置值。同样,当 StatementHandler 组件完成与数据库交互后,在通过 ResultSetHandler 组件从 Statement 对象中获取 ResultSet 对象,然后将 ResultSet 对象转换为对应的 java 对象。