MyBatis(一)MyBatis介绍和配置详解

在Java程序里面去操作数据库,最原始的办法是使用JDBC的API。需要分为六步:

  • 注册驱动
  • 通过DriverManager获取一个Connection
  • 通过Connection创建一个Statement对象。
  • 通过Statement的execute()方法执行SQL,返回结果集ResultSet
  • 将ResultSet结果集转换成POJO对象。
  • 关闭各种资源

像Connection获取,结果集的封装,资源关闭这些代码在开发的过程中会出现大量冗余,所以出现了很多对JDBC进行封装的框架来简化操作。

Apache DbUtils:
DbUtils 解决的最核心的问题就是结果集的映射,提供了一系列的支持泛型的ResultSetHandler,帮助我们把 ResultSet 封装成JavaBean。
DbUtils提供了一个QueryRunner类,它对数据库的增删改查的方法进行了封装, 在 QueryRunner 的构造函数里面,我们可以传入一个数据源DataSource,让框架帮我们管理连接。

queryRunner = new QueryRunner(dataSource);
String sql = "select * from blog";
List<Blog> list = queryRunner.query(sql, new BeanListHandler<>(Blog.class));

注意:DbUtils要求数据库的字段跟对象的属性名称完全一致,才可以实现自动映射。

Spring JDBC
Spring也对原生的JDBC进行了封装,并且给我们提供了一个模板方法JdbcTemplate,来简化我们对数据库的操作。
和dbutils类似,spring帮助我们管理datasource和connection,并且提供了RowMapper接口,我们只要实现RowMapper接口,并且重写mapRow()方法,就可以将结果集转换成Java对象。

public class EmployeeRowMapper implements RowMapper {
    @Override
    public Object mapRow(ResultSet resultSet, int i) throws SQLException {
        Employee employee = new Employee();
        employee.setEmpId(resultSet.getInt("emp_id"));
        employee.setEmpName(resultSet.getString("emp_name"));
        employee.setEmail(resultSet.getString("email"));
        return employee;
    }
}

jdbcTemplate = new JdbcTemplate( new DruidDataSource());         
list = jdbcTemplate.query(" select * from emp_table", new EmployeeRowMapper());

上面两种都是对JDBC笔记简单的封装,虽然简化了操作,但是缺少一些扩展功能,比方说SQL语句还是需要在代码里硬编码,不便于修改,无法生成动态SQL,没有缓存等

这就有了功能更加丰富的ORM框架(Object Relational Mapping),帮助我们解决程序对象和关系型数据库的相互映射的问题。

MyBatis使用

MyBatis 就是一个“半自动化”的ORM框架(一般称Hibernate为全自动化),它的封装程度没有Hibernate那么高,不会自动生成全部的SQL语句,但是相对性能也更好一些  (框架封装的越多,说明内部处理越多,相对来说性能越差,原生的JDBC API的性能反而是最好的)

先看看在单独使用Mybatis的时候我们是怎么配置的

  • 引入Mybatis jar包
  • 创建一个全局配置文件mybatis-config.xml 
  • 创建映射器文件,Mapper.xml,通常来说一张表对应一个Mapper文件

使用demo

    public void Test() throws IOException {
        String resource = "mybatis-config.xml";
        InputStream inputStream = Resources.getResourceAsStream(resource);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        SqlSession session = sqlSessionFactory.openSession();
        try {
            //执行SQL有两种方法:
            //1.通过SqlSession接口上的方法,传入Statement ID来执行SQL
            //2.定义一个Mapper接口。这个接口全路径必须跟Mapper.xml里面的namespace对应起来,方法也要跟Statement ID一一对应
            BlogMapper mapper = session.getMapper(BlogMapper.class);
            Blog blog = mapper.selectBlogById(1);
            System.out.println(blog);
        } finally {
            session.close();
        }
    }

从上面的代码可以看出MyBatis的几大核心对象:SqlSessionFactoryBuiler、SqlSessionFactory、SqlSession 和Mapper对象

核心对象

1)SqlSessionFactoryBuilder
用来构建 SqlSessionFactory 的(建造者模式),SqlSessionFactory作为会话工厂只需要一个就够了,所以只要创建了SqlSessionFactory,Builder就可以销毁了 所以它的生命周期只存在于方法的局部。

2)SqlSessionFactory
SqlSessionFactory 是用来创建SqlSession的,每次应用程序访问数据库,都需要创建一个会话。所以 SqlSessionFactory 应该存在于应用的整个生命周期中(作用域是应用作用域),并且是单例的。

