MyBatis 初窥(二)

一、MyBatis 实际使用案例

编程式使用

  JavaApi编程的方式使用。

1.引入Mybatis jar 包
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.4-snapshot</version>
        </dependency>

        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>5.1.21</version>
        </dependency>
2.创建一个全局配置文件,里面是MyBatis一些核心行为控制,比如mybatis-config.xml。这里只定义的数据源和Mapper映射器路径。
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>

    <properties resource="db.properties"></properties>
    <settings>
        <!-- 打印查询语句 -->
        <setting name="logImpl" value="STDOUT_LOGGING" />

        <!-- 控制全局缓存(二级缓存),默认 true-->
        <setting name="cacheEnabled" value="true"/>

        <!-- 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。默认 false  -->
        <setting name="lazyLoadingEnabled" value="true"/>
        <!-- 当开启时,任何方法的调用都会加载该对象的所有属性。默认 false,可通过select标签的 fetchType来覆盖-->
        <setting name="aggressiveLazyLoading" value="true"/>
        <!--  Mybatis 创建具有延迟加载能力的对象所用到的代理工具,默认JAVASSIST -->
        <!--<setting name="proxyFactory" value="CGLIB" />-->
        <!-- STATEMENT级别的缓存,使一级缓存,只针对当前执行的这一statement有效 -->
        <!--
                <setting name="localCacheScope" value="STATEMENT"/>
        -->
        <setting name="localCacheScope" value="SESSION"/>
    </settings>

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

<!--    <typeHandlers>
        <typeHandler handler="com.dia.type.MyTypeHandler"></typeHandler>
    </typeHandlers>-->

    <!-- 对象工厂 -->
<!--    <objectFactory type="com.dia.objectfactory.GPObjectFactory">
        <property name="gupao" value="666"/>
    </objectFactory>-->

<!--    <plugins>
        <plugin interceptor="com.dia.interceptor.SQLInterceptor">
            <property name="gupao" value="betterme" />
        </plugin>
        <plugin interceptor="com.dia.interceptor.MyPageInterceptor">
        </plugin>
    </plugins>-->

    <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>

    <mappers>
        <mapper resource="BlogMapper.xml"/>
        <mapper resource="BlogMapperExt.xml"/>
    </mappers>

</configuration>

properties文件
jdbc.driver=com.mysql.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatis?useUnicode=true&characterEncoding=utf-8&rewriteBatchedStatements=true
jdbc.username=root
jdbc.password=123456

3.编写映射文件:Mapper.xml,通常来说对应一张业务表,我们会在这个mapper文件里编写增删改查的SQL语句,以及参数和结果集。

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.dia.mapper.BlogMapper">

    <resultMap id="BaseResultMap" type="blog">
        <id column="bid" property="bid" jdbcType="INTEGER"/>
<!--
        <result column="name" property="name" jdbcType="VARCHAR" typeHandler="com.dia.type.MyTypeHandler"/>
-->
        <result column="name" property="name" jdbcType="VARCHAR"/>
        <result column="author_id" property="authorId" jdbcType="INTEGER"/>
    </resultMap>

    <select id="selectBlogById" resultMap="BaseResultMap" statementType="PREPARED" >
        select * from blog where bid = #{bid}
    </select>

</mapper>

4.使用Mybatis执行一个查询。

  mybati的目的就是简化JDBC的操作,那么它必须要提供一个可执行增删改查的对象,在MyBatis里是SqlSession接口,他是和数据库建立的一个连接/会话。
  问题来了,SqlSession是如何创建的?Mybatis核心行为的控制(各种配置项以及开启缓存等)都在全局的配置文件中,所以必须基于全局配置文件创建,mybatis 提供了一个工厂类来创建SqlSession。


public interface BlogMapper {
    /**
     * 根据主键查询文章
     * @param bid
     * @return
     */
    public Blog selectBlogById(Integer bid);
}


public class BlogExampleTest {

