Mybatis学习笔记

1 Mybatis原理

一般都是一个数据库对应一个SqlSessionFactory​对象。


什么是框架?

  1. 框架其实就是对通用代码的封装,提前写好了一堆接口和类型,

MyBatis的特点:

  1. MyBatis是一个支持定制化SQL,存储过程以及高级映射得到优秀的持久层框架。
  2. MyBatis避免了几乎所有的JDBC代码和手动设置参数以及获取结果集。
  3. MyBatis可以使用简单的XML或注解用于配置和原始映射,将接口和ava的POJO(Plain Old」Java Objects,普通的ava对象)映射成数据库中的记录。
  4. MyBatis是一个半自动的ORM(Object Relation Mapping)框架。

1.1 关于Mybatis中的事务管理机制

在mybatis-config.xml中,可以通过以下的配置进行mybatis的事务管理。

<transactionManager type="JDBC"/>

2 SeqSession工具类封装

package 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;

public class SqlSessionUtil {
    // 一个sqlSessionFactory对应一个environment,一个environment通常一个数据库
    private static SqlSessionFactory sqlSessionFactory;

    static  {
        try {
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("mybatis-config.xml"));
        } catch (IOException e) {
            throw new RuntimeException(e);
        }
    }
    private SqlSessionUtil() {}

    /**
     * 获取会话对象
     * @return
     * @throws IOException
     */
    public static SqlSession openSession() throws IOException {
        SqlSession sqlSession = sqlSessionFactory.openSession();
        return sqlSession;
    }
}

3 使用MyBatis完成CURD

import org.apache.ibatis.session.SqlSession;
import org.junit.jupiter.api.Test;
import pojo.Car;
import utils.SqlSessionUtil;

import java.io.IOException;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

public class CarMapperTest {
    @Test
    void testInsertCar() throws IOException {
        SqlSession sqlSession = SqlSessionUtil.openSession();

        Map<String, Object> map = new HashMap<>();
        map.put("k1", "103");
        map.put("k2", "奔驰E300L");
        map.put("k3", 50.3);
        map.put("k4", "2020-10-01");
        map.put("k5", "燃油车");

        int count = sqlSession.insert("insertCar", map);
        System.out.println(count);
        sqlSession.commit();
        sqlSession.close();
    }

    @Test
    void testSelectCarByPojo() throws IOException {
        SqlSession sqlSession = SqlSessionUtil.openSession();

        Car car = new Car(null, "555", "奥迪A8", 78.9, "2024-10-01", "燃油车");

        int count = sqlSession.insert("insertCar", car);
        System.out.println(count);
        sqlSession.commit();
        sqlSession.close();
    }

    @Test
    void testDeleteById() throws IOException {
        SqlSession sqlSession = SqlSessionUtil.openSession();
        int count = sqlSession.delete("deleteById", 113);

        sqlSession.commit();
        sqlSession.close();
        System.out.println(count);
    }

    @Test
    public void testUpdateCarByPOJO() throws IOException {
        // 准备数据
        Car car = new Car();
        car.setId(106);
        car.setCarNum("102");
        car.setBrand("比亚迪汉");
        car.setGuidePrice(30.23);
        car.setProduceTime("2018-09-10");
        car.setCarType("电车");
        // 获取SqlSession对象
        SqlSession sqlSession = SqlSessionUtil.openSession();
        // 执行SQL语句
        int count = sqlSession.update("updateById", car);

        sqlSession.commit();
        sqlSession.close();
        System.out.println("更新了几条记录:" + count);
    }

    @Test
    public void testSelectCarById() throws IOException {
        // 获取SqlSession对象
        SqlSession sqlSession = SqlSessionUtil.openSession();
        // 执行SQL语句
        Object car = sqlSession.selectOne("selectCarById", 1);
        System.out.println(car);
    }

