2 MyBatis框架基础知识

1.1 框架

 

对于程序员来说,框架是一套资源,这套资源中包含jar包、文档。还有一些包含源码、代码示例等。这套资源从相关的官网上可以下载。一般是以压缩文件的形式出现。

1.1.1 Mybatis的下载

 

MyBatis可以在github官网下载

http://github.com/mybatis

 

 

1.1.2 MybatisJar

 

MyBatis框架的解压目录中只有一个Jar包,它是Mybatis的核心Jar包。另外还有个lib目录,其中存放者MyBatis所依赖的Jar包。所以,使用MyBatis需要将其核心Jar包和lib下的所有Jar包导入。

 

 

1.2 MyBatis概述

 

 

1.2.1 MyBatis简介

 

MyBatis是一个优秀的基于Java的持久层框架它内部封装了JDBC,使开发者只需要SQL语句本身,而不用再花费精力去处理注入注册驱动等。

MyBatis通过xml或注解的方式将要执行的各种statement配置起来,并通过java对象和statementSQL的动态参数进行映射生成最终执行的SQL语句,最后由MyBatis框架执行SQL并将结果映射成java对象并返回。

1.2.2 MyBatisHibernate

 

hibernate和mybatis是当前流行的ORM框架。hibernate对数据库结构提供了较为完整的封装。mybatis主要着力点在于java对象与SQL之间的映射关系。

Hibernate框架是提供了全面的数据库封装机制的”全自动”ORM,即实现了POLP和数据库表之间的映射,以及SQL的自动生成和执行。

相比于此,MyBaties只能算作是”半自动”ORM。

因为MyBaties需要程序员自己去编写SQL语句,程序员可以结合数据库自身的特点灵活控制SQL语句。因此能够实现比Hibernate等全自动ORM框架更高的查询效率,能够完成复杂查询。

 

1.2.3 MyBatis体系结构

 

 

1.3 MyBatis工作原理

 

 

连接数据库的四要素再mybatis.xml中,SQL语句在mapper.xml文件中。Mybatis.xml进行注册。上图就展示了ORM框架。

 

1.4 第一个Mybatis程序

 

需求:实现Student信息写入到DB中

 

1.4.1基本程序

 

项目:primary

 

(1)导入Jar包

 

除了需要导入MyBatis的核心Jar包及依赖Jar包外,还需要导入MySql的驱动Jar包,JUnit测试的Jar包。核心Jar包与依赖Jar包,均在MyBatis框架的解压目录下。

 

(2)定义实体类

 

 

 

(3) 配置mapper.xml

 

 

 

dtd文件的设置,使得提示信息从本地获取。上图中的http://mybatis.org/dtd/mybatis-3-mapper.dtd该地址放到下图中的key栏中,本地的dtd文件地址添加到下图中的location栏中。

 

 

 

(4) 配置MyBatis.xml

environmentsdefault属性的作用:指定 所用environment的id

 

 

 

 

 

(5) 获得SqlSession对象,对数据操作

 

 

 

1.5  通过阅读源码解决一些问题

1.5.1 在上图中创建SqlSessionFactory时有一个参数时inputStream,为什么不需要关闭?

 


 

 

 

1.5.2 SqlSession的创建:给一群变量赋初始值

 

1.5.3

增删改的操作都是调用的update操作

 

1.5.4 SqlSession的提交

 

1.5.5 SqlSession的关闭:如果做了提交,就不会执行回滚;如果不提交就会执行回滚。

 

 

1.6 给类的全名起别名,添加到配置文件中

 

 

 

2.1 单表的CURD(增删改查)的基本操作

2.2 把没有id的对象插入到数据库后直接获得id赋值到该对象。通过在insert语句后加select @@identity。

 

 

2.3 删除、更改时的mapper配置文件

 

查询时,SqlSession所调用的方法以及返回类型(这里是列表)。

 

2.4 模糊查询 & #{}、${}

Statement和preparedstatement之间的关系和区别

关系:PreparedStatement继承自Statement,都是接口
   区别:PreparedStatement可以使用占位符,是预编译的,批处理比Statement效率高  

PreparedStatement能够防止SQL注入PreparedStatement执行效率高#{}就相当于PreparedStatement使用的是动态参数。而${}就相当于Statement,使用的是字符串拼接

 

 

3.1 解决属性和数据库字段名不一致的情况

 

1、给数据库字段名起别名为持久化类的属性名

 

2、使用resultMap来映射数据库字段名称和持久化类属性名的不同

 

 

3.2  Mapper动态代理

使用Mapper的动态代理,可以直接省略Dao层的实现类,只需要借助Dao层的接口来实现。使用接口的全名作为mapper的命名空间namespace的名字,使用接口中方法名作为mapper中SQL语句的id。这样直到了namespce和Mapper中SQL语句的id就直接可以知道要执行那句SQL语句了。

 