    @Test
    public void TestExample() throws IOException {
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

        SqlSession session = sqlSessionFactory.openSession();
        try {
            Blog blog = (Blog)session.selectOne("com.dia.mapper.BlogMapper.selectBlogById",new Integer(1));
            System.out.println(blog.getBid()+">>>>"+blog.getName()+">>>>>"+blog.getAuthorId());
        } finally {
            session.close();
        }
    }
}

=====================================
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@223191a6]
==>  Preparing: select * from `blog` where bid = ? 
==> Parameters: 1(Integer)
<==    Columns: bid, name, author_id
<==        Row: 1, RabbitMQ延时消息, 1001
<==      Total: 1

  这样的调用方式,解决了重复代码、资源管理、SQL耦合、结果集映射的四大问题。

还存在问题:

1、Statement是硬编码,维护起来不方便;
2、不能在编译时候进行类型检查,如果namespace或者StatementID出错,只能在运行时报错。

mybatis给我们推荐了一种新的方法:定义一个Mapper接口的方式。这个接口全路径必须跟Mapper.xml里面的namespace对应起来,方法也要跟StatementID一一对应。

        @Test
    public void TestExample() throws IOException {
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

        SqlSession session = sqlSessionFactory.openSession();
        try {

//            Blog blog = (Blog)session.selectOne("com.dia.mapper.BlogMapper.selectBlogById",new Integer(1));
//            System.out.println(blog.getBid()+">>>>"+blog.getName()+">>>>>"+blog.getAuthorId());
            BlogMapper mapper = session.getMapper(BlogMapper.class);
            Blog blog = mapper.selectBlogById(1);

        } finally {
            session.close();
        }
    }
============================================
Setting autocommit to false on JDBC Connection [com.mysql.jdbc.JDBC4Connection@3023df74]
==>  Preparing: select * from `blog` where bid = ? 
==> Parameters: 1(Integer)
<==    Columns: bid, name, author_id
<==        Row: 1, RabbitMQ延时消息, 1001
<==      Total: 1

二、MyBatsi的核心对象及其生命周期

SqlSessinFactoryBuilder

  用来构建SqlSessionFactory的,然而SqlSessionFactory单例即可,完成构建SqlSessionFactory也就没有存在的意义了,生命周期局限于方法内部。

SqlSessionFactory(单例)

  SqlSessionFactory用于构建SqlSession,应用程序每次访问数据库都需要创建一个会话。我们一直需要会话的存在,所以SqlSessionFactory的生命周期也是我们整个程序的生命周期(作用域是应用作用域)。创建SqlSession只需要一个人来做就行了,否则会产生混乱和浪费资源。所以采用单例模式。

SqlSession

  SqlSession是一个会话,因为它不是线程安全的,不能在线程间共享。所以我们要在请求开始的时候创建一个SqlSession,在请求结束或者说方法执行完毕之后要及时关闭它(一次请求或者操作中)。

Mapper

  其实是一个被代理对象,在SqlSession中获取

BlogMapper mapper = session.getMapper(BlogMapper.class);

他的作用是发送SQL来操作数据库的数据,它应该在一个SqlSession事务方法之内。

mybatis-核心对象生命周期

三、核心配置解读

Configuration

  配置类Configuration。它贯穿MyBatis执行流程的每个环节。这个类下面还有很多属性,跟其他的子标签也能对应上。

properties

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

setting

mybatis的一些核心配置


image.png
image.png
image.png
image.png

typeAliases

  typeAliases是类型的别名,跟linux系统的别名是一样的。可以为Bean创建别名,也可以指定单个类,或者指定一个package。
用法如下:

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

配置了别名在mapper就可以使用其别名了:

    <resultMap id="BaseResultMap" type="blog">
        <id column="bid" property="bid"  />
<!--
        <result column="name" property="name" jdbcType="VARCHAR" typeHandler="com.dia.type.MyTypeHandler"/>
-->
        <result column="name" property="name"  />
        <result column="author_id" property="authorId" />
    </resultMap>

