MyBatis基本配置与介绍

1,034 阅读11分钟

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项目

创建好项目以后我们就要进行几步操作了。

  1. 添加依赖包
<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是为了进行测试。

  1. 在resource中创建两个配置文件

image.png

其中,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>
  1. 编写数据库表对应的实体类,方便进行映射
@AllArgsConstructor
@NoArgsConstructor
@Data
public class User {
  private int id;
  private String name;
  private int age;
  private String description;
}

是不是很简单,我们不用再写get和set方法了~ 这就是引入了lombok的好处了~

  1. 编写一个mapper接口
public interface UserMapper {
  @Select("select * from user")
  List<User> findAllUser();
}

整体项目结构如下:

image.png

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);
  }
}

测试结果如下

image.png

我们也可以使用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接口一个目录下!

image.png

至此,基本配置成功!

二. 动态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>

执行结果如下:

image.png 可以看见,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>

我们给条件中添加一个年龄

image.png 查询结果如下:

image.png 可见,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元素也知道如何将他们去除。

image.png 可以明显看出where的作用:
1. 在sql中根据我们的条件自动添加 where 单词。
2. 会自动去除where后出现的第一个and,使其满足我们的条件。

set标签和where的作用类似,只是set是作用于修改语句。

  1. 自动在修改条件的后面添加set
  2. 去除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>

我们执行一下,结果如下:

image.png

在做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中一级缓存是默认开启的,并且一直无法关闭。注意要满足一级缓存一定有两个条件!

  1. 同一个session
  2. 相同的sql语句

3.2 二级缓存

mybatis二级缓存的作用域是一个mapper的namespace,同一个namespace中sql可以从缓存中命中。
开启二级缓存:

  1. 在namespace中添加(也可以在全局配置文件中进行配置。) image.png

  2. 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中#{} 和 ${}的区别是什么?

#{}是编译预处理,${}是直接进行字符串替换。

  1. Mybatis 在处理 #{} 时,会将 SQL 中的 #{} 替换为 ? 号,调⽤ PreparedStatement 的 set ⽅法来赋值;使⽤ #{} 可以有效的防⽌ SQL 注⼊,提⾼系统安全性;

  2. MyBatis 在处理 时 , 就 是 把 {} 替换成变量的值。

5.2 Mybatis有几种分页方式?

  1. 数组分页
  2. SQL分页
  3. 拦截器分页
  4. RowBounds分页

5.3 Mybatis是如何进行分页的?分页插件的原理是什么?

MyBatis 使⽤ RowBounds 对象进⾏分⻚,它是针对 ResultSet 结果集执⾏的内存分⻚,⽽⾮物理分⻚。可以在 SQL 内直接书写带有物理分⻚的参数来完成物理分⻚功能,也可以使⽤分⻚插件来完成物理分⻚。

分⻚插件的基本原理是使⽤ MyBatis 提供的插件接⼝,实现⾃定义插件,在插件的拦截⽅法内拦截待执⾏的 SQL,然后重写 SQL,根据 dialect ⽅⾔,添加对应的物理分⻚语句和物理分⻚参数。

5.4 Mybatis逻辑分页和物理分页的区别是什么?

  1. 物理分⻚速度上并不⼀定快于逻辑分⻚,逻辑分⻚速度上也并不⼀定快于物理分⻚。

  2. 物理分⻚总是优于逻辑分⻚:没有必要将属于数据库端的压⼒加到应⽤端来,就算速度上存在优势,然⽽其它 性能上的优点⾜以弥补这个缺点。

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有三种基本的执行器。

  1. SimpleExecutor:每执⾏⼀次 update 或 select,就开启⼀个 Statement 对象,⽤完⽴刻关闭 Statement 对 象;
  2. ReuseExecutor:执⾏ update 或 select,以 SQL 作为 key 查找 Statement 对象,存在就使⽤,不存在就创 建,⽤完后,不关闭 Statement 对象,⽽是放置于 Map 内,供下⼀次使⽤。简⾔之,就是重复使⽤ Statement 对象;
  3. BatchExecutor:执⾏ update(没有 select,JDBC 批处理不⽀持select),将所有 SQL 都添加到批处理中 (addBatch()),等待统⼀执⾏(executeBatch()),它缓存了多个 Statement 对象,每个 Statement对 象 都是 addBatch() 完毕后,等待逐⼀执⾏ executeBatch() 批处理。与 JDBC 批处理相同。