Mybatis

目录

一、介绍

二、如何编写Mybatis

1、引入依赖

2、编写 Mybatis 配置文件

3、编写 MyBatis 工具类

4、编写实体类、接口

5、编写实体类与数据库表之间的映射文件

6、编写测试类

三、主配置文件(mybatis-config.xml)介绍

1、properties 属性

 2、typeAliases 类型别名

四、映射文件(xxxMapper.xml)介绍

1、insert 标签

1.1 返回主键值(selectKey 标签)

 1.2 批量插入

 2、delete 标签

2.1 批量删除

3、select 标签

3.1 单条查询和集合查询

3.2 模糊查询

 3.3 批量查询

 3.4 多参数接收 

3.5 resultMap标签 

3.5.1 标签介绍

3.5.2 一对多关联关系

3.5.3 多对一关联关系

 4、 动态 SQL *

4.1 if 标签

4.2 where 标签 

4.3 set 标签

4.4 trim 标签

4.5 choose、when、otherwise 标签

4.6 sql 标签 

4.7 bind 标签

五、#{}和${}的区别 *

六、动态表名

七、注解实现CRUD

1、添加

1.1 返回主键值

2、批量添加

 3、更新

4、删除

5、批量删除

八、注解实现查询

1、查询单个

2、查询多个

3、 模糊查询

 4、统计查询

5、返回Map查询结果

6、 分页查询

6.1 手动分页

 6.2 插件分页

7、一对多关联关系

 8、多对一关联关系

 九、缓存

1、一级缓存

1.1、一级缓存失效

2、二级缓存


一、介绍

Mybatis 一款可以自定义 SQL、存储过程和高级映射的持久层框架。

使用简单的 XML 或注解来配置和映射原生信息,将接口和 Java 的 实体类映射成数据库中的记录。

优点

简单易学

灵活:sql 写在 xml 里,便于统一管理和优化。通过 sql 语句可以满足操作数据库的所有需求。

解除 sql 与程序代码的耦合:提供DAO层,将业务逻辑和数据访问逻辑分离。

提供 xml 标签,支持编写动态 sql

二、如何编写Mybatis

注意:需创建Maven项目,搭建好一个数据库

1、引入依赖

导入 MyBatis 相关 jar 包,导入到 pom.xml 文件里面

2、编写 Mybatis 配置文件

resources 目录下创建 mybatis-config.xml 文件

<?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>
    <!-- 指定项目的最终运行环境 -->
    <environments default="develop">
        <!-- 具体的运行环境,比开发环境、测试环境、生产环境 -->
        <environment id="develop">
            <!-- 指定数据库的事务管理方式,在这里指定为一个固定值叫 JDBC 即可,表示使用 JDBC 的事务来处理请求 -->
            <transactionManager type="JDBC" />
            <!-- 定义数据源,POOLED 表示使用连接池技术 -->
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://192.168.72.130:3306/mybatis?useSSL=false&amp;characterEncoding=UTF8&amp;timeZone=UTC"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
        <!-- 定义的测试环境 -->
        <environment id="test">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://192.168.72.130:3306/test?useSSL=false&amp;characterEncoding=UTF8&amp;timeZone=UTC"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>
	<!-- 定义实现类与表之间的映射 -->
    <mappers>
        <!-- 通过 resource 属性来指定映射mapper文件的路径,注意:要使用/线来进行分隔,而不是使用 . 来分隔 -->
        <mapper resource="mapper/DepartMapper.xml"/>
    </mappers>
</configuration>

3、编写 MyBatis 工具类

创建 utils 目录,新建 MybatisUtils.class 文件

/**
 * 读取 mybatis-config.xml 配置文件(文件包含连接数据库,实体类与表的映射)
 */

public class MybatiaUtil {

    private static final SqlSessionFactory sqlSessionFactory;

    static {
        try {
            //1、读取配置文件
            InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
            //2、通过 SqlSessionFactoryBuilder 对象创建 SqlSessionFactory
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }

    public static SqlSession getSession() {
        //3、调用 openSession 方法 创建 SqlSession 对象(封装了所有关于mysql操作的方法)
        return sqlSessionFactory.openSession();
    }
}

4、编写实体类、接口

实体类略

接口类我通常习惯存放在 java.mapper 目录下,例如这里的 DepartMapper 接口。接口类中编写具体的方法。

public interface DepartMapper {
    // 添加
    int insert(Depart depart);
    // 更新
    int update(Depart depart);
    // 删除
    int delete(Integer id);
    // 单条查询
    Depart get(Integer id);
    // 查询全部
    List<Depart> list();
}

5、编写实体类与数据库表之间的映射文件

在 resource 目录下新建 mapper 包,用于存放映射文件。编写 DepartMapper.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">

<!-- 注意:该映射文件的作用就是取代 DepartDao 接口的实现类 DepartDaoImpl -->

<!-- namespace 属性的值为要映射的接口的完整路径 -->
<mapper namespace="mapper.DepartMapper">

