在我上大学的时候,最流行的JavaEE框架是 SSH (Struts+Spring+Hibernate),如今同窗们应该都在学 SSM(Spring+SpringMVC+MyBatis)了。从历史演变来看,Spring是愈来愈强大,而MyBatis则是顶替了Hibernate的地位。今天的“主角”就是MyBatis。前端
咱们先聊一聊ORM(Object Relational Mapping),翻译为“对象关系映射”,就是经过实例对象的语法,完成关系型数据库的操做的技术。ORM用于实现面向对象编程语言里不一样类型系统的数据之间的转换,实际上是建立了一个可在编程语言里使用的"虚拟对象数据库"。java
ORM 把数据库映射成对象:mysql
基于传统ORM框架的产品有不少,其中就有耳熟能详的Hibernate。ORM经过配置文件,使数据库表和JavaBean类对应起来,提供简便的操做方法,增、删、改、查记录,再也不拼写字符串生成sql,编程效率大大提升,同时减小程序出错机率,加强数据库的移植性,方便测试。git
可是有些时候我仍是喜欢原生的JDBC,由于在某些特殊的应用场景中,对于sql的应用复杂性比较高,或者须要对sql的性能进行优化,这些ORM框架就显得很笨重。Hibernate这类“全自动化”框架,对数据库结构封装的较为完整,这种一站式的解决方案未必适用于全部的业务场景。程序员
幸运的是,不仅我一我的有这种感觉,好久以前你们开始关注一个叫 iBATIS 的开源项目,它相对传统ORM框架而言更加的灵活,被定义为“半自动化”的ORM框架。2010年,谷歌接管了iBATIS,MyBatis就随之诞生了。虽然2010年我都还没上大学,但很惋惜,MyBatis在国内的大火的比较晚,我在校园期间都没有接触过。github
MyBatis 是一款优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。MyBatis 避免了几乎全部的 JDBC 代码和手动设置参数以及获取结果集。MyBatis 可使用简单的 XML 或注解来配置和映射原生类型、接口和 Java 的 POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。算法
MyBatis为半自动化,须要本身书写sql语句,须要本身定义映射。增长了程序员的一些操做,可是带来了设计上的灵活。而且也是支持Hibernate的一些特性,如延迟加载,缓存和映射等,并且随之SSM架构的成熟,MyBatis确定会被授予有愈来愈多新的特性。那么接下来就开始 MyBatis 的实战演练吧!spring
下面讲解在SpringBoot 中,使用MyBatis的基本操做。sql
在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
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 语句的痛苦。
人老是趋向于懒惰的,我开始指望于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框架,就是让程序员能减小框架的束缚。
在为前端报表数据查询写接口的时候,咱们常常须要分页返回数据。例如:返回第 1~ 20行,或21~40行数据等。咱们不只须要返回指定行数区间的数据,还须要算出来该查询条件下一共有多少行数据。我写过不少数据库的分页sql:Oracle经过rownum,mysql经过 limit,sql server经过 top,等等。标准不同,当分页的查询多了,代码写起来很冗余。网上和MyBatis完美结合的分页插件,下面我推荐的是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,完成分页。
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);
使用缓存可使应用更快地获取数据,避免频繁的数据库交互,尤为是在查询越多、缓存命中率越高的状况下,使用缓存的做用就越明显。MyBatis 做为持久化框架,提供了很是强大的查询缓存特性,能够很是方便地配置和定制使用。通常提到 MyBatis 缓存的时候,都是指二级缓存。一级缓存(也叫本地缓存)默认会启用,而且不能控制,所以不多会提到。
咱们先看看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整合以后,若是没有事务,一级缓存是没有意义的。
二级缓存默认关闭,它是mapper级别的缓存,多个SqlSession去操做同一个Mapper的sql语句,多个SqlSession能够共用二级缓存,二级缓存是跨SqlSession的。
例如:UserMapper有一个二级缓存区域(按namespace分),其它mapper也有本身的二级缓存区域(按namespace分)。每个namespace的mapper都有一个二级缓存区域,两个mapper的namespace若是相同,这两个mapper执行sql查询到数据将存在相同的二级缓存区域中。
默认的二级缓存会有以下效果。
对于SpringBoot项目,开启二级缓存须要在配置文件中加上@EnableCaching 的注解。并且二级缓存通常配合Redis之类的key-value 数据库来使用,具体的实践,本文将不作详述。