目录
1.数据库连接创建,释放频繁造成西戎资源的浪费,从而影响系统性能,使用数据库连接池可以解决问题。
2.sql语句在代码中硬编码,造成代码的不已维护,实际应用中sql的变化可能较大,sql代码和java代码没有分离开来维护不方便。
3.使用preparedStatement向有占位符传递参数存在硬编码问题因为sql中的where子句的条件不确定,同样是修改不方便
4.对结果集中解析存在硬编码问题,sql的变化导致解析代码的变化,系统维护不方便。
5、JDBC没有提供缓存,增加了数据库压力。

提供给外部使用的接口API,开发人员通过这些本地API来操纵数据库。接口层一接收到调用请求就会调用数据处理层完成具体的数据处理。其核心是SqlSession接口。
负责具体的SQL查找、SQL解析、SQL执行和执行结果映射处理等。它主要的目的是根据调用的请求完成一次工作。

在Mybatis初始化过程中,会加载mybatis-config.xml配置文件、映射文件以及Mapper接口中的信息,解析后的配置信息会形成相应的对象并保存到Configuration对象中。利用该configuration对象创建SqlSessionFactory对象。待Mybatis初始化之后。开发人员可以通过初始化得到SqlSessionFactory创建SqlSession对象并完成数据库操作。
是一个所有配置型的的容器对象。
对应的是scripting模块。Mybatis中的scripting模块,会根据用户传入的实参,解析映射文件中定义的SQL节点,并形成数据库可执行的SQL语句。之后会处理SQL语句中的占位符,绑定用户传入的实参
负责根据用户传递的parameterObject,动态生成SQL语句,将信息封装到BoundSql对象中并返回



负责最基础的功能支撑,包括连接管理、事务管理、配置加载和缓存处理,这些都是共用的东西,将他们抽取成最基础的组件。为上层的数据处理层提供最基础的支撑。


在mybatis初始化过程中,会加载mybatis-config.xml配置文件、映射配置文件以及Mapper接口中的注解信息,解析后的配置信息会形成相应的对象并保存到Configuration对象中。

(1)入口:
Mybatis的初始化流程的入口是SqlSessionFactoryBuilder的build()方法:
- public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
- try {
- //创建XMLConfigBuilder对象
- XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
- //执行XML解析
- //创建DefaultSqlSessionFactory对象
- return build(parser.parse());
- } catch (Exception e) {
- throw ExceptionFactory.wrapException("Error building SqlSession.", e);
- } finally {
- ErrorContext.instance().reset();
- try {
- reader.close();
- } catch (IOException e) {
- // Intentionally ignore. Prefer previous error.
- }
- }
- }
(2)XMLConfigBuilder对象:
继承BaseBuilder抽象类,XML配置构建起,主要负责解析mybatis-config.xml配置文件:
- private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
- //创建Configuration对象
- super(new Configuration());
- ErrorContext.instance().resource("SQL Mapper Configuration");
- //设置Configuration的variables属性,把props绑定到configuration的props属性上
- this.configuration.setVariables(props);
- this.parsed = false;
- this.environment = environment;
- this.parser = parser;
- }
- //parse判断是否解析过
- public Configuration parse() {
- //若已经解析过了 就抛出异常
- if (parsed) {
- throw new BuilderException("Each XMLConfigBuilder can only be used once.");
- }
- //标志已解析
- parsed = true;
- //解析XML configuration节点
- parseConfiguration(parser.evalNode("/configuration"));
- return configuration;
- }
- //方法实现说明:解析我们mybatis-config.xml的 configuration节点
-
- private void parseConfiguration(XNode root) {
- try {
- /**
- * 解析 properties节点
- *
- */
- propertiesElement(root.evalNode("properties"));
- /**
- * 解析我们的mybatis-config.xml中的settings节点
- */
- Properties settings = settingsAsProperties(root.evalNode("settings"));
- loadCustomVfs(settings);
- // 指定 MyBatis 所用日志的具体实现,未指定时将自动查找。
- loadCustomLogImpl(settings);
- ///解析我们的别名
- typeAliasesElement(root.evalNode("typeAliases"));
- //解析我们的插件(比如分页插件)
- pluginElement(root.evalNode("plugins"));
- objectFactoryElement(root.evalNode("objectFactory"));
- objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
- reflectorFactoryElement(root.evalNode("reflectorFactory"));
- // 设置settings 和默认值
- settingsElement(settings);
- // read it after objectFactory and objectWrapperFactory issue #631
-
- // 解析我们的mybatis环境
- environmentsElement(root.evalNode("environments"));
- // 解析数据库厂商
- databaseIdProviderElement(root.evalNode("databaseIdProvider"));
- // 解析我们的类型处理器节点
- typeHandlerElement(root.evalNode("typeHandlers"));
- //最最最最最重要的就是解析我们的mapper
- mapperElement(root.evalNode("mappers"));
- } catch (Exception e) {
- throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
- }
- }