3)SqlSession
SqlSession是一个会话,因为它不是线程安全的,所以不能在线程间共享。每次请求开始的时候都需要创建一个SqlSession对象,在请求结束的时候要及时关闭它    作用域:一次请求或者交互中

4)Mapper
Mapper(实际上是一个代理对象 后面会分析)的作用是发送SQL来操作数据库,是从SqlSession中获取的,所以它的作用域应该和SqlSession相关,在一个 SqlSession事务方法之内才有效

对象 生命周期
SqlSessionFactoryBuiler 方法局部(method)
SqlSessionFactory(单例) 应用级别(application)
SqlSession 请求和操作(request/method)
Mapper

方法(method) 

核心配置解读

<?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" />

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

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

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

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

    <!-- 对象工厂 -->
<!--    <objectFactory type="com.chenpp.objectfactory.CPObjectFactory">
        <property name="name" value="cpp"/>
    </objectFactory>-->

<!--    <plugins>
        <plugin interceptor="com.chenpp.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"/>
    </mappers>

</configuration>

一级标签

configuration
configuration 是整个配置文件的根标签,对应 MyBatis 里面最重要的配置类 Configuration(单例)

properties
用来配置参数信息,比如最常见的数据库连接信息。
为了避免直接把参数写死在 xml 配置文件中,我们可以把这些参数单独放在properties 文件中,用 properties 标签引入进来,然后在xml 配置文件中用${}引用就可以了。

settings
setttings里面是MyBatis的一些核心配置,各种参数的配置
https://mybatis.org/mybatis-3/zh/configuration.html#settings

属性名  描述 有效值 默认值
cacheEnabled 全局地开启或关闭配置文件中的所有映射器已经配置的任何缓存。 true | false true
lazyLoadingEnabled 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置 fetchType 属性来覆盖该项的开关状态。 true | false false
aggressiveLazyLoading 当开启时,任何方法的调用都会加载该对象的所有属性。 否则,每个属性会按需加载(参考 lazyLoadTriggerMethods)。 true | false false 
defaultExecutorType 配置默认的执行器。SIMPLE 就是普通的执行器;REUSE 执行器会重用预处理语句(prepared statements); BATCH 执行器将重用语句并执行批量更新。 SIMPLE REUSE BATCH SIMPLE
lazyLoadTriggerMeth ods
 
指定哪个对象的方法触发一次延迟加载。 用逗号分隔的方法列表。

equals,clone

hashCode,toString

localCacheScope MyBatis 利用本地缓存机制(Local Cache)防止循环引用(circular references)和加速重复嵌套查询。 默认值为 SESSION,这种情况下会缓存一个会话中执行的所有查询。 若设置值为 STATEMENT,本地会话仅用在语句执行上,对相同 SqlSession 的不同调用将不会共享数据 SESSION | STATEMENT SESSION
logImpl 指定 MyBatis 所用日志的具体实现,未指定时将自动查找。 SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING 未设置

 

 

 

 

 

 

 

 

 

 

 

 

typeAliases
类型别名,用来简化全路径类名的拼写。可以指定单个类,也可以指定一个package,自动转换。
MyBatis里面有系统预先定义好的类型别名,在TypeAliasRegistry中。

typeHandlers
用于把Java对象转换为数据库的值,或者把数据库的值转换成 Java 对象;比放说(String和varchar)
对于一些基础类型,MyBatis里内置了一些基本的TypeHandler, 都注册在TypeHandlerRegistry,他们都继承了抽象类BaseTypeHandler,我们也可以实现自己的TypeHandler

需要实现以下四个抽象方法

使用的时候,需要先在mybatis-config.xml里注册对应的TypeHandler

<!-- mybatis-config.xml -->
<typeHandlers>
  <typeHandler handler="org.mybatis.example.ExampleTypeHandler"/>
</typeHandlers>

然后在我们需要使用的字段上指定对应的typeHandler就可以了

