爬取页面中...

Mybatis数据库操作框架


Mybatis

第 1 章 简介及准备

1.1 简介

1.1.1 框架

框架是一个半成品,解决了软件开发过程中的普遍性问题,简化了开发步骤,提高了开发效率。

1.1.2 ORM

ORM 全称“Object Relational Mapping”,是一种程序设计技术,用于实现面向对象编程语言里不同类型系统的数据之间的转换。即实现了将程序中的一个对象与表中的一行数据对应

ORM 框架提供了持久化类与表的映射关系,在运行时把对象持久化到数据库中。

1.1.3 持久化和持久层

数据持久化:

  • 持久化就是将程序的数据在持久状态和瞬时状态转化的过程。
  • 内存:断电即失,数据不能持久化存储。

持久层:

可以理解成数据保存在数据库或者硬盘一类可以保存很长时间的设备里面,不像放在内存中那样断电就消失了,也就是把数据存在持久化设备上。

1.1.4 Mybatis 简介

Mybatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。

Mybatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。

Mybatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。

1.2 Mybatis 安装

添加 jar 包或者 Maven 依赖,添加到 Maven 仓库:

<!-- https://mvnrepository.com/artifact/org.mybatis/mybatis -->
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.6</version>
</dependency>

1.4 Lombok

1.4.1 简介

一款基于注解简化实体类开发的插件。

在没使用 Lombok 之前,我们需要自己手动敲实体类中的类似于 Getter and Setter 的代码,利用该插件只需要在实体类上掺入响应的注解,便可达到简化开发的目的。

将注解放在类上,则生成所有属性的方法;也可以放在局部属性上,则生成当前属性的方法。

1.4.2 安装

1、IDEA 中搜索该插件名称,下载。

2、导入依赖或者 jar 包

<!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.20</version>
    <scope>provided</scope>
</dependency>

以下是 Lombok 的注解方式:

@Getter and @Setter
@FieldNameConstants
@ToString
@EqualsAndHashCode
@AllArgsConstructor, @RequiredArgsConstructor and @NoArgsConstructor
@Log, @Log4j, @Log4j2, @Slf4j, @XSlf4j, @CommonsLog, @JBossLog, @Flogger, @CustomLog
@Data
@Builder
@SuperBuilder
@Singular
@Delegate
@Value
@Accessors
@Wither
@With
@SneakyThrows
@val
@var
@UtilityClass

如下注解即可自动生成类似于 Getter and Setter 的代码。

// 以下三个足以
@Data // 其它包括toString()、Getter()等方法
@AllArgsConstructor // 有参
@NoArgsConstructor // 无参
public class Student {
    private int id;
    private String username;
    private String password;
    private String name;
    private String sex;
    private int age;
    private String hobbies;
}

第 2 章 Mybatis 框架使用

入门就是使用 Mybatis 的基本过程,包括各种配置以及实现等。

注意查看 Mybatis 官方文档,方便入门。

2.1 创建 Mybatis 核心配置文件

该文件是 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">

<!--
    environment 元素体中包含了事务管理和连接池的配置。
    mappers 元素则包含了一组映射器(mapper),这些映射器的 XML 映射文件包含了 SQL 代码和映射定义信息。
-->

<!--核心配置文件-->
<configuration>
    <!--环境配置文件,可以直接切换不同的数据库环境-->
    <environments default="development">
        <environment id="development">
            <!--默认事务管理,可以自动配置-->
            <transactionManager type="JDBC"/>
            <!--连接数据库-->
            <dataSource type="POOLED">
                <property name="driver" value=""/>
                <property name="url" value=""/>
                <property name="username" value=""/>
                <property name="password" value=""/>
            </dataSource>
        </environment>
    </environments>

    <!--可以用注解代替以下内容-->
    <!--每一个Mapper.xml都必须在Mybatis核心配置文件中注册,不能删除-->
    <mappers>
        <!--必须配置接口配置文件路径,这里注意Maven的约定大于配置-->
        <!--resource用的是/路径-->
        <mapper resource=""/>
    </mappers>
</configuration>

示例如下:

<?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="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/qifeng"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
        <environment id="product">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/qifeng"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </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://localhost:3306/qifeng"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
    </environments>

    <mappers>
        <mapper resource="StudentMapper.xml"/>
    </mappers>
</configuration>

2.2 创建实体类

在 Mybatis 中,实体类是必要的,需要数据库表中的字段名和实体类中的属性一一对应但不要求一致,否则不能依靠 Mybatis 传递参数。

示例如下:

// 以下三个足以
@Data // 其它包括toString()、Getter()等方法
@AllArgsConstructor // 有参
@NoArgsConstructor // 无参
public class Student {
    private int id;
    private String username;
    private String password;
    private String name;
    private String sex;
    private int age;
    private String hobbies;
}

2.3 创建 Mybatis 工具类

该类并不是必须的,只是因为在使用 Mybatis 时,需要读取配置文件,因此我们一般将这些操作封装到 MybatisUtil 中。

工具类基础代码:

package com.luochen.utils;

import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.IOException;
import java.io.InputStream;

/**
 * 封装MyBatis工具类
 */
public class MyBatisUtil {

    // 必须创建SqlSessionFactory对象
    private static SqlSessionFactory sqlSessionFactory;

