热门框架系列 (一) -- MyBatis解析全局配置文件

@TOC# 热门框架系列
记录在程序走的每一步___auth:huf


从新的篇章开始;篇章阅读需要先关注; 因为笔者想参与技术文章的评选.;需要一定的粉丝量; 粉丝量达到一定数量.所有文章阅读限制将会全面放开;谢谢大家的支持
在我Spring源码系列片中; 我们曾经写了一篇Mybatis手写源码; 那篇源码篇章 主要是讲 Spring 如何整合Mybatis; 缺查补漏; 我们把Mybatis 相关知识点全部补齐;源码我们全部过一遍; Mybatis源码要比Spring源码要简单得多; 我们用两章 就可以全部补齐; 之后 ;我们会先进入到并发篇章; 热门框架篇章我们会一直更新下去; 但是不是持续更新; 有新的框架 就会往这里更新; 请大伙持续关注;
我们知道 Mybatis 是一个半ORM 框架; 我们使用Mybatis 重在他可以自由定制 sql 自由拼接Mapper 然后转换成为我们的Object 对象; 我们之前是站在Mybatis的角度 去开发 我们的mybatis-spring.jar

以不太一样的角度 去看Mybatis; 我们以Mybatis-config.xml的角度

我们创建一个项目; 使用了alibaba 的 Druid 我们从这个角度 切进去 看一下我们是如何加载整个过程的;
首先 我们使用 SqlSessionFactoryBuilder().build() 来加载出SqlSessionFactory build里面做了什么操作;
首先传入Config配置文件 可以是Reder 可以是inputStream 可以是Configuration
在这里插入图片描述
最终都是看来是解析xml文件; 我们可以发现 Mybatis的底层 使用了 Document 或者是sax技术 来解释我们的XML;这样我们很轻易的得到 节点 以及节点上面的属性; 我们打开org.apache.ibatis 包 我们可以发现; 我们可以看到很多类别; 例如 datasource cache exceptions
在这里插入图片描述
如果是我们开发一个框架; 我们也会把 select update delete insert 当做对象 进行封装;

我们把封装 类型大致分别如下
数据层: 也就是Mapper.xml 里面文件内容的解析; sql的查找 插入 删除 以及修改;那么节点里面就有其他的对象进行拼装; 例如返回的对象 传入的对象; 节点里面 sql语法的定义; xml语法的定义; 以及注解的定义;
应用层: datasource , 事物 , 异常, 缓存, 等等…


在这里插入图片描述
找了一个图 这里就不再陈述;


从代码层面,我们解析一下Mybatis的 全局配置文件;
在这里插入图片描述
SqlSessionFactory 是创建Session的工厂 我们操作数据库 我们依赖这个工厂; 该工厂主要是做了什么事情; 是怎么做的?

  开始加载build 配置文件
  public SqlSessionFactory build(Reader reader, String environment, Properties properties) {
    try {
      使用Document,sax 技术 进行解析; 最后得到 Configuration 
      XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties);
      return build(parser.parse());
      
    } catch (Exception e) {
    .....
  }

在这里插入图片描述
在这里插入图片描述
我们得知 SqlSeesionFactory 主要是为了加载 Configuration ;最后把Configuration 放进来;

在XMLConfigBuilder.parse() 做了什么?
在这里插入图片描述
在这里插入图片描述
他把整个 Configuration 根目录加载了进来;
在这里插入图片描述
然后进行逐个节点的解析; 这些参数一会 在下面会有解释; Configuration的主要组成元素;
在这里插入图片描述
主要做了一个Configuration 拼装; 如果对这方面感兴趣的小伙伴
可以 直接打开: XMLConfigBuilder.parseConfiguration方法中


以下来自【博客园:https://www.cnblogs.com/wuzhenzhao/p/11092526.html】

configuration

configuration 是整个配置文件的根标签,实际上也对应着MyBatis 里面最重要的配置类Configuration。它贯穿MyBatis 执行流程的每一个环节。这里面有很多的属性,跟其他的子标签也能对应上。

注意:MyBatis 全局配置文件顺序是固定的,否则启动的时候会报错。

properties

第一个是properties 标签,用来配置参数信息,比如最常见的数据库连接信息。为了避免直接把参数写死在xml 配置文件中,我们可以把这些参数单独放在properties 文件中,用properties 标签引入进来,然后在xml 配置文件中用${}引用就可以了。可以用resource 引用应用里面的相对路径,也可以用url 指定本地服务器或者网络的绝对路径。

我们为什么要把这些配置独立出来?有什么好处?或者说,公司的项目在打包的时候,有没有把properties 文件打包进去?

