MyBatis

MyBatis

MyBatis 是一个持久层框架,前身是 ibatis, 在 ibatis3.x 时,更名为 MyBatis。

MyBatis 在 java 和 sql 之间提供更灵活的映射方案,mybatis 可以将对数据表的操作(sql,方法)等等直接剥离,写到 xml 配置文件,实现和 java代码的解耦。

中文网:http://www.mybatis.cn/mybatis/index.html

1. 原理图

请添加图片描述

2. 快速入门

1)创建数据表

CREATE DATABASE `mybatis` CREATE TABLE `monster` (
    `id` INT NOT NULL AUTO_INCREMENT, 
    `age` INT NOT NULL, 
    `birthday` DATE DEFAULT NULL,
    `email` VARCHAR(255) NOT NULL ,
    `gender` TINYINT NOT NULL, 
    `name` VARCHAR(255) NOT NULL,
    `salary` DOUBLE NOT NULL,
    PRIMARY KEY (`id`)
) CHARSET=utf8

2)导入相关依赖

<dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
    <version>5.1.49</version>
</dependency>
<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>3.5.7</version>
</dependency>
<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.32</version>
    <scope>compile</scope>
</dependency>

3)创建mybatis配置文件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="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://127.0.0.1:3306/mybatis?useSSL=true&amp;useUnicode=true&amp;characterEncoding=UTF-8"/>
                <property name="username" value="root"/>
                <property name="password" value="hsp"/>
            </dataSource>
        </environment>
    </environments>
</configuration>

4)创建实体类

@Data
public class Monster {
    private Integer id;
    private Integer age;
    private String name;
    private String email;
    private Date birthday;
    private double salary;
    private Integer gender;
}

@Data为lombok注解,能自动生成getter、setter以及toString方法。

5)创建Mapper接口

public interface MonsterMapper {
	//添加方法
    public void addMonster(Monster monster);    
}

6)创建配置文件MonsterMapper.xml配置映射关系

<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.lhs.mapper.MonsterMapper">
    <insert id="addMonster" parameterType="com.lhs.pojo.Monster" useGeneratedKeys="true" keyProperty="id" >
        INSERT INTO monster (age,birthday,email,gender,name,salary)
        VALUES(#{age},#{birthday},#{email},#{gender},#{name},#{salary})
    </insert>
</mapper>

说明:

  • select:实现一个查询操作
  • insert:表示一个添加操作
  • id=“addMonster” 表示 MonsterMapper 接口的方法名
  • resultType=“xx” 返回的结果类型,如果没有就不需要写
  • parameterType=“com.lhs.pojo.Monster” 表示该方法输入的参数类型
  • (age,birthday,email,gender,name,salary) 表的字段名
  • #{age},#{birthday},#{email},#{gender},#{name},#{salary} 是实体类 Monster 的属性名

7)在mybatis-config.xml 中添加配置文件

<mappers>
    <!-- 这里会引入(注册)我们的 Mapper.xml 文件 -->
    <mapper resource="com/lhs/mapper/MonsterMapper.xml"/>
</mappers>

8)创建工具类来获取sqlsession

public class MyBatisUtils {
    private static SqlSessionFactory sqlSessionFactory;