3.3 多查询条件无法整体接收问题

1、使用Map封装,传参数为封装好的Map,在SQL语句中的#{}中放Map中d对应的key值,即#{key}

2、使用参数的索引号

 

 

4.1 动态SQL

动态SQL,主要用于解决查询条件不确定的情况:在程序运行期间,根据用户提交的查询条件进行查询。提交的查询条件不同,执行的SQL语句不同,若将每种情况都逐一的写出来将出现大量的情况,所以此时就用到了动态SQL。

4.2 <if/>标签 和 <where/>标签

注意事项:

 

对于该标签的执行,当test的值为true时,会将其包含的SQL片段拼接到其所在的SQL语句中。

示例:

 

 

在上图代码中如果使用 where 1 = 1  的话,在数据量特别大的情况下会大大降低执行效率。所以引出了<where />标签

 

 

 

 

4.3 <choose />标签

该标签中只可以包含<when /> 和 <otherwise />,可以包含多个<when /> 与一个<otherwise />。它们联合使用,完成java中的开关语句switch...case功能。

示例:

 

 

4.4 <foreach /> 标签

传递数组、列表、自定义类的列表参数的用法:

<select id="selectStudentsByForEach" resultMap="studentMapper">

 select tid,tname,tage,tscore

 from student

 <!-- 传递的是Integer数组,先判断数组是否为空 -->

 <if test="array.length > 0">

 where tid in

 <foreach collection="array" item="myid" open="(" close=")" separator=",">

 #{myid}

 </foreach>

 </if>

 

 </select>

 

 <select id="selectStudentsByForEach2" resultMap="studentMapper">

 select tid,tname,tage,tscore

 from student

 <!-- 传递的是Integer列表,先判断列表是否为空 -->

 <if test="list.size > 0">

 where tid in

 <foreach collection="list" item="myid" open="(" close=")" separator=",">

 #{myid}

 </foreach>

 </if>

 

 </select>

 

 <select id="selectStudentsByForEach3" resultMap="studentMapper">

 select tid,tname,tage,tscore

 from student

 <!-- 传递的是Student列表,先判断列表是否为空 -->

 <if test="list.size > 0">

 where tid in

 <foreach collection="list" item="stu" open="(" close=")" separator=",">

 #{stu.id}

 </foreach>

 </if>

 

 </select>

4.5 SQL片段 

 

 

 

 

5.1 关联关系

当查询内容涉及到具有关联关系的多个表时,就需要使用关联查询。根据表与表间的关联关系不同,关联查询分为四种:

1、一对一关联查询

2、一对多关联查询

3、多对一关联查询

4、多对多关联查询

外键一定是定义在多方表中。

 

5.2 一对多 & 多对一关联查询

1、通过多表连接查询和多次单表查询实现一对多查询,注意当使用多次单表查询的时候可以使用延迟加载,所以这种方法用的比较多。

*Beans层的Country类的成员变量

private Integer cid;

private String cname;

//关联属性,一对多,一方可以看到多方

private Set<Minister> ministers;
*mapper SQL语句实现文件

<?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="edu.sdut.dao.ICountryDao">

 <resultMap type="Country" id="countryMapper">

 <id column="cid" property="cid"/>

 <result column="cname" property="cname"/>

 <!-- 对集合的封装 -->

 <collection property="ministers" ofType="Minister">

 <id column="mid" property="mid"/>

 <result column="mname" property="mname"/>

 </collection>

 </resultMap>

<!-- 多表连接查询 -->

 <select id="selectCountryById" resultMap="countryMapper">

 <!-- 将mid和mname封装到Country类的集合成员变量中,但是

 mybatis不能自动封装变量到集合的映射,所以借助resultMap将查询

 出来的数据做映射 -->

 select cid,cname,mid,mname

 from country,minister

 where countryId=cid and cid=#{cid}

 </select>

 

 

 

 <!-- 第二次查询 通过第一次查询返回的参数 -->

 <select id="selectMinisterByCountry" resultType="Minister">

 select mid,mname from minister where countryId=#{xxx}

 </select>

 <!-- 使用两次查询实现多表查询,可以使用延迟加载 -->

<resultMap type="Country" id="countryMapper2">

 <id column="cid" property="cid"/>

 <result column="cname" property="cname"/>

 <!-- 通过两次查询获取数据 -->

 <!-- ofType表示集合所属的泛型

 select表示查询Minister类的语句id

 column表示传递的参数

 最后查询的结果封装到集合当中-->

 <collection property="ministers" 

 	ofType="Minister" 

 	select="selectMinisterByCountry"

 	column="cid"/>

 </resultMap>

