JAVAORM框架之Mybatis (Ibatis) 详解
Mybatis基础概念
Mybatis是一个持久层框架
它对JDBC操作数据库进行封装,让我们更关注SQL本身,而不需要花费精力去处理例如注册驱动、创建connection、创建statement、手动设置参数、结果集检索等jdbc繁杂的过程代码。下面是Mybatis的架构图
Mybatis又是如何解决JDBC中存在的问题呢?
1、 创建数据库连接相关操作,存在硬编码
a) 解决方案:通过Mybatis全局配置文件,对数据库连接进行配置
2、 statement相关操作,存在硬编码
a) 解决方案:通过Mapper映射文件,对statement相关处理进行配置。
3、 频繁开启数据库连接,会降低数据库处理性能。
a) 解决方案:通过Mybatis全局配置文件,配置连接池。
Mybatis开放方式演进
Mybatis框架核心要点
关联查询
所谓的关联查询就是一对一和一对多以及多对多的应用,例如下面的例子
-
一对一 : 例如在获取订单的时候需要获取该订单所属的用户信息
解决思路 : 使用ResultMap或者ResultType自定义一个POJO进行结果映射 -
一对多 : 例如在获取用户信息的时候需要获取该用户的所有订单信息
解决思路 : 自定义一个POJO使用(只能使用)ResultMap进行结果映射
延迟加载(懒加载)
关于延迟加载
- MyBatis中的延迟加载,也称为懒加载,是指在进行关联查询时,按照设置延迟规则推迟对关联对象的select查询。延迟加载可以有效的减少数据库压力。
- Mybatis的延迟加载,需要通过resultMap标签中的association和collection子标签才能演示成功。
- Mybatis的延迟加载,也被称为是嵌套查询,对应的还有嵌套结果的概念,可以参考一对多关联的案例。
- 注意:MyBatis的延迟加载只是对关联对象的查询有延迟设置,对于主加载对象都是直接执行查询语句的。
延迟加载分类
MyBatis根据对关联对象查询的select语句的执行时机,分为三种类型:直接加载、侵入式加载与深度延迟加载
- 直接加载: 执行完对主加载对象的select语句,马上执行对关联对象的select查询。
- 侵入式延迟:执行对主加载对象的查询时,不会执行对关联对象的查询。但当要访问主加载对象的详情时,就会马上执行关联对象的select查询。即对关联对象的查询执行,侵入到了主加载对象的详情访问中。也可以这样理解:将关联对象的详情侵入到了主加载对象的详情中,即将关联对象的详情作为主加载对象的详情的一部分出现了。
- 深度延迟:执行对主加载对象的查询时,不会执行对关联对象的查询。访问主加载对象的详情时也不会执行关联对象的select查询。只有当真正访问关联对象的详情时,才会执行对关联对象的select查询。
延迟加载策略需要在Mybatis的全局配置文件中,通过
如何使用
直接加载 通过对全局参数:lazyLoadingEnabled进行设置,默认就是false。
<settings>
<!-- 延迟加载总开关 -->
<setting name="lazyLoadingEnabled" value="false"/>
</settings>
侵入式加载
<settings>
<!-- 延迟加载总开关 -->
<setting name="lazyLoadingEnabled" value="true"/>
<!-- 侵入式延迟加载开关 -->
<setting name="aggressiveLazyLoading" value="true"/>
</settings>
深度延迟加载
<settings>
<!-- 延迟加载总开关 -->
<setting name="lazyLoadingEnabled" value="true"/>
<!-- 侵入式延迟加载开关 -->
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
动态SQL
动态SQL的思想:就是使用不同的动态SQL标签去完成SQL字符串的拼接处理。
解决的问题 :
- 在映射文件中,会编写很多有重叠部分的SQL语句,比如SELECT语句和WHERE语句等这些重叠语句,该如何处理
- 如果页面传递过来一个参数,但是SQL语句中的条件有多个,此时会发生问题。
主要标签
if标签 where标签 sql片段 foreach标签
Mybatis缓存
- Mybatis提供查询缓存,如果缓存中有数据就不用从数据库中获取,用于减轻数据压力,提高系统性能。
- Mybatis的查询缓存总共有两级,我们称之为一级缓存和二级缓存,如图:
• 一级缓存是SqlSession级别的缓存。在操作数据库时需要构造 sqlSession对象,在对象中有一个数据结构(HashMap)用于存储缓存数据。不同的sqlSession之间的缓存数据区域(HashMap)是互相不影响的。
• 二级缓存是Mapper(namespace)级别的缓存。多个SqlSession去操作同一个Mapper的sql语句,多个SqlSession可以共用二级缓存,二级缓存是跨SqlSession的。
一级缓存原理图
-
第一次发起查询用户id为1的用户信息,先去找缓存中是否有id为1的用户信息,如果没有,从数据库查询用户信息,将查询到的用户信息存储到一级缓存中。
-
如果中间sqlSession去执行commit操作(执行插入、更新、删除),清空SqlSession中的一级缓存,这样做的目的为了让缓存中存储的是最新的信息,避免脏读。
-
第二次发起查询用户id为1的用户信息,先去找缓存中是否有id为1的用户信息,缓存中有,直接从缓存中获取用户信息。
二级缓存原理图
1. 第一次调用mapper下的SQL去查询用户信息。查询到的信息会存到该mapper对应的二级缓存区域内。
2. 第二次调用相同namespace下的mapper映射文件中相同的SQL去查询用户信息。会去对应的二级缓存内取结果。
3. 如果调用相同namespace下的mapper映射文件中的增删改SQL,并执行了commit操作。此时会清空该namespace下的二级缓存。
如何开启二级缓存(默认关闭)
- 在核心配置文件SqlMapConfig.xml中加入以下内容(开启二级缓存总开关):
<!-- 开启二级缓存总开关 -->
<settings>
<setting name="cacheEnabled" value="true"/>
</settings>
- 在UserMapper映射文件中,加入以下内容,开启二级缓存:
<!-- 开启本mapper下的namespace的二级缓存,默认使用的是mybatis提供的PerpetualCache -->
<cache></cache>
说明 : 由于二级缓存的数据不一定都是存储到内存中,它的存储介质多种多样,比如说存储到文件系统中,所以需要给缓存的对象执行序列化。如果该类存在父类,那么父类也要实现序列化。(既查询结果对象要实现序列化接口)
Mybatis逆向工程
由于Mybatis是半自动化的ORM框架,所以仍然有很多事情需要我们去做
例如 : 编写与数据库表对应的实体,编写Mapper接口,编写Mapper配置文件
所谓的Mybatis逆向工程仅仅是一个项目所以只需要在下面配置中填写好自己数据库的相关信息,运行main方法既可以为我们生成POJO类,Mapper接口,Mapper配置文件
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
"http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">
<generatorConfiguration>
<context id="testTables" targetRuntime="MyBatis3">
<commentGenerator>
<!-- 是否去除自动生成的注释 true:是 : false:否 -->
<property name="suppressAllComments" value="true" />
</commentGenerator>
<!--数据库连接的信息:驱动类、连接地址、用户名、密码 -->
<jdbcConnection driverClass="com.mysql.jdbc.Driver"
connectionURL="jdbc:mysql://localhost:3306/mybatis" userId="root" password="root">
</jdbcConnection>
<!-- <jdbcConnection driverClass="oracle.jdbc.OracleDriver" connectionURL="jdbc:oracle:thin:@127.0.0.1:1521:yycg"
userId="yycg" password="yycg"> </jdbcConnection> -->
<!-- 默认false,把JDBC DECIMAL 和 NUMERIC 类型解析为 Integer,为 true时把JDBC DECIMAL
和 NUMERIC 类型解析为java.math.BigDecimal -->
<javaTypeResolver>
<property name="forceBigDecimals" value="false" />
</javaTypeResolver>
<!-- targetProject:生成PO类的位置 -->
<javaModelGenerator targetPackage="com.kkb.ms.po"
targetProject=".\src">
<!-- enableSubPackages:是否让schema作为包的后缀 -->
<property name="enableSubPackages" value="false" />
<!-- 从数据库返回的值被清理前后的空格 -->
<property name="trimStrings" value="true" />
</javaModelGenerator>
<!-- targetProject:mapper映射文件生成的位置 -->
<sqlMapGenerator targetPackage="com.kkb.ms.mapper"
targetProject=".\src">
<!-- enableSubPackages:是否让schema作为包的后缀 -->
<property name="enableSubPackages" value="false" />
</sqlMapGenerator>
<!-- targetPackage:mapper接口生成的位置 -->
<javaClientGenerator type="XMLMAPPER"
targetPackage="com.kkb.ms.mapper" targetProject=".\src">
<!-- enableSubPackages:是否让schema作为包的后缀 -->
<property name="enableSubPackages" value="false" />
</javaClientGenerator>
<!-- 指定数据库表 -->
<table schema="" tableName="user"></table>
<table schema="" tableName="order"></table>
</context>
</generatorConfiguration>
注意 :每次执行逆向工程代码之前,先删除原来已经生成的mapper xml文件再进行生成。mapper.xml文件的内容不是被覆盖而是进行内容追加,会导致mybatis解析失败。po类及mapper.java文件的内容是直接覆盖没有此问题。
PageHelper分页插件
如果你也在用Mybatis,建议尝试该分页插件,这个一定是最方便使用的分页插件。
使用方法如下
首先增加依赖
<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.1.6</version>
</dependency>
配置PageHelper
• Mybatis全局配置文件
<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor">
<!-- config params as the following -->
<property name="helperDialect" value="mysql"/>
</plugin>
</plugins>
• Spring配置文件
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- other configuration -->
<property name="plugins">
<array>
<bean class="com.github.pagehelper.PageInterceptor">
<property name="properties">
<!-- config params as the following -->
<value>
helperDialect=mysql
</value>
</property>
</bean>
</array>
</property>
</bean>
在项目中使用PageHelper
//获取第1页,10条内容,默认查询总数count
PageHelper.startPage(1, 10);
List<Country> list = countryMapper.selectAll();
//用PageInfo对结果进行包装
PageInfo page = new PageInfo(list);
//测试PageInfo全部属性
//PageInfo包含了非常全面的分页属性
assertEquals(1, page.getPageNum());
assertEquals(10, page.getPageSize());
注意事项
1. 需要分页的查询语句,必须是处于PageHelper.startPage(1, 10);后面的第一条语句。
2. 如果查询语句是使用resultMap进行的嵌套结果映射,则无法使用PageHelper进行分页。
Mybatis插件介绍
参考地址:https://www.cnblogs.com/fangjian0423/p/mybatis-interceptor.html
StatementHandler
ParameterHandler
ResultSetHandler
注解开发
使用注解开发,我们不再需要XML配置文件
常用注解说明
增删改查-静态SQL
- @Insert:相当于
标签,实现新增 - @Update: 相当于
标签,实现更新 - @Delete: 相当于
标签,实现删除 - @Select: 相当于标签,实现查询
多表关联
• @Results: 相当于<resultMap>标签,需要和@Result注解一起使用。
• @Result: 相当于<result>和<id>标签,实现结果集中某一列的数据映射
* column 数据库的列名
* property 需要装配的属性名
* one 需要使用的@One 注解(@Result(one=@One()))
* many 需要使用的@Many 注解(@Result(many=@many()))
• @One: 相当于<association>标签,实现一对一关系映射
• @Many:相当于<collection>标签,实现一对多关系映射
• @One和@Many注解的属性:
* select 属性:代表将要执行的 sql 语句
* fetchType 属性:代表加载方式,一般如果要延迟加载都设置为 LAZY 的值
• 使用格式:
1. @Results({@Result(),@Result()})或@Results(@Result())
2. @Result(column=" ",property="",one=@One(select=""))
辅助注解
- @Options:相当于标签属性的设置
- @Param:如果你的映射器的方法需要多个参数,这个注解可以被应用于映射器的方法参数来给每个参数一个名字。
其他注解
- @CacheNamespace:相当于
标签,实现二级缓存。
属性:implemetation,eviction,flushInterval,size,readWrite,blocking和properties