    static {
        try {
            //使用 mybatis 第一步:获取 sqlSessionFactory 对象
            String resource = "mybatis-config.xml";
            InputStream inputStream = Resources.getResourceAsStream(resource);
            sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    public static SqlSession getSqlSession() {
        return sqlSessionFactory.openSession();
    }
}

SqlSession 提供了对数据库执行 SQL 命令所需的所有方法。可以通过 SqlSession 实例来直接执行已映射的 SQL 语句

9)测试

@Test
public void testAdd(){
    Monster monster = new Monster();
    monster.setAge(18);
    monster.setName("lxg");
    monster.setBirthday(new Date());
    monster.setEmail("123@11.com");
    monster.setSalary(11.11);
    monster.setGender(1);
    monsterMapper.addMonster(monster);
    // 执行完sql语句后会自动填充id的值
    System.out.println(monster.getId());
    //增删改,需要提交事务
    if (sqlSession != null) {
        sqlSession.commit();
        sqlSession.close();
    }
    System.out.println("保存成功!");

}

3. 日志输出

<dependency>
    <groupId>org.projectlombok</groupId>
    <artifactId>lombok</artifactId>
    <version>1.18.32</version>
    <scope>compile</scope>
</dependency>
<settings>
    <setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>

4. 原生api完成crud

我们除了创建mapper对象完成crud外,也可以用sqlSession直接来操作mapper

手动提交事务

@After
public void commit(){
    if (sqlSession != null) {
        sqlSession.commit();
        sqlSession.close();
    }
}

4.1 增

@Test
public void test03(){
    Monster monster = new Monster();
    monster.setAge(18);
    monster.setName("xxx");
    monster.setBirthday(new Date());
    monster.setEmail("12345@11.com");
    monster.setSalary(111.11);
    monster.setGender(1);
    int res = sqlSession.insert("com.lhs.mapper.MonsterMapper.addMonster",monster);
    System.out.println(res);
}

4.2 删

@Test
public void test04(){
    Monster monster = new Monster();
    monster.setId(1);
    int res = sqlSession.delete("com.lhs.mapper.MonsterMapper.deleteMonster",monster);
    System.out.println(res);
}

4.3 改

@Test
public void test05(){
    Monster monster = new Monster();
    monster.setId(3);
    monster.setName("yyy");
    int res = sqlSession.delete("com.lhs.mapper.MonsterMapper.updateMonster",monster);
    System.out.println(res);
}

4.4 查

@Test
public void test06(){
    List<Monster> monsters = sqlSession.selectList("com.lhs.mapper.MonsterMapper.getAllMonster");
    System.out.println(monsters);
}

5. 注解完成crud

我们可以直接将sql语句写在注解上,就不用在mapper.xml文件中配置sql语句了。

public interface MonsterMapper {
    @Insert(value = "INSERT INTO monster (age,birthday,email,gender,name,salary) VALUES(#{age},#{birthday},#{email},#{gender},#{name},#{salary})")
    public void addMonster(Monster monster);

    @Select(value = "select * from monster")
    public List<Monster> getAllMonster();

    @Delete(value = "delete from monster where id = #{id}")
    public void deleteMonster(Monster monster);

    @Update(value = "update from monster (age,birthday,email,gender,name,salary) VALUES(#{age},#{birthday},#{email},#{gender},#{name},#{salary}) where id = #{id}")
    public void updateMonster(Monster monster);
}

在mapper.xml中指定mapper接口即可

<mapper namespace="com.lhs.mapper.MonsterMapper" />

6. 配置文件

6.1 properties 属性

通过该属性,可以指定一个外部的 jdbc.properties 文件,引入我们的 jdbc 连接信息

jdbc.properties:

jdbc.user=root
jdbc.password=83929317fan
jdbc.url=jdbc:mysql://127.0.0.1:3306/mybatis?userSSL=false&amp;userUnicode=true&amp;characterEncoding=UTF-8
jdbc.driver=com.mysql.jdbc.Driver

mybatis-config.xml:

<!-- 这里就是引入 jdbc.properties 文件 -->
<properties resource="jdbc.properties"/>
<environments default="development">
    <environment id="development">
        <transactionManager type="JDBC"/>
        <dataSource type="POOLED">
            <property name="driver" value="${jdbc.driver}" />
            <property name="url" value="${jdbc.url}" />
            <property name="username" value="${jdbc.user}" />
            <property name="password" value="${jdbc.password}" />
        </dataSource>
    </environment>
</environments>

6.2 typeAliases 别名处理器

<typeAliases>
    <package name="com.lhs.pojo"/>
</typeAliases>
<select id="getAllMonster" resultType="Monster">
    select * from monster
</select>

6.3 完整配置

<?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>
    <settings>
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>
    <typeAliases>
        <!-- 如果一个包下有很多的类,我们可以直接引入包,这样
        该包下面的所有类名,可以直接使用
        -->
        <package name="com.lhs.pojo"/>
        <!-- 为某个 mapper 指定一个别名, 下面可以在 XxxxxMapper.xml 做相应简化处理 -->
    </typeAliases>
    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="${jdbc.driver}" />
                <property name="url" value="${jdbc.url}" />
                <property name="username" value="${jdbc.user}" />
                <property name="password" value="${jdbc.password}" />
            </dataSource>
        </environment>
    </environments>