<!-- 第一次查询 -->

 <select id="selectCountryById2" resultMap="countryMapper2">

 

 select cid,cname

 from country

 where cid=#{cid}

 </select> 

 </mapper>

 

 

2、通过多表连接查询和多次单表查询实现多对一查询,注意当使用多次单表查询的时候可以使用延迟加载,所以这种方法用的比较多。

 

*Beans层的Minister类的成员变量

public class Minister {

private Integer mid;

private String mname;

private Country country;

}
*Mapper文件

<?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="edu.sdut.dao.IMinisterDao">

 

<resultMap type="Minister" id="ministerMapper">

<id column="mid" property="mid"/>

<result column="mname" property="mname"/>

<!-- 对查询对象成员变量的封装  此处使用的是association标签对单对象及进行封装

使用javaType属性标识封装类型 -->

<association property="country" javaType="Country">

<id column="cid" property="cid"/>

<result column="cname" property="cname"/>

</association>

</resultMap>

<!-- 多对一多表连接查询,依然需要借助resultMap进行映射 -->

<select id="selectMinisterById" resultMap="ministerMapper">

select mid,mname,cid,cname

from minister,country

where countryId=cid and mid=#{xxx}

</select>

 

 

 

 <!-- 多对一查询 ,使用两次查询实现多表查询, -->

 <select id="selectCountryById3" resultType="Country">

 select cid,cname from country where cid=#{xxx}

 </select>

 <resultMap type="Minister" id="ministerMapper2">

 <id column="mid" property="mid"/>

 <result column="mname" property="mname"/>

 <association property="country"

 	javaType="Country" 

 	select="selectCountryById3"

 	column="countryId"/>

 </resultMap>

 

 <select id="selectMinisterById2" resultMap="ministerMapper2">

 select mid,mname,countryId from minister where mid=#{mid}

 </select>

 </mapper>

 

5.3 自关联查询

 

所谓自关联是指,自己即充当一方,又充当多方。

递归调用实现自关联查询,查询某id的所有子孙类。

 

*beans实现类

public class NewsLabel {

private Integer id;

private String name;

private Set<NewsLabel> children;

}

*mapper配置

<?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="edu.sdut.dao.INewsLabelDao">

 

<!-- 查询指定栏目的所有子孙栏目 -->

<resultMap type="NewsLabel" id="newsLabelMapper">

<id column="id" property="id"/>

<result column="name" property="name"/>

<!-- 自查询,使用递归调用查询,select属性再次执行下面的

select语句,递归调用 -->

<collection property="children" ofType="NewsLabel" 

select="selectChildrenByParent"

column="id"/>

</resultMap>

<select id="selectChildrenByParent" resultMap="newsLabelMapper">

select id,name from newslabel

where pid=#{xxx}

</select>

 </mapper>

附加一个SQL表

 

5.4 多对多关联查询

多对多是两个一对多构成的。建立一张中间表。其中中间表是多方。有外键的一定是多方。

 

 

 

 

 

6.1 延迟加载

MyBatis中的延迟加载,也称为懒加载,是指再关联查询的时候按照设置延迟规则推迟对关联对象的select查询。延迟加载可以有效的减少数据库压力。

需要注意的是,Mybatis的延迟加载只是对关联对象的查询有延迟设置,对于主加载对象都是直接执行查询语句的。

 

6.2 关联对象加载机制

MyBatis根据对关联对象查询的select语句的执行机制,分为三种:直接加载、侵入式延迟加载与深度延迟加载。

直接加载:执行完对主加载对象的select语句,马上执行对关联对象的select查询。

侵入式延迟:执行对主加载对象的查询时,不会执行对关联对象的查询。当当要访问主加载对象的详情的时候,就会马上执行关联对象的selec查询。即对关联对象的查询,侵入到了主加载对象的详情访问中。或者说:将关联对象的详情进入到了主加载对象的详情中,即将关联对象的详情作为主加载对象的一部分执行。

深度延迟:执行对主加载对象的查询时,不会执行关联对象的查询。访问主加载对象的详情也不会执行关联对象的select查询。只有当真正访问关联对象的详情的时候,才会执行对关联对象的select查询。

需要注意的是,延迟加载的应用要求。关联对象的查询与主加载对象的查询必须是分别进行的select语句。不能是使用多表连接所进行的select查询。因为多表连接查询,其实质实对一张表的查询,对由多个表连接后形成的一张表的查询。会一次性将多张表所有信息查询出来。

MyBatis中对于延迟加载设置,可以应用到一对一、一对多、多对一、多对多的所有关联关系查询中。

 

在主配置文件中配置:

 

 

 

7.1  查询缓存