xpath会读取dtd文件中定义好的标签规则,从而对xml文件进行解析 .
再mybatis的核心配置文件解析的过程中,解析到mappers节点时,会进一步解析mapper映射文件。
当扫描到mappers节点后会去执行mapperElement方法,根据不同的mapper配置方式获取不同的输入流(例如XML文件配置的mapper就获取XMLMapperBuilder)。
- private void mapperElement(XNode parent) throws Exception {
- if (parent != null) {
- /**
- * 获取我们mappers节点下的一个一个的mapper节点
- */
- for (XNode child : parent.getChildren()) {
- /**
- * 判断我们mapper是不是通过批量注册的
- *
- */
- if ("package".equals(child.getName())) {
- String mapperPackage = child.getStringAttribute("name");
- configuration.addMappers(mapperPackage);
- } else {
- /**
- * 判断从classpath下读取我们的mapper
- *
- */
- String resource = child.getStringAttribute("resource");
- /**
- * 判断是不是从我们的网络资源读取(或者本地磁盘得)
- *
- */
- String url = child.getStringAttribute("url");
- /**
- * 解析这种类型(要求接口和xml在同一个包下)
- *
- *
- */
- String mapperClass = child.getStringAttribute("class");
-
- /**
- * 我们得mappers节点只配置了
- *
- */
- if (resource != null && url == null && mapperClass == null) {
- ErrorContext.instance().resource(resource);
- /**
- * 把我们的文件读取出一个流
- */
- InputStream inputStream = Resources.getResourceAsStream(resource);
- /**
- * 创建读取XmlMapper构建器对象,用于来解析我们的mapper.xml文件
- */
- XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
- /**
- * 真正的解析我们的mapper.xml配置文件(说白了就是来解析我们的sql)
- */
- mapperParser.parse();
- } else if (resource == null && url != null && mapperClass == null) {
- ErrorContext.instance().resource(url);
- InputStream inputStream = Resources.getUrlAsStream(url);
- XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
- mapperParser.parse();
- } else if (resource == null && url == null && mapperClass != null) {
- Class> mapperInterface = Resources.classForName(mapperClass);
- configuration.addMapper(mapperInterface);
- } else {
- throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
- }
- }
- }
- }
- }
XMLMapperBuilder对mapper.xml配置文件的解析
- /**
- * 方法实现说明:真正的去解析我们的Mapper.xml(EmployeeMapper.xml)
- * @author:xsls
- * @return:
- * @exception:
- * @date:2019/8/30 16:43
- */
- public void parse() {
- /**
- * 判断当前的Mapper是否被加载过
- */
- if (!configuration.isResourceLoaded(resource)) {
- /**
- * 真正的解析我们的
- *
- */
- configurationElement(parser.evalNode("/mapper"));
- /**
- * 把资源保存到我们Configuration中
- */
- configuration.addLoadedResource(resource);
-
- bindMapperForNamespace();
- }
-
- parsePendingResultMaps();
- parsePendingCacheRefs();
- parsePendingStatements();
- }
-
-
- private void configurationElement(XNode context) {
- try {
- /**
- * 解析我们的namespace属性
- *
- */
- String namespace = context.getStringAttribute("namespace");
- if (namespace == null || namespace.equals("")) {
- throw new BuilderException("Mapper's namespace cannot be empty");
- }
- /**
- * 保存我们当前的namespace 并且判断接口完全类名==namespace
- */
- builderAssistant.setCurrentNamespace(namespace);
- /**
- * 解析我们的缓存引用
- * 说明我当前的缓存引用和DeptMapper的缓存引用一致
- *
- 解析到org.apache.ibatis.session.Configuration#cacheRefMap<当前namespace,ref-namespace>
- 异常下(引用缓存未使用缓存):org.apache.ibatis.session.Configuration#incompleteCacheRefs
- */
- cacheRefElement(context.evalNode("cache-ref"));
- /**
- * 解析我们的cache节点
- *
- 解析到:org.apache.ibatis.session.Configuration#caches
- org.apache.ibatis.builder.MapperBuilderAssistant#currentCache
- */
- cacheElement(context.evalNode("cache"));
- /**
- * 解析paramterMap节点(该节点mybaits3.5貌似不推荐使用了)
- */
- parameterMapElement(context.evalNodes("/mapper/parameterMap"));
- /**
- * 解析我们的resultMap节点
- * 解析到:org.apache.ibatis.session.Configuration#resultMaps
- * 异常 org.apache.ibatis.session.Configuration#incompleteResultMaps
- *
- */
- resultMapElements(context.evalNodes("/mapper/resultMap"));
- /**
- * 解析我们通过sql节点
- * 解析到org.apache.ibatis.builder.xml.XMLMapperBuilder#sqlFragments
- * 其实等于 org.apache.ibatis.session.Configuration#sqlFragments
- * 因为他们是同一引用,在构建XMLMapperBuilder 时把Configuration.getSqlFragments传进去了
- */
- sqlElement(context.evalNodes("/mapper/sql"));
- /**
- * 解析我们的select | insert |update |delete节点
- * 解析到org.apache.ibatis.session.Configuration#mappedStatements
- */
- buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
- } catch (Exception e) {
- throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e);
- }
- }
XMLStatementBuilder-解析insert、update、delete等节点生成SQL语句:
- //方法实现说明:用于解析我们的的inset|select|update|delte节点的
- public XMLStatementBuilder(Configuration configuration, MapperBuilderAssistant builderAssistant, XNode context, String databaseId) {
- super(configuration);
- this.builderAssistant = builderAssistant;
- this.context = context;
- this.requiredDatabaseId = databaseId;
- }
-
- public void parseStatementNode() {
- // 我们的insert|delte|update|select 语句的sqlId
- String id = context.getStringAttribute("id");
- //判断我们的insert|delte|update|select 节点是否配置了 数据库厂商标注
- String databaseId = context.getStringAttribute("databaseId");
-
- // 匹配当前的数据库厂商id是否匹配当前数据源的厂商id
- if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
- return;
- }
-
- // 获得节点名称:select|insert|update|delete
- String nodeName = context.getNode().getNodeName();
- // 根据nodeName 获得 SqlCommandType枚举
- SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
- //判断是不是select语句节点
- boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
- // 获取flushCache属性 默认值为isSelect的反值:查询:默认flushCache=false 增删改:默认flushCache=true
- boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
- /**
- * 获取useCache属性
- * 默认值为isSelect:查询:默认useCache=true 增删改:默认useCache=false
- */
- boolean useCache = context.getBooleanAttribute("useCache", isSelect);
-
- /**
- * resultOrdered: 是否需要处理嵌套查询结果 group by (使用极少)
- * 可以将比如 30条数据的三组数据 组成一个嵌套的查询结果
- */
- boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);
-
- /**
- * 解析我们的sql公用片段
- *
-
- employee where id=#{id}
-
- 将
解析成sql语句 放在 - */
- // Include Fragments before parsing
- XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
- includeParser.applyIncludes(context.getNode());
-
- /**
- * 解析我们sql节点的参数类型
- */
- String parameterType = context.getStringAttribute("parameterType");
- // 把参数类型字符串转化为class
- Class> parameterTypeClass = resolveClass(parameterType);
-
- /**
- * 查看sql是否支撑自定义语言
- *
-
-
-
- */
- String lang = context.getStringAttribute("lang");
- /**
- * 获取自定义sql脚本语言驱动 默认:class org.apache.ibatis.scripting.xmltags.XMLLanguageDriver
- */
- LanguageDriver langDriver = getLanguageDriver(lang);
-
- // Parse selectKey after includes and remove them.
- /**
- * 解析我们
- */
- processSelectKeyNodes(id, parameterTypeClass, langDriver);
-
- // Parse the SQL (pre:
and were parsed and removed) - /**
- * 我们insert语句 用于主键生成组件
- */
- KeyGenerator keyGenerator;
- /**
- * selectById!selectKey
- * id+!selectKey
- */
- String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
- /**
- * 把我们的命名空间拼接到keyStatementId中
- * com.tuling.mapper.Employee.saveEmployee!selectKey
- */
- keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
- /**
- *
- *判断我们全局的配置类configuration中是否包含以及解析过的组件生成器对象
- */
- if (configuration.hasKeyGenerator(keyStatementId)) {
- keyGenerator = configuration.getKeyGenerator(keyStatementId);
- } else {
-
- /**
- * 若我们配置了useGeneratedKeys 那么就去除useGeneratedKeys的配置值,
- * 否者就看我们的mybatis-config.xml配置文件中是配置了
- *
默认是false - * 并且判断sql操作类型是否为insert
- * 若是的话,那么使用的生成策略就是Jdbc3KeyGenerator.INSTANCE
- * 否则就是NoKeyGenerator.INSTANCE
- */
- keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
- configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
- ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE;
- }
-
- /**
- * 通过class org.apache.ibatis.scripting.xmltags.XMLLanguageDriver来解析我们的
- * sql脚本对象 . 解析SqlNode. 注意, 只是解析成一个个的SqlNode, 并不会完全解析sql,因为这个时候参数都没确定,动态sql无法解析
- */
- SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
- /**
- * STATEMENT,PREPARED 或 CALLABLE 中的一个。这会让 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement,默认值:PREPARED
- */
- StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
- /**
- * 这是一个给驱动的提示,尝试让驱动程序每次批量返回的结果行数和这个设置值相等。 默认值为未设置(unset)(依赖驱动)
- */
- Integer fetchSize = context.getIntAttribute("fetchSize");
- /**
- * 这个设置是在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。默认值为未设置(unset)(依赖驱动)。
- */
- Integer timeout = context.getIntAttribute("timeout");
- /**
- * 将会传入这条语句的参数类的完全限定名或别名。这个属性是可选的,因为 MyBatis 可以通过类型处理器(TypeHandler) 推断出具体传入语句的参数,默认值为未设置
- */
- String parameterMap = context.getStringAttribute("parameterMap");
- // 从这条语句中返回的期望类型的类的完全限定名或别名。 注意如果返回的是集合,那应该设置为集合包含的类型,而不是集合本身。
- //可以使用 resultType 或 resultMap,但不能同时使用
- String resultType = context.getStringAttribute("resultType");
- /**解析我们查询结果集返回的类型 */
- Class> resultTypeClass = resolveClass(resultType);
- //外部 resultMap 的命名引用。结果集的映射是 MyBatis 最强大的特性,如果你对其理解透彻,许多复杂映射的情形都能迎刃而解。
- //可以使用 resultMap 或 resultType,但不能同时使用。
- String resultMap = context.getStringAttribute("resultMap");
-
- String resultSetType = context.getStringAttribute("resultSetType");
- ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
- if (resultSetTypeEnum == null) {
- resultSetTypeEnum = configuration.getDefaultResultSetType();
- }
-
- //解析 keyProperty keyColumn 仅适用于 insert 和 update
- String keyProperty = context.getStringAttribute("keyProperty");
- String keyColumn = context.getStringAttribute("keyColumn");
- String resultSets = context.getStringAttribute("resultSets");
-
- //为我们的insert|delete|update|select节点构建成我们的mappedStatment对象
- builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
- fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
- resultSetTypeEnum, flushCache, useCache, resultOrdered,
- keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
- }