typeHandler

  由于Java类型和数据库类型并不是一一对应的,比如(String与Varchar、char、text),所以我们要把对象转换为数据库对象的值,同理也需要把数据库对象的值转换为Java对象,这两个方向的转换,需要用到typeHandler。
  当参数类型和返回值是一个对象时,我们没有做任何配置,为什么对象里面一个String属性,可以转换为数据库里面的VARCHAR?
  这是因为Mybatis已经内置了很多TypeHandler(在type包下),他们全部都注册在TypeHandlerRegistry中,他们都继承了抽象类BaseTypeHandler,泛型就是要处理的Java数据类型。
  这个也是为什么大部分类型都不需要处理。当我们查询数据和登记数据,做数据的类型转换时,就会自动调用对应Typehanlder方法。

objectFactory

  当我们把数据库返回的结果集转换为实体类的时候,需要创建对象的实例,由于我们不知道需要处理的类是什么,有哪些属性和类型。我们不能直接new Object(),只能通过反射来实现,objectFactory就是用来创建对象的工厂。

public interface ObjectFactory {

  // 设置参数的时候调用
  default void setProperties(Properties properties) {
    // NOP
  }

  // 创建对象(无参数构造函数)
  <T> T create(Class<T> type);

  // 创建对象(有参数构造函数)
  <T> T create(Class<T> type, List<Class<?>> constructorArgTypes, List<Object> constructorArgs);

  // 判断是否集合
  <T> boolean isCollection(Class<T> type);

}

  ObjectFactory有一个默认的实现类DefaultObjectFactory。创建对象的方法最终都调用了instantiateClass(),这里能看到反射的代码。
  默认情况,所有的对象都由DefaultObjectFactory实现。

plugin

  插件机制是mybatis很强大的功能,mybatis预留了插件的接口,让Mybatis更容易扩展。

environment

  environment标签用来管理数据库的环境,比如我们可以有开发环境、测试环境、生产数据库。(其实就是管理数据源的东西)

transactionManager

  如果配置成JDBC,则会使用Connection对象的Commit()、rollback()、close()管理事务。
  如果配置为managed,会把事务交给容器管理,比如JBOSS、WEBLOGIC。我们跑的如果是本地环境,如果配置为mannaged就没有事务。

dataSource

  数据源即是数据的来源,一个数据源就对应一个数据库。在java里面是他是对数据库连接的一个抽象。
  一般的数据源都会包括连接池管理的功能,所以很多时候也把DataSource直接成为连接池,也就是带连接池的数据源。

为什么要用连接池?

  除了连接池之外,大家应该也听过很多其他的池,比如线程池、内存池、对象池,池化技术的目的大多一样。
  如果没有连接池,那么每个用户、每次会话连接数据库都需要直接创建和释放连接,这个过程是需要消耗时间和资源的,并且会消耗应用和服务器的性能。
  如果采用池化技术,在应用程序里关闭连接的时候,物理连接比并没有被关闭,而是回到连接池里面。
  从这个角度来考虑,一般的连接池都会有初始连接数、最大连接数、回收时间等等,提供提前创建、资源重用、数量控制、超时管理等等。
  Mybatis自带两种数据源,unpooled和pooled,也可以配置成其他数据库,比如C3P0、HIkari等等。市面上流行的数据源,一般都有连接池功能。
  在跟Spring集成的时候,事务和数据源都会交给Spring来管理,不再使用Mybatis配置的数据源。

mapper

  <mapper>标签配置的是映射器,也就是Mapper.xml的路径,这些配置的目的就是在mybatis启动的时候,去扫描这些映射器,创建映射关系。

有四种表达方式:

1.使用相对与类路径的资源引用(resource)

<mappers>
        <mapper resource="xxxMapper.xml">
</mappers>

2.使用完全限定资源定位符(绝对路径)(URL)

<mappers>
        <mapper resource="file://app/xx/mappers/xxxMapper.xml">
</mappers>

3.使用映射器接口实现类的完全限定类名

<mappers>
        <mapper class="com.dia.xx.mapper.xxxMapper">
</mappers>

4.将包内的映射器接口实现全部注册为映射器(最常用)

<mappers>
        <mapper class="com.dia.xx.mapper">
</mappers>

批量插入以及动态SQL还有原生的插件等等

  这些东西都是日常使用的,直接进入官网

推荐阅读更多精彩内容