    <!-- 添加
     insert 标签用于定义添加方法
     id 属性的值与接口中的添加方法名一致,方法名必须唯一(即该方法不允许重写重载)
     parameterType 属性用于指定参数类型,需要完整路径(包名+类名)
     #{ } 表示占位符
     -->
    <insert id="insert" parameterType="entity.Depart">
        insert into tb_depart(name) values(#{name})
    </insert>

    <!-- 更新 -->
    <update id="update" parameterType="entity.Depart">
        update tb_depart set name = #{name} where id = #{id}
    </update>

    <!-- 删除 -->
    <delete id="delete" parameterType="entity.Depart">
        delete from tb_depart where id = #{id}
    </delete>

    <!-- 单条查询
     resultType 属性的值为返回值类型
     -->
    <select id="get" parameterType="int" resultType="entity.Depart">
        select id, name from tb_depart where id = #{id}
    </select>

    <!-- 查询全部 -->
    <select id="list" resultType="entity.Depart">
        select id, name from tb_depart
    </select>
</mapper>

注意:要将该 DepartMapper.xml 注册到 mybatis-config.xml 文件中,此后每编写一个Mapper.xm文件都要注册一次!!!

6、编写测试类

public class MybatisTest {
    public static void main(String[] args) {
        //1、获取SqlSession对象
        SqlSession session = MybatiaUtil.getSession();
        //2、获取接口对象
        DepartMapper mapper = session.getMapper(DepartMapper.class);

        //添加
        mapper.insert(Depart.builder().name("财务部").build());
        //提交事务
        session.commit();

        //更新
        mapper.update(Depart.builder().id(2).name("侦探部").build());
        session.commit();

        //删除
        mapper.delete(3);
        session.commit();

        //单条查询
        System.out.println(mapper.get(1));

        //全部查询
        System.out.println(mapper.list());
    }
}

 

三、主配置文件(mybatis-config.xml)介绍

注意:为了不产生冲突,这里我重新创建了新的 Mybatis 项目,过程与上面一致

1、properties 属性

用于引入外部的属性配置文件,一般用于把连接数据库信息的内容放到这个文件中,然后通过此标签引入到 Mybatis 的主配置文件中来。

在 resources 目录下新建一个叫 jdbc.properties 文件(文件名称可以任意)

jdbc.driver=com.mysql.cj.jdbc.Driver
jdbc.url=jdbc:mysql://localhost:3306/mybatis?useSSL=false&characterEncoding=UTF8&timeZone=UTC
jdbc.username=root
jdbc.password=123456

在 mybatis-config.xml 文件中使用 properties 标签来引用

<?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>
    <!-- 引入外部的 properties 配置文件 -->
    <properties resource="jdbc.properties"/>
    <environments default="develop">
        <environment id="develop">
            <transactionManager type="JDBC" />
            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.driver}"/>
                <property name="url" value="${jdbc.url}"/>
                <property name="username" value="${jdbc.username}"/>
                <property name="password" value="${jdbc.password}"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="mapper/EmployeeMapper.xml " />
    </mappers>
</configuration>

引入后通过 ${key} 的方式来使用,这里的 key 是 properties 文件中定义的等号左边的内容。

 Mybatis加载配置的优先级:

通过方法参数传递的属性具有最高优先级,resource/url 属性中指定的配置文件次之,最低优先级的则是 properties 元素中指定的属性。

 

 2、typeAliases 类型别名

typeAlias 子标签用于为 Java 实体类设置一个缩写名称,减少代码冗余量

<configuration>
    <properties resource="jdbc.properties"/>
    
    <typeAliases>
        <!-- 通过 typeAlias 子标签来定义每一个别名,属性 type 的值是具体的类型,则 alias 是指定这个类型的别名 -->
        <typeAlias type="com.entity.Employee" alias="Employee"/>
    </typeAliases>
    
    <environments default="develop">
        <environment id="develop">
            <transactionManager type="JDBC" />
            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.driver}"/>
                <property name="url" value="${jdbc.url}"/>
                <property name="username" value="${jdbc.username}"/>
                <property name="password" value="${jdbc.password}"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="mapper/EmployeeMapper.xml " />
    </mappers>
</configuration>

但是有一个新问题产生了:如果我们的项目中类很多,那么我们就需要在这个地方定义很多的别名。这时,使用 package 子标签来进行修改

<configuration>
    <properties resource="jdbc.properties"/>
    <typeAliases>
        <!-- 通过 package 子标签来进行批量别名指定,name 属性的值就是实体类的包名 -->
        <package name="com.entity"/>
    </typeAliases>
    <environments default="develop">
        <environment id="develop">
            <transactionManager type="JDBC" />
            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.driver}"/>
                <property name="url" value="${jdbc.url}"/>
                <property name="username" value="${jdbc.username}"/>
                <property name="password" value="${jdbc.password}"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="mapper/EmployeeMapper.xml" />
    </mappers>
</configuration>

 

四、映射文件(xxxMapper.xml)介绍

1、insert 标签

1.1 返回主键值(selectKey 标签)

有时候新增一条数据不仅仅要知道是否插入成功,因为后面的逻辑可能还需要这条新增数据的主键,这时候可以使用 selectKey 标签获取自增主键,从而避免再次查询数据库。另外,有些业务需要自定义数据表的主键,这个时候也可以使用 selectKey 标签来实现,它可以随意的设置生成主键的方式。

这里介绍如何获取自增主键,有两种方法

  • 第一种:在 insert 标签上指定 key 相关的属性。修改 EmployeeMapper.xml 文件中的 insert 标签
