MyBatis简析
一、框架及原理分析 |
MyBatis是支持定制化SQL、存储过程以及高级映射的持久层框架,主要完成2件事情。 1,封装JDBC 2,利用反射打通Java类与SQL之间的相互转换。 MyBatis的主要设计目的是让对执行SQL语句时输入输出的数据管理更加方便,所以方便地写出SQL和方便地获取SQL的执行结果,才是MyBatis的核心竞争力 |
二、基本构成 |
一、配置文件 4大标签 +2小标签 配置标签:<configuration> 根标签 1 ## 设置标签:<settings> : <setting name="" value="true\false"> ####name:缓存能力cacheEnabled、延迟加载lazyLoadingEnabled、 2 ## 类型别名:<typeAliases>:<typeAlias type="Bean" alias="" > 3 ## 环境标签:<environments default = "">:<environment id=""> #####事务管理:<transactionManager type="JDBC\"> #####数据源: <dataSource type="POOLED">:<property > 4 ## 映射标签:<mappers>:<mapper resource="BeanMapper.xml"> 二、主要构件 Configuration 储存配置文件的配置信息 SqlSession 表示和数据库交互时的会话,完成必要数据库的增删改查。 Executor 生成SQL语句+维护查询缓存 StatementHandler 操作JDBC Statement ParameterHandler 传入参数 [ Java类型 ],转换,JDBC Statement数据类型 [long->bingint] ResultSetHandler JDBC返回的ResultSet结果集对象,转换,List类型的集合 TypeHandler Java数据类型,映射、转换,JDBC 数据类型(=表_列_类型) MappedStatement 维护一条<select|update|delete|insert>节点的封装 SqlSource 根据传入参数parameterObject,动态生成SQL+信息封装到BoundSql对象+并返回 BoundSql 表示动态生成的SQL语句以及相应的参数信息 补充:以上主要成员在一次数据库操作中基本都会涉及,在SQL操作重点需要关注SQL参数什么时候被设置和结果集怎么转换为JavaBean对象,这两个过程正好对应StatementHandler和ResultSetHandler类中的处理逻辑。 |
三、MyBatis缓存 |
MyBatis提供查询缓存,用于减轻数据库压力,提高性能。 一级缓存:SqlSession级别 =不共享 默认开启 ## 每个SqlSession对象都有一个哈希表用于缓存数据。 ## 同一SqlSession对象执行2次,第一次查询完毕,将结果缓存。第二次查询,直接返回缓存结果。 二级缓存:Mapper(namespace)级别= 多sqlSession共享 默认关闭 ## 多个SqlSession对象共享二级缓存,跨SqlSession ## 不同SqlSession对象执行2次相同SQL语句,第一次查询完毕,将结果缓存。第二次查询,直接返回二级缓存结果。 SQL语句更新操作 ## 当SQL语句进行(删除/添加/更新),会清空对应的缓存,保证缓存中存储的信息都是最新的数据。 ## MyBatis的弱势:二级缓存对细粒度的数据级别的实现不友好,比如(如下需求:对商品信息进行缓存,由于商品信息查询访问量大,但要求用户每次能查询最新的商品信息,此时如果使用mybatis的二级缓存就无法实现当一个商品变化时只刷新该商品信息,而不刷新其他商品信息,因为mybatis的二级缓存区域以mapper为单位划分,当一个商品信息变化时,会将所有商品信息的缓存数据全部清空。解决此类问题需要在业务层根据需求对数据有针对性缓存,具体业务具体实现)=类似同步... |
四、源码流程分析 |
第一步,MyBatis的初始化: 解析配置文件+初始化Configuration ### 加载配置文件 InputStream inputStream = Resources.getResourceAsStream( resource); ### 构建sqlSessionFactory sessionFactory = new SqlSessionFactory Builder().build(inputStream); ### 首先会创建SqlSessionFactory建造者对象,然后由它创建SqlSessionFactory。 这里用到的是建造者模式,建造者模式最简单的理解就是不手动new对象,而是由其他类来进行对象的创建。 ### XMLConfigBuilder对象进行XML配置文件解析,实际为configuration节点的解析操作。节点的依次解析 [Node: properties 、settings、typeAliases、plugins、objectFactory、objectWrapperFactory、reflectorFactory、 environments、databaseIdProvider、typeHandlers、mappers ] ### 在configuration节点下会依次解析properties/settings/.../mappers等节点配置。在解析environments节点时,会根据transactionManager的配置来创建事务管理器,根据dataSource的配置来创建DataSource对象,这里面包含了数据库登录的相关信息。在解析mappers节点时,会读取该节点下所有的mapper文件,然后进行解析,并将解析后的结果存到configuration对象中。 ### 解析完MyBatis配置文件后,configuration就初始化完成了,然后根据configuration对象来创建SqlSession,到这里时,MyBatis的初始化的征程已经走完了。。 第二步,MyBatis的SQL查询流程: ### 通过封装JDBC进行操作,然后使用Java反射技术完成JavaBean对象到数据库参数之间的相互转换,这种映射关系就是TypeHandler对象来完成。在获取数据表对应的元数据时,会保存该表所有列的数据库类型。 ### DefaultSqlSessin类 : 创建sqlSession的过程其实就是根据configuration中的配置来创建对应的类,然后返回创建的sqlSession对象。调用selectOne方法进行SQL查询,selectOne方法最后调用的是selectList,在selectList中,会查询configuration中存储的MappedStatement对象,mapper文件中一个sql语句的配置对应一个MappedStatement对象,然后调用执行器进行查询操作。 ### 执行器在query操作中,优先会查询缓存是否命中,命中则直接返回,否则从数据库中查询。 [ CachingExecutor类:获取关联参数的sql,boundSql、创建cache key值、 queryFromDatabase()方法:获取二级缓存实例 queryFromDatabase()方法:先往localCache中插入一个占位对象,再往缓存中写入数据,也就是缓存查询结果 ] ###真正的doQuery操作是由SimplyExecutor代理来完成的,该方法中有2个子流程,一个是SQL参数的设置,另一个是SQL查询操作和结果集的封装。 #####SimpleExecutor类: 子流程1:SQL查询参数的设置 stmt = prepareStatement(handler, ms.getStatementLog()); 子流程2:SQL查询操作 return handler.<E>query(stmt, resultHandler); ### 子流程1 Sql查询参数的设置 ######SimpleExecutor类:首先获取Connection连接,然后准备statement,然后就设置SQL查询中的参数值。 [ 打开一个connection连接,在使用完后不会close,而是存储下来,当下次需要打开连接时就直接返回。 ] ######DefaultParameterHandler类: 设置SQL参数值,从ParameterMapping中读取参数值和类型,然后设置到SQL语句中 ### 子流程2 Sql查询结果集的封装 ##### ResultSetWrapper是ResultSet的包装类,调用getFirstResultSet方法获取第一个ResultSet,同时获取数据库的MetaData数据,包括数据表列名、列的类型、类序号等,这些信息都存储在ResultSetWrapper类中了。然后调用handleResultSet方法来来进行结果集的封装。 ##### 调用handleRowValues方法进行结果值的设置。 ##### mapping.typeHandler.getResult会获取查询结果值的实际类型,比如我们user表中id字段为int类型,那么它就对应Java中的Integer类型,然后通过调用statement.getInt("id")来获取其int值,其类型为Integer。metaObject.setValue方法会把获取到的Integer值设置到Java类中的对应字段。 ##### metaValue.setValue方法最后会调用到Java类中对应数据域的set方法,这样也就完成了SQL查询结果集的Java类封装过程。最后贴一张调用栈到达Java类的set方法中的快照: |