    @Test
    public void testSelectCarAll() throws IOException {
        // 获取SqlSession对象
        SqlSession sqlSession = SqlSessionUtil.openSession();
        // 执行SQL语句
        List<Object> cars = sqlSession.selectList("selectCarAll");
        // 输出结果
        cars.forEach(car -> System.out.println(car));
    }
}
<?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="pojo.Car">
    <insert id="insertCar">
        INSERT INTO t_car (car_num, brand, guide_price, produce_time, car_type) VALUES (#{carNum},#{brand},#{guidePrice},#{produceTime},#{carType});
    </insert>

    <delete id="deleteById">
        delete from t_car where id=#{id}
    </delete>

    <update id="updateById">
        update t_car set
                         car_num = #{carNum}, brand = #{brand},
                         guide_price = #{guidePrice}, produce_time = #{produceTime},
                         car_type = #{carType}
        where id = #{id}
    </update>

    <select id="selectCarById" resultType="pojo.Car">
        select
            id, car_num as carNum, brand, guide_price as guidePrice, produce_time as produceTime, car_type as carType
        from
            t_car
        where
            id = #{id}
    </select>

    <!--虽然结果是List集合,但是resultType属性需要指定的是List集合中元素的类型。-->
    <select id="selectCarAll" resultType="pojo.Car">
        <!--记得使用as起别名,让查询结果的字段名和java类的属性名对应上。-->
        select
        id, car_num as carNum, brand, guide_price as guidePrice, produce_time as produceTime, car_type as carType
        from
        t_car
    </select>
</mapper>

4 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>
    <environments default="development">
        <!--一个sqlSessionFactory对应一个environment,一个environment通常对应一个数据库-->
        <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/powernode"/>
                <property name="username" value="root"/>
                <property name="password" value="root"/>
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="CarMapper.xml"/>
        <mapper resource="CarMapper2.xml"/>
    </mappers>
</configuration>
  • configuration:根标签,表示配置信息。
  • environments:环境(多个),以“s”结尾表示复数,也就是说mybatis的环境可以配置多个数据源。
    • default属性:表示默认使用的是哪个环境,default后面填写的是environment的id。default的值只需要和environment的id值一致即可
  • environment:具体的环境配置(主要包括:事务管理器的配置 + 数据源的配置
    • id: 给当前环境一个唯一标识,该标识用在environments的default后面,用来指定默认环境的选择。
  • transactionManager:配置事务管理器。
    • type属性:指定事务管理器具体使用什么方式,可选值包括两个
      • JDBC:使用JDBC原生的事务管理机制。底层原理:事务开启conn.setAutoCommit(false); ...处理业务...事务提交conn.commit();
      • MANAGED:交给其它容器来管理事务,比如WebLogic、JBOSS等。如果没有管理事务的容器,则没有事务。没有事务的含义:只要执行一条DML语句,则提交一次
  • dataSource:指定数据源
    • type属性:用来指定具体使用的数据库连接池的策略,可选值包括三个
      • UNPOOLED:采用传统的获取连接的方式,虽然也实现Javax.sql.DataSource接口,但是并没有使用池的思想。
      • POOLED:采用传统的javax.sql.DataSource规范中的连接池,mybatis中有针对规范的实现。
      • JNDI:采用服务器提供的JNDI技术实现,来获取DataSource对象,不同的服务器所能拿到DataSource是不一样。如果不是web或者maven的war工程,JNDI是不能使用的。
  • mappers:在mappers标签中可以配置多个sql映射文件的路径。
  • mapper:配置某个sql映射文件的路径。
    • resource属性:使用相对于类路径的资源引用方式。
    • url属性:使用完全限定资源定位符(URL)方式。

1 MyBatis小技巧

1.1 #{}${}的区别

注意:查询语句不需要commit,其他三种语句则需要commit

// 使用#{}输出的信息
2024-09-19 21:23:23.560 [main] DEBUG c.p.mybatis.mapper.CarMapper.selectByCarType - ==>  Preparing: select id, car_num as carNum, brand, guide_price as guidePrice, produce_time as produceTime, car_type as carType from t_car where car_type = ?
2024-09-19 21:23:23.650 [main] DEBUG c.p.mybatis.mapper.CarMapper.selectByCarType - ==> Parameters: 新能源(String)
2024-09-19 21:23:23.849 [main] DEBUG c.p.mybatis.mapper.CarMapper.selectByCarType - <==      Total: 3


// 使用${}报的错误
2024-09-19 21:21:55.038 [main] DEBUG c.p.mybatis.mapper.CarMapper.selectByCarType - ==>  Preparing: select id, car_num as carNum, brand, guide_price as guidePrice, produce_time as produceTime, car_type as carType from t_car where car_type = 新能源
2024-09-19 21:21:55.116 [main] DEBUG c.p.mybatis.mapper.CarMapper.selectByCarType - ==> Parameters: 

org.apache.ibatis.exceptions.PersistenceException: 
### Error querying database.  Cause: java.sql.SQLSyntaxErrorException: Unknown column '新能源' in 'where clause'
### The error may exist in CarMapper.xml
### The error may involve defaultParameterMap
### The error occurred while setting parameters
### SQL: select id,                car_num      as carNum,                brand,                guide_price  as guidePrice,                produce_time as produceTime,                car_type     as carType         from             t_car         where             car_type = 新能源
### Cause: java.sql.SQLSyntaxErrorException: Unknown column '新能源' in 'where clause'

#{}​底层使用PreparedStatement​:先进行SQL语句的编译,然后给SQL语句的占位符?进行赋值,没有SQL注入的风险。

${}​底层使用Statement​,SQL无预编译,直接拼接参数,有SQL注入的攻击风险。

  • 所有参数位置,都应该使用#{}​。
  • 需要动态表名等,才使用${}​。例如要动态输入表名。

优先使用#{},避免SQL注入的风险

1.2 什么时候使用${}

Preparing: select id, car_num as carNum, brand, guide_price as guidePrice, produce_time as produceTime, car_type as carType from t_car order by produce_time ?
Parameters: ASC(String)

如果需要将SQL语句的关键字放到SQL语句中,只能使用${}

1.3 拼接表名

向SQL语句中拼接表明,就需要使用${}

1.4 批量删除

delete from t_car where id in(${ids})

1.5 模糊查询

1.6 别名机制

<typeAliases>
    <!--别名自己指定的 -->
    <!--<typeAlias type="com.powernode.mybatis.pojo.Car" alias="Car"/>
    <typeAlias type="com.powernode.mybatis.pojo.Log" alias="log"/>-->
    
    <!--采用默认的别名机制-->
    <!--<typeAlias type="com.powernode.mybatis.pojo.Car"/>
    <typeAlias type="com.powernode.mybatis.pojo.Log"/>-->

    <!--将指定包下的所有类全部自动起别名,适合于包下有很多类的情况-->
    <package name="com.powernode.mybatis.pojo"/>
</typeAliases>


<select id="selectByCarType" resultType="Car">
    select id,
           car_num      as carNum,
           brand,
           guide_price  as guidePrice,
           produce_time as produceTime,
           car_type     as carType
    from
        t_car
    where
        car_type = #{carType}
</select>

所有别名不区分大小写,namespace不能使用别名机制

1.7 mybaits-config.xml文件中的mappers标签

mapper标签的属性有三个

  • resource:从类的根路径下开始查找资源,采用这种方式的话,配置文件需要放到类路径当中才行。
  • url:绝对路径的方式。这种方式下配置文件可以放到任意路径下,只要提供一个绝对路径即可。(这种方式使用极少,因为移植性太差。)
  • class:写mapper接口的全限定接口名(带有包名)。
    • 如果你class指定是:com.powernode.mybatis,mapper.CarMapper,那么mybatis框架会自动去com/powernode/mybatis/mapper目录下查找CarMapper.xml文件。也就是说:如果你采用这种方式,那么你必须保证CarMapper.xmL文件和CarMapper接口必须在同一个目录下,并且名字一致。
<mappers>
    <!--<mapper resource="CarMapper.xml"/>
    <mapper resource="LogMapper.xml"/>-->

    <!--<mapper class="com.powernode.mybatis.mapper.CarMapper"/>
    <mapper class="com.powernode.mybatis.mapper.LogMapper"/>-->

    <!--这种方式在实际开发中最常用-->
    <package name="com.powernode.mybatis.mapper"/>
</mappers>

1.8 IDEA配置文件模板

File -> Setting -> Editor -> File and Code Templates -> +

1.9 插入数据时自动获取生成的主键

<!--
    useGeneratedKeys="true"  表示使用自动生成的主键值
    keyProperty="id"  表示主键值赋值给对象的哪个属性
-->
<insert id="insertCarUserGenerateKeys" useGeneratedKeys="true" keyProperty="id">
    insert into  t_car(id, car_num, brand, guide_price, produce_time, car_type)
    values (null, #{carNum}, #{brand}, #{guidePrice}, #{produceTime}, #{carType})
</insert>

2 MyBatis参数处理

单个参数-普通类型getEmploy(Long id)#{变量名}
单个参数- List类型getEmploy(List id)#{变量名[0]}
单个参数-对象类型addEmploy(Employ e)#{对象中属性名}
单个参数- Map类型addEmploy(Map<String,Object> m)#{map中属性名}
多个参数-无@ParamgetEmploy(Long id,String name)#{变量名}//新版兼容
多个参数-有@ParamgetEmploy(@Param(“id”)Long id, @Param(“name”)String name)#{param指定的名}
扩展:getEmploy(@Param(“id”)Long id,
@Param(“ext”)Map<String,Object> m,
@Param(“ids”)List ids,
@Param(“emp”)Employ e)
#{id}、 #{ext.name}、#​{ext.age}, #{ids[0]}、#​{ids[1]}, #{e.email}、#​{e.age}

2.1 单个简单类型参数

2.2 Map参数

2.3 POJO类(实体类参数)

2.4 多参数

<select id="selectByNameAndGender" resultType="Student">
    <!-- select * from t_student where name = #{arg0} and gender = #{arg1} -->
    select * from t_student where name = #{param1} and gender = #{param2}
</select>

2.5 @Param注解(命名参数)

List<Student> selectByNameAndGender2(@Param("name") String name, @Param("gender") Character gender);

3 查询语句专题

3.1 返回Car

3.2 返回List<Car>

3.3 返回Map

3.4 返回List<Map>

3.5 返回Map(String, Map)

3.6 resultMap结果映射

查询结果的列名和java对象的属性名对应不上怎么办?

  1. 使用as给列起别名

    select id,
               car_num      as carNum,
               brand,
               guide_price  as guidePrice,
               produce_time as produceTime,
               car_type     as carType
        from t_car
        where id = #{id}
    
  2. 使用resultMap进行结果映射

    <resultMap id="carResultMap" type="car">
        <!--如果有主键,建议这里配置一个id标签,注意:这不是必须的。
        但是官方的解释是什么呢?这样的配置可以让mybatis提高效率。-->
        <id property="id" column="id"/>
        <result property="carNum" column="car_num" javaType="string" jdbcType="VARCHAR"/>
        <!--如果column和property是一样的,这个可以省略。-->
    	<!--<!<result property="brand" column="brand"/> -->
        <result property="guidePrice" column="guide_price"/>
        <result property="produceTime" column="produce_time"/>
        <result property="carType" column="car_type"/>
    </resultMap>
    
  3. 是否开启驼峰命名自动映射(配置setttings)

注意:使用自动映射的前提是Java和MySQL的命名要符合规范

在MyBatis的核心配置文件mybatis-config.xml编写如下的代码

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

3.7 返回总记录条数

<select id="selectTotal" resultType="long">
    select count(*) from t_car
</select>

4 动态SQL

4.1 if 标签

<?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.powernode.mybatis.mapper.CarMapper">
    <!--
    1. if标签中test属性是必须的。
    2. if标签中test属性的值是false或者trUe。
    3. 如果test是true,则if标签中的sgL语句就会拼接。反之,则不会拼接。
    4. test属性中可以使用的是:
        当使用了@Param注解,那么test中要出现的是@Parami注解指定的参数名。@Param("brand"),那么这里只能使用brand
        当没有使用@Param注解,那么test中要出现的是:param1 param2 param3 arg0 arg1 arg2...
        当使用了P0J0,那么test中出现的是P0J0类的属性名。
    5. 在mybatis的动态SQL当中,不能使用&&,只能使用and
    -->
    <select id="selectByMultiCondition" resultType="car">
        select * from t_car where 1=1
        <if test="brand != null and brand != ''">
            and brand like "%"#{brand}"%"
        </if>
        <if test="guidePrice != null and guidePrice != ''">
            and guide_price > #{guidePrice}
        </if>
        <if test="carType != null and carType != ''">
            and car_type = #{carType}
        </if>
    </select>
</mapper>

4.2 where 标签

where标签的作用:让where子句更加动态智能。

  • 所有条件都为空时,wheret标签保证不会生成where子句。
  • 自动去除某些条件前面多余的and或or。
<select id="selectByMultiConditionWithWhere" resultType="car">
    select * from t_car
    <!--where标签是专门负责where子句动态生成的 -->
    <where>
        <if test="brand != null and brand != ''">
            and brand like "%"#{brand}"%"
        </if>
        <if test="guidePrice != null and guidePrice != ''">
            and guide_price > #{guidePrice}
        </if>
        <if test="carType != null and carType != ''">
            and car_type = #{carType}
        </if>
    </where>
</select>

4.3 trim 标签

trim标签的属性:

  • prefix:在trim标签中的语句前添加内容
  • suffix:在trim标签中的语句后添加内容
  • prefixOverrides:前缀覆盖掉(去掉)
  • suffixOverrides:后缀覆盖掉(去掉)
<!--
    prefix:加前缀
    suffix:加后缀
    prefixOverrides:删除前缀
    suffixOverrides:删除后缀

    prefix="where" 是在trim标签所有内容的前面添加where
    suffixOverrides="and|or" 把trim标签中内容的后缀and或or去掉
-->
<select id="selectByMultiConditionWithTrim" resultType="car">
    select * from t_car
    <trim prefix="where" suffixOverrides="and|or">
        <if test="brand != null and brand != ''">
            brand like "%"#{brand}"%" or
        </if>
        <if test="guidePrice != null and guidePrice != ''">
            guide_price > #{guidePrice} and
        </if>
        <if test="carType != null and carType != ''">
            and car_type = #{carType}
        </if>
    </trim>
</select>

4.4 set 标签

主要使用在updatei语句当中,用来生成set关键字,同时去掉最后多余的, 比如我们只更新提交的不为空的字段,如果提交的数据是空或者"”,那么这个字段我们将不更新。

<update id="updateBySet">
    update t_car
    <set>
        <if test="carNum != null and carNum != ''">car_num = #{carNum},</if>
        <if test="brand != null and brand != ''">brand = #{brand},</if>
        <if test="guidePrice != null and guidePrice != ''">guide_price = #{guidePrice},</if>
        <if test="produceTime != null and produceTime != ''">produce_time = #{produceTime},</if>
        <if test="carType != null and carType != ''">car_type = #{carType},</if>
    </set>
    where id = #{id}
</update>

4.5 choose when otherwise 标签

这个三个标签一般同时使用,类似于Java中的if..else if...else语句

<select id="selectByChoose" resultType="car">
    select * from t_car
    <where>
        <choose>
            <when test="brand != null and brand != ''">
                brand like "%"#{brand}"%"
            </when>
            <when test="guidePrice != null and guidePrice != ''">
                guide_price > #{guidePrice}
            </when>
            <otherwise>
                car_type = #{carType}
            </otherwise>
        </choose>
    </where>
</select>

4.6 foreach标签

用于循环数组或集合,动态生成sql,

4.6.1 批量删除

第一种写法

<!--
    foreach标签的属性:
        collection:指定数组或者集合
        item:代表数组或集合中的元素
        separator:循环之间的分隔符
        open:foreach循环拼接的所有sqL语句的最前面以什么开始
        close:foreach循环拼接的所有sqL语句的最后面以什么结束
-->
<delete id="deleteByIds">
    <!--
		第一种写法
        delete from t_car where id in (
        <foreach collection="ids" item="id" separator=",">
            #{id}
        </foreach>
        )
    -->
    delete from t_car where id in
    <foreach collection="ids" item="id" separator="," open="(" close=")">
        #{id}
    </foreach>
</delete>

第二种写法

<delete id="deleteByIds2">
    delete from t_car where
    <foreach collection="ids" item="id" separator="or">
        id=#{id}
    </foreach>
</delete>

4.6.2 批量添加

<insert id="insertBatch">
    insert into t_car values
    <foreach collection="cars" item="car" separator=",">
        (null, #{car.carNum}, #{car.brand}, #{car.guidePrice}, #{car.produceTime}, #{car.carType})
    </foreach>
</insert>

4.7 sql标签与include标签

sql标签用来声明sql片段 include标签用来将声明的sql片段包含到某个sql语句当中 作用:代码复用。易维护。

<!--声明一个sql片段-->
<sql id="carColumnNameSql">
   id,
   car_num      as carNum,
   brand,
   guide_price  as guidePrice,
   produce_time as produceTime,
   car_type     as carType
</sql>

<select id="selectById" resultType="car">
    select
        <!--将声明的sql片段包含进来-->
        <include refid="carColumnNameSql"/>
    from t_car where id = #{id}
</select>

5 MyBatis的高级映射及延迟加载

5.1 多对一

多种方式,常见的包括三种:

  1. 一条SQL语句,级联属性映射。
  2. 一条SQL语句,association。
  3. 两条SQL语句,分步查询。(这种方式常见,优点一是可复用,优点二是支持懒加载)

5.1.1 第一种方式:级联属性映射

<resultMap id="studentResultMap" type="Student">
    <id property="sid" column="sid"/>
    <result property="sname" column="sname"/>
    <result property="clazz.cid" column="cid"/>
    <result property="clazz.cname" column="cname"/>
</resultMap>

<select id="selectById" resultMap="studentResultMap">
    select s.sid,
           s.sname,
           c.cid,
           c.cname
    from t_stu s
             left join t_clazz c on s.cid = c.cid
    where s.sid = #{sid}
</select>

5.1.2 第二种方式:级联属性映射

<resultMap id="studentResultMapAssociation" type="student">
    <id property="sid" column="sid"/>
    <result property="sname" column="sname"/>
    <!--
        association:一个Student对象关联一个Clazz对象
            property:提供要映射的POJO属性名
            javaType:指定要映射的java类型
    -->
    <association property="clazz" javaType="clazz">
        <id property="cid" column="cid"/>
        <result property="cname" column="cname"/>
    </association>
</resultMap>


<select id="selectByIdAssociation" resultMap="studentResultMapAssociation">
    select s.sid,
           s.sname,
           c.cid,
           c.cname
    from t_stu s
             left join t_clazz c on s.cid = c.cid
    where s.sid = #{sid}
</select>

5.1.3 第三种方式:分步查询

5.2 一对多

有两种实现方式

  1. collection(了解一些即可)
  2. 分布查询

5.2.1 第一种方式 collection

<resultMap id="clazzResultMap" type="Clazz">
    <id property="cid" column="cid"/>
    <result property="cname" column="cname"/>
    <collection property="students" ofType="student">
        <id property="sid" column="sid"/>
        <result property="sname" column="sname"/>
    </collection>
</resultMap>

<select id="selectByCollection" resultMap="clazzResultMap">
    select c.cid, c.cname, s.sid, s.sname
    from t_clazz c
             left join t_stu s on c.cid = s.cid
    where c.cid = #{cid}
</select>

5.2.2 第二种方式:分布查询

6 MyBatis的缓存

缓存通常是我我们程序开发中优化程序的重要手段。

缓存的作用:通过减少IO的方式,来提高程序的执行效率。

mybatis的缓存:将selecti语句的查询结果放到缓存(内存)当中,下一次还是这条selecti语句的话,直接从缓存中取,不再查数据库。一方面是减少了IO。另一方面不再执行繁琐的查找算法。效率大大提升。

mybatis缓存包括:

  • 一级缓存:将查询到的数据存储到SqlSession中。
  • 二级缓存:将查询到的数据存储到SqlSessionFactory中。
  • 或者集成其它第三方的缓存:比如EhCache【Uava语言开发的】、Memcache【C语言开发的】等。

缓存只针对于DQL​语句,也就是说缓存机制只对应select语句。

6.1 一级缓存

一级缓存默认是开启的,不需要做任何配置。

原理:只要便用同一个SqlSession对象执行同一条SQL语句,就会走缓存。

以下两种情况一级缓存会失效:

  1. 执行了sqlSession的clearCache()方法,这是手动清空缓存,
  2. 执行了INSERT或DELETE或UPDATE语句。不管你是操作哪张表的,都会清空一级缓存

6.2 二级缓存

二级缓存的范围是SqlSessionFactory。

使用二级缓存需要具备以下几个条件:

  1. <setting name="cacheEnabled" value=true>全局性地开启或关闭所有映射器配置文件中已配置的任何缓存,默认就是true,无需设置。
  2. 在需要便用二级缓存的SqlMapper.xml文件中添加配置(标签):<cache/>
  3. 便用二级缓存的实体类对象必须是可序列化的,也就是必须实现java.io.Serializable接口。
  4. SqlSession对像关闭或提交之后,一级缓存中的数据才会被写入到二级缓存当中,此时二级缓存才可用。

二级缓存的失效:只要两次查询之间出现了增删改操作。二级缓存就会失效。【一级缓存也会失效】

6.3 MyBatis缓存查询的顺序

  • 先查询二级缓存,因为二级缓存中可能会有其他程序已经查出来的数据,可以拿来直接使用。
  • 如果二级缓存没有命中,再查询一级缓存。
  • 如果一级缓存也没有命中,则查询数据库。
  • SqlSession关闭之后,一级缓存中的数据会写入二级缓存。

6.4 MyBatis集成EhCache(了解)

注意:只能使用其他的第三方技术来代替二级缓存,但是一级缓存是无法代替的。

  • 第一步:引入jar包
<dependency>
    <groupId>org.mybatis.caches</groupId>
    <artifactId>mybatis-ehcache</artifactId>
    <version>1.3.0</version>
</dependency>
  • 第二步:创建EHCache的配置文件ehcache.xml
<?xml version="1.0" encoding="UTF-8" ?>
<ehcache xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:noNamespaceSchemaLocation="http://ehcache.org/ehcache.xsd"
         updateCheck="false">

    <!--diskStore: 持久化到磁盘上时的存储位置-->
    <diskStore path="D:\\aspring\\ehcache"/>

    <!--
      name:缓存名称。
      maxElementsInMemory:缓存最大个数。
      eternal:对象是否永久有效,一但设置了,timeout将不起作用。
      timeToIdleSeconds:设置对象在失效前的允许闲置时间(单位:秒)。仅当eternal=false对象不是永久有效时使用,可选属性,默认值是0,也就是可闲置时间无穷大。
      timeToLiveSeconds:设置对象在失效前允许存活时间(单位:秒)。最大时间介于创建时间和失效时间之间。仅当eternal=false对象不是永久有效时使用,默认是0.,也就是对象存活时间无穷大。
      overflowToDisk:当内存中对象数量达到maxElementsInMemory时,Ehcache将会对象写到磁盘中。
      diskSpoolBufferSizeMB:这个参数设置DiskStore(磁盘缓存)的缓存区大小。默认是30MB。每个Cache都应该有自己的一个缓冲区。
      maxElementsOnDisk:硬盘最大缓存个数。
      diskPersistent:是否缓存虚拟机重启期数据 Whether the disk store persists between restarts of the Virtual Machine. The default value is false.
      diskExpiryThreadIntervalSeconds:磁盘失效线程运行时间间隔,默认是120秒。
      memoryStoreEvictionPolicy:当达到maxElementsInMemory限制时,Ehcache将会根据指定的策略去清理内存。默认策略是LRU(最近最少使用)。你可以设置为FIFO(先进先出)或是LFU(较少使用)。
      clearOnFlush:内存数量最大时是否清除。
   -->

    <defaultCache
            maxElementsInMemory="10000"
            eternal="false"
            timeToIdleSeconds="120"
            timeToLiveSeconds="120"
            overflowToDisk="false"
            maxElementsOnDisk="10000000"
            diskPersistent="false"
            diskExpiryThreadIntervalSeconds="120"
            memoryStoreEvictionPolicy="LRU"
    />

    <cache
            name="cacheSpace"
            maxElementsInMemory="10000"
            eternal="false"
            timeToIdleSeconds="120"
            timeToLiveSeconds="120"
            overflowToDisk="false"
            maxElementsOnDisk="10000000"
            diskPersistent="false"
            diskExpiryThreadIntervalSeconds="120"
            memoryStoreEvictionPolicy="LRU"
    />

</ehcache>
  • 第三步:设置二级缓存的类型(不设置的话,默认使用MyBatis内置的缓存):在SQL映射文件(例如:CarMapper.xml)中添加如下代码。
<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>
  • 第四步:正常使用。

7 MyBati的逆向工程

所谓的逆向工程是:根据数据库表逆向生成java的pojo类,SqlMapper.xml文件,以及Mapper接口类等。

  • 第一步:引入jar包和对应的插件(添加依赖)
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.powernode</groupId>
    <artifactId>mybatis-010-generator1</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <properties>
        <maven.compiler.source>21</maven.compiler.source>
        <maven.compiler.target>21</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.mybatis</groupId>
            <artifactId>mybatis</artifactId>
            <version>3.5.16</version>
        </dependency>

        <dependency>
            <groupId>com.mysql</groupId>
            <artifactId>mysql-connector-j</artifactId>
            <version>9.0.0</version>
        </dependency>

        <dependency>
            <groupId>org.junit.jupiter</groupId>
            <artifactId>junit-jupiter-api</artifactId>
            <version>5.11.2</version>
            <scope>test</scope>
        </dependency>

        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.5.6</version>
        </dependency>
    </dependencies>


    <!--配置mybatis逆向工程的插件-->
    <!--定制构建过程-->
    <build>
        <!--可配置多个插件-->
        <plugins>
            <!--其中的一个插件:mybatis逆向工程插件-->
            <plugin>
                <!--插件的GAV坐标-->
                <groupId>org.mybatis.generator</groupId>
                <artifactId>mybatis-generator-maven-plugin</artifactId>
                <version>1.4.1</version>
                <!--允许覆盖-->
                <configuration>
                    <overwrite>true</overwrite>
                </configuration>
                <!--插件的依赖-->
                <dependencies>
                    <!-- https://mvnrepository.com/artifact/org.mybatis.generator/mybatis-generator-core -->
                    <dependency>
                        <groupId>org.mybatis.generator</groupId>
                        <artifactId>mybatis-generator-core</artifactId>
                        <version>1.4.2</version>
                    </dependency>
                    <!--mysql驱动依赖-->
                    <dependency>
                        <groupId>com.mysql</groupId>
                        <artifactId>mysql-connector-j</artifactId>
                        <version>9.0.0</version>
                    </dependency>
                </dependencies>
            </plugin>
        </plugins>
    </build>
</project>
  • 第二步:创建MyBatis的核心配置文件
  • 第三步:创建逆向工程的配置文件,配置文件名是固定的:generatorConfig.xml)
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE generatorConfiguration
        PUBLIC "-//mybatis.org//DTD MyBatis Generator Configuration 1.0//EN"
        "http://mybatis.org/dtd/mybatis-generator-config_1_0.dtd">

<generatorConfiguration>
    <!--
        targetRuntime有两个值:
            MyBatis3Simple:生成的是基础版,只有基本的增删改查。
            MyBatis3:生成的是增强版,除了基本的增删改查之外还有复杂的增删改查。一般使用增强版
    -->
    <context id="DB2Tables" targetRuntime="MyBatis3Simple">
        <!--防止生成重复代码-->
        <plugin type="org.mybatis.generator.plugins.UnmergeableXmlMappersPlugin"/>

        <commentGenerator>
            <!--是否去掉生成日期-->
            <property name="suppressDate" value="true"/>
            <!--是否去除注释-->
            <property name="suppressAllComments" value="true"/>
        </commentGenerator>

        <!--连接数据库信息-->
        <jdbcConnection driverClass="com.mysql.cj.jdbc.Driver"
                        connectionURL="jdbc:mysql://localhost:3306/mybatis"
                        userId="root"
                        password="mysqlmm0424">
        </jdbcConnection>

        <!-- javaBean的生成策略:生成pojo包名和位置 -->
        <javaModelGenerator targetPackage="com.powernode.mybatis.pojo" targetProject="src/main/java">
            <!--是否开启子包-->
            <property name="enableSubPackages" value="true"/>
            <!--是否去除字段名的前后空白-->
            <property name="trimStrings" value="true"/>
        </javaModelGenerator>

        <!-- 生成SQL映射文件的包名和位置 -->
        <sqlMapGenerator targetPackage="com.powernode.mybatis.mapper" targetProject="src/main/resources">
            <!--是否开启子包-->
            <property name="enableSubPackages" value="true"/>
        </sqlMapGenerator>

        <!-- 生成Mapper接口的包名和位置 -->
        <javaClientGenerator
                type="xmlMapper"
                targetPackage="com.powernode.mybatis.mapper"
                targetProject="src/main/java">
            <property name="enableSubPackages" value="true"/>
        </javaClientGenerator>

        <!-- 表名和对应的实体类名
        tableName设置为*号:表示对应所有表,此时不写domainObjectName
        -->
        <table tableName="t_car" domainObjectName="Car"/>
        <table tableName="t_stu" domainObjectName="Student"/>
    </context>
</generatorConfiguration>
  • 第四步:使用插件直接生成相应的文件。

generator插件的使用

8 MyBatis分页插件

  • 第一步:添加依赖。
<dependency>
    <groupId>com.github.pagehelper</groupId>
    <artifactId>pagehelper</artifactId>
    <version>6.1.0</version>
</dependency>
  • 第二步:在MyBatis的核心配置文件中配置分页插件。
<!--MyBatis的分页拦截器-->
<plugins>
    <plugin interceptor="com.github.pagehelper.PageInterceptor"/>
</plugins>
  • 第三步:开始编写代码来使用分页插件。
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.powernode.mybatis.mapper.CarMapper;
import com.powernode.mybatis.pojo.Car;
import com.powernode.mybatis.utils.SqlSessionUtil;
import org.apache.ibatis.session.SqlSession;
import org.junit.jupiter.api.Test;

import java.util.List;

public class CarMapperTest {
    @Test
    public void testSelectAll() {
        SqlSession sqlSession = SqlSessionUtil.openSession();
        CarMapper mapper = sqlSession.getMapper(CarMapper.class);

        // 在执行DQL语句之间,开启分页功能
        int pageNum = 2;
        int pageSize = 3;
        PageHelper.startPage(pageNum, pageSize);

        List<Car> cars = mapper.selectAll();
//        cars.forEach(System.out::println);
        // 封装分页信息对象
        // navigatePages一般为奇数
        PageInfo<Car> carPageInfo = new PageInfo<>(cars, 3);
        System.out.println(carPageInfo);
        sqlSession.close();
    }

    @Test
    public void testSelectByPage() {
        // 每页显示的条数
        int pageSize = 3;
        // 显示第几页:页码
        int pageNum = 3;

        int startIndex = (pageNum -1) * pageSize;
        SqlSession sqlSession = SqlSessionUtil.openSession();
        CarMapper mapper = sqlSession.getMapper(CarMapper.class);
        List<Car> cars = mapper.selectByPage(startIndex, pageSize);
        sqlSession.close();
        cars.forEach(System.out::println);
    }
}