    <mappers>
        <!-- 这里会引入(注册)我们的 Mapper.xml 文件 -->
        <!--        <mapper resource="com/lhs/mapper/MonsterMapper.xml"/>-->

        <!--        也可直接指定包名-->
        <package name="com.lhs.mapper"/>
    </mappers>
</configuration>

7. SQL 映射文件

MyBatis 的真正强大在于它的语句映射(在 XxxMapper.xml 配置), 由于它的异常强大, 如果拿它跟具有相同功能的 JDBC 代码进行对比,你会立即发现省掉了将近 95% 的代码。MyBatis 致力于减少使用成本,让用户能更专注于 SQL 代码。

SQL 映射文件常用的几个顶级元素(按照应被定义的顺序列出):

  • cache – 该命名空间的缓存配置。
  • cache-ref – 引用其它命名空间的缓存配置。
  • resultMap – 描述如何从数据库结果集中加载对象,是最复杂也是最强大的元素。
  • parameterType - 将会传入这条语句的参数的类全限定名或别名
  • sql – 可被其它语句引用的可重用语句块。
  • insert – 映射插入语句。
  • update – 映射更新语句。
  • delete – 映射删除语句。
  • select – 映射查询语句。

7.1 parameterType(输入参数类型)

parameterType可以传入简单类型,比如按照 id 查 Monster,也可以传入 POJO 类型

当传入的参数类是 String 时,可以使用 ${} 来接收参数

<!-- 实现 findMonsterByNameORId -->
<select id="findMonsterByNameORId" parameterType="Monster"
        resultType="Monster">
    SELECT * FROM monster
    WHERE id=#{id} OR name=#{name}
</select>

<!-- 看看模糊查询的使用 取值 需要 ${value} 取值-->
<select id="findMonsterByName" parameterType="String" resultType="Monster">
    SELECT * FROM monster
    WHERE name LIKE '%${value}%' </select>

7.2 传入HashMap

HashMap 传入参数更加灵活,比如可以灵活的增加查询的属性,而不受限于Pojo 属性本身。

可以将返回结果也设置为map: resultType="map"

1)增加接口

//查询 id > 10 并且 salary 大于 40, 要求传入的参数是 HashMap
public List<Monster> findMonsterByIdAndSalary_PrameterHashMap(Map<String, Object> map);

2)修改mapper

<!-- 实现 findMonsterByIdAndSalary_PrameterHashMap -->
<select id="findMonsterByIdAndSalary_PrameterHashMap" parameterType="map" resultType="Monster">
    SELECT * FROM monster
    WHERE id > #{id} AND salary > #{salary}
</select>

3)测试

@Test
public void findMonsterByIdAndSalary_PrameterHashMap() {
    Map<String, Object> map = new HashMap<String, Object>();
    map.put("id", 1);//条件
    map.put("salary", 924);
    List<Monster> monsterList =
        monsterMapper.findMonsterByIdAndSalary_PrameterHashMap(map);
    for (Monster monster : monsterList) {
        System.out.println(monster);
    }
    if (sqlSession != null) {
        sqlSession.close();
    }
}

7.3 resultMap(结果集映射)

当实体类的属性和表的字段名字不一致时,我们可以通过 resultMap 进行映射,从而屏蔽实体类属性名和表的字段名的不同。