在mybatis中,sqlsession对数据库的操作,将委托给执行其Executor来完成;mybatis执行过程中,主要的执行模块是:sqlsession -> Executor -> statementHandler -> 数据库。
四个核心组件:








- public interface Executor {
-
- //ResultHandler 对象的枚举
- ResultHandler NO_RESULT_HANDLER = null;
-
- //更新 or 插入 or 删除,由传入的 MappedStatement 的 SQL 所决定
-
- int update(MappedStatement ms, Object parameter) throws SQLException;
-
- // 查询带缓存key查询
-
List query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws SQLException; -
- // 不走缓存查询
-
-
List query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException; -
- // 调用存过查询返回游标对象
-
Cursor queryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds) throws SQLException; -
- // 刷入批处理语句
- List
flushStatements() throws SQLException; -
- //提交事务
- void commit(boolean required) throws SQLException;
-
- //回滚事务
- void rollback(boolean required) throws SQLException;
-
- //创建缓存key
- CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql);
-
- // 判断是否缓存
- boolean isCached(MappedStatement ms, CacheKey key);
- // 清除本地缓存
- void clearLocalCache();
-
- // 延迟加载
- void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class> targetType);
-
- //获取一个事务
- Transaction getTransaction();
- // 关闭事务
- void close(boolean forceRollback);
-
- //判断是否关闭
- boolean isClosed();
-
- // 设置包装的 Executor 对象
- void setExecutorWrapper(Executor executor);
-
- }
维护一级缓存,是simple、reuse、batch这三个执行器的父类,主要逻辑是维护缓存,其他实现交给之类。