1.提取,利于多处引用,维护简单;
2.把配置文件放在外部,避免修改后重新编译打包,只需要重启应用;
3.程序和配置分离,提升数据的安全性,比如生产环境的密码只有运维人员掌握。

settings

这是 MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为。 下表描述了设置中各项的意图、默认值等。

设置名描述有效值默认值
cacheEnabled全局地开启或关闭配置文件中的所有映射器已经配置的任何缓存。true / falsetrue
lazyLoadingEnabled延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置 fetchType属性来覆盖该项的开关状态。true / falsefalse
aggressiveLazyLoading当开启时,任何方法的调用都会加载该对象的所有属性。 否则,每个属性会按需加载(参考 lazyLoadTriggerMethods)。true /falsefalse (在 3.4.1 及之前的版本默认值为 true)
multipleResultSetsEnabled是否允许单一语句返回多结果集(需要驱动支持)。true /falsetrue
useColumnLabel使用列标签代替列名。不同的驱动在这方面会有不同的表现,具体可参考相关驱动文档或通过测试这两种不同的模式来观察所用驱动的结果。true / falsetrue
useGeneratedKeys允许 JDBC 支持自动生成主键,需要驱动支持。 如果设置为 true 则这个设置强制使用自动生成主键,尽管一些驱动不能支持但仍可正常工作(比如 Derby)。true / falseFalse
autoMappingBehavior指定 MyBatis 应如何自动映射列到字段或属性。 NONE 表示取消自动映射;PARTIAL 只会自动映射没有定义嵌套结果集映射的结果集。 FULL 会自动映射任意复杂的结果集(无论是否嵌套)。NONE, PARTIAL, FULLPARTIAL
autoMappingUnknownColumnBehavior指定发现自动映射目标未知列(或者未知属性类型)的行为。NONE:不做任何反应。WARNING:输出提醒日志 ('org.apache.ibatis.session.AutoMappingUnknownColumnBehavior’的日志等级必须设置为 WARN)NONE
defaultExecutorType配置默认的执行器。SIMPLE 就是普通的执行器;REUSE 执行器会重用预处理语句(prepared statements); BATCH 执行器将重用语句并执行批量更新。SIMPLE REUSE BATCHSIMPLE
defaultStatementTimeout设置超时时间,它决定驱动等待数据库响应的秒数。任意正整数未设置 (null)
defaultFetchSize为驱动的结果集获取数量(fetchSize)设置一个提示值。此参数只可以在查询设置中被覆盖。任意正整数未设置 (null)
safeRowBoundsEnabled允许在嵌套语句中使用分页(RowBounds)。如果允许使用则设置为 false。true / falseFalse
safeResultHandlerEnabled允许在嵌套语句中使用分页(ResultHandler)。如果允许使用则设置为 false。true / falseTrue
mapUnderscoreToCamelCase是否开启自动驼峰命名规则(camel case)映射,即从经典数据库列名 A_COLUMN 到经典 Java 属性名 aColumn 的类似映射。true / falseFalse
localCacheScopeMyBatis 利用本地缓存机制(Local Cache)防止循环引用(circular references)和加速重复嵌套查询。 默认值为 SESSION,这种情况下会缓存一个会话中执行的所有查询。 若设置值为 STATEMENT,本地会话仅用在语句执行上,对相同 SqlSession 的不同调用将不会共享数据。SESSION / STATEMENTSESSION
jdbcTypeForNull当没有为参数提供特定的 JDBC 类型时,为空值指定 JDBC 类型。 某些驱动需要指定列的 JDBC 类型,多数情况直接用一般类型即可,比如 NULL、VARCHAR 或 OTHER。JdbcType 常量,常用值:NULL, VARCHAR 或 OTHER。OTHER
lazyLoadTriggerMethods指定哪个对象的方法触发一次延迟加载。 用逗号分隔的方法列表。equals,clone,hashCode,toString
defaultScriptingLanguage指定动态 SQL 生成的默认语言。一个类型别名或完全限定类名。org.apache.ibatis.scripting.xmltags.XMLLanguageDriver
defaultEnumTypeHandler指定 Enum 使用的默认 TypeHandler 。(新增于 3.4.5) 一个类型别名或完全限定类名。org.apache.ibatis.type.EnumTypeHandler
callSettersOnNulls指定当结果集中值为 null 的时候是否调用映射对象的 setter(map 对象时为 put)方法,这在依赖于 Map.keySet() 或 null 值初始化的时候比较有用。注意基本类型(int、boolean 等)是不能设置成 null 的。true / falsefalse
returnInstanceForEmptyRow当返回行的所有列都是空时,MyBatis默认返回 null。 当开启这个设置时,MyBatis会返回一个空实例。 请注意,它也适用于嵌套的结果集 (如集合或关联)。(新增于 3.4.2) true / falsefalse
logPrefix指定 MyBatis 增加到日志名称的前缀。任何字符串未设置
logImpl指定 MyBatis 所用日志的具体实现,未指定时将自动查找。SLF4J / LOG4J / LOG4J2 / JDK_LOGGING / COMMONS_LOGGING /
STDOUT_LOGGING / NO_LOGGING未设置
proxyFactory指定 Mybatis 创建具有延迟加载能力的对象所用到的代理工具。 CGLIB / JAVASSIST JAVASSIST(MyBatis 3.3 以上)
vfsImpl指定 VFS 的实现 自定义 VFS 的实现的类全限定名,以逗号分隔。未设置
useActualParamName允许使用方法签名中的名称作为语句参数名称。 为了使用该特性,你的项目必须采用 Java 8 编译,并且加上 -parameters 选项。(新增于 3.4.1)true / falsetrue
configurationFactory指定一个提供 Configuration 实例的类。这个被返回的 Configuration 实例用来加载被反序列化对象的延迟加载属性值。 这个类必须包含一个签名为static Configuration getConfiguration() 的方法。 (新增于 3.2.3) 类型别名或者全类名.未设置

typeAliases

TypeAlias 是类型的别名,跟Linux 系统里面的alias 一样,主要用来简化全路径类名的拼写。比如我们的参数类型和返回值类型都可能会用到我们的Bean,如果每个地方都配置全路径的话,那么内容就比较多,还可能会写错。我们可以为自己的Bean 创建别名,既可以指定单个类,也可以指定一个package,自动转换。配置了别名以后,只需要写别名就可以了,比如com.gupaoedu.domain.Blog都只要写blog 就可以了。MyBatis 里面有系统预先定义好的类型别名,在TypeAliasRegistry 中。

<typeAliases>
  <typeAlias alias="Author" type="domain.blog.Author"/>
  <typeAlias alias="Blog" type="domain.blog.Blog"/>
</typeAliases>

当这样配置时,Blog 可以用在任何使用 domain.blog.Blog 的地方。也可以指定一个包名,MyBatis 会在包名下面搜索需要的 Java Bean,比如:

<typeAliases>
  <package name="domain.blog"/>
</typeAliases>

每一个在包 domain.blog 中的 Java Bean,在没有注解的情况下,会使用 Bean 的首字母小写的非限定类名来作为它的别名。 比如 domain.blog.Author 的别名为 author;若有注解,则别名为其注解值。见下面的例子:

@Alias("author")
public class Author {
    ...
}

typeHandlers *

由于Java 类型和数据库的JDBC 类型不是一一对应的(比如String 与varchar),所以我们把Java 对象转换为数据库的值,和把数据库的值转换成Java 对象,需要经过一定的转换,这两个方向的转换就要用到TypeHandler。有的同学可能会有疑问,我没有做任何的配置,为什么实体类对象里面的一个String属性,可以保存成数据库里面的varchar 字段,或者保存成char 字段?这是因为MyBatis 已经内置了很多TypeHandler(在type 包下),它们全部全部注册在TypeHandlerRegistry 中,他们都继承了抽象类BaseTypeHandler,泛型就是要处理的Java 数据类型。

在这里插入图片描述

当我们做数据类型转换的时候,就会自动调用对应的TypeHandler 的方法。如果我们需要自定义一些类型转换规则,或者要在处理类型的时候做一些特殊的动作,就可以编写自己的TypeHandler,跟系统自定义的TypeHandler 一样,继承抽象类BaseTypeHandler。有4 个抽象方法必须实现,我们把它分成两类:set 方法从Java 类型转换成JDBC 类型的,get 方法是从JDBC 类型转换成Java 类型的。

在这里插入图片描述
比如我们想要在获取或者设置String 类型的时候做一些特殊处理,我们可以写一个String 类型的TypeHandler

public class MyTypeHandler extends BaseTypeHandler<String> {
    public void setNonNullParameter(PreparedStatement ps, int i, String parameter, JdbcType jdbcType)
            throws SQLException {
        // 设置 String 类型的参数的时候调用,Java类型到JDBC类型
        // 注意只有在字段上添加typeHandler属性才会生效
        // insertBlog name字段
        System.out.println("---------------setNonNullParameter1:"+parameter);
        ps.setString(i, parameter);
    }

    public String getNullableResult(ResultSet rs, String columnName) throws SQLException {
        // 根据列名获取 String 类型的参数的时候调用,JDBC类型到java类型
        // 注意只有在字段上添加typeHandler属性才会生效
        System.out.println("---------------getNullableResult1:"+columnName);
        return rs.getString(columnName);
    }

    public String getNullableResult(ResultSet rs, int columnIndex) throws SQLException {
        // 根据下标获取 String 类型的参数的时候调用
        System.out.println("---------------getNullableResult2:"+columnIndex);
        return rs.getString(columnIndex);
    }

    public String getNullableResult(CallableStatement cs, int columnIndex) throws SQLException {
        System.out.println("---------------getNullableResult3:");
        return cs.getString(columnIndex);
    }
}

第二步,在mybatis-config.xml 文件中注册:

<typeHandlers>
<typeHandler handler="com.wuzz.type.MyTypeHandler"></typeHandler>
</typeHandlers>

第三步,在我们需要使用的字段上指定,比如:插入值的时候,从Java 类型到JDBC 类型,在字段属性中指定typehandler:

<insert id="insertBlog" parameterType="com.wuzz.domain.Blog">
  insert into blog (bid, name, author_id)
  values (#{bid,jdbcType=INTEGER},
  #{name,jdbcType=VARCHAR,typeHandler=com.wuzz.type.MyTypeHandler},
  #{authorId,jdbcType=INTEGER})
</insert>

返回值的时候,从JDBC 类型到Java 类型,在resultMap 的列上指定typehandler:

<result column="name" property="name" jdbcType="VARCHAR"
typeHandler="com.wuzz.type.MyTypeHandler"/>

处理枚举类型

若想映射枚举类型 Enum,则需要从 EnumTypeHandler 或者 EnumOrdinalTypeHandler 中选一个来使用。

比如说我们想存储取近似值时用到的舍入模式。默认情况下,MyBatis 会利用 EnumTypeHandler 来把 Enum 值转换成对应的名字。

注意 EnumTypeHandler 在某种意义上来说是比较特别的,其他的处理器只针对某个特定的类,而它不同,它会处理任意继承了 Enum 的类。

不过,我们可能不想存储名字,相反我们的 DBA 会坚持使用整形值代码。那也一样轻而易举: 在配置文件中把 EnumOrdinalTypeHandler 加到 typeHandlers 中即可, 这样每个 RoundingMode 将通过他们的序数值来映射成对应的整形数值。

objectFactory *

当我们把数据库返回的结果集转换为实体类的时候,需要创建对象的实例,由于我们不知道需要处理的类型是什么,有哪些属性,所以不能用new 的方式去创建。在MyBatis 里面,它提供了一个工厂类的接口,叫做ObjectFactory,专门用来创建对象的实例,里面定义了4 个方法。

public interface ObjectFactory {
    void setProperties(Properties var1);

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

    <T> T create(Class<T> var1, List<Class<?>> var2, List<Object> var3);

    <T> boolean isCollection(Class<T> var1);
}

在这里插入图片描述
ObjectFactory 有一个默认的实现类DefaultObjectFactory,创建对象的方法最终都调用了instantiateClass(),是通过反射来实现的。如果想要修改对象工厂在初始化实体类的时候的行为,就可以通过创建自己的对象工厂,继承DefaultObjectFactory 来实现(不需要再实现ObjectFactory 接口)。

public class MyObjectFactory extends DefaultObjectFactory {
    @Override
    public Object create(Class type) {
        System.out.println("创建对象方法:" + type);
        if (type.equals(Blog.class)) {
            Blog blog = (Blog) super.create(type);
            blog.setName("object factory");
            blog.setBid(1111);
            blog.setAuthorId(2222);
            return blog;
        }
        Object result = super.create(type);
        return result;
    }
}

我们可以直接用自定义的工厂类来创建对象:

public class ObjectFactoryTest {
    public static void main(String[] args) {
        MyObjectFactory factory = new MyObjectFactory();
        Blog myBlog = (Blog) factory.create(Blog.class);
        System.out.println(myBlog);
    }
}

应用场景举例:

比如有一个新锐手机品牌在一个电商平台上面卖货,为了让预约数量好看一点,只要有人预约,预约数量就自动乘以3。这个时候就可以创建一个ObjectFactory,只要是查询销量,就把它的预约数乘以3 返回这个实体类。

1、什么时候调用了objectFactory.create()?

创建DefaultResultSetHandler 的时候,和创建对象的时候。

2、创建对象后,已有的属性为什么被覆盖了?

在DefaultResultSetHandler 类的395 行getRowValue()方法里面里面调用了applyPropertyMappings()。

3、返回结果的时候,ObjectFactory 和TypeHandler 哪个先工作?

先是ObjectFactory,再是TypeHandler。肯定是先创建对象。PS:step out 可以看到一步步调用的层级。

插件(plugins)

MyBatis 允许你在已映射语句执行过程中的某一点进行拦截调用。默认情况下,MyBatis 允许使用插件来拦截的方法调用包括:

Executor (update, query, flushStatements, commit, rollback, getTransaction, close, isClosed)
ParameterHandler (getParameterObject, setParameters)
ResultSetHandler (handleResultSets, handleOutputParameters)
StatementHandler (prepare, parameterize, batch, update, query)
  这些类中方法的细节可以通过查看每个方法的签名来发现,或者直接查看 MyBatis 发行包中的源代码。 如果你想做的不仅仅是监控方法的调用,那么你最好相当了解要重写的方法的行为。 因为如果在试图修改或重写已有方法的行为的时候,你很可能在破坏 MyBatis 的核心模块。 这些都是更低层的类和方法,所以使用插件的时候要特别当心。通过 MyBatis 提供的强大机制,使用插件是非常简单的,只需实现 Interceptor 接口,并指定想要拦截的方法签名即可。

// ExamplePlugin.java
@Intercepts({@Signature(
  type= Executor.class,
  method = "update",
  args = {MappedStatement.class,Object.class})})
public class ExamplePlugin implements Interceptor {
  public Object intercept(Invocation invocation) throws Throwable {
    return invocation.proceed();
  }
  public Object plugin(Object target) {
    return Plugin.wrap(target, this);
  }
  public void setProperties(Properties properties) {
  }
}
<!-- mybatis-config.xml -->
<plugins>
  <plugin interceptor="org.mybatis.example.ExamplePlugin">
    <property name="someProperty" value="100"/>
  </plugin>
</plugins>

上面的插件将会拦截在 Executor 实例中所有的 “update” 方法调用, 这里的 Executor 是负责执行低层映射语句的内部对象。

environments、environment

environments 标签用来管理数据库的环境,比如我们可以有开发环境、测试环境、生产环境的数据库。可以在不同的环境中使用不同的数据库地址或者类型。

<environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/><!-- 单独使用时配置成MANAGED没有事务 -->
            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.driver}"/>
                <property name="url" value="${jdbc.url}"/>
                <property name="username" value="${jdbc.username}"/>
                <property name="password" value="${jdbc.password}"/>
            </dataSource>
        </environment>
    </environments>

一个environment 标签就是一个数据源,代表一个数据库。这里面有两个关键的标签,一个是事务管理器,一个是数据源。

transactionManager

如果配置的是JDBC,则会使用Connection 对象的commit()、rollback()、close()管理事务。如果配置成MANAGED,会把事务交给容器来管理,比如JBOSS,Weblogic。因为我们跑的是本地程序,如果配置成MANAGE 不会有任何事务。如果是Spring + MyBatis , 则没有必要配置, 因为我们会直接在applicationContext.xml 里面配置数据源,覆盖MyBatis 的配置。

mappers

mappers标签配置的是我们的映射器,也就是Mapper.xml 的路径。这里配置的目的是让MyBatis 在启动的时候去扫描这些映射器,创建映射关系。我们有四种指定Mapper 文件的方式:

1.使用相对于类路径的资源引用(resource)
2.使用完全限定资源定位符(绝对路径)(URL)
3.使用映射器接口实现类的完全限定类名
4.将包内的映射器接口实现全部注册为映射器(最常用)

<!-- 使用相对于类路径的资源引用 -->
<mappers>
  <mapper resource="org/mybatis/builder/AuthorMapper.xml"/>
  <mapper resource="org/mybatis/builder/BlogMapper.xml"/>
  <mapper resource="org/mybatis/builder/PostMapper.xml"/>
</mappers>
<!-- 使用完全限定资源定位符(URL) -->
<mappers>
  <mapper url="file:///var/mappers/AuthorMapper.xml"/>
  <mapper url="file:///var/mappers/BlogMapper.xml"/>
  <mapper url="file:///var/mappers/PostMapper.xml"/>
</mappers>
<!-- 使用映射器接口实现类的完全限定类名 -->
<mappers>
  <mapper class="org.mybatis.builder.AuthorMapper"/>
  <mapper class="org.mybatis.builder.BlogMapper"/>
  <mapper class="org.mybatis.builder.PostMapper"/>
</mappers>
<!-- 将包内的映射器接口实现全部注册为映射器 -->
<mappers>
  <package name="org.mybatis.builder"/>
</mappers>
  • 5
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Like Java Long Time

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值