<mapper namespace="com.lhs.mapper.UserMapper">
    <!-- 实现 addUser -->
    <insert id="addUser" parameterType="User">
        INSERT INTO user (user_name,user_email) VALUES(#{username}, #{useremail})
    </insert>
    <!-- 实现 findAllUser【resultmap 屏蔽属性名和字段名不一致 1.复用性好,高大上 】 -->
    <!-- 1. resultMap 表示我们定义一个结果映射
        2. type="User" 返回的真正的数据类型还是 User
        3. id="findAllUserMap", 给 resultMap 取名
        4. column="user_name" 表中的字段
        5. property="username" 表示对象的属性
-->
    <resultMap type="User" id="findAllUserMap">
        <result column="user_name" property="username"/>
        <result column="user_email" property="useremail"/>
    </resultMap>
    <select id="findAllUser" resultMap="findAllUserMap" >
        SELECT * FROM user
    </select>
</mapper>

8. 动态 SQL 语句

动态 SQL 是 MyBatis 的强大特性之一,使用 JDBC 或其它类似的框架,根据不同条件拼接 SQL 语句非常麻烦,例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号等,SQL 映射语句中的强大的动态 SQL 语言, 可以很好的解决这个问题 。

比如我们查询Monster 时,如果程序员输入的age 不大于0, 我们的sql语句就不带age 。更新 Monster 对象时,没有设置的新的属性值,就保持原来的值,设置了新的值,才更新。

动态 SQL 提供了如下几种常用的标签,类似我们 Java 的控制语句:

  • if [判断]
  • where [拼接 where 子句]
  • choose/when/otherwise [类似 java 的 switch 语句, 注意是单分支]
  • foreach [类似 in ]
  • trim [替换关键字/定制元素的功能]
  • set [在 update 的 set 中,可以保证进入 set 标签的属性被修改,而没有进入 set 的,保持原来的值]

8.1 if

需求:请查询 age 大于 10 的所有妖怪,如果程序员输入的 age 不大于 0, 则输出所有的妖怪

//根据 age 查询结果
public List<Monster>
findMonsterByAge(@Param("age") Integer age);
<!-- 实现 findMonsterByAge -->
<select id="findMonsterByAge" resultType="Monster" parameterType="Integer">
    SELECT * FROM monster WHERE 1=1
    <if test="age > 0">
        AND age > #{age}
    </if>
</select>

8.2 where

需求:查询 id 大于 20 的,并且名字是 “牛魔王” 的所有妖怪,注意,如果名字为空,或者输入的 id 小于 0, 则不拼接 sql 语句

//根据 id 和名字来查询结果
public List<Monster> findMonsterByIdAndName(Monster monster);
<!-- 实现 findMonsterByIdAndName 【where 和 if】 -->
<select id="findMonsterByIdAndName" parameterType="Monster" resultType="Monster">
    SELECT * FROM monster
    <!-- 使用 where 标签开始拼接
where 标签的好处是. 会自动的加入 where 子句. mybatis 会自动的去掉多余的 AND
-->
    <where>
        <if test="id > 0">
            AND id > #{id}
        </if>
        <if test="name != null and name != ''">
            AND name = #{name}
        </if>
    </where>
</select>

8.3 choose/when/otherwise

需求:如果给的 name 不为空,就按名字查询妖怪,如果指定的 id>0,就按 id 来查询妖怪, 要求使用 choose/when/otherwise 标签实现, 传入参数要求使用 Map

public List<Monster>
findMonsterByIdAndName_choose(Map<String, Object> map);
<!-- findMonsterByIdAndName_choose[使用 choose 标签] -->
<select id="findMonsterByIdAndName_choose" parameterType="map"
        resultType="Monster">
    SELECT * FROM monster
    <choose>
        <!-- 这里 test="name" 这 name 就是你 传入的 map 参数对应的 key -->
        <when test="name != null and name != ''">
            WHERE name = #{name}
        </when>
        <when test="id > 0">
            WHERE id > #{id}
        </when>
        <otherwise>
            WHERE 1 = 1
        </otherwise>
    </choose>
</select>

8.4 forEach

需求:查询 monster_id 为 20, 22, 34 的妖怪

public List<Monster>
findMonsterById_forEach(Map<String, Object> map);
<!-- 实现 findMonsterById_forEach[使用 forEach] -->
<select id="findMonsterById_forEach" parameterType="map" resultType="Monster">
    SELECT * FROM monster
    <if test="ids != null and ids != ''">
        <where>
            id IN
            <foreach collection="ids" item="id" open="(" separator="," close=")">
                #{id}
            </foreach>
        </where>
    </if>
</select>

8.5 trim

trim 可以替换一些关键字

需求:按名字查询妖怪,如果 sql 语句有 and | or 就替换成 where

public List<Monster> findMonsterByName_Trim(Map<String, Object> map);
<select id="findMonsterByName_Trim" parameterType="map" resultType="Monster">
    select * from monster
    <trim prefix="where" prefixOverrides="and|or">
        <if test="name !=null and name!=''">
            and name=#{name}
        </if>
    </trim>
</select>

8.6 set

需求: 请对指定 id 的妖怪进行 修改,如果没有设置新的属性,则保持原来的值

public void updateMonster_set(Map<String, Object> map);
<!-- updateMonster_set set 标签 -->
<update id="updateMonster_set" parameterType="map">
    UPDATE monster
    <set>
        <if test="age != null and age != ''">
            age = #{age}, </if>
        <if test="birthday != null and birthday != ''">
            birthday = #{birthday}, </if>
        <if test="email != null and email != ''">
            email = #{email}, </if>
        <if test="gender != null and gender != ''">
            gender = #{gender}, </if>
        <if test="name != null and name != ''">
            name = #{name}, </if>
        <if test="salary != null and salary != ''">
            salary = #{salary}, </if>
    </set>
    WHERE id = #{id}
</update>

9. 映射关系一对一

项目中 1 对 1 的关系是一个基本的映射关系,比如:Person(人) — IDCard(身份证)

9.1 通过mapper文件完成映射

public interface PersonMapper {
    //通过 Person 的 id 获取到 Person,包括这个 Person 关联的 IdenCard 对象
    public Person getPersonById(Integer id);
}

方式一:

<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.lhs.mapper.PersonMapper">
    <!-- 实现 getPersonById [方式 1] -->
    <!-- 定义一个 resultMap 来映射返回的结果 -->
    <resultMap type="Person" id="personResultMap">
        <!-- id:一个 ID 结果;标记出作为 ID 的结果可以帮助提高整体性能, 通常指定主键
		property="id" 表示 person 对象的哪个属性代表主键
		column="id" 表示对应的表的字段 -->
        <id property="id" column="id"/>
        <result property="name" column="name"/>
        <!-- association: 一个复杂类型的关联 -->
        <association property="card" javaType="com.lhs.entity.IdenCard">
            <!-- 将关联的 card 对象哪些属性放入到这个 resultMap -->
            <result property="id" column="id"/>
            <result property="card_sn" column="card_sn"/>
        </association>
    </resultMap>
    <select id="getPersonById" parameterType="Integer" resultMap="personResultMap">
        select * from person , idencard WHERE person.id=#{id}
        AND person.card_id = idencard.id;
    </select>
</mapper>

方式二:

<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.lhs.mapper.PersonMapper">
    <!-- 1 对 1 的第二种方式[推荐] -->
    <!-- 定义一个 resultMap 来映射返回的结果 -->
    <resultMap type="Person" id="personResultMap2">
        <!-- id:一个 ID 结果;标记出作为 ID 的结果可以帮助提高整体性能, 通常指定主键
		property="id" 表示 person 对象的哪个属性代表主键
		column="id" 表示对应的表的字段 -->
        <id property="id" column="id"/>
        <result property="name" column="name"/>
        <!-- 说明当前这个 person 的 card 属性,这个数据从 getIdenCardById 获取
card_id: 是从 select * from person WHERE id=#{id}给他的. 相当于, 把多表联查分解成单表操作, 简洁,易于维护, 推荐 -->
        <association property="card" column="card_id" select="com.lhs.mapper.IdenCardMapper.getIdenCardById"
                     />
    </resultMap>
    <select id="getPersonById2" parameterType="Integer"
            resultMap="personResultMap2">
        select * from person WHERE id=#{id}
    </select>
</mapper>

9.2 通过注解完成映射

public interface PersonMapperAnnotation {
    //通过 Person 的 id 获取到 Person,包括这个 Person 关联的 IdenCard 对象
    @Select("select * from person WHERE id=#{id}")
    @Results({
        @Result(id = true, property = "id", column = "id"), 
        @Result(property = "name", column = "name"), 
        @Result(property = "card", column = "card_id", one = @One(select =
"com.lhs.mapper.IdenCardMapperAnnotaion.getIdenCardById"))
    })
    public Person getPersonById(Integer id);
}

10. 映射关系多对一

项目中多对 1 的关系是一个基本的映射关系,多对 1, 也可以理解成是 1 对多。

例如:User — Pet: 一个用户可以养多只宠物。

10.1 通过mapper文件完成映射

userMapeer.xml:

<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.lhs.mapper.UserMapper">
    <!-- collection : 一对多关联查询, 表示一个用户可能对应多个 pet 对象
	ofType: 集合中元素对象的类型 -->
    <resultMap type="com.lhs.entity.User" id="UserResultMap">
        <id property="id" column="id"/>
        <result property="name" column="name"/>
        <!-- column="id" 就是当前 User 表的主键字段名,通过这个 user 的 id,
		去查询对应的 pet 有哪些 -->
        <collection property="pets" ofType="com.lhs.entity.Pet" column="id" select="com.lhs.mapper.PetMapper.getPetByUserId"></collection>	
    </resultMap>
    <select id="getUserById" parameterType="Integer" resultMap="UserResultMap">
        select * from mybatis_user where id=#{id}
    </select>
</mapper>

petMapper.xml:

<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.hspedu.mapper.PetMapper">
    <resultMap type="com.hspedu.entity.Pet" id="PetResultMap">
        <id property="id" column="id"/>
        <result property="nickname" column="nickname"/>
        <association property="user" column="user_id" select="com.hspedu.mapper.UserMapper.getUserById"></association>
    </resultMap>
    <!-- 老韩解读:
    1. user_id=#{userId} userId 就是调用 getPetByUserId 传入的用户 id
    2. userId 名称,由程序员来确定,比如你写成 id,uId 等都可以-->
    <select id="getPetByUserId" parameterType="Integer" resultMap="PetResultMap">
        select * from mybatis_pet where user_id=#{userId}
    </select>
    <select id="getPetById" parameterType="Integer" resultMap="PetResultMap">
        select * from mybatis_pet where id=#{id}
    </select>
</mapper>

10.2 通过注解完成映射

userMapper:

public interface UserMapperAnnotation {
    @Select("select * from mybatis_user where id=#{id}")
    @Results({
        @Result(id=true,property="id",column="id"),
        @Result(property="name",column="name"),
        @Result(property="pets",column="id", many=@Many(select="com.lhs.mapper.PetMapperAnnotation.getPetByUserId"))
    })
    public User getUserById(Integer id);
}

petMapper:

public interface PetMapperAnnotation {
    //通过 User 的 id 来获取 pet 对象,可能有多个,因此使用 List 接收
    @Select("select * from mybatis_pet where user_id=#{userId}")
    @Results({
        @Result(id = true, property = "id", column = "id"), 
        @Result(property = "nickname", column = "nickname")
    })
    public List<Pet> getPetByUserId(Integer userId);
    
    //通过 pet 的 id 获取 Pet 对象
    @Select("select id AS tnId,nickname AS tnNickname, user_id AS tnUser_id from
            mybatis_pet where id=#{id}")
            @Results({
                @Result(id = true, property = "id", column = "tnId"), 
                @Result(property = "nickname", column = "tnNickname"),
                @Result(property = "user", column = "tnUser_id", one = @One(select =
"com.lhs.mapper.UserMapperAnnotation.getUserById"))
            })
            public Pet getPetById(Integer id);
            }

11. 缓存

默认情况下,mybatis 是启用一级缓存的/本地缓存/local Cache,它是 SqlSession 级别的。

同一个 SqlSession 接口对象调用了相同的 select 语句,会直接从缓存里面获取,而不是再去查询数据库。

11.1 一级缓存

需求: 当我们第 1 次查询 id=1 的 Monster 后,再次查询 id=1 的 monster 对象,就会直接从一级缓存获取,不会再次发出 sql。

@Test
public void level1CacheTest() {
    Monster monster = monsterMapper.getMonsterById(1);
    System.out.println("--" + monster + "--");
    // 当我们再次查询 id=1 的 Monster 时,直接从一级缓存获取,不会再次发出 sql
    monster = monsterMapper.getMonsterById(1);
    System.out.println("--" + monster + "--");
    if (sqlSession != null) {
        sqlSession.commit();
        sqlSession.close();
    }
    System.out.println("操作成功");
}

失效原因分析:

  • 关闭 sqlSession 会话后, 再次查询,会到数据库查询
  • 当执行 sqlSession.clearCache() 会使一级缓存失效
  • 当对同一个 monster 修改,该对象在一级缓存会失效

11.2 二级缓存

二级缓存和一级缓存都是为了提高检索效率的技术,最大的区别就是作用域的范围不一样,一级缓存的作用域是 sqlSession 会话级别,在一次会话有效,而二级缓存作用域是全局范围,针对不同的会话都有效。

使用步骤:

1)在mybatis-config.xml中开启二级缓存(不设置也行,默认就是开启状态)

<settings>
    <setting name="logImpl" value="STDOUT_LOGGING"/>
    <!-- 开启二级缓存 -->
    <setting name="cacheEnabled" value="true"/>
</settings>

2)在xxxMapper.xml中配置二级缓存