    static {
        try {
            // MyBatis 核心配置文件路径
            String resource = "mybatis-config.xml";
            // 读取配置文件
            InputStream inputStream = Resources.getResourceAsStream(resource);
            // 使用MyBatis获取SqlSessionFactory对象
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 获取SqlSession对象
     * SqlSession 提供了在数据库执行 SQL 命令所需的所有方法
     *
     * @return SqlSession对象
     */
    public static SqlSession getSqlSession() {
        // sqlSessionFactory.openSession(true);   // 设置自动提交事务
        return sqlSessionFactory.openSession();
    }

    /**
     * 一定要关闭SqlSession对象,否则会出错
     * @param sqlSession 需要关闭的SqlSession对象
     */
    public static void close(SqlSession sqlSession){
        if (sqlSession != null) {
            sqlSession.close();
        }
    }
}

代理模式,对 SqlSession 接口进行加强,实现在关闭 SqlSession 时,手动提交或回滚。

package com.luochen.utils;

import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.InputStream;
import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;

/**
 * @Author 洛尘大大
 * @projectName MyBatis
 * @title MybatisUtil
 * @Date 2021/10/18 15:13
 * @Description Mybatis工具类
 */
public class MybatisUtil {
    /**
     * 创建SqlSessionFactory工厂
     */
    private static final SqlSessionFactory sqlSessionFactory;

    static {
        // 读取MyBatis核心配置文件
        InputStream resourceAsStream = MybatisUtil.class.getClassLoader().getResourceAsStream("mybatis-config.xml");
        // 给SqlSessionFactory赋值
        sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
    }

    /**
     * 获取SqlSession对象,对该对象进行加强,可以自动提交或者回滚
     *
     * @return 返回SqlSession对象
     */
    public static SqlSession getSqlSession() {
        // 获取SqlSession对象
        SqlSession sqlSession = sqlSessionFactory.openSession();

        // 获取SqlSession字节码文件
        Class<?> clazz = sqlSession.getClass();

        // 获取clazz类加载器
        ClassLoader classLoader = clazz.getClassLoader();

        // 创建接口字节码文件数组
        Class<?>[] interfaces = {clazz};

        // 创建InvocationHandler实现类,即匿名内部类
        InvocationHandler invocationHandler = (proxy, method, args) -> {
            // 对SqlSession实现动态代理增强
            String close = "close";
            if (close.equals(method.getName())) {
                // 如果调用close方法,则需要提交或者回滚
                try {
                    sqlSession.commit();
                } catch (Exception e) {
                    e.printStackTrace();
                    sqlSession.rollback();
                }
            }
            return method.invoke(sqlSession, args);
        };

        return (SqlSession) Proxy.newProxyInstance(classLoader, interfaces, invocationHandler);
    }
}

2.5 创建 Mapper 接口

这里的 Mapper 接口就是 Dao 接口,只是名字和实现方式不一样,实现的功能一样。

注意:和 MVC 的使用不同,在使用 Mybatis 时,我们一般创建以 Mapper 结尾的接口,当然名字不是很重要,一般这样写,因为之后是按照配置文件中的 mapper 属性配置实现接口的。

示例如下:

package com.luochen.dao;

import com.luochen.pojo.Student;

import java.util.List;

/**
 * 本来是Dao命名,但在MyBatis中,利用mapper获取,只是命名而已,也可以不这样命名
 */
public interface StudentMapper {
    // 查询学生信息
    List<Student> getStudentList();
    Student getStudentById(int id);

    // 添加学生
    int insertStudent(Student student);

    // 更新学生信息
    int updateStudent(Student student);

    // 删除学生
    int deleteStudent(int id);
}

2.6 创建 Mapper 接口映射配置文件

该配置文件,就相当于接口的实现类,因为 Mybatis 只针对数据库进行操作,因此该配置文件中就只是涉及数据库的各种操作,即在该文件中,定义 SQL 语句,最后由 Mybatis 实现对应的操作。

该配置文件中可以传递接收参数,SQL 中的参数也能由实体类提供,接收参数语法如下:

<!--预编译传参,先将参数替换成?,再赋值-->
#{参数名称}

<!--字符串直接拼接,不能防止SQL注入-->
${参数名称}

注意:#{},${}中的参数可以是 arg+参数下标位置(0开始),或者是 param+参数下标位置(1开始)。

如果需要传递多个参数就应该用实体类或者 Map 集合。

如果传入参数为一个对象,则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">

<!--绑定一个对应的Dao/Mapper接口,相当于接口实现类-->
<!--用的是.路径-->
<mapper namespace="">

    <!--id对应接口中的需要被实现的方法,相当于重写方法-->
    <!--resultType对应返回的类型-->
    <!--parameterType:传递参数类型-->
    <select id="" parameterType="" resultType=""></select>

    <!--插入语句-->
    <insert id="" parameterType=""></insert>

    <!--更新语句-->
    <!--快捷键upd,只有连接数据库后才会出现-->
    <update id="" parameterType=""></update>

    <!--删除语句-->
    <delete id="" parameterType=""></delete>
</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">

<!--绑定一个对应的Dao/Mapper接口,相当于接口实现类-->
<mapper namespace="com.luochen.dao.StudentMapper">

    <!--id对应接口中的需要被实现的方法,相当于重写方法-->
    <!--resultType对应返回的类型-->

    <select id="getStudentList" resultType="com.luochen.pojo.Student">
        select *from student
    </select>

    <!--parameterType:传递参数类型-->
    <select id="getStudentById" parameterType="int" resultType="com.luochen.pojo.Student">
        select *from student where id=#{id}
    </select>

    <!--插入语句-->
    <insert id="insertStudent" parameterType="com.luochen.pojo.Student">
        insert into student(username,password,name,sex,age,hobbies) values (#{username},#{password},#{name},#{sex},#{age},#{hobbies})
    </insert>

    <!--更新语句-->
    <!--快捷键upd,只有连接数据库后才会出现-->
    <update id="updateStudent" parameterType="com.luochen.pojo.Student">
        update student set username=#{username},password=#{password},name=#{name},sex=#{sex},age=#{age},hobbies=#{hobbies} where id=#{id}
    </update>

    <!--删除语句-->
    <delete id="deleteStudent" parameterType="int">
        delete from student where id=#{id}
    </delete>
</mapper>

注意:上述内容,我是直接放在 Maven 工程的 resources 目录下,因此可以直接访问,如果存在其它地方则可能会导致该配置文件不能导出的错误,解决办法就是在 pom.xml 中配置如下代码:

<!--在build中配置resoureces,来防止我们资源导出失效的问题-->
<build>
    <resources>
        <resource>
            <directory>src/main/resources/</directory>
            <includes>
                <include>**/*.properties</include>
                <include>**/*.xml</include>
            </includes>
            <filtering>true</filtering>
        </resource>
        <resource>
            <directory>src/main/java</directory>
            <includes>
                <include>**/*.properties</include>
                <include>**/*.xml</include>
            </includes>
            <filtering>true</filtering>
        </resource>
    </resources>
</build>

2.7 创建测试类

该类只是用于测试 Mybatis 是否出错,以及能不能连接数据库,不是必须。

测试类使用 JUnit 类创建。

注意:对于数据库操作的增删改默认都需要手动提交事务,否则无法改变数据库内容,查询则不需要提交事务。

设置自动提交事务:

创建 SqlSession 对象时,对 sqlSessionFactory.openSession() 传入 true 参数,即:

sqlSessionFactory.openSession(true);

一般步骤:

// 获取SqlSession对象
SqlSession sqlSession = MyBatisUtil.getSqlSession();

// 获取mapper接口对象
StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);

// 调用mapper接口中的方法
List<Student> studentList = mapper.getStudentList();

// 实现某些数据库操作
studentList.forEach(System.out::println);

// 关闭SqlSession对象
MyBatisUtil.close(sqlSession);

示例如下:

package com.luochen.dao;

import com.luochen.pojo.Student;
import com.luochen.utils.MyBatisUtil;
import org.apache.ibatis.session.SqlSession;
import org.junit.jupiter.api.Test;

import java.util.List;

public class StudentMapperTest {
    @Test
    public void getStudentListTest() {
        // 获取SqlSession对象
        SqlSession sqlSession = MyBatisUtil.getSqlSession();

        // 方式一:getMapper
        StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
        List<Student> studentList = mapper.getStudentList();
        studentList.forEach(System.out::println);

        MyBatisUtil.close(sqlSession);
    }

    @Test
    // 查询语句并不需要提交事务
    public void getStudentByIdTest() {
        SqlSession sqlSession = MyBatisUtil.getSqlSession();
        StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
        Student studentById = mapper.getStudentById(17);
        System.out.println(studentById);
        MyBatisUtil.close(sqlSession);
    }

    @Test
    // 插入操作
    public void insertStudentTest() {
        SqlSession sqlSession = MyBatisUtil.getSqlSession();
        StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
        int res = mapper.insertStudent(new Student(1, "123", "123", "冯玉祥", "男", 12, "王者"));
        if (res > 0) {
            System.out.println("数据插入成功!!!");
        }
        // 提交事务
        sqlSession.commit();
        MyBatisUtil.close(sqlSession);
    }

    @Test
    // 更新操作
    public void updateStudentTest() {
        SqlSession sqlSession = MyBatisUtil.getSqlSession();
        StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
        int rs = mapper.updateStudent(new Student(1,"娃哈哈","456789","芈月","女",23,"王者"));
        if (rs > 0) {
            System.out.println("修改学生信息成功!!!");
        }

        sqlSession.commit();

        MyBatisUtil.close(sqlSession);
    }

    @Test
    // 删除操作
    public void deleteStudentTest() {
        SqlSession sqlSession = MyBatisUtil.getSqlSession();
        StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
        int rs = mapper.deleteStudent(17);
        if (rs > 0) {
            System.out.println("删除成功!!!");
        }
        sqlSession.commit();
        MyBatisUtil.close(sqlSession);
    }
}

2.8 Map 传参和模糊查询

针对之前的某些代码操作进行优化,非常重要。

2.8.1 Map 传参

利用 Map 集合的键值对,方便我们传值尤其是传多个参数,可以做到不用实体类,尤其是在更新数据库操作中比较重要,但不正规。

  • Map 传递参数,直接在 sql 中取出 key 即可! 【parameterType=”map”】
  • 对象传递参数,直接在 sql 中取对象的属性即可! 【parameterType=”object”】
  • 只有一个基本类型参数的情况下,可以直接在 sql 中取到! 【parameterType=”数据类型”,或者不写】
// 利用Map更新学生信息
int updateMapStudent(Map<String,Object> map);
<!--数据更新,传入map参数,利用map键值对给SQL语句传参-->
<update id="updateMapStudent" parameterType="map">
    update student set username=#{userName},password=#{passWord},name=#{name} where id=#{id}
</update>

测试类方法:

@Test
public void updateMapStudent() {
    SqlSession sqlSession = MyBatisUtil.getSqlSession();
    StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
    HashMap<String, Object> map = new HashMap<>();
    // 可以不注意参数顺序
    map.put("userName","sn");
    map.put("id",18);
    map.put("passWord","888888");
    map.put("name","神农");
    int rs = mapper.updateMapStudent(map);
    if (rs > 0) {
        System.out.println("数据更新成功!!!");
    }
    // 事务提交
    sqlSession.commit();
    MyBatisUtil.close(sqlSession);
}

2.8.2 模糊查询

1、mapper 接口配置文件中配置 SQL 语句

// 模糊查询
List<Student> getStudentLike(String name);

(1)直接拼接

<!--配置文件中拼接%字符串-->
<select id="getStudentById" resultType="com.luochen.pojo.Student">
    select *from student where name like "%"#{name}"%"
</select>

(2)concat() 字符串拼接

<!--配置文件中拼接%字符串-->
<select id="getStudentById" resultType="com.luochen.pojo.Student">
    select *from student where name like concat('%', #{name},'%')
</select>
@Test
public void getStudentLike() {
    SqlSession sqlSession = MyBatisUtil.getSqlSession();
    StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);
    List<Student> list = mapper.getStudentLike("炎");
    list.forEach(System.out::println);
    MyBatisUtil.close(sqlSession);
}

2、Java 代码中实现

// 模糊查询
List<Student> getStudentLike(String name);
<select id="getStudentById" resultType="com.luochen.pojo.Student">
    select *from student where like #{name}
</select>
@Test
public void getStudentLike() {
    SqlSession sqlSession = MyBatisUtil.getSqlSession();
    StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);

    // java代码中,拼接%字符串,实现模糊查询
    List<Student> list = mapper.getStudentLike("%炎%");

    list.forEach(System.out::println);
    MyBatisUtil.close(sqlSession);
}

2.9 Mybatis 传递参数

2.9.1 匿名参数传递

匿名参数传递也叫顺序参数传递,必须强调顺序,显得不够灵活。

参数类型如下:

