Spring系列-实战篇(4)-你有多了解MyBatis

1. 概念

在我上大学的时候,最流行的JavaEE框架是 SSH (Struts+Spring+Hibernate),如今同窗们应该都在学 SSM(Spring+SpringMVC+MyBatis)了。从历史演变来看,Spring是愈来愈强大,而MyBatis则是顶替了Hibernate的地位。今天的“主角”就是MyBatis。前端

1.1. ORM的历史演变

咱们先聊一聊ORM(Object Relational Mapping),翻译为“对象关系映射”,就是经过实例对象的语法,完成关系型数据库的操做的技术。ORM用于实现面向对象编程语言里不一样类型系统的数据之间的转换,实际上是建立了一个可在编程语言里使用的"虚拟对象数据库"。java

ORM 把数据库映射成对象:mysql

  • 数据库的表(table) --> 类(class)
  • 记录(record,行数据)--> 对象(object)
  • 字段(field)--> 对象的属性(attribute)

基于传统ORM框架的产品有不少,其中就有耳熟能详的Hibernate。ORM经过配置文件,使数据库表和JavaBean类对应起来,提供简便的操做方法,增、删、改、查记录,再也不拼写字符串生成sql,编程效率大大提升,同时减小程序出错机率,加强数据库的移植性,方便测试。git

可是有些时候我仍是喜欢原生的JDBC,由于在某些特殊的应用场景中,对于sql的应用复杂性比较高,或者须要对sql的性能进行优化,这些ORM框架就显得很笨重。Hibernate这类“全自动化”框架,对数据库结构封装的较为完整,这种一站式的解决方案未必适用于全部的业务场景。程序员

幸运的是,不仅我一我的有这种感觉,好久以前你们开始关注一个叫 iBATIS 的开源项目,它相对传统ORM框架而言更加的灵活,被定义为“半自动化”的ORM框架。2010年,谷歌接管了iBATIS,MyBatis就随之诞生了。虽然2010年我都还没上大学,但很惋惜,MyBatis在国内的大火的比较晚,我在校园期间都没有接触过。github

1.2. 开启MyBatis

MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎全部的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可使用简单的 XML 或注解来配置和映射原生类型、接口和 Java 的 POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。算法

MyBatis为半自动化,须要本身书写sql语句,须要本身定义映射。增长了程序员的一些操做,可是带来了设计上的灵活。而且也是支持Hibernate的一些特性,如延迟加载,缓存和映射等,并且随之SSM架构的成熟,MyBatis确定会被授予有愈来愈多新的特性。那么接下来就开始 MyBatis 的实战演练吧!spring

2. MyBatis 基本使用

下面讲解在SpringBoot 中,使用MyBatis的基本操做。sql

2.1. 基础配置

在SpringBoot中集成 MyBatis 的方式很简单,只须要引用 MyBatis的starter包便可,不过针对不一样的数据源,须要导入所依赖的驱动jar包(如:mysql(mysql-connector-java-x.jar)/oracle(ojdbcx.jar)/sql server(sqljdbcx.jar)等)数据库

pom.xml(示例)

<!--mybatis-->
<dependency>
    <groupId>org.mybatis.spring.boot</groupId>
    <artifactId>mybatis-spring-boot-starter</artifactId>
    <version>1.3.2</version>
</dependency>
<!--oracle jdbc-->
<dependency>
    <groupId>com.oracle</groupId>
    <artifactId>ojdbc6</artifactId>
    <version>6</version>
</dependency>
<!--druid 数据源-->
<dependency>
    <groupId>com.alibaba</groupId>
    <artifactId>druid-spring-boot-starter</artifactId>
    <version>1.1.9</version>
</dependency>

对于相关数据源的链接信息,须要在application.properties中配置,一样提供示例

# Oracle数据库的链接信息
spring.datasource.url=jdbc:oracle:thin:@ip:port/instance
spring.datasource.username=username
spring.datasource.password=password
spring.datasource.driver-class-name=oracle.jdbc.driver.OracleDriver

#mybatis 驼峰式命名映射,可将ResultMap返回值经过驼峰式映射给pojo
mybatis.configuration.map-underscore-to-camel-case=true

#mybatis xml文件路径
mybatis.mapper-locations=classpath:mapper/*Mapper.xml

#开启mybatis dao层的日志
logging.level.com.df.stage.tasktimer.mapper=debug

2.2. 使用MyBatis方式一:xml配置

MyBatis3 以前,须要手动获取SqlSession,并经过命名空间来调用MyBatis方法,比较麻烦。而MyBatis3 就开始支持接口的方式来调用方法,这也成为当前即为广泛的用法,本文就以此为例。

经过在Java中写dao层的 Interface 类,而后与之对应写一个 xml 文件,做为 Interface 的实现,以下:

DfTimerTaskMapper.java

@Mapper
public interface DfTimerTaskMapper {
    /**
     * 查询 df_timer_task 表
     * @param searchValue
     * @return
     */
    public List<DfTimerTask> queryTask(@Param("searchValue") String searchValue);
}