<cache eviction="FIFO" flushInterval="30000" size="360" readOnly="true"/>

说明:

  • eviction=“FIFO” :缓存策略 先进先出
  • flushInterval=“30000”:每 30000 毫秒 刷新一次,和数据库保持一致
  • size:二级缓存设置最大保持的 360 个对象, 超过了,就启用 fifo 策略处理 ,默认1024
  • readOnly:只读,为了提高效率

四大策略:

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

11.3 EhCache 缓存

EhCache 是一个纯 Java 的缓存框架,具有快速、精干等特点。

MyBatis 有自己默认的二级缓存(前面我们已经使用过了),但是在实际项目中,往往使用的是更加专业的第三方缓存产品 作为 MyBatis 的二级缓存,EhCache 就是非常优秀的缓存产品。

使用步骤:

1)引入相关依赖

<dependency>
    <groupId>net.sf.ehcache</groupId>
    <artifactId>ehcache-core</artifactId>
    <version>2.6.11</version>
</dependency>

2)加入配置文件ehcache.xml

<?xml version="1.0" encoding="UTF-8"?>
<ehcache>
    <!--diskStore:为缓存路径,ehcache 分为内存和磁盘两级,此属性定义磁盘的缓存位置。
    参数解释如下:
    user.home – 用户主目录
    user.dir – 用户当前工作目录
    java.io.tmpdir – 默认临时文件路径
    -->
    <diskStore path="java.io.tmpdir/Tmp_EhCache"/>
    <!--defaultCache:默认缓存策略,当 ehcache 找不到定义的缓存时,则使用这个缓存策略。
    只能定义一个。
    -->
    <defaultCache
                  eternal="false" maxElementsInMemory="10000" 
                  overflowToDisk="false" diskPersistent="false"
                  timeToIdleSeconds="1800"
                  timeToLiveSeconds="259200" memoryStoreEvictionPolicy="LRU"/>