查询缓存的使用,主要是为了提高查询访问速度,将用户对同一数据的重复查询过程简化,不再每次均从数据库查询获取结果数据,从而提高访问速度。

 

MyBatis的查询缓存机制,根据缓存区的作用域域声明周期,可划分为两种:一级缓存与二级缓存。

MyBatis查询缓存的作用域是根据映射文件mapper的namespace划分的,相同namespace的mapper查询数据存放在同一个缓存区域。不同namespace下的数据互不干扰。无论是一级缓存还是二级缓存,都是按照namespace进行分别存放的。

但是一、二缓存的不同之处在于,SqlSession一旦关闭,则SqlSession中的数据将不存在,即一级缓存就不复存在,而二级缓存的生命周期会与整个应用程序同步,与SqlSessioin是否关闭无关。换句话说,一级缓存是在同一线程(同一SqlSession)间共享的,而二级缓存是在不同线程(不同SqlSession)间共享数据。

 

7.2 一级缓存

MyBatis一级缓存是基于org.apache.ibatis.cache.impl.PerpetualCache类的HashMap本地缓存,其中Map的value是结果对象,Map的key是SQL的ID+SQL语句其和hibernate的查询依据不同,Hibernate的查询依据是查询对象的ID。其作用域是SqlSession,在同一个SqlSession中两次执行相同的sql查询语句,第一次执行完毕后,会将查询结果写入到查询缓存中,第二次会从缓存中直接获取数据,而不再到数据库中进行查询,从而提高查询效率。缓存查询的依据是SQL的ID,而不是结果对象的ID。

当一个SqlSession结束后,该SqlSession中的一级缓存也就不存在了。MyBatis默认一级缓存是开启状态的,且不能关闭。

 

7.3 增删改对一级缓存的影响

增删改操作都会刷新(清空)一级缓存。(无论是否提交

 

7.4 内置二级缓存

由于Mybatis从缓存中读取数据的依据与SQL的id有关,而非查询出的对象,所以,使用二级缓存的目的,不是在多个查询间共享数据结果(所有查询中只要查询结果中存在改对象的,就直接从缓存中读取,这是对查询结果的共享,Hibernate中的缓存就是为了在多个查询间共享数据结果,但MyBatis的不是),而是为了防止同一查询(相同的SqlID,相同的SQL语句)的反复执行。

MyBatis内置的二级缓存为org.apache.ibatis.cache.impl.PerpetualCache。

 

7.4.1 内置二级缓存的开启

1、在持久化类中实现序列化Serializable接口

2、在mappper中配置

 

7.4.2 增删改对二级缓存的影响

增删改同样也会清空二级缓存

对于二级缓存的清空,实质上是对key所查找key对应的value置为null,而并非将<key,value>对,即Entry对象删除

DB中进行select查询的条件是:

1)缓存中根本就不存在key对应的Entry对象

2)缓存中存在该key所对应的Entry对象,但value为null

 

7.4.3 二级缓存的使用原则

1)多个namespace不要操作同一张表

由于二级缓存中的数据是基于namespace的,即不同namespace中的数据互不干扰。若某个用户在某个namespace下表执行了增删改操作,该操作只会引发当前namespace下的二级缓存的刷新,而对其他的namespace下的二级缓存没有影响,这样的话,其他二级缓存中的数据依然是未更新的数据,也就出现了多个namespace中数据不一致的现象。

 

2)不要再关联关系表上执行增删改操作

一个namespace一般是对同一个表进行操作的,若表间存在关联关系,也就意味着同一个表可能会出现多个namespace中,这样就存在一个风险,若某一个namespace中对一个表进行增删改操作时影响到了其关联表的数据,而着个关联表的数据的修改只会刷新当前namespace下的二级缓存,而对另一namespace下的二级缓存数据没有影响。这也是多个namespace下对同一张表的操作。

 

3)查询多余修改时使用二级缓存

在查询操作远远多于增删改的情况下可以使用二级查询。因为任何增删改都将刷新二级缓存。

 

 

 

7.4.4  增删改时不刷新二级缓存

<insert flushCache = false>修改  ,对一级缓存不起作用。

 

7.5 MyBatis注解式开发

mybatis的注解,主要是用于替换映射文件的,而映射文件中无非存放着增删改查的SQL映射标签。所以,mybatis注解,就是要替换映射文件中的SQL标签。

注解的基础语法

注解后没有分号的

注解首字母是大写的,因为注解与类、接口是同一级别的。一个注解,后台对应着一个@Interface

在同一语法单元上,同一注解只能使用一次

在注解与语法单元之间可以隔若干空行、注释等非代码内容

猜你喜欢

转载自blog.csdn.net/txgANG/article/details/81021440