<insert id="add" parameterType="Employee" keyProperty="id" keyColumn="id" useGeneratedKeys="true">
    insert into tb_employee(name,age,did) values (#{name}, #{age}, #{did})
</insert>
  • keyProperty:实体对象中的哪一个属性是主键

  • keyColumn:数据库表中的主键名称

  • useGeneratedKeys:是否使用生成的主键值,默认为 false 表示不使用,我们在这里要设置为true 表示使用

  •  第二种:在 insert 标签中使用 selectKey 子标签来实现。修改 insert 标签
    <insert id="add" parameterType="Employee">
        <selectKey keyColumn="id" keyProperty="id" order="AFTER" resultType="int">
            SELECT LAST_INSERT_ID()
        </selectKey>
        insert into tb_employee(name,age,did) values (#{name}, #{age}, #{did})
    </insert>
  1. keyProperty:实体类中主键名称

  2. keyColumn:表中的主键名称

  3. resultType:实体类中主键的类型

  4. order:表示返回值的时机是在执行完插入语句之前(Before)还是之后(After)

 selectKey 标签的作用:首先在数据库插入对象,然后执行 select LAST_INSERT_ID()获取数据库里自动生成的主键,最后赋值给对象的对应属性。

 

 1.2 批量插入

需要批量插入数据的需求,如何在 MyBatis 中实现?

先在 EmployeeMapper 接口中添加一个用于演示批量添加的方法

public interface EmployeeMapper {
    //单条添加
    int add(Employee employee);
    // 批量添加
    int batchAdd(List<Employee> employees);
    
    //...... 省略

在 EmployeeMapper.xml 文件中添加如下的配置

 <!-- 批量添加 -->
 <!-- insert into tb_employee(name,age,did)  values (#{name}, #{age}, #{did}),(#{name}, #{age}, #{did}),(#{name}, #{age}, #{did}) -->
 <insert id="batchAdd">
     insert into tb_employee(name,age,did)  values
     <foreach collection="list" item="employee" separator=",">
         (#{employee.name}, #{employee.age}, #{employee.did})
     </foreach>
 </insert>

使用 foreach 子标签来循环生成我们需要的SQL语句,在这个标签中属性说明如下:

  • collection:它是我们在做批量插入时的参数的集合类型(即 batchAdd(List<Employee> employees)中参数的类型

  • item:循环过程中的临时变量名称,可以随便取

  • separator:它的作用是在生成的语句之间的分隔符是什么

测试

@Test
    public void testBatchAdd() {
        List<Employee> employees = new ArrayList<>();
        employees.add(Employee.builder().name("小华").age(18).did(1).build());
        employees.add(Employee.builder().name("小红").age(19).did(1).build());
        employees.add(Employee.builder().name("小张").age(20).did(1).build());
        employees.add(Employee.builder().name("小刘").age(21).did(1).build());
        mapper.batchAdd(employees);
    }

 

 2、delete 标签

2.1 批量删除

在 EmployeeMapper 接口文件中添加批量删除方法

    //单条删除
    int remove(Integer id);

    // 批量删除方法
    int batchRemove(List<Integer> ids);

在 EmployeeMapper.xml 文件中添加如下的内容来实现批量的删除功能

    <!-- 批量删除 -->
    <!-- delete from 表名 where id in (1,2,3,4,5) -->
    <delete id="batchRemove">
        delete from tb_employee where id in
        <foreach collection="list" item="id" open="(" close=")" separator=",">
            #{id}
        </foreach>
    </delete>
  • open:开始的符号

  • close:结束的符号  

 

3、select 标签

3.1 单条查询和集合查询

<!-- 单条查询
 parameterType 属性的值为指定的参数类型
 resultType 属性的值为返回值类型
-->
<select id="get" parameterType="int" resultType="entity.Depart">
    select id, name from tb_depart where id = #{id}
</select>

<!-- 查询全部 -->
<select id="list" resultType="entity.Depart">
    select id, name from tb_depart
</select>

 

3.2 模糊查询

模糊查询方法的映射配置

 <select id="findLikeName">
        <!-- 第一种:使用concat()函数拼接"%" -->
        select id,name,age,did from tb_employee where name like concat('%', #{name}, '%')
        <!-- 第二种:测试时输入"%" -->
        select id,name,age,did from tb_employee where name like #{name}
 </select>

测试

@Test
    public void testFindLikeName() {
        //第一种
        List<Employee> employees = mapper.findLikeName("羽");

        //第二种
        List<Employee> employees = mapper.findLikeName("%羽%");

        System.out.println(employees);
    }

 

 3.3 批量查询

其实就是 in 查询,例如 select * from 表 where id in (1,2,3,4)

批量查询方法的映射配置

<select id="findByIds">
    select id,name,age,did from tb_employee where id in
    <foreach collection="list" item="id" open="(" close=")" separator=",">
        #{id}
    </foreach>
</select>

测试

@Test
    public void testFindByIds() {
        List<Integer> ids = Arrays.asList(1, 2, 3, 4);
        List<Employee> employees = mapper.findByIds(ids);
        System.out.println(employees);
    }

 

 3.4 多参数接收 

在实际开发中,经常会传递多个参数。

  • 第一种:把需要的参数封装到一个 Map 结构的集合中,然后把这个集合作为参数传递给 MyBatis 相关的方法来进行处理

重新定义一个多参数查询的方法

public interface EmployeeMapper {
    // 使用Map类型作为多参数查询的参数
    List<Employee> findByNameAndAge2(Map<String, Object> map);
}

 添加这个方法的映射配置

<mapper namespace="com.mapper.EmployeeMapper">
    <select id="findByNameAndAge2">
        select id,name,age,did from tb_employee where name=#{name} and age=#{age}
    </select>
</mapper>

添加测试方法

@Test
    public void testFindByNameAndAge2() {
        Map<String, Object> map = new HashMap<>();
        map.put("name", "关羽");
        map.put("age", 35);
        List<Employee> employees = mapper.findByNameAndAge2(map);
        System.out.println(employees);
    }
  • 第二种:使用注解 @Param 
public interface EmployeeMapper {
    List<Employee> findByNameAndAge3(@Param("name") String name, @Param("age") Integer age);
}

 映射配置与上文相同

测试

@Test
    public void testFindByNameAndAge3() {
        List<Employee> employees = mapper.findByNameAndAge3("关羽", 35);
        System.out.println(employees);
    }

3.5 resultMap标签 

3.5.1 标签介绍

在前面使用过一个叫 resultType 属性来得到查询返回的结果。但当返回的字段名称和实体的属性名称不一致时,使用这个属性就不能成功映射。为了解决这个问题,我们需要使用 resultMap 的标签来实现。

假设我们将实体类中的 name 属性修改为 names

public class Employee {
    private Integer id;
    private String names;   // 将原来的 name 修改为 names
    private Integer age;
    private Integer did;
}
  • 第一种:保持原来的配置方法,即使用 resultType 属性
<select id="getById" resultType="employee">
    select id,name,age,did from tb_employee where id=#{id}
</select>

然后在 sql 语句中使用别名

<select id="getById" resultType="employee">
    select id,name as names,age,did from tb_employee where id=#{id}
</select>
  • 第二种:使用 resultMap 标签
<resultMap id="employeeMap" type="employee">
   <id property="id" column="id" />
   <result property="name" column="names"/>
   <result property="age" column="ages"/>
   <result property="did" column="did"/>
</resultMap>
<select id="getById" resultMap="employeeMap">
    select id,name as names,age ages,did from tb_employee where id=#{id}
</select>
  • id:定义这个 resultMap 的引用名称,需要唯一,用于select中的 resultMap 属性引用
  •  type:返回数据的类型别名或完整路径
  • 在子标签中,id 子标签用于定义主键,result 子标签用于定义除了主键以外的其他属性
  • 所有的 property 属性都是指向实体对象中的属性名称,所有 column 属性所指向的是sql语句中写书的字段名称(如果有别名就是别名)

 

3.5.2 一对多关联关系

创建一个叫 Depart 的实体类和对应的表,用于存放部门信息。一对多关系是从部门来查询员工,即一个部门有多个员工,因此我们需要在部门的实体为中定义员工的列表。

public class Depart {
    private Integer id;
    private String name;
    // 一个部门对应多个员工
    private List<Employee> employees;
}

编写 DepartMapper.java 接口

public interface DepartMapper {
    Depart getById(Integer id);
}

编写 DepartMapper.xml 文件

<mapper namespace="com.openlab.mapper.DepartMapper">
    <resultMap id="departResultMap" type="Depart">
        <id property="id" column="id"/>
        <result property="name" column="name"/>
        <!-- 一对多 -->
        <collection property="employees" ofType="Employee">
            <id property="id" column="eid" />
            <result property="name" column="ename" />
            <result property="age" column="age" />
            <result property="did" column="did"/>
        </collection>
    </resultMap>
    
    <select id="getById" resultMap="departResultMap">
        select d.id,d.name, e.id eid, e.name ename, age, did
        from tb_depart d inner join tb_employee e on d.id=e.did
        where d.id=#{id}
    </select>

</mapper>

collection 标签用于定义一对多的关联关系,它的属性说明如下:

  • property:这个属性的值对应在 Depart.java 实体类中的 List<Employee> employees 中的 employees 

  • ofType:这个属性的值对应上面属性中集合中的泛型的实际类型。

collection 标签中的子标签说明:

  • id:用于指定被关联对象(一对多的多)的主键映射,property 属性对应实体类中的主键名,而 column 属性对应 sql 中查询的字段名

  • result:用于指定被关联对象的其他属性和字段的对应关系,property指向的就是实体类中属性名,而 column 指向的就是 sql 中查询的字段名。

 

3.5.3 多对一关联关系

多对一关系是指多个员工属于一个部门,从员工信息来查询部门信息,修改 Employee.java 实体类,在这个实体类中定义员工和部门对对应关系。

public class Employee {
    private Integer id;
    private String name;
    private Integer age;
    private Integer did;
    private Depart depart;
}

修改 EmployeeMapper.java 接口

public interface EmployeeMapper {
    Employee getId(Integer id);
}

修改 EmployeeMapper.xml 文件

<mapper namespace="com.openlab.mapper.EmployeeMapper">

    <resultMap id="employeeResultMap" type="Employee">
        <id property="id" column="id"/>
        <result property="name" column="name"/>
        <result property="age" column="age"/>
        <result property="did" column="did"/>
        <!-- 多对一 -->
        <association property="depart" column="did" javaType="Depart">
            <id property="id" column="did"/>
            <result property="name" column="dname"/>
        </association>
    </resultMap>
    <select id="getId" resultMap="employeeResultMap">
        select e.id,e.name,age,d.id did,d.name dname
        from tb_employee e,tb_depart d
        where e.did=d.id and e.id=#{id}
    </select>
</mapper>

多对一关联关系配置需要使用 association 标签来指定,这个属性的属性和子标签说明如下:

  • property:用于指定被关联对象(多对一的一)中所对应的(多对一的多)属性名称

  • column:指定的是被关联对象的主键(相当于外键)

  • javaType:它是多对一关联关系返回的类型。注意,在一对多中,这里是 ofType。

  • id标签:用于指定被关联对象的主键

  • result标签:用于指定被关联对象的除了主键以外的属性映射

 4、 动态 SQL *

在 MyBatis 中,最有魅力所在就是支持动态 SQL 语句。在 MyBatis 中支持 if、where、set、trim、choice、sql、bind等标签,来实现不同场景下的动态 SQL 语句。

Mybatis 中,一共有 9 种动态 SQL 标签。

4.1 if 标签

我们在做查询时,会根据不同的条件来进行查询。此时可能会传递多个参数,而不同参数有可能有值,也有可以没有值。为了解决在不同条件下执行不同的 SQL 语句,可以使用 if 来判断。

定义一个根据姓名和年龄查询的方法,这里就不放代码了

编写映射文件

<mapper namespace="com.mapper.EmployeeMapper">
    <select id="findByNameAndAge" resultType="Employee">
        select id,name,age
        from tb_employee
        where 1=1 <!-- 因为不确定条件存不存在,解决条件为空时where失去存在的意义 -->
        <if test="name!=null and name!=''">
            and name like #{name}
        </if>
        <if test="age!=null and age > 0">
            and age=#{age}
        </if>
    </select>
</mapper>

4.2 where 标签 

在上例中,当我们的查询条件都没有值时,我们使用到了一个硬写的条件 where 1=1。我们不希望在 sql 语句中出现这句话,而是希望当只有在一个以上的 < if > 标签有值时才去插入“WHERE”子句到SQL中。

修改映射文件

<mapper namespace="com.mapper.EmployeeMapper">
    <select id="findByNameAndAge" resultType="Employee">
        select id,name,age
        from tb_employee
        <where>
            <if test="name!=null and name!=''">
                and name like #{name}
            </if>
            <if test="age!=null and age > 0">
                and age=#{age}
            </if>
        </where>
    </select>
</mapper>

 注意where 标签会自动去除首个条件中的 AND,这样就可以不用去管第一个条件是否为空,可以直接给所有 if 标签中的条件都加上 AND,但是如果前边已经有一个存在的条件,那接下来所有的 if 中的条件开头 AND 是必须要加的 。

4.3 set 标签

进行数据更新时,字段有值就执行更新,如果没值则该字段的内容不会更新。此时就需要使用 set 标签现配合 if 标签来实现。

<update id="updateEmployee">
    update tb_employee
    <set>
        <if test="name!=null and name!=''">
            name=#{name},
        </if>
        <if test="age!=null and age>0">
            age=#{age},
        </if>
        <if test="did!=null and did>0">
            did=#{did}
        </if>
    </set>
    where id=#{id}
</update>

set 标签会动态前置 SET 关键字,同时也会删除无关的逗号

4.4 trim 标签

trim 标签是一个格式化的标记,可以完成 set 或者是 where 标签的功能。

一般用于去除 sql 语句中多余的 andor 、逗号,或者给 sql 语句前拼接 “where”“set” 以及 “values(”  等前缀,或者添加 “)“ 等后缀,可用于选择性插入、更新、删除或者条件查询等操作。

与 where 标签等价的自定义 trim 标签为:

<trim prefix="WHERE" prefixOverrides="AND | OR ">
...
</trim>

与 set 标签等价的自定义 trim 标签为:

<trim prefix="SET" suffixOverrides=",">
...
</trim>

如果我们将 7.2 的例子中 if 标签内的 and 全部去掉,程序会报 SQL 语法错误,因为当两个条件都存在时,需要使用 and 或者是 or 来进行连接才行,而现在缺少这个连接关键字。

下面使用 trim 标签,修改映射文件

<mapper namespace="com.mapper.EmployeeMapper">
    <select id="findByNameAndAge" resultType="Employee">
        select id,name,age from tb_employee
        <trim prefix="where" suffixOverrides="and ">
            <if test="name!=null and name!=''">
                name like #{name} and
            </if>
            <if test="age!=null and age > 0">
                age=#{age} and
            </if>
        </trim>
    </select>
</mapper>
  • prefix:用于指定添加的前缀

  • prefixOverrides:去除 sql 语句前面的关键字或者字符

  • suffix:用于指定添加的后缀

  • suffixOverrides:去除 sql 语句后面的关键字或者字符

4.5 choose、when、otherwise 标签

有些时候,不想用到所有的条件语句,而只想从中择其一二。针对这种情况,MyBatis 提供了 choose 标签,它有点像 Java 中的 switch 语句。

将 7.3 的例子改成——如果提供了 “name” 就按 “name” 查找,如果提供了 “age” 就按 “age” 查找,若两者都没有提供,就返回所有符合条件的 Employee 员工信息。

<select id="findUseChoose" resultType="Employee">
    select id,name,age,did from tb_employee
    <trim prefix="where" prefixOverrides="and ">
        <choose>
            <when test="name!=null and name!=''">
                name like #{name}
            </when>
            <when test="age!=null and age > 0">
                age=#{age}
            </when>
            <otherwise>
                1=1
            </otherwise>
        </choose>
    </trim>
</select>

4.6 sql 标签 

实际开发中会遇到许多相同的 SQL,比如根据某个条件筛选,这个筛选很多地方都能用到,我们可以将其抽取出来成为一个公用的部分,这样修改也方便,一旦出现了错误,只需要改这一处便能处处生效了。

当多种类型的查询语句的查询字段或者查询条件相同时,可以将其定义为常量,方便调用。

例如,我们将 select id,name,age from tb_employee 抽取出来成为公用的 SQL,在对应位置调用即可

<!-- 定义公共的 sql -->
<sql id="querySQL">select id,name,age from tb_employee</sql>

<select id="findByNameAndAge" resultType="Employee">
    <!-- 调用 -->
    <include refid="querySQL"/>

    <trim prefix="where" suffixOverrides="and ">
        <if test="name!=null and name!=''">
           name like #{name} and
        </if>
        <if test="age!=null and age > 0">
            age=#{age} and
        </if>
    </trim>
</select>

include 标签和 sql 标签是共生的,include 用于引用 sql 标签定义的常量

refid 这个属性就是指定 <sql> 标签中的 id 值(唯一标识)。

4.7 bind 标签

bind 标签可以从 OGNL 表达式中创建一个变量并将其绑定到上下文

比如,在模糊查询时,需要在查询条件前后拼接 % 号,除了使用前面讲过的两种接收外,还可以使用 bind 来进行处理。

<select id="findByNameAndAge" resultType="Employee">
    select id,name,age from tb_employee

    <bind name="likeCondition" value="name+'%'"/>

    <trim prefix="where" suffixOverrides="and ">
        <if test="name!=null and name!=''">
            name like #{likeCondition} and
        </if>
        <if test="age!=null and age > 0">
            age=#{age} and
        </if>
    </trim>
</select>

  注意:在 bind 标签中的 value 属性中,只需要指定参数的名称即可,无须添加 #{} 符号。

 测试

@Test
public void testFindByNameAndAge() {
    //原来的方式
    //String name="%小%";
    
    //使用 bind 标签后
    String name="小";
    Integer age = 21;

    SqlSession session = MyBatisUtil.openSession();
    EmployeeMapper mapper = session.getMapper(EmployeeMapper.class);
    List<Employee> employees = mapper.findByNameAndAge(name, age);
    session.close();
    employees.forEach(System.out::println);
}

五、#{}和${}的区别 *

  1. 使用 #{} 时,在编译成 SQL 语句是会使用占位符,而使用 ${} 时使用拼接的方式是直接把值放到这个位置。

  2. 使用 #{} 的方式在底层使用的是 PrepareStatement 来处理,而使用 ${} 是使用 Statement 来处理。

什么时候使用 #{},什么时候使用 ${}

答案:我们在写 SQL 语句的参数时,我们使用 #{},而在使用一些公共的变量时我们使用 ${}。比如我们在 EmployeeMapper.xml 文件中所有的 SQL 语句中都有相同的表名,那么我们就可以定义一个变量来存放表名,然后通过 ${} 来进行拼接。

 

六、动态表名

映射文件中用到的表叫 tb_employee,在很多个功能中都使用了,我们可以把这个表统一起来,让它以参数的形式来使用。  

可以理解为调用方法时需多传入一个参数——表名,即多参数接收

1、修改 EmployeeMapper.xml 映射文件中所有用到表名的地方

<select id="findByIds">
    <!--原来的:select id,name,age,did from tb_employee where id in-->
    select id,name,age,did from ${tableName} where id in
    <!-- 注意:tableName 使用的是${},这里的 collection 应与方法中的注释类型相同 -->
    <foreach collection="ids" item="id" open="(" close=")" separator=",">
        #{id}
    </foreach>
</select>

2、修改接口文件中的方法  

List<Employee> findByIds(@Param("ids") List<Integer> ids, @Param("tableName") String tableName);

3、测试 

@Test
    public void testFindByIds() {
        List<Integer> ids = Arrays.asList(1, 2, 3, 4);
        List<Employee> employees = mapper.findByIds(ids, "tb_employee");
        System.out.println(employees);
    }

 

七、注解实现CRUD

注解开发简单,不需要编写映射文件(xxxMapper.xml),可以直接在接口文件(.java)中实现 CRUD,即就是将之前 .xml 映射文件中的 CRUD 转为注解的形式

新建一个 Mybatis 项目,流程与上面一致。

在 mybatis-config.xml 文件中进行接口映射时,不再使用 mapper 标签的 resource 属性来指定,而是 class 属性

<mappers>
    <mapper class="com.mapper.DepartMapper"/>
</mappers>

注意

  1. 使用注解方式来进行时,需要使用 class 属性

  2. class 属性的值是通过 . 来进行分隔的

 

1、添加

修改 DepartMapper.java 接口,在 insert 方法上使用 @Insert 注解来实现添加功能

@Insert("insert into tb_depart(name) values(#{name})")
void insert(Depart depart);

测试

public class DepartMapperTest {
    SqlSession session;

    @Before
    public void init() {
        session = MybatisUtil.openSession();
    }


    @After
    public void destroy() {
        session.commit();
        session.close();
    }

    @Test
    public void testInsert() {
        DepartMapper mapper = session.getMapper(DepartMapper.class);
        mapper.insert(Depart.builder().name("人事部").build());
    }

}

 

1.1 返回主键值

希望在部门添加成功后,能够得到当前部门的编号,我们需要在 insert 方法上再添加一个叫 @SelectKey 的注解。效果与 selectKey 标签等同。

注意:

  • 需要前置注解才能生效:@Insert 或 @InsertProvider 或 @Update 或 @UpdateProvider,否则无效。
  • 如果指定了 @SelectKey 注解,那么 MyBatis 就会忽略掉由 @Options 注解所设置的生成主键

修改 DepartMapper.java 接口类中的 insert 方法

@Insert("insert into tb_depart(name) values(#{name})") 
@SelectKey(before = false, keyColumn = "id",
        keyProperty = "id",
        statement = "select last_insert_id()",
        resultType = Integer.class
)
void insert(Depart depart);
  • before属性: true 或 false 以指明 SQL 语句应被在插入语句的之前还是之后执行
  • keyColumn属性:数据库表中对应的主键名称
  • keyProperty属性:实体对象哪个属性是主键
  • statement属性:将会被执行的 SQL 字符串数组
  • resultType属性: keyProperty 的 Java 类型

2、批量添加

使用文本代码块(""".....""")进行编写。由于要使用 foreach 来遍历所有的部门集合列表,所以要在这个注解中使用 script 标签,只有这个标签中的其他标签才能够被正确的解析。

修改 DepartMapper.java 接口中的 insertBatch 方法

 @Insert("""
    <script>
        insert into tb_depart(name) values
        <foreach collection="list" item="depart" separator=",">
            (#{depart.name})
        </foreach>
    </script>
""")
void insertBatch(List<Depart> departs);

测试

@Test
public void testInsertBatch() {
    DepartMapper mapper = session.getMapper(DepartMapper.class);
    mapper.insertBatch(Arrays.asList(
        Depart.builder().name("财务部").build(),
        Depart.builder().name("拓展部").build(),
        Depart.builder().name("教学部").build()
    ));
}

 

 3、更新

修改 DepartMapper.java 接口中的 update 方法,在这个方法上使用 @Update 注解来实现更新功能

//不使用动态SQL
//@Update("update tb_depart set name=#{name} where id=#{id}")

//使用动态SQL
@Update("""
    <script>
        update tb_depart
        <trim prefix="set" suffixOverrides=",">
            <if test="name!=null and name!=''">
                name=#{name},
            </if>
        </trim>
        where id=#{id}
    </script>
""")
void update(Depart depart);

4、删除

在 DepartMapper.java 接口中的 delete 方法上使用 @Delete 注解

@Delete("delete from tb_depart where id=#{id}")
void delete(Integer id);

5、批量删除

与批量添加相同,使用 @Delete 注解,结合 foreach 来实现这个功能

@Delete("""
    <script>
        delete from tb_depart where id in 
        <foreach collection="list" item="id" open="(" close=")" separator=",">
            #{id}
        </foreach>
    </script>
""")
void deleteBatch(List<Integer> ids);

测试

@Test
public void testDeleteBatch() {
    DepartMapper mapper = session.getMapper(DepartMapper.class);
    mapper.deleteBatch(Arrays.asList(7,11));
}

八、注解实现查询

1、查询单个

修改 DepartMapper.java 接口类中的 getDepartById 方法,在这个方法上使用 @Select 注解进行查询。

@Select("select id,name from tb_depart where id=#{id}")
Depart getDepartById(Integer id);

2、查询多个

@Select("select id, name from tb_depart")
List<Depart> getAll();

测试

    @Test
    public void testGetAll() {
        DepartMapper mapper = session.getMapper(DepartMapper.class);
        List<Depart> departs = mapper.getAll();

        for (int i = 0; i < departs.size(); i++) {
            Depart depart = departs.get(i);
            System.out.println(depart);
        }

        // for(循环变量类型 循环变量 : 数组或集合){}
        for (Depart depart : departs) {
            System.out.println(depart);
        }

        departs.forEach((depart) -> {
            System.out.println(depart);
        });

        departs.forEach(depart -> {
            System.out.println(depart);
        });

        departs.forEach(depart -> System.out.println(depart));

        departs.forEach(System.out::println);
    }

3、 模糊查询

@Select("""
    <script>
        select id,name from tb_depart
        <trim prefix="where" prefixOverrides="and ">
            <if test="name!=null and name!=''">
                and name like concat(#{name}, '%')
            </if>
        </trim>
    </script>
""")
List<Depart> getDepartsLikeName(String name);

 4、统计查询

在实际开发中,经常需要获取表的总记录数,此时就需要对表中的数据进行统计查询。我们在 DepartMapper.java 接口中添加获取总记录数的方法。

@Select("select count(*) from tb_depart")
long count();

5、返回Map查询结果

有时我们希望通过查询语句得到的结果放入到 Map 结构中,然后再返回。@ResultType 注解用于设置查询返回的结果类型。当一个查询返回基本类型或者包装类时,一般需要使用 @ResultType 注解指定明确的结果类型, 

我们在 DepartMapper.java 接口添加此方法。  

@Select("select id,name from tb_depart where id=#{id}")
@ResultType(Map.class)
Map<String, Object> getByIdToMap(Integer id);

测试

    @Test
    public void testGetByIdToMap() {
        DepartMapper mapper = session.getMapper(DepartMapper.class);
        Map<String, Object> map = mapper.getByIdToMap(2);
        for (Map.Entry<String, Object> entry : map.entrySet()) {
            System.out.println(entry.getKey() + " = " +  entry.getValue());
        }
    }

6、 分页查询

6.1 手动分页

在 DepartMapper.java 接口的 getDeparts 方法来实现手动分页

@Select("select id,name from tb_depart order by id desc limit #{offset}, #{pageSize}")
List<Depart> getDeparts(@Param("offset") int offset, @Param("pageSize") int pageSize);

测试

    @Test
    public void testGetDeparts() {
        int page = 3;  // 页码
        int pageSize = 3; // 每页显示记录数
        int offset = (page - 1) * pageSize; // 分页时的起始偏移值,它是从 0 开始
        DepartMapper mapper = session.getMapper(DepartMapper.class);
        List<Depart> departs = mapper.getDeparts(offset, pageSize);
        departs.forEach(System.out::println);
    }

 6.2 插件分页

在 MyBatis 中有四大组件

  • Executor执行器,如:update、query、commit、rollback、close

  • StatementHandlerSQL语句处理器,如:prepare、parameter、batch、update

  • ParameterHandler参数处理器,如:getParameterObject、setParameters

  • ResultSetHandler结果集处理器,如:handlerResultSet、handlerOutputParameters

这四大组件我们都可以通过拦截器来进行拦截

插件分页其实就是通过拦截器来实现对要执行的 SQL 语句添加分页的代码

我们在使用分页查询时,只需要将 #{offset} 和 #{pageSize} 作为参数传入即可,PageHelper 就帮助我们做了这件事。

1、将分页插件的依赖添加到 pom.xml 文件中

<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper</artifactId>
    <version>6.1.0</version>
</dependency>

2、在 mybatis-config.xml 配置文件中使用 plugins 标签配置拦截器插件

<plugins>
    <plugin interceptor="com.github.pagehelper.PageInterceptor" />
</plugins>

 3、通过 DepartMapper.java 接口中的 getAll 方法来使用

@Select("select id, name from tb_depart")
List<Depart> getAll();

4、使用 PageHelper.startPage() 方法自动分页

    @Test
    public void testGetAllByPageHelper() {

        PageHelper.startPage(2, 3, "id desc");

        DepartMapper mapper = session.getMapper(DepartMapper.class);
        List<Depart> departs = mapper.getAll();

        PageInfo<Depart> pageInfo = new PageInfo<>(departs);
        System.out.println("总页数:" + pageInfo.getPages());
        System.out.println("当前页码:" + pageInfo.getPageNum());
        System.out.println("每页显示记录数:" + pageInfo.getPageSize());
        System.out.println("总记录数:" + pageInfo.getTotal());
        List<Depart> list = pageInfo.getList();
        list.forEach(System.out::println);
    }

7、一对多关联关系

部门对于员工来说,是一对多的关联关系,一个部门对于多个员工。在查询部门的同时把该部门下的员工也一并查询出来,则需要通过一对多的关联查询

修改部门的实体类,在类中添加对员工的引用

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
public class Depart {
    private Integer id;
    private String name;
    private List<Employee> employees;
}

  在 EmployeeMapper.java 接口类中定义 findEmployeeByDid() 方法

//根据部门id查员工
@Select("select id, name, age, did from tb_employee where did=#{did}")
List<Employee> findEmployeeByDid(Integer did);

在 DepartMapper.java 接口类中定义 findDepartById() 方法

//根据部门编号查询部门
@Select("select id,name from tb_depart where id=#{id}")
@Results(id = "departMap", value = {
        @Result(id = true, property = "id", column = "id"),
        @Result(property = "name", column = "name"),
        @Result(property = "employees", column = "id",
                many = @Many(
                        select = "com.mapper.EmployeeMapper.findEmployeesByDid", fetchType = FetchType.LAZY))
})
Depart findDepartById(Integer id);

 这样我们就实现了通过部门编号查询对应部门下的所有员工的信息,即 sql 语句:

 select e.id, e.name, e.age, did, d.name from tb_employee e inner join tb_depart d on e.id = d.id

注解说明:

使用 mybatis 开发时,数据库返回的结果集和实体类字段不对应,需要手动指定映射关系:

  •  一种是使用在 xml 文件中指定 resultMap 标签,指定 id,需要时直接引用 id (详见 3.5)
  • 另一种在使用注解开发的时候,只能通过注解 @Results 来指定对应关系了

@Results 注解用于定义多个列和 Java 对象属性之间的映射关系

        id 属性为当前结果集唯一标识符,后续可用 @ResultMap 直接来引用,减少冗余代码

        value 属性为结果集映射关系

@Result 注解用于定义单个列和 Java 对象属性之间的映射关系

        id 属性 —— 表示是否为主键,默认为 false

        column 属性—— 指定数据库字段名称

   property 属性 —— 实体类属性名称

@Result 注解还可以实现关联映射

        many 属性 —— 用于映射多的一方,这里就是指员工,通过 @Many 注解来进行关联处理

        property 属性 —— 映射实体类中的多的那个属性( private List<Employee> employees 中的 employees)

        column 属性则是对部门表中哪一个字段是员工表中的外键

        @Many 注解用于映射一对多关联关系,

                select 属性 —— 多的一方接口中的相应方法名的完整路径,

                fetchType 属性 —— 设置对数据的抓取策略,它主要有两个值:

                       1、 LAZY:延迟加载,即要使用时才加载(发出SQL语句)

                        2、EAGER:立即加载,不管理是否使用都加载(发出SQL语句)

 

 8、多对一关联关系

员工与部门之间的关系就是多对一关联关系,查询员工时一并把该员工对应的部门查询出来,这时就需要使用多对一关联查询。

与一对多一样,我们需要一个根据部门 id 获取部门信息的方法,即 getDepartById(),这个方法之前定义过,这里不再列出。

在员工实体类中添加对部门的引用

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Employee {
    private Integer id;
    private String name;
    private Integer age;
    private Integer did;
    private Depart depart;
}

在 EmployeeMapper.java 接口中定义根据员工编号查询员工信息的方法

@Select("select id,name,age,did from tb_employee where id=#{id}")
@Results(id = "employeeMap", value = {
    @Result(id = true, property = "id", column = "id"),
    @Result(property = "name", column = "name"),
    @Result(property = "age",column = "age"),
    @Result(property = "depart", column = "did",
        one = @One(
            select = "com.mapper.DepartMapper.getDepartById", fetchType = FetchType.EAGER
        )
    )
})
Employee findEmployeeById(Integer id);

 注解说明:

 @One注解用于映射多对一关联关系,

                select 属性 —— 一的一方接口中的相应方法名的完整路径

                fetchType 属性 —— 设置对数据的抓取策略,它主要有两个值:

                       1、 LAZY:延迟加载,即要使用时才加载(发出SQL语句)

                        2、EAGER:立即加载,不管理是否使用都加载(发出SQL语句)

 九、缓存

缓存(即cache)的作用是为了减少数据库的压力,提高数据库的性能

从数据库中查询出来的对象在使用完后不销毁,而是存储在内存(缓存)中,当再次需要获取该对象时,直接从内存中获取,不再向数据库执行 select 语句,减少对数据库的查询次数,提高了数据库的性能。

缓存是使用 Map 集合存储数据

在 MyBatis 中有分为一级缓存和二级缓存

1、一级缓存

一级缓存也叫本地缓存(Local Cache),作用域为 SqlSession 对象。

在同一个 SqlSession 中两次执行相同的 sql 语句,第一次执行完毕会将数据库查询的数据写到缓存(内存),第二次会从缓存中获取数据而不进行数据库查询(即不会再发出 sql 语句),大大提高了查询效率。

当 SqlSession 对象调用 close 或者 flush 方法后,该对象中的缓存(Cache) 就会被清空。

一级缓存默认启动且不能关闭,但是可以调用 clearCache() 方法来清空本地缓存,或者改变缓存的作用域。 

1.1、一级缓存失效

一级缓存失效的四种情况:

  • 不同的 SqlSession 对应不同的一级缓存

  • 同一个 SqlSession 但是查询条件不同

  • 同一个 SqlSession 两次查询期间执行了任何一次增删改操作

  • 同一个 SqlSession 两次查询期间手动清空了缓存(调用 session.clearCache())

2、二级缓存

二级缓存(second level cache)是多个 SqlSession 共享的,其作用域是 mapper 的同一个namespace,全局作用域缓存

MyBatis 默认不开启二级缓存,需要在 MyBtais 全局配置文件中进行 setting 配置开启二级缓存。

MyBatis 提供二级缓存的接口以及实现,缓存实现要求实体类实现 Serializable 接口。二级缓存在 SqlSession 关闭或提交之后才会生效。

修改实体类,让实体类实现 Serializable 接口

@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class Employee implements Serializable {
    private Integer id;
    private String name;
    private Integer age;
}

Serializable 接口是一个标记接口,实现了这个接口后,就可以进行持久化操作,而且可以进行网络传输。

在 mybatis-config.xml 主配置文件中开启二级缓存

<settings>
    <setting name="cacheEnabled" value="true"/>
</settings>

使用 setting 标签来开启,它的 name 值为 cacheEnabled,value 值为 true ,表示开启。

在 EmployeeMapper.xml 映射文件中使用 cache 标签来开启对 Employee 的二级缓存支持

<mapper namespace="com.mapper.EmployeeMapper">
    <cache/>
    <insert id="insert">
        insert into tb_employee(name,age) values (#{name},#{age})
    </insert>
    <select id="getEmployeeById" resultType="Employee">
        select id,name,age from tb_employee where id=#{id}
    </select>
</mapper>

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值