DfTimerTaskMapper.xml

<?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.df.stage.tasktimer.mapper.DfTimerTaskMapper">
    <select id="queryTask" resultType="com.df.stage.tasktimer.pojo.DfTimerTask" parameterType="String">
        select * from df_timer_task
        <where>
            <if test="searchValue!=null">
                task_code like '%'||#{searchValue}||'%'
                or method_type =#{searchValue}
                or method_name like '%'||#{searchValue}||'%'
                or status =#{searchValue}
            </if>
        </where>
    </select>
</mapper>

上一节中咱们在application.properties 文件中有配置MyBatis中 xml
配置文件的位置,在SpringBoot 项目启动时则会扫描全部Mapper的xml文件,并经过 mapper的namespace 找到与之对应的dao层 Interface类,将其注册为Spring的Bean,那么就能够经过IOC,随便调用 dao层的方法啦。

能够看到我在示例中用到了 where、if 等标签,正是这些标签使得MyBatis更加具备灵活性。MyBatis的动态sql,避免了不少其余框架拼接 SQL 语句的痛苦。

2.3. 使用MyBatis方式一:注解

人老是趋向于懒惰的,我开始指望于jdbc的一些特性。如今写一个dao层方法,还要在xml中写对应的实现,能不能作到我只写Java就能够了?很幸运,我能想到的MyBatis都作到了。

Java中自定义注解类,就是自定义了想要规范输入的元数据。就像MyBatis 的xml中那些标签同样,一样能够经过在Java接口中添加注解的方式,实现方法的sql。例如:

DfTimerTaskMapper.java

@Mapper
public interface DfTimerTaskMapper {
 /**
     * 查询已存在task_code 的数量
     * @param taskCode
     * @return
     */
    @Select("select count(1) from df_timer_task where task_code=#{taskCode}")
    public int countTask(@Param("taskCode")String taskCode);
}

只须要经过在 Interface 的抽象方法上方,经过注解sql,就能实现dao层的方法,不须要再写 Mapper的xml。

那么在平常开发中,“xml配置”和“注解”这两种方式咱们该作何选择呢?个人偏向是简单的sql经过注解方式实现。复杂的sql,例如须要用到动态sql,或者sql语句过长须要排版美化的,都经过xml配置的方式实现。固然,仁者见仁,智者见智。你怎么喜欢就怎么来,MyBatis做为“半自动化”ORM框架,就是让程序员能减小框架的束缚。

3. 分页查询

在为前端报表数据查询写接口的时候,咱们常常须要分页返回数据。例如:返回第 1~ 20行,或21~40行数据等。咱们不只须要返回指定行数区间的数据,还须要算出来该查询条件下一共有多少行数据。我写过不少数据库的分页sql:Oracle经过rownum,mysql经过 limit,sql server经过 top,等等。标准不同,当分页的查询多了,代码写起来很冗余。网上和MyBatis完美结合的分页插件,下面我推荐的是PageHelper。

3.1. PageHelper 分页器

先直接上使用的代码吧,使用PageHelper插件仅须要经过pom.xml添加jar包

<!--分页器 pagehelper-->
<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper-spring-boot-starter</artifactId>
    <version>1.2.10</version>
</dependency>

使用PageHelper的方式也很简单,先执行PageHelper.startPage(pageIndex,pageSize,true)方法,传入你定义的页面码pageIndex,和每页的记录数pageSize,而后紧跟着执行你自定义的查询语句。最后根据查询语句返回的对象列表,建立PageInfo的实例,PageInfo对象的属性里面就包含所需的:总记录数、总页数、查询数据列表,等等。

PageHelper.startPage(pageIndex,pageSize,true);
List<DfTimerTaskLogV> dfTimerTaskLogVList=  dfTimerTaskLogMapper.queryLog(executeStatus,
       taskCode,methodType,methodName,fromBeginTime,toBeginTime,fromFinishTime,toFinishTime);
PageInfo<DfTimerTaskLogV> pageInfo=new PageInfo<>(dfTimerTaskLogVList);
// 分页查询的数据集 List<DfTimerTaskLogV> :pageInfo.getList();
//总记录数 long:pageInfo.getTotal();

若是咱们打印出dao层的执行sql,会发现虽然咱们的的查询语句中并无实现分页,可是PageHelper已经替咱们加上了分页的sql。PageHelper首先将前端传递的参数保存到Page这个对象中,接着将Page的副本存放入ThreadLoacl中,这样能够保证分页的时候,参数互不影响,接着利用了MyBatis提供的拦截器,取得ThreadLocal的值,从新拼装分页SQL,完成分页。

3.2. 数据返回封装