#插入
<insert id="insertBlog" parameterType="blog">
    insert into blog (bid, name, author_id)
    values (#{bid,jdbcType=INTEGER}, #{name,jdbcType=VARCHAR,typeHandler=com.chenpp.type.MyTypeHandler}, #{authorId,jdbcType=CHAR})
</insert>
#查询结果映射
 <resultMap id="BaseResultMap" type="blog">
        <id column="bid" property="bid" jdbcType="INTEGER"/>
        <result column="name" property="name" jdbcType="VARCHAR" typeHandler="com.chenpp.type.MyTypeHandler"/>
        <result column="author_id" property="authorId" jdbcType="INTEGER"/>
</resultMap>
    

objectFactory
ObjectFactory用于创建实体类的实例,当我们把数据库返回的结果集转换为实体类的时候,就会使用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然后重写create()方法就可以了

plugins
插件是MyBatis的一个很强大的机制,跟很多其他的框架一样,MyBatis预留了插件的接口,让MyBatis更容易扩展。这个会在后面详细分析

environmentsenvironment
environments标签用来管理数据库的环境,比如我们可以有开发环境、测试环境、生产环境等不同环境的数据库。可以在不同的环境中使用不同的数据库。这里面有两个关键的标签,一个是事务管理器,一个是数据源

transactionManager
如果配置的是JDBC,就会直接使用 JDBC 的提交和回滚设置,它依赖于从数据源得到的连接来管理事务作用域
如果配置成MANAGED,会把事务交给容器来管理,比如JBOSS,Weblogic等。如果直接在本地环境运行程序,配置成MANAGE的话不会有任何事务。

dataSource
dataSource 元素使用标准的 JDBC 数据源接口来配置 JDBC 连接对象的资源。有三种内建的数据源类型(也就是UNPOOLED,POOLED,JNDI)
UNPOOLED– 这个数据源的实现只是每次请求时打开和关闭连接。
POOLED–使用来连接池管理 JDBC 连接对象,复用连接
JNDI – 这个数据源的实现是为了能在如 EJB 或应用服务器这类容器中使用
在跟Spring集成的时候,事务和数据源都会交给Spring来管理。

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

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

Mapper映射配置文件

一共有8个主要标签

cache – 给定命名空间的缓存配置(是否开启二级缓存)。
cache-ref – 其他命名空间缓存配置的引用。
resultMap – 用来描述如何从数据库结果集中来加载对象  数据库结果集和java对象的映射关系
sql – 可被其他语句引用的可重用语句块。
增删改查标签:
insert ,update ,delete ,select 

MyBatis的扩展使用

动态SQL

基于OGNL表达式,帮助我们更方便的拼接SQL

MyBatis的动态标签主要有四类: if, choose(when,otherwise),trim (where, set),foreach。

if —— 用于做判断,条件写在test中

choose (when, otherwise) —— 用于做条件选择

<select id="findActiveBlogLike"
     resultType="Blog">
  SELECT * FROM BLOG WHERE state = ‘ACTIVE’
  <choose>
    <when test="title != null">
      AND title like #{title}
    </when>
    <when test="author != null and author.name != null">
      AND author_name like #{author.name}
    </when>
    <otherwise>
      AND featured = 1
    </otherwise>
  </choose>
</select>

trim, where, set——会帮助我们去掉多余的where,and,逗号之类的符号,使用trim还可以指定获取去掉前缀/后缀

where 元素只会在至少有一个子元素的条件返回 SQL 子句的情况下才去插入“WHERE”子句。而且,若语句的开头为“AND”或“OR”,where 元素也会将它们去除。
如果 where 元素没有按正常套路出牌,我们可以通过自定义 trim 元素来定制 where 元素的功能。比如,和 where 元素等价的自定义 trim 元素为:

<trim prefix="WHERE" prefixOverrides="AND |OR ">
  ...
</trim>

foreach —— 适用于需要遍历集合的时候

<select id="selectPostIn" resultType="domain.blog.Post">
  SELECT *
  FROM POST P
  WHERE ID in
  <foreach item="item" index="index" collection="list"
      open="(" separator="," close=")">
        #{item}
  </foreach>
</select>

批量SQL

使用for each拼接长SQL

缺点:MySQL 的服务端对于接收的数据包有大小限制,max_allowed_packet 默认是4M,需要修改对应的参数配置

使用BatchExecutor进行批操作

在我们的全局配置文件中,可以配置默认的 Executor 的类型。其中有一种BatchExecutor
<setting name="defaultExecutorType" value="BATCH"/>
也可以在创建会话的时候指定执行器类型
SqlSession session = sqlSessionFactory.openSession(ExecutorType.BATCH);
BatchExecutor底层是就是调用了JDBC的ps.addBatch()方法(在BatchExecutor调用了StatementHandler的batch(),在里面调用了statement的addBatch()方法)

嵌套(关联)查询/ N+1 / 延迟加载

对于一对一的关联查询有两种配置方式

嵌套结果:

 <!-- 根据文章查询作者,一对一查询的结果,嵌套结果 -->
    <resultMap id="BlogWithAuthorResultMap" type="com.chenpp.domain.associate.BlogAndAuthor">
        <id column="bid" property="bid" jdbcType="INTEGER"/>
        <result column="name" property="name" jdbcType="VARCHAR"/>
        <!-- 联合查询,将author的属性映射到ResultMap -->
        <association property="author" javaType="com.chenpp.domain.Author">
            <id column="author_id" property="authorId"/>
            <result column="author_name" property="authorName"/>
        </association>
    </resultMap>

  <!-- 根据文章查询作者,一对一,嵌套结果,无N+1问题 -->
    <select id="selectBlogWithAuthorResult" resultMap="BlogWithAuthorResultMap" >
        select b.bid, b.name, b.author_id, a.author_id , a.author_name
        from blog b
        left join author a
        on b.author_id=a.author_id
        where b.bid = #{bid, jdbcType=INTEGER}
    </select>

 

嵌套查询

 <!-- 另一种联合查询(一对一)的实现,但是这种方式有“N+1”的问题 -->
    <resultMap id="BlogWithAuthorQueryMap" type="com.chenpp.domain.associate.BlogAndAuthor">
        <id column="bid" property="bid" jdbcType="INTEGER"/>
        <result column="name" property="name" jdbcType="VARCHAR"/>
        <association property="author" javaType="com.chenpp.domain.Author"
                     column="author_id" select="selectAuthor"/> <!-- selectAuthor 定义在下面-->
    </resultMap>

  <!-- 根据文章查询作者,一对一,嵌套查询,存在N+1问题,可通过开启延迟加载解决 -->
    <select id="selectBlogWithAuthorQuery" resultMap="BlogWithAuthorQueryMap" >
        select b.bid, b.name, b.author_id, a.author_id , a.author_name
        from blog b
        left join author a
        on b.author_id=a.author_id
        where b.bid = #{bid, jdbcType=INTEGER}
    </select>

 <!-- 嵌套查询 -->
    <select id="selectAuthor" parameterType="int" resultType="com.chenpp.domain.Author">
        select author_id authorId, author_name authorName
        from author where author_id = #{authorId}
    </select>

对于第二种嵌套查询,因为是分两次查询的,当我们查询了博客信息之后,会再发送一条SQL到数据库查询作者信息。如果查询出N条博客记录,那么就需要再执行N次作者的SQL查询  这就是所谓的N+1问题
在MyBatis里面可以通过开启延迟加载的开关来解决这个问题

  <!-- 延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。默认 false  -->
  <setting name="lazyLoadingEnabled" value="true"/>
  <!-- 当开启时,任何方法的调用都会加载该对象的所有属性。默认 false,可通过select标签的 fetchType来覆盖-->
  <setting name="aggressiveLazyLoading" value="false"/>
  <!--  Mybatis 创建具有延迟加载能力的对象所用到的代理工具,默认JAVASSIST -->
  <setting name="proxyFactory" value="CGLIB" />

简单来说,lazyLoadingEnabled决定了是否延迟加载。
aggressiveLazyLoading决定了是不是对象的所有方法都会触发查询。

逻辑翻页与物理翻页

在我们查询数据库的操作中,有两种翻页方式,一种是逻辑翻页(假分页),一种是物理翻页(真分页)。逻辑翻页的原理是把所有数据查出来,在内存中删选数据。 物理翻页是真正的翻页,比如MySQL 使用limit ,Oracle使用rownum 。

逻辑翻页

MyBatis 里面有一个逻辑分页对象 RowBounds,里面主要有两个属性,offset 和limit(从第几条开始,查询多少条).
我们可以在Mapper接口的方法上加上这个参数, 而不需要修改xml里的SQL语句达到逻辑翻页的效果。

public List<Blog> selectBlogList(RowBounds rowBounds);

物理翻页

1.直接传入参数(或者包装一个page对象),在SQL语句中翻页。

<select id="selectBlogPage" parameterType="map" resultMap="BaseResultMap">
        select * from blog limit #{curIndex} , #{pageSize}
    </select>

2.使用翻页的插件,比如PageHelper,后面会介绍下MyBatis插件的原理和PageHelper的实现
简单地来说,就是根据 PageHelper 的参数,改写我们的 SQL语句

 

 

 

发布了54 篇原创文章 · 获赞 16 · 访问量 6991

猜你喜欢

转载自blog.csdn.net/qq_35448165/article/details/104458102