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

栏目: Java · 发布时间: 6年前

内容简介:在我上大学的时候,最流行的JavaEE框架是 SSH (Struts+Spring+Hibernate),现在同学们应该都在学 SSM(Spring+SpringMVC+MyBatis)了。从历史演变来看,Spring是越来越强大,而MyBatis则是顶替了Hibernate的地位。今天的“主角”就是MyBatis。我们先聊一聊ORM(Object Relational Mapping),翻译为“对象关系映射”,就是通过实例对象的语法,完成关系型数据库的操作的技术。ORM用于实现面向对象编程语言里不同类型系

1. 概念

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

1.1. ORM的历史演变

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

ORM 把数据库映射成对象:

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

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

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

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

1.2. 开启MyBatis

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

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

2. MyBatis 基本使用

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

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 数据库来使用,具体的实践,本文将不做详述。


以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网

查看所有标签

猜你喜欢:

本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们

ANSI Common Lisp

ANSI Common Lisp

Paul Graham / Prentice Hall / 1995-11-12 / USD 116.40

For use as a core text supplement in any course covering common LISP such as Artificial Intelligence or Concepts of Programming Languages. Teaching students new and more powerful ways of thinking abo......一起来看看 《ANSI Common Lisp》 这本书的介绍吧!

Base64 编码/解码
Base64 编码/解码

Base64 编码/解码

SHA 加密
SHA 加密

SHA 加密工具

HEX HSV 转换工具
HEX HSV 转换工具

HEX HSV 互换工具