默认是创建simpleExecutor,并开启二级缓存
Mybatis提供了缓存策略,通过缓存策略来减少数据库的查询次数,从而提高性能,分为以及缓存和二级缓存。
当我们使用Mybatis开启一次和数据库的会话,Mybatis会创建出一个SqlSession对象表示一次数据库会话,建立一个简单的缓存,将每次查询的结果缓存起来,当下次查询的时候,如果判断先前有个完全一样的查询,会直接从缓存中直接将结果取出,返回给用户,不需要再进行一次数据库查询了。
对于会话(session)级别的数据缓存我们称之为一级缓存。
一级缓存默认是开启的,一级缓存是基于SqlSession生命周期的。
查询的时候想去localcache查,查到了直接返回,没有查到就去查数据库,把结果写入localcache并把结果返回。如果在第一次查询之后对数据进行了修改,在executor中执行update操作时会执行一次clearlocalcache方法,清楚了缓存,所以第二次去查询也无法命中缓存,需要再去数据库中查
一级缓存中,最大的共享范围就是一个SqlSession内部,如果多个SqlSession之间需要共享缓存,就需要用到二级缓存。开启二级缓存之后,会用CachingExecutor装饰Executor,进入一级缓存的查询流程前,先用CachingExecutor进行二级缓存
CachingExecutor会先于一级缓存进行缓存查询,如果没有再执行正常的查询。

SQL执行的入口分析



查询语句的执行分析 