  • arg 类型:arg+参数位置下标,从 0 开始。
  • param 类型:param+参数位置下标,从 1 开始。
// 可以是多个参数
Student getStudent(String name);
<select id="getStudent" resultType="com.luochen.pojo.Student">
    SELECT
        *
    FROM
        student
    WHERE 
        s_name LIKE CONCAT('%', #{arg0},'%')
</select>
<select id="getStudent" resultType="com.luochen.pojo.Student">
    SELECT
        *
    FROM
        student
    WHERE 
        s_name LIKE CONCAT('%', #{param1},'%')
</select>

2.9.2 使用 @Param 注解

使用 @Param 注解可以显示的告诉 Mybatis 参数的名字,这样在 xml 中就可以按照参数名去引用了。

// 可以是多个参数
Student getStudent(@Param("s_name") String name);
<select id="getStudent" resultType="com.luochen.pojo.Student">
    SELECT
        *
    FROM
        student
    WHERE 
        s_name LIKE CONCAT('%', #{s_name},'%')
</select>

2.9.3 Map 传参

参照本章的 2.8 节。

2.9.4 JavaBean 传递多个参数

使用 Bean 的方式来传递多个参数,利用 parameterType 指定为对应的 Bean 类型即可。

1、普通对象传递

Student getStudent(Student student);

直接使用 Bean 中的属性名即可。

<select id="getStudent" parameterType="com.luochen.pojo.Student" resultType="com.luochen.pojo.Student">
    SELECT
        *
    FROM
        student
    WHERE 
        s_name LIKE CONCAT('%', #{name},'%')
</select>

2、@Param 注解传递

Student getStudent(@Param("stu") Student student);

由于接口中的参数为 Bean 对象,因此使用 @Param 注解创建的变量为该 Bean 对象,可以使用对象中的属性。

<select id="getStudent" resultType="com.luochen.pojo.Student">
    SELECT
        *
    FROM
        student
    WHERE 
        s_name LIKE CONCAT('%', #{stu.name},'%')
</select>

2.9.5 JSON 传递参数

该方法暂时没有试验。

2.10 主键回填

一般发生在插入数据时存在主键回填需求,常用的有三种,如下:

Mapper 接口中的添加对象方法:

int addStudent(@Param("stu") Student student);

2.10.1 添加 useGeneratedKeys

在插入节点上添加 useGeneratedKeys 属性,同时设置接收回传主键的属性

<insert id="addStudent" keyProperty="stu.id" useGeneratedKeys="true">
    INSERT INTO student ( s_id, s_name, t_id, time )
    VALUES
        (#{stu.id},#{stu.name},#{stu.teacher.id},#{stu.time})
</insert>

2.10.2 last_insert_id() 查询主键

该方式是利用 MySql 插入数据时,自动填充主键。

<insert id="addStudent">
    <!--
        selectKey:表示选择键,通常都是用于主键回填功能 
        keyProperty:表示回填的值设置到哪个属性上
        resultType:表示回填的值的数据类型,必须有
        order:表示主键回填的时机 AFTER表示数据保存后 BEFORE表示数据插入之前
      -->
    <selectKey keyProperty="stu.id" resultType="java.lang.String" order="AFTER">
        SELECT LAST_INSERT_ID()
    </selectKey>
    INSERT INTO student ( s_id, s_name, t_id, time )
    VALUES
        (#{stu.id},#{stu.name},#{stu.teacher.id},#{stu.time})
</insert>

2.10.3 程序编码获取主键

该方式是利用程序进行主键的生成,最后插入表。

<insert id="addStudent">
    <selectKey keyProperty="stu.id" resultType="string" order="BEFORE">
        <!--程序编码获取主键,该种方式获取的键较长-->
        SELECT REPLACE(UUID(),'-','')
    </selectKey>
    INSERT INTO student ( s_id, s_name, t_id, time )
    VALUES
        (#{stu.id},#{stu.name},#{stu.teacher.id},#{stu.time})
</insert>

第 3 章 核心配置解析

Mybatis 的配置文件包含了会深深影响 Mybatis 行为的设置和属性信息,一定要注意以下属性顺序,配置文档的顶层结构如下:

  • configuration(配置)
    • properties(属性)
    • settings(设置)
    • typeAliases(类型别名)
    • typeHandlers(类型处理器)
    • objectFactory(对象工厂)
    • plugins(插件)
    • environments(环境配置)
      • environment(环境变量)
        • transactionManager(事务管理器)
        • dataSource(数据源)
    • databaseIdProvider(数据库厂商标识)
    • mappers(映射器)

3.1 environments 环境

Mybatis 可以配置成适应多种环境,这种机制有助于将 SQL 映射应用于多种数据库之中,不过要记住:尽管可以配置多个环境,但每个 SqlSessionFactory 实例只能选择一种环境。

<?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 resource="db.properties"/>

    <!--两个环境属性,只能选择一个-->
    <environments default="development2">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/qifeng"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>

        <environment id="development2">
            <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>
        <mapper resource="StudentMapper.xml"/>
    </mappers>
</configuration>

3.2 properties 属性

作用:引入外部文件,动态改变属性名称。

如果一个属性在不只一个地方进行了配置,那么,Mybatis 将按照下面的顺序来加载:

  • 首先读取在 properties 元素体内指定的属性。
  • 然后根据 properties 元素中的 resource 属性读取类路径下属性文件,或根据 url 属性指定的路径读取属性文件,并覆盖之前读取过的同名属性。
  • 最后读取作为方法参数传递的属性,并覆盖之前读取过的同名属性。

通过方法参数传递的属性具有最高优先级,resource/url 属性中指定的配置文件次之,最低优先级的则是 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 resource="db.properties"/>

    <environments default="development2">
        <environment id="development2">
            <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>
        <mapper resource="StudentMapper.xml"/>
    </mappers>
</configuration>

3.3 typeAliases 类型别名

该种方式起的别名,不区分大小写。

3.3.1 typeAlias 属性

类型别名可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置,意在降低冗余的全限定类名书写。

Mybatis-config.xml 配置:

<!--起别名-->
<typeAliases>
    <typeAlias type="com.luochen.pojo.Student" alias="Student"/>
</typeAliases>

StudentMapper.xml 配置:

<select id="getStudent" resultType="Student">
    select *from student
</select>

3.3.2 package 属性

扫描实体类的包,自动为该包内的实体类分配类型别名,默认别名为实体类首字母小写字母。若实体类有注解名,则别名为注解名。

<!--起别名-->
<typeAliases>
    <package name="com.luochen.pojo"/>
</typeAliases>

该包下有 Student 实体类,因此别名为 student。

<select id="getStudent" resultType="student">
    select *from student
</select>

3.3.3 常见 Java 类型别名

规律:基本数据类型别名为类型前加下划线(_),其它引用数据类型则直接使用全字母小写。

别名 映射的类型
_byte byte
_long long
_short short
_int int
_integer int
_double double
_float float
_boolean boolean
string String
byte Byte
long Long
short Short
int Integer
integer Integer
double Double
float Float
boolean Boolean
date Date
decimal BigDecimal
bigdecimal BigDecimal
object Object
map Map
hashmap HashMap
list List
arraylist ArrayList
collection Collection
iterator Iterator

3.4 settings 设置

这是 Mybatis 中极为重要的调整设置,它们会改变 Mybatis 的运行时行为。

请阅读官网文档

3.5 mapper 映射

有四种方式:

1、使用相对于类路径的资源引用(推荐使用)

<mappers>
      <mapper resource="com/luochen/dao/StudentMapper.xml"/>
</mappers>

2、使用完全限定资源定位符(URL)

即绝对路径,不要使用。

<mappers>
  <mapper url="file:///var/mappers/AuthorMapper.xml"/>
</mappers>

3、使用映射器接口实现类的完全限定类名

<mappers>
  <mapper class="com.luochen.dao.StudentMapper"/>
</mappers>

注意:

  • 接口和它的配置文件名称必须相同。
  • 接口和它的配置文件必须在同一文件夹下。

4、将包内的映射器接口实现全部注册为映射器

<mappers>
  <package name="com.luochen.dao"/>
</mappers>

注意:

  • 接口和它的配置文件名称必须相同。
  • 接口和它的配置文件必须在同一文件夹下。

3.6 作用域(Scope)和生命周期

不同作用域和生命周期类别是至关重要的,因为错误的使用会导致非常严重的并发问题。

1、SqlSessionFactoryBuilder

  • 这个类可以被实例化、使用和丢弃,一旦创建了 SqlSessionFactory,就不再需要它了。
  • SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域(也就是局部方法变量)。

2、SqlSessionFactory

  • SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由丢弃它或重新创建另一个实例。
  • 使用 SqlSessionFactory 的最佳实践是在应用运行期间不要重复创建多次,多次重建 SqlSessionFactory 被视为一种代码“坏习惯”。
  • SqlSessionFactory 的最佳作用域是应用作用域。 有很多方法可以做到,最简单的就是使用单例模式或者静态单例模式。

3、SqlSession

  • 每个线程都应该有它自己的 SqlSession 实例。
  • SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域。
  • 绝对不能将 SqlSession 实例的引用放在一个类的静态域,甚至一个类的实例变量也不行。
  • 绝不能将 SqlSession 实例的引用放在任何类型的托管作用域中,比如 Servlet 框架中的 HttpSession。
  • 确保每次都执行关闭操作。

第 4 章 XML 映射器

4.1 Mybatis 级联查询

目的:解决数据库表的字段名和实体类的属性名不一致。

4.1.1 简单结果集映射

实现实体类中的属性与数据库字段名别名一致。

接口实现类配置文件中的设置如下:

<!--column数据表中的字段名,property实体类中的属性,一一映射,可以不用映射所有值-->
<resultMap id="StudentMap" type="student">
    <result column="password1" property="password"/>
</resultMap>

<select id="getStudent" resultMap="StudentMap">
    select *from student
</select>

4.1.2 多对一映射

多对一映射,也可用作一对一映射。

关联 - association:适用于对象的结果映射。

重要的属性名如下:

<!--嵌套查询-->
<association property="实体类属性名" column="数据库表字段名" javaType="实体类属性类型" select="嵌套查询接口"/>
<!--结果查询-->
<association property="实体类对象属性名" javaType="实体类属性类型">
    <result property="实体类属性名" column="SQL中该属性的字段或别名"/>
</association>

例如:多个学生对应一个老师,因此学生实体类中就有一个老师的实体类对象,需要在配置文件中利用 resultMap 配置映射。

// 学生实体类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {
    private String id;
    private String name;
    private Teacher teacher;
}
// 教师实体类
@NoArgsConstructor
@AllArgsConstructor
@Data
public class Teacher {
    private String id;
    private String name;
}

1、按照子查询嵌套映射

即查询里嵌套查询,SQL 语句可以分段,映射流程比较清晰,代码量较大。

<!--按照循环嵌套查询-->
<select id="getStudent" resultMap="rMap">
    select *from student
</select>

<resultMap id="rMap" type="student">
    <result property="id" column="s_id"/>
    <result property="name" column="s_name"/>

    <!--javaType:映射属性java结果类型;column:数据库表列名,用于向下传参的属性-->
    <association property="teacher" column="t_id" javaType="teacher" select="getTeacher"/>
</resultMap>

<!--自动识别参数,一定要注意数据库表中的字段名跟类中的属性名一致-->
<select id="getTeacher" resultMap="teacherTable">
    select * from teacher where t_id=#{t_id}
</select>
<resultMap id="teacherTable" type="teacher">
    <result property="id" column="t_id"/>
    <result property="name" column="t_name"/>
</resultMap>

2、按照结果映射

对结果做映射,SQL 语句比较复杂,结果映射有时得不到需要的数据。

<!--按照结果嵌套-->
<select id="getStudent2" resultMap="rMap2">
    select s.s_id s_id,s.s_name s_name,t.t_id t_id,t.t_name t_name from student s,teacher t where s.t_id=t.t_id
</select>
<resultMap id="rMap2" type="student">
    <result property="id" column="s_id"/>
    <result property="name" column="s_name"/>
    <association property="teacher" javaType="teacher">
        <result property="id" column="t_id"/>
        <result property="name" column="t_name"/>
    </association>
</resultMap>

4.1.3 一对多映射

集合 - collection:适用于映射集合

重要的属性名如下:

<!--子查询时-->
<collection property="实体类集合属性名" javaType="实体类集合属性类型" ofType="集合泛型类" select="子查询接口" column="数据库表字段名"/>
<!--结果查询-->
<collection property="实体类集合属性名" ofType="集合泛型类">
    <result property="实体类属性名" column="SQL中该属性的字段或别名"/>
</collection>

例如:一个老师对应多个学生,因此在老师实体类中有一个学生类型的集合。

// 学生实体类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class Student {
    private String id;
    private String name;
    private String tid;
}
// 教师实体类
@NoArgsConstructor
@AllArgsConstructor
@Data
public class Teacher {
    private String id;
    private String name;

    //老师拥有多个学生
    private List<Student> students;
}

1、按照子查询嵌套

<!--子查询嵌套-->
<select id="getTeacher2" resultMap="trMap2">
    select *from teacher where t_id=#{id}
</select>
<resultMap id="trMap2" type="teacher">
    <result property="id" column="t_id"/>
    <result property="name" column="t_name"/>
    <!--javaType:映射字段结果类型;ofType:泛型类型;column:数据库表的列名-->
    <collection property="students" javaType="ArrayList" ofType="student" select="getStudents" column="t_id"/>
</resultMap>
<select id="getStudents" resultType="student">
    select s_id id,s_name name,t_id tid from student where t_id=#{id}
</select>

2、按照结果映射

<!--按照结果映射-->
<select id="getTeacher" resultMap="trMap">
    select t.t_id t_id,t_name,s_name from teacher t,student s where s.t_id=t.t_id and t.t_id=#{id};
</select>

<resultMap id="trMap" type="teacher">
    <result property="id" column="t_id"/>
    <result property="name" column="t_name"/>

    <!--集合使用collection,集合中的泛型信息,使用ofType获取-->
    <collection property="students" ofType="student">
        <result property="name" column="s_name"/>
    </collection>
</resultMap>

4.2 使用注解

Mybatis 注解开发,不需要配置 xml 映射文件,但仅限于简单的 SQL 语句,比较复杂的 SQL 语句推荐使用 XML。

注意:使用注解开发时,不能在同一文件夹创建和 Mapper 接口相同名称的实现配置文件,否则会报错。

Mybatis 不支持方法重载。

4.2.1 单个参数

package com.luochen.dao;

import com.luochen.pojo.Student;

import java.util.List;
import java.util.Map;

public interface StudentMapper {
    // 单个参数
    @Select("select *from student where id=#{id}")
    List<Student> getStudent(int id);
}

4.2.2 多个参数

一定要使用@Param(“参数”),利用该注解传参。

package com.luochen.dao;

import com.luochen.pojo.Student;

import java.util.List;
import java.util.Map;

public interface StudentMapper {   
    //多个参数,一定要使用@Param,利用该注解传参
    @Select("select *from student limit #{start},#{limit}")
    List<Student> getLimit(@Param("start") int start,@Param("end") int end);
}

@Param() 注意:

  • 基本类型的参数或者 String 类型,需要加上;
  • 引用类型不需要加;
  • 如果只有一个基本类型的话,可以忽略,但是建议大家都加上;
  • 我们在 SQL 中引用的就是我们这里的 @Param() 中设定的属性名。

第 5 章 日志

5.1 日志工厂

Mybatis 内置日志工厂会基于运行时检测信息选择日志委托实现。它会(按罗列的顺序)使用第一个查找到的实现。当没有找到这些实现时,将会禁用日志功能。

作用:方便进行进行排错。

利用 settings 进行设置。

设置名 描述 有效值 默认
logImpl 指定 Mybatis 所用日志的具体实现,未指定时将自动查找。 SLF4J | LOG4J | LOG4J2 | JDK_LOGGING | COMMONS_LOGGING | STDOUT_LOGGING | NO_LOGGING 未设置

5.2 STDOUT_LOGGING

STDOUT_LOGGING:标准日志工厂实现。

核心配置文件中配置:

<!--绝对不能有多余的空格或字母写错-->
<settings>
    <setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>

5.3 LOG4J

Log4j 是 Apache 的一个开源项目,有以下几个作用:

  • 可以控制日志信息输送的目的地是控制台、文件、GUI组件,甚至是套接口服务器、NT的事件记录器、UNIX Syslog守护进程等;
  • 可以控制每一条日志的输出格式;
  • 通过定义每一条日志信息的级别,能更加细致地控制日志的生成过程。
  • 可以通过一个配置文件来灵活地进行配置,而不需要修改应用的代码。

1、导入依赖

<!-- https://mvnrepository.com/artifact/log4j/log4j -->
<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>

2、创建配置文件

该配置文件就是定义日出输出的格式、地点、时间等,配置很多,百度搜索即可。

log4j 中的日志级别及优先级:ALL< DEBUG(调试) < INFO(消息) < WARN(警告) < ERROR(错误) < FATAL <OFF ,通过定义每一条日志信息的级别,我们能更加细致地控制日志的生成过程。

#将等级为DEBUG的日志信息输出到console和file这两个目的地,console和file的定义在下面的代码
log4j.rootLogger=DEBUG,console,file

#控制台输出的相关设置
log4j.appender.console = org.apache.log4j.ConsoleAppender
log4j.appender.console.Target = System.out
log4j.appender.console.Threshold=DEBUG
log4j.appender.console.layout = org.apache.log4j.PatternLayout
log4j.appender.console.layout.ConversionPattern=[%c]-%m%n

#文件输出的相关设置
log4j.appender.file = org.apache.log4j.RollingFileAppender
log4j.appender.file.File=./logs/logFile.log
log4j.appender.file.MaxFileSize=10mb
log4j.appender.file.Threshold=DEBUG
log4j.appender.file.layout=org.apache.log4j.PatternLayout
log4j.appender.file.layout.ConversionPattern=[%p][%d{yyyy-MM-dd}][%c]%m%n

#日志输出级别
log4j.logger.org.mybatis=DEBUG
log4j.logger.java.sql=DEBUG
log4j.logger.java.sql.Statement=DEBUG
log4j.logger.java.sql.ResultSet=DEBUG
log4j.logger.java.sql.PreparedStatement=DEBUG

3、核心配置文件设置

<!--绝对不能有多余的空格或字母写错-->
<settings>
    <setting name="logImpl" value="log4j"/>
</settings>

4、测试

package luochen.dao;

import com.luochen.dao.StudentMapper;
import com.luochen.pojo.Student;
import com.luochen.utils.MyBatisUtil;
import org.apache.ibatis.session.SqlSession;
import org.apache.log4j.Logger;
import org.junit.Test;

import java.util.List;

public class StudentMapperTest {
    // 创建日志对象 Logger
    static final Logger logger = Logger.getLogger(StudentMapperTest.class);

    @Test
    public void log4j(){
        logger.info("进入info");
    }
}

第 6 章 动态 SQL

动态 SQL:指根据不同的条件拼接成不同的 SQL 语句。

注意:拼接 SQL 语句时,有些符号不能被识别,需要放在 <\![CDATA[ sql 片段 ]]> 中。

6.1 if

即条件语句,满足该条件,则拼接该条件下的 SQL 语句。

<if test="条件">
    <!--待拼接的SQL语句-->
</if>
<select id="getTeacher" resultMap="rMap" parameterType="map">
    select *from teacher
    <if test="id != null">
        where t_id=#{id}
    </if>
</select>
<resultMap id="rMap" type="teacher">
    <result property="id" column="t_id"/>
    <result property="name" column="t_name"/>
</resultMap>

6.2 choose (when, otherwise)

类似于 switch 语句,满足某个 when 则拼接该条件下的 SQL 语句,如果传入的参数都不在 when 所示的参数中,则调用 otherwise。

<choose>
    <when test="条件">
        <!--待拼接的SQL语句-->
    </when>
    <when test="条件">
        <!--待拼接的SQL语句-->
    </when>
    <otherwise>
        <!--如果传入的参数不存在于上述的when中,则调用此方法-->
        <!--待拼接的SQL语句-->
    </otherwise>
</choose>
<!--传入单个参数的动态SQL语句:choose-when-otherwise-->
<select id="getTeacher" resultMap="rMap" parameterType="map">
    select *from teacher
    <choose>
        <when test="id!=null">
            where t_id=#{id}
        </when>
        <when test="name!=null">
            where t_name=#{name}
        </when>
        <otherwise>
            where t_id='2002'
        </otherwise>
    </choose>
</select>
<resultMap id="rMap" type="teacher">
    <result property="id" column="t_id"/>
    <result property="name" column="t_name"/>
</resultMap>

上述代码只适合传入单个参数或者条件,一旦传入多个参数,则 SQL 语句拼接错误。

6.3 trim (where, set)

trim (where, set):为了解决 6.2 中的传入多个参数造成 SQL 语句拼接错误的问题。

6.3.1 where 元素

只会在子元素返回任何内容的情况下才插入 “WHERE” 子句。而且,若子句的开头为 “AND” 或 “OR”,where 元素也会将它们去除。

<!--传入多个参数的动态SQL语句:trim (where, set)-->
<select id="getTeacher" resultMap="rMap" parameterType="map">
    select *from teacher
    <where>
        <if test="id != null">
            or t_id=#{id}
        </if>
        <if test="name!=null">
            or t_name=#{name}
        </if>
    </where>
</select>
<resultMap id="rMap" type="teacher">
    <result property="id" column="t_id"/>
    <result property="name" column="t_name"/>
</resultMap>

6.3.2 set 元素

用于动态更新语句的类似解决方案叫做 set。set 元素可以用于动态包含需要更新的列,忽略其它不更新的列。

<!--动态更新SQL语句:set -->
<select id="getTeacher" resultMap="rMap" parameterType="map">
    update teacher
    <set>
        <if test="name!=null">
            t_name=#{name},
        </if>
    </set>
    where t_id=#{id}
</select>
<resultMap id="rMap" type="teacher">
    <result property="id" column="t_id"/>
    <result property="name" column="t_name"/>
</resultMap>

set 元素会动态地在行首插入 SET 关键字,并会删掉额外的逗号(这些逗号是在使用条件语句给列赋值时引入的)。

6.3.3 trim 元素

1、通过自定义 trim 元素来定制 where 元素的功能。比如,和 where 元素等价的自定义 trim 元素为:注意参数 prefixOverrides。

<!--传入多个参数的动态SQL语句:trim-where)-->
<select id="getTeacher" resultMap="rMap" parameterType="map">
    select *from teacher
    <!--where覆盖and或者or-->
    <trim prefix="where" prefixOverrides="and |or">
        <if test="id != null">
            or t_id=#{id}
        </if>
        <if test="name!=null">
            or t_name=#{name}
        </if>
    </trim>
</select>
<resultMap id="rMap" type="teacher">
    <result property="id" column="t_id"/>
    <result property="name" column="t_name"/>
</resultMap>

prefixOverrides 属性会忽略通过管道符分隔的文本序列(注意此例中的空格是必要的)。上述例子会移除所有 prefixOverrides 属性中指定的内容,并且插入 prefix 属性中指定的内容。

2、和 set 元素等价的语句为:注意参数 suffixOverrides。

<select id="getTeacher" resultMap="rMap" parameterType="map">
    update teacher
    <trim prefix="set" suffixOverrides=",">
        <if test="name!=null">
            t_name=#{name},
        </if>
    </trim>
    where t_id=#{id}
</select>
<resultMap id="rMap" type="teacher">
    <result property="id" column="t_id"/>
    <result property="name" column="t_name"/>
</resultMap>

6.4 SQL 片段

相当于 Java 中的方法,将重复的 SQL 语句抽取出来,实现代码复用。

SQL 片段中最好不含有 where。

<!--创建SQL片段-->
<sql id="名称">
    内容
</sql>

<!--调用SQL片段-->
<include refid="SQL片段名称">
<!--抽取的SQL片段-->
<sql id="if">
    <if test="id != null">
        or t_id=#{id}
    </if>
    <if test="name!=null">
        or t_name=#{name}
    </if>
</sql>

<!--传入多个参数的动态SQL语句:trim-where)-->
<select id="getTeacher" resultMap="rMap" parameterType="map">
    select *from teacher
    <trim prefix="where" prefixOverrides="and |or ">
        <include refid="if"></include>
    </trim>
</select>
<resultMap id="rMap" type="teacher">
    <result property="id" column="t_id"/>
    <result property="name" column="t_name"/>
</resultMap>

6.4 foreach

遍历集合中的数据,拼接 SQL 字符串。

<!--查询满足多个条件的数据,相当于select * from teacher where t_id=? or t_id=?-->
<select id="getTeacher" resultMap="rMap" parameterType="map">
    select * from teacher
    <where>
        <foreach collection="idList" index="index" item="item" open="(" separator="or" close=")">
            t_id=#{item}
        </foreach>
    </where>
</select>
<resultMap id="rMap" type="teacher">
    <result property="id" column="t_id"/>
    <result property="name" column="t_name"/>
</resultMap>

参数说明:

  • collection:要遍历的 list 集合,参数名就是传递的集合名。
  • index:集合索引。
  • item:集合数据。
  • open:拼接的字符串首字符或字符串,一定要注意关键字或符号之间有空格
  • separator:要拼接的 SQL 字符串的间隔符号,例如“,”,”or” 等。
  • close:SQL 字符串拼接的末尾字符串或字符。

第 7 章 Mybatis 缓存

7.1 缓存简介

1、缓存定义:

  • 缓存就是存在内存中的临时数据。
  • 将用户经常查询的数据放在缓存(内存)中,用户去查询数据就不用从磁盘上(关系型数据库数据文件)查询,而是从缓存中查询,从而提高查询效率,解决了高并发系统的性能问题。

2、缓存:

  • 减少和数据库的交互次数,减少系统开销,提高系统效率。

7.2 Mybatis 缓存

7.2.1 简介

Mybatis 包含一个非常强大的查询缓存特性,它可以非常方便地定制和配置缓存。缓存可以极大的提升查询效率。

Mybatis系统中默认定义了两级缓存:一级缓存和二级缓存

  • 默认情况下,只有―级缓存开启。(SqlSession 级别的缓存也称为本地缓存)
  • 二级缓存需要手动开启和配置,是基于 namespace 级别的缓存。
  • 为了提高扩展性,Mybatis 定义了缓存接口 Cache。我们可以通过实现 Cache 接口来自定义二级缓存。
  • 只有 SqlSession 关闭才能使用缓存。

7.2.2 缓存效果

  • 映射语句文件中的所有 select 语句的结果将会被缓存。
  • 映射语句文件中的所有 insert、update 和 delete 语句会刷新缓存。
  • 缓存会使用最近最少使用算法(LRU, Least Recently Used)算法来清除不需要的缓存。
  • 缓存不会定时进行刷新(也就是说,没有刷新间隔)。
  • 缓存会保存列表或对象(无论查询方法返回哪种)的 1024 个引用。
  • 缓存会被视为读/写缓存,这意味着获取到的对象并不是共享的,可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。

7.2.3 清除策略

  • LRU – 最近最少使用:移除最长时间不被使用的对象。
  • FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
  • SOFT – 软引用:基于垃圾回收器状态和软引用规则移除对象。
  • WEAK – 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象。

默认的清除策略是 LRU。

7.3 一级缓存

一级缓存也叫本地缓存,仅仅对一个会话中的数据进行缓存,即创建 sqlSession 到其关闭就对数据进行缓存。

  • 与数据库同一次会话期间查询到的数据会放在本地缓存中。
  • 以后如果需要获取相同的数据,直接从缓存中拿,没必须再去查询数据库。

7.4 二级缓存

7.4.1 简介

  • 二级缓存也叫全局缓存,由于一级缓存作用域太低,所以诞生了二级缓存;
  • 是基于 namespace 级别的缓存,一个名称空间,对应一个二级缓存。

7.4.2 工作机制

  • 一个会话查询一条数据,这个数据就会被放在当前会话的一级缓存中;
  • 如果当前会话关闭,则该会话对应的一级缓存就不存在了,一级缓存中的数据被保存到二级缓存中;
  • 新的会话查询信息,就可以从二级缓存中获取内容;
  • 不同的 mapper 查出的数据会放在自己对应的缓存(map)中。

7.4.3 cache 标签

启用全局的二级缓存,只需要在你的 Mapper 映射文件中添加一行:

<cache/>

缓存只作用于 cache 标签所在的映射文件中的语句。如果你混合使用 Java API 和 XML 映射文件,在共用接口中的语句将不会被默认缓存。你需要使用 @CacheNamespaceRef 注解指定缓存作用域。

官网配置(可自行配置):

<cache
  eviction="FIFO"
  flushInterval="60000"
  size="512"
  readOnly="true"/>

这个更高级的配置创建了一个 FIFO 缓存,每隔 60 秒刷新,最多可以存储结果对象或列表的 512 个引用,而且返回的对象被认为是只读的,因此对它们进行修改可能会在不同线程中的调用者产生冲突。

  • flushInterval(刷新间隔)属性:可以被设置为任意的正整数,设置的值应该是一个以毫秒为单位的合理时间量。 默认情况是不设置,也就是没有刷新间隔,缓存仅仅会在调用语句时刷新。
  • size(引用数目)属性:可以被设置为任意正整数,要注意欲缓存对象的大小和运行环境中可用的内存资源。默认值是 1024。
  • readOnly(只读)属性:可以被设置为 true 或 false。只读的缓存会给所有调用者返回缓存对象的相同实例。 因此这些对象不能被修改。这就提供了可观的性能提升。而可读写的缓存会(通过序列化)返回缓存对象的拷贝。 速度上会慢一些,但是更安全,因此默认值是 false。

如果 cache 没有配置,则会报实体类没有序列化的错误,因此需要实现实体类序列化,或者配置 cache 即可。

7.4.4 开启全局缓存

如果使用缓存就必须在 settings 中配置如下代码:

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

测试代码:

package com.luochen.dao;


import com.luochen.pojo.Teacher;
import com.luochen.utils.MyBatisUtil;
import org.apache.ibatis.session.SqlSession;
import org.junit.Test;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

public class MapperTest {
    @Test
    public void getTeacher(){
        // 创建两个SqlSession对象
        SqlSession sqlSession = MyBatisUtil.getSqlSession();
        SqlSession sqlSession2 = MyBatisUtil.getSqlSession();

        HashMap<String, Object> map = new HashMap<>();
        ArrayList<String> list = new ArrayList<>();
        list.add("2001");
        list.add("2002");
        map.put("idList",list);

        TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class);
        List<Teacher> teacher = mapper.getTeacher(map);
        MyBatisUtil.close(sqlSession);
        teacher.forEach(System.out::println);

        TeacherMapper mapper2 = sqlSession2.getMapper(TeacherMapper.class);
        List<Teacher> teacher2 = mapper2.getTeacher(map);
        teacher2.forEach(System.out::println);
        System.out.println(teacher==teacher2); // true
        MyBatisUtil.close(sqlSession2);
    }
}
<cache eviction="FIFO"
       flushInterval="60000"
       size="512"
       readOnly="true"/>

<select id="getTeacher" resultMap="rMap" parameterType="map">
    select * from teacher
    <where>
        <foreach collection="idList" index="index" item="item" open="and (" separator="or" close=")">
            t_id=#{item}
        </foreach>
    </where>
</select>
<resultMap id="rMap" type="teacher">
    <result property="id" column="t_id"/>
    <result property="name" column="t_name"/>
</resultMap>

7.4.5 Mybatis 缓存原理

一级缓存关闭后,其中的数据存储在二级缓存中,如果在二级缓存中有数据就不需要连接数据库了,因此,Mybatis 缓存是先访问二级缓存中的数据,再访问数据库中的数据。

7.5 自定义缓存 encache

一般不用,用 Radis 实现缓存。

第 8 章 PageHelper

PageHelper官方网站

PageHelper 是一个分页插件,当需要展示数据时,可以先全部查出来,再存储到该插件对象中,避免多次查询数据库。

1、导入依赖:

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

2、插件配置

<!--在Mybatis核心配置文件中进行配置 -->
<plugins>
    <plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
</plugins>

3、测试

@Test
public void getAllStudentTest(){
    // 读取配置文件
    InputStream resourceAsStream = MyBatisTest.class.getClassLoader().getResourceAsStream("mybatis-config.xml");
    // 创建SqlSessionFactory对象
    SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
    // 创建SqlSession对象,这里面存储的就是Connection对象
    SqlSession sqlSession = sqlSessionFactory.openSession();
    // 创建接口动态代理,使得接口不需要实现类
    StudentMapper mapper = sqlSession.getMapper(StudentMapper.class);

    // 开启分页,获取当前页,一定要先开启,再执行方法,页数从1开始
    PageHelper.startPage(1,5);
    // 执行方法
    List<Student> student = mapper.getAllStudent();
    PageInfo<Student> studentPageInfo = new PageInfo<>(student);
    System.out.println("总条数:" + studentPageInfo.getTotal());
    System.out.println("总页数:" + studentPageInfo.getPages());
    //当前页数据展示
    studentPageInfo.getList().forEach(System.out::println);

    sqlSession.close();
}

第 9 章 配置数据源

这里配置阿里巴巴的 Druid 连接池。

1、导入依赖:

<dependency>
  <groupId>com.alibaba</groupId>
  <artifactId>druid</artifactId>
  <version>1.2.8</version>
</dependency>

2、创建 DruidDataSourceFactory, 并继承 PooledDataSourceFactory,替换数据源。

之所以继承 PooledDataSourceFactory,是因为 Mybatis 中的默认数据源就是继承该类,因此我们也需要继承,并传入我们的数据源。

原本的数据源:

public class PooledDataSourceFactory extends UnpooledDataSourceFactory {

    public PooledDataSourceFactory() {
        this.dataSource = new PooledDataSource();
    }
}

我们自己的数据源:

public class DruidDataSourceFactory extends PooledDataSourceFactory {
    public DruidDataSourceFactory() {
        this.dataSource = new DruidDataSource();//替换数据源
    }
}

3、设置 Mybatis 核心配置类:

<environments default="development">
    <environment id="development">
        <transactionManager type="JDBC"/>
        <dataSource type="com.luochen.utils.DruidDataSourceFactory">
            <!--注意:Druid连接池的数据库驱动名称需要改为driverClass取配置文件属性-->
            <property name="driverClass" value="${driver}"/>
            <property name="url" value="${url}"/>
            <property name="username" value="${username}"/>
            <property name="password" value="${password}"/>
        </dataSource>
    </environment>
</environments>

  目录