PageHelper针对分页查询返回的数据集提供了封装类PageInfo,但团队开发过程当中,PageInfo定义的属性名不必定符合咱们的要求,那咱们能不能自定义返回的类类型呢?固然能够。上节在分析PageInfo的实现原理时了解到,是经过Page对象存储在ThreadLocal中实现,咱们只要获取Page值就好了。下面提供我封装的类

PageQueryResult.java

/**
 * 基于分页的方法改造
 * PageHelper -> PageInfo -> PageSerializable
 * @param <T>
 */
public class PageQueryResult<T> implements Serializable {
    private static final long serialVersionUID = 1L;
    protected long count;
    protected List<T> result;

    public PageQueryResult() {
    }

    public PageQueryResult(List<T> list) {
        this.result = list;
        if (list instanceof Page) {
            this.count = ((Page) list).getTotal();
        } else {
            this.count = (long) list.size();
        }

    }

    public static <T> PageQueryResult<T> of(List<T> list) {
        return new PageQueryResult(list);
    }

    public long getCount() {
        return this.count;
    }

    public void setCount(long total) {
        this.count = total;
    }

    public List<T> getResult() {
        return this.result;
    }

    public void setResult(List<T> list) {
        this.result = list;
    }

    public String toString() {
        return "PageQueryResult{count=" + this.count + ", result=" + this.result + '}';
    }
}

调用方式示例:

PageHelper.startPage(pageIndex,pageSize,true);
PageQueryResult<DfTimerTaskLogV> pageQueryResult=new PageQueryResult<>(dfTimerTaskLogMapper.queryLog(executeStatus,
                taskCode,methodType,methodName,fromBeginTime,toBeginTime,fromFinishTime,toFinishTime));
return Response.ok().data(pageQueryResult);

4. MyBatis缓存

使用缓存可使应用更快地获取数据,避免频繁的数据库交互,尤为是在查询越多、缓存命中率越高的状况下,使用缓存的做用就越明显。MyBatis 做为持久化框架,提供了很是强大的查询缓存特性,能够很是方便地配置和定制使用。通常提到 MyBatis 缓存的时候,都是指二级缓存。一级缓存(也叫本地缓存)默认会启用,而且不能控制,所以不多会提到。

4.1. 一级缓存

咱们先看看SqlSession的定义:在 MyBatis 中,你可使用 SqlSessionFactory 来建立 SqlSession。一旦你得到一个 session 以后,你可使用它来执行映射了的语句,提交或回滚链接,最后,当再也不须要它的时候,你能够关闭 session。使用 MyBatis-Spring 以后,你再也不须要直接使用 SqlSessionFactory 了,由于你的 bean 能够被注入一个线程安全的 SqlSession,它能基于 Spring 的事务配置来自动提交、回滚、关闭 session。咱们在使用MyBatis时是能够手动建立和关闭SqlSession,但也能够向本文同样,经过接口的方式调用方法,将SqlSession交给Spring框架来接管。

一级缓存是默认开启的。MyBatis提供了一级缓存的方案来优化在数据库会话间重复查询的问题。实现的方式是每个SqlSession中都持有了本身的缓存,一种是SESSION级别,即在一个MyBatis会话中执行的全部语句,都会共享这一个缓存。一种是STATEMENT级别,能够理解为缓存只对当前执行的这一个statement有效。

MyBatis一般和Spring进行整合开发。Spring将事务放到Service中管理,对于每个service中的sqlsession是不一样的,这是经过mybatis-spring中的org.mybatis.spring.mapper.MapperScannerConfigurer建立sqlsession自动注入到service中的。 每次查询以后都要进行关闭sqlSession,关闭以后数据被清空。因此spring整合以后,若是没有事务,一级缓存是没有意义的。

4.2. 二级缓存

二级缓存默认关闭,它是mapper级别的缓存,多个SqlSession去操做同一个Mapper的sql语句,多个SqlSession能够共用二级缓存,二级缓存是跨SqlSession的。

例如:UserMapper有一个二级缓存区域(按namespace分),其它mapper也有本身的二级缓存区域(按namespace分)。每个namespace的mapper都有一个二级缓存区域,两个mapper的namespace若是相同,这两个mapper执行sql查询到数据将存在相同的二级缓存区域中。

默认的二级缓存会有以下效果。

  • 映射语句文件中的全部SELECT语句将会被缓存。
  • 映射语句文件中的全部 INSERT、UPDATE、DELETE 语句会刷新缓存。
  • 缓存会使用Least Recently Used( LRU,最近最少使用 的)算法来收回。
  • 根据时间表( 如 no Flush Interval,没有刷新间隔),缓存不会以任什么时候间顺序来刷新。
  • 缓存会存储集合或对象( 不管查询方法返回什么类型的值)的1024 个 引用。

对于SpringBoot项目,开启二级缓存须要在配置文件中加上@EnableCaching 的注解。并且二级缓存通常配合Redis之类的key-value 数据库来使用,具体的实践,本文将不作详述。

相关文章
相关标签/搜索