mybatis是一款持久层的框架,需要和数据库进行打交道,所以要测试mybatis的话,我们事先应该先创建一个数据库表,并往表中插入一些数据供我们使用。
一.准备阶段
1. 创建一个数据库,并创建一个user表
create table user(
id int(5) primary key,
name varchar(20),
age int(2),
description varchar(100)
) default charset=utf8;
insert into user values(1,'吕建友',21,'还不错哟');
insert into user values(2,'小龙女',12,'还不错哟');
insert into user values(3,'奥特曼',56,'还不错哟');
insert into user values(4,'迪迦',42,'还不错哟');
insert into user values(5,'成龙',66,'还不错哟');
2. 创建一个普通Maven项目
创建好项目以后我们就要进行几步操作了。
- 添加依赖包
<dependencies>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<version>8.0.11</version>
</dependency>
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.5.3</version>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.16</version>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>
前两个依赖是必须的依赖,引入lombok是为了简化bean的开发,junit是为了进行测试。
- 在resource中创建两个配置文件
其中,database.properties的配置如下【该配置文件非必须,仅为后期方便进行修改】
driver=com.mysql.cj.jdbc.Driver
url = jdbc:mysql://localhost:3306/lvjianyou
username=root
password=root
mybatis.xml的配置如下【mybatis的主配置文件,必须!文件名可更改】
这个配置文件中东西较多,可根据自己需要进行删除或者添加
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<!-- 引入database.properties -->
<properties resource="database.properties"/>
<settings>
<!--开启驼峰匹配-->
<setting name="mapUnderscoreToCamelCase" value="true"/>
<!--开启二级缓存,全局总开关,这里关闭,mapper中开启了也没用-->
<setting name="cacheEnabled" value="false"/>
<!--开启sql日志打印-->
<setting name="logImpl" value="STDOUT_LOGGING" />
</settings>
<!-- 配置别名,顺序别打乱,xml中的格式是非常严格的-->
<typeAliases>
<package name="com.jianyou.pojo"/>
</typeAliases>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${driver}"/>
<property name="url" value="${url}"/>
<property name="username" value="${username}"/>
<property name="password" value="${password}"/>
</dataSource>
</environment>
</environments>
<mappers>
<!--resource指定的是classpath下面的mapper文件,只需配置相对路径-->
<mapper resource="mapper/UserMapper.xml"/>
<!--class属性指定从某个类上读取映射规则,用于注解绑定sql的情景-->
<mapper class="com.jianyou.mapper.UserMapper"/>
<!--package指定扫描某个包下的所有类,同样用于注解绑定sql的情况-->
<package name="com.jianyou.mapper"/>
</mappers>
</configuration>
- 编写数据库表对应的实体类,方便进行映射
@AllArgsConstructor
@NoArgsConstructor
@Data
public class User {
private int id;
private String name;
private int age;
private String description;
}
是不是很简单,我们不用再写get和set方法了~ 这就是引入了lombok的好处了~
- 编写一个mapper接口
public interface UserMapper {
@Select("select * from user")
List<User> findAllUser();
}
整体项目结构如下:
3..使用Junit进行测试
在test包中创建一个MabatisTest进行测试
@Test
public void test1() throws IOException {
InputStream is = Resources.getResourceAsStream("mybatis.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
SqlSession sqlSession = factory.openSession();
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
List<User> allUser = mapper.findAllUser();
for (User user : allUser) {
System.out.println(user);
}
}
测试结果如下
我们也可以使用xml来配置sql语句,创建一个UserMapper.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.jianyou.mapper.UserMapper">
<select id="findAllUser" resultType="com.jianyou.pojo.User">
select * from user
</select>
</mapper>
注意:要将xml文件放在和mapper接口一个目录下!
至此,基本配置成功!
二. 动态SQL
- if
- choose (when, otherwise)
- trim(where,set)
- foreach
2.1 if
使用if最常见的场景就是在条件查询的时候了吧!我们提供给用户多种查询的情况,用户往往会进行一些筛选的查询。在UserMapper中添加一个接口findUserByCondition,同时在UserMapper.xml中添加sql Sql语句如下:
<select id="findUserByCondition" resultType="com.jianyou.pojo.User" parameterType="userVo">
select * from user
where id>0
<if test="name != null"> //如果传入的用户姓名不为空
and name="'"+#{name}+"'" //拼接上姓名
</if>
<if test="age != null"> // 如果传入的年龄不为空
and age=#{age} //在sql中拼接上年龄
</if>
</select>
执行结果如下:
可以看见,mybatis自动为我们拼接上了sql语句,并且我们也成功查询出来了结果。
2.2 choose when otherwise
有一些时候,我们不想用到所有的条件语句,而只想从其中选择一二。针对于这种情况的话,Mybatis提供了choose元素,它有点像Java中的switch语句,执行一次,只能选择其中一个分支!
<select id="findUserByChoose" resultType="com.jianyou.pojo.User" parameterType="UserVo">
select * from user
<where>
<choose>
<when test="name!=null"> //如果姓名不为空的话,直接走这个分支,然后退出,下同
name like #{name}
</when>
<when test="age !=0">
age > #{age}
</when>
<otherwise>
age = 21
</otherwise>
</choose>
</where>
</select>
我们给条件中添加一个年龄
查询结果如下:
可见,choose的作用已经发挥出来了。
2.3 trim where set
我们先来测试一下where,看一下如下的sql语句
<select id="findUserSecond" resultType="com.jianyou.pojo.User" parameterType="userVo">
select * from user
where
<if test="age !=0">
and age= #{age}
</if>
<if test="name != null">
and name=#{name}
</if>
</select>
如果我们没有将age和name传入的话会发生什么? 是不是直接就是 select * from user where 了,或者说我们没有传入age,只传入了name,这时候sql会变成什么?不难看出应该是:select * from user where and name = ? ,这样肯定是不好的,所有where的作用是只有在一个if条件有值得情况下才去插入“WHERE”子句。而且,若后面的内容是以“AND”或者“or”开头的,where元素也知道如何将他们去除。
可以明显看出where的作用:
1. 在sql中根据我们的条件自动添加 where 单词。
2. 会自动去除where后出现的第一个and,使其满足我们的条件。
set标签和where的作用类似,只是set是作用于修改语句。
- 自动在修改条件的后面添加set
- 去除sql最后多出来的一个“,”
trim标签
| 属性 | 描述 |
|---|---|
| prefix | 给sql语句拼接的前缀 |
| suffix | 给sql语句拼接的后缀 |
| prefixOverrides | 去除sql语句前面的关键字或者字符,该关键字或者字符由prefixOverrides属性指定,假设该属性指定为"AND",当sql语句的开头为"AND",trim标签将会去除该"AND" |
| suffixOverrides | 去除sql语句后面的关键字或者字符,该关键字或者字符由suffixOverrides属性指定,当sql语句结尾为","时,trim标签会去掉"," |
foreach
当我们需要对一个集合进行遍历,通常是在构建In条件语句的时候,比如
<select id="findUserByIds" resultType="com.jianyou.pojo.User">
select * from user
where id in
<foreach collection="list" item="item" index="index" open="(" separator="," close=")">
#{item}
</foreach>
</select>
我们执行一下,结果如下:
在做mybatis的mapper.xml文件的时候,我们时常用到这样的情况:动态生成sql语句的查询条件,这个时候我们就可以用mybatis的foreach了
foreach元素的属性主要有item,index,collection,open,separator,close。
- item: 集合中元素迭代时的别名,该参数为必选。
- index:在list和数组中,index是元素的序号,在map中,index是元素的key,该参数可选
- open:foreach代码的开始符号,一般是(和close=")"合用。常用在in(),values()时。该参数可选
- separator:元素之间的分隔符,例如在in()的时候,separator=","会自动在元素中间用“,“隔开,避免手动输入逗号导致sql错误,如in(1,2,)这样。该参数可选。
- close: foreach代码的关闭符号,一般是)和open="("合用。常用在in(),values()时。该参数可选。
- collection: 要做foreach的对象,作为入参时,List对象默认用"list"代替作为键,数组对象有"array"代替作为键,Map对象没有默认的键。当然在作为入参时可以使用@Param("keyName")来设置键,设置keyName后,list,array将会失效。 除了入参这种情况外,还有一种作为参数对象的某个字段的时候。举个例子:如果User有属性List ids。入参是User对象,那么这个collection = "ids".如果User有属性Ids ids;其中Ids是个对象,Ids有个属性List id;入参是User对象,那么collection = "ids.id"
在使用foreach的时候最关键的也是最容易出错的就是collection属性,该属性是必须指定的,但是在不同情况下,该属性的值是不一样的,主要有一下3种情况:
- 如果传入的是单参数且参数类型是一个List的时候,collection属性值为list .
- 如果传入的是单参数且参数类型是一个array数组的时候,collection的属性值为array .
- 如果传入的参数是多个的时候,我们就需要把它们封装成一个Map了,当然单参数也可以封装成map,实际上如果你在传入参数的时候,在MyBatis里面也是会把它封装成一个Map的,map的key就是参数名,所以这个时候collection属性值就是传入的List或array对象在自己封装的map里面的key.
三 缓存
mybatis的缓存一共分为一级缓存和二级缓存,下面分别来进行一个介绍。
3.1 一级缓存
Mybatis的一级缓存作用域是session,在同一个session中,如果执行相同的SQL(相同的语句和参数),mybatis就不会执行sql,而是直接从缓存中命中返回。
原理:mybatis执行查询时会去缓存区命中,如果命中的话则直接返回,没有命中则返回sql,从数据库中查询。
在mybatis中一级缓存是默认开启的,并且一直无法关闭。注意要满足一级缓存一定有两个条件!
- 同一个session
- 相同的sql语句
3.2 二级缓存
mybatis二级缓存的作用域是一个mapper的namespace,同一个namespace中sql可以从缓存中命中。
开启二级缓存:
-
在namespace中添加(也可以在全局配置文件中进行配置。)
-
Java实体类必须实现序列化
四 高级结果映射ResultMap
4.1 一对一映射
@Data
public class Order {
private int id;
private Integer userId;
private String number;
private Date createTime;
private String note;
private User user; //一个订单只能有一个用户下单
}
在mapper.xml中这样进行进行配置
<!-- 一个订单对应一个用户-->
<!-- property:对象的属性 column:数据库中具体的列-->
<resultMap id="OrderUserResultMap" type="order">
<id property="id" column="id"></id>
<result property="createTime" column="create_time"></result>
<result property="note" column="note"></result>
<result property="userId" column="user_id"></result>
<result property="number" column="number"></result>
<!-- assocition: 配置一对一的属性-->
<!-- property: order里面的属性名-->
<!-- javaType: 属性的类型-->
<association property="user" javaType="user">
<id property="id" column="id"></id>
<result property="name" column="name"/>
<result property="age" column="age"/>
<result property="description" column="description"/>
</association>
</resultMap>
4.2 一对多映射
@Data
public class User implements Serializable {
private int id;
private String name;
private int age;
private String description;
private List<Order> orders; //一个用户有多个订单
}
在mapper.xml中进行的配置
<!-- 一个用户对应多个订单-->
<resultMap id="UserOrderResultMap" type="user">
<id property="id" column="id"/>
<result property="description" column="description"/>
<result property="name" column="name"/>
<result property="age" column="age"/>
<!-- 开始配置一对多的关系-->
<!-- property:实体类中集合的名称-->
<!-- javatype:集合的类型-->
<!-- ofType:集合中存放的具体类型-->
<collection property="orders" javaType="list" ofType="order">
<id property="id" column="id"/>
<result property="number" column="number"/>
<result property="createTime" column="create_time"/>
<result property="note" column="node"/>
</collection>
</resultMap>
五 常见面试题介绍
5.1 Mybatis中#{} 和 ${}的区别是什么?
#{}是编译预处理,${}是直接进行字符串替换。
-
Mybatis 在处理 #{} 时,会将 SQL 中的 #{} 替换为 ? 号,调⽤ PreparedStatement 的 set ⽅法来赋值;使⽤ #{} 可以有效的防⽌ SQL 注⼊,提⾼系统安全性;
-
MyBatis 在处理 时 , 就 是 把 {} 替换成变量的值。
5.2 Mybatis有几种分页方式?
- 数组分页
- SQL分页
- 拦截器分页
- RowBounds分页
5.3 Mybatis是如何进行分页的?分页插件的原理是什么?
MyBatis 使⽤ RowBounds 对象进⾏分⻚,它是针对 ResultSet 结果集执⾏的内存分⻚,⽽⾮物理分⻚。可以在 SQL 内直接书写带有物理分⻚的参数来完成物理分⻚功能,也可以使⽤分⻚插件来完成物理分⻚。
分⻚插件的基本原理是使⽤ MyBatis 提供的插件接⼝,实现⾃定义插件,在插件的拦截⽅法内拦截待执⾏的 SQL,然后重写 SQL,根据 dialect ⽅⾔,添加对应的物理分⻚语句和物理分⻚参数。
5.4 Mybatis逻辑分页和物理分页的区别是什么?
-
物理分⻚速度上并不⼀定快于逻辑分⻚,逻辑分⻚速度上也并不⼀定快于物理分⻚。
-
物理分⻚总是优于逻辑分⻚:没有必要将属于数据库端的压⼒加到应⽤端来,就算速度上存在优势,然⽽其它 性能上的优点⾜以弥补这个缺点。
5.5 Mybatis延迟加载的原理是什么?
Mybatis 仅⽀持 association 关联对象和 collection 关联集合对象的延迟加载,association 指的就是⼀对⼀, collection 指的就是⼀对多查询。在MyBatis配置⽂件中,可以配置是否启⽤延迟加载 lazyLoadingEnabled=true|false。 ]
它的原理是:
使⽤ CGLIB 创建⽬标对象的代理对象,当调⽤⽬标⽅法时,进⼊拦截器⽅法,⽐如调⽤ a.getB().getName(),拦截器 invoke() ⽅法发现 a.getB() 是 null 值,那么就会单独发送事先保存好的查询关联 B 对 象的 SQL,把 B 查询上来,然后调⽤ a.setB(b),于是 a 的对象 b 属性就有值了,接着完成 a.getB().getName() ⽅ 法的调⽤。这就是延迟加载的基本原理。
5.6 说一下mybatis的一级缓存和二级缓存?
一级缓存的范围是一个session,当同一个session,并且执行相同的sql语句的时候,mybatis不会去查询数据库,而是直接从缓存中返回,默认一直打开。
二级缓存的范围是一个namespace命名空间,默认不打开二级缓存,要开启二级缓存可以在主配置文件中配置,或者在xml文件中配置,并且对应的Java实体类必须要实现Serializable序列化接口。
缓存更新:当某一个作用域【session或者namespace】进行了crud操作以后,默认该作用域下的所有select操作都将被清空缓存。
5.7 Mybatis有哪些执行器?
Mybatis有三种基本的执行器。
- SimpleExecutor:每执⾏⼀次 update 或 select,就开启⼀个 Statement 对象,⽤完⽴刻关闭 Statement 对 象;
- ReuseExecutor:执⾏ update 或 select,以 SQL 作为 key 查找 Statement 对象,存在就使⽤,不存在就创 建,⽤完后,不关闭 Statement 对象,⽽是放置于 Map 内,供下⼀次使⽤。简⾔之,就是重复使⽤ Statement 对象;
- BatchExecutor:执⾏ update(没有 select,JDBC 批处理不⽀持select),将所有 SQL 都添加到批处理中 (addBatch()),等待统⼀执⾏(executeBatch()),它缓存了多个 Statement 对象,每个 Statement对 象 都是 addBatch() 完毕后,等待逐⼀执⾏ executeBatch() 批处理。与 JDBC 批处理相同。