</ehcache>

参数解释:

  • name:缓存名称。
  • maxElementsInMemory:缓存最大数目。
  • maxElementsOnDisk:硬盘最大缓存个数。
  • eternal:对象是否永久有效,一但设置了,timeout 将不起作用。
  • overflowToDisk:是否保存到磁盘,当系统宕机时。
  • timeToIdleSeconds:设置对象在失效前的允许闲置时间(单位:秒)。仅当 eternal=false对象不是永久有效时使用,可选属性,默认值是 0,也就是可闲置时间无穷大。
  • timeToLiveSeconds:设置对象在失效前允许存活时间(单位:秒)。最大时间介于创建时间和失效时间之间。仅当 eternal=false 对象不是永久有效时使用,默认是 0,也就是对象存活时间无穷大。
  • diskPersistent:是否缓存虚拟机重启期数据。
  • memoryStoreEvictionPolicy:当达到 maxElementsInMemory 限制时,Ehcache 将会根据指定的策略去清理内存。默认策略是 LRU(最近最少使用)。
  • clearOnFlush:内存数量最大时是否清除。

3)在 XxxMapper.xml 中启用 EhCache

<cache type="org.mybatis.caches.ehcache.EhcacheCache"/>

细节说明:

  • MyBatis 提供了一个接口 Cache,只要实现了该 Cache 接口,就可以作为二级缓存产品和 MyBatis 整合使用,Ehcache 就是实现了该接口
  • MyBatis 默认情况(即一级缓存)是使用的 PerpetualCache 类实现 Cache 接口的,是核心类
  • 当我们使用了 Ehcahce 后,就是 EhcacheCache 类实现 Cache 接口的,是核心类
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

林小果呀

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值