【精选必看】MyBatis映射文件及动态SQL,一级,二级缓存介绍

MyBatis映射文件

在这里插入图片描述

< r e s u l t M a p > <resultMap> <resultMap>

MyBatis映射文件中除了<insert><delete><update><select>外,还有一些标签可以使用:

resultMap

标签的作用的自定义映射关系。

MyBatis可以将数据库结果集封装到对象中,是因为结果集的列名和对象属性名相同:

当POJO属性名和数据库列名不一致时,MyBatis无法自动完成映射关系。

此时有两种解决方案:

  1. Sql语句的查询字段起与POJO属性相同的别名。

    <select id="findAll" resultType="com.mybatis.pojo.Teacher">
       select tid as id,tname as teacherName from teacher;
    </select>
    
  2. 自定义映射关系

    • 在映射文件中,使用<resultMap>自定义映射关系:
    <!-- id:自定义映射名 type:自定义映射的对象类型  -->
    <resultMap id="teacherMapper" type="com.mybatis.pojo.Teacher">
      <!-- id定义主键列  property:POJO属性名 column:数据库列名  -->
      <id property="id" column="tid"></id>
      <!-- result定义普通列  property:POJO属性名 column:数据库列名  -->
      <result property="teacherName" column="tname"></result>
    </resultMap>
    
    • <select>标签中,使用resultMap属性代替resultType属性,使用自定义映射关系。
    <select id="findAll" resultMap="teacherMapper">
       select * from teacher
    </select>
    

< sql>&< include>

<sql>用来定义可重用的Sql片段,通过<include>引入该片段。如:Sql语句的查询字段起与POJO属性相同的别名,该Sql片段就可以重用。

<sql id="selectAllField">
   select tid as id,tname as teacherName
</sql>


<select id="findAll" resultType="com.mybatis.pojo.Teacher">
  <include refid="selectAllField"></include>
   from teacher;
</select>


<select id="findById" resultType="com.mybatis.pojo.Teacher">
  <include refid="selectAllField"></include>
   from teacher where tid = #{id}
</select>

特殊字符处理

在Mybatis映射文件中尽量不要使用一些特殊字符,如:<>等。

我们可以使用符号的实体来表示:(带后面的分号)

符号实体
<& lt;
>& gt;
&& amp;
& apos;
& quot;

如:

<select id="findById2" resultType="com.mybatis.pojo.Teacher">
  <include refid="selectAllField"></include>
   from teacher where tid &gt; #{id}
</select>

动态SQL

在这里插入图片描述

一个查询的方法的Sql语句不一定是固定的。比如电商网站的查询商品,用户使用不同条件查询,Sql语句就会添加不同的查询条件。此时就需要在方法中使用动态Sql语句。

< i f > < if> <if>

<if>标签内的Sql片段在满足条件后才会添加,用法为:<if test="条件">。例如:根据不同条件查询用户:

  1. 持久层接口添加方法

    // 用户通用查询
    List<User> findByCondition(User user);
    
  2. 映射文件添加标签

    <select id="findByCondition" parameterType="com.mybatis.pojo.User" resultType="com.mybatis.pojo.User">
       select * from user where 1 = 1
      <if test="username != null and username.length() != 0">
         and username like #{username}
      </if>
      <if test="sex != null and sex.length() != 0">
         and sex = #{sex}
      </if>
      <if test="address != null and address.length() != 0">
         and address = #{address}
      </if>
    </select>
    
  3. 编写测试方法

    @Test
    public void testFindByCondition(){
      User user = new User();
      List<User> users1 = userMapper2.findByCondition(user);
      //users1.forEach(System.out::println);
    
    
      user.setUsername("%张三%");
      List<User> users2 = userMapper2.findByCondition(user);
      users2.forEach(System.out::println);
    
    
      user.setAddress("北京");
      List<User> users3 = userMapper2.findByCondition(user);
      users3.forEach(System.out::println);
    }
    
  1. if中的条件不能使用&&/||,而应该使用and/or

  2. if中的条件可以直接通过属性名获取参数POJO的属性值,并且该值可以调用方法。

  3. where后为什么要加1=1?

    任意条件都可能拼接到Sql中。如果有多个条件,从第二个条件开始前都需要加And关键字。加上1=1这个永久成立的条件,就不需要考虑后面的条件哪个是第一个条件,后面的条件前都加And关键字即可。

< w h e r e > <where> <where>

<where>可以代替sql中的where 1=1 和第一个and,更符合程序员的开发习惯,使用<where>后的映射文件如下:

<select id="findByCondition" resultType="com.mybatis.user.User" parameterType="com.mybatis.user.User">
   select * from user
  <where>
    <if test="username != null and username.length() != 0">
       username like #{username}
    </if>
    <if test="sex != null and sex.length() != 0">
       and sex = #{sex}
    </if>
  </where>
</select>

< s e t > <set> <set>

<set>标签用在update语句中。借助<if>,可以只对有具体值的字段进行更新。<set>会自动添加set关键字,并去掉最后一个if语句中多余的逗号。

<update id="update" parameterType="com.mybatis.user.User">
   update user
  <set>
    <if test="username != null and username.length() > 0">
       username = #{username},
    </if>
    <if test="sex != null and sex.length() > 0">
       sex = #{sex},
    </if>
  </set>
  <where>
     id = #{id}
  </where>
</update>

< c h o o s e > < choose> <choose> < w h e n > < when> <when> < o t h e r w i s e > < otherwise> <otherwise>

这些标签表示多条件分支,类似JAVA中的switch...case<choose>类似switch<when>类似case<otherwise>类似default,用法如下:

<select id="findByCondition" resultType="com.mybatis.user.User" parameterType="com.mybatis.user.User">
   select * from user
  <where>
    <choose>
      <when test="username.length() &lt; 5">
         username like #{username}
      </when>
      <when test="username.length() &lt; 10">
         username = #{username}
      </when>
      <otherwise>
         id = 1
      </otherwise>
    </choose>
  </where>
</select>

这段代码的含义为:用户名<5时使用模糊查询,用户名>=5并且<10时使用精确查询,否则查询id为1的用户

< f o r e a c h > <foreach> <foreach>

<foreach>类似JAVA中的for循环,可以遍历集合或数组。<foreach>有如下属性:

  • collection:遍历的对象类型
  • open:开始的sql语句
  • close:结束的sql语句
  • separator:遍历每项间的分隔符
  • item:表示本次遍历获取的元素,遍历List、Set、数组时表示每项元素,遍历map时表示键值对的值。
  • index:遍历List、数组时表示遍历的索引,遍历map时表示键值对的键。
遍历数组

我们使用<foreach>遍历数组进行批量删除。

  1. 持久层接口添加方法

    void deleteBatch(int[] ids);
    
  2. 映射文件添加标签

    <delete id="deleteBatch" parameterType="int">
       delete from user
      <where>
        <foreach open="id in(" close=")" separator="," collection="array" item="id" >
           #{id}
        </foreach>
      </where>
    </delete>
    
  3. 编写测试方法

    @Test
    public void testDeleteBatch(){
      int[] ids = {9,11};
      userMapper.deleteBatch(ids);
      session.commit();
    }
    
遍历Collection

<foreach>遍历List和Set的方法是一样的,我们使用<foreach>遍历List进行批量添加。

  1. 持久层接口添加方法

    void insertBatch(List<User> users);
    
  2. 映射文件添加标签

    <insert id="insertBatch" parameterType="com.mybatis.user.User">
       insert into user values
      <foreach collection="list" item="user" separator=",">
         (null ,#{user.username},#{user.sex},#{user.address})
      </foreach>
    </insert>
    
  3. 编写测试方法

    @Test
    public void testInsertBatch(){
      User user1 = new User("程序员1", "男", "北京");
      User user2 = new User("程序员2", "女", "上海");
      List<User> users = new ArrayList();
      users.add(user1);
      users.add(user2);
    
    
      userMapper2.insertBatch(users);
      session.commit();
    }
    
遍历Map

我们使用<foreach>遍历Map进行多条件查询。

  1. 持久层接口添加方法

    /**
       * 多条件查询
       * @param map 查询的条件键值对 键:属性名 值:属性值
       * @return
       */
    List<User> findUser(@Param("queryMap") Map<String,Object> map);
    
  2. 映射文件添加标签

    <select id="findUser" parameterType="map" resultType="com.mybatis.pojo.User">
       select * from user
      <where>
        <foreach collection="queryMap" separator="and" index="key" item="value">
           ${key} = #{value}
        </foreach>
      </where>
    </select>
    
  3. 编写测试方法

    @Test
    public void testFindUser(){
      Map<String,Object> queryMap = new HashMap();
      queryMap.put("sex","男");
      queryMap.put("address","北京");
      List<User> users = userMapper2.findUser(queryMap);
      users.forEach(System.out::println);
    }
    

MyBatis缓存

在这里插入图片描述

缓存介绍

缓存是内存当中一块存储数据的区域,目的是提高查询效率。MyBatis会将查询结果存储在缓存当中,当下次执行相同的SQL时不访问数据库,而是直接从缓存中获取结果,从而减少服务器的压力。

  • 什么是缓存?

    存在于内存中的一块数据。

  • 缓存有什么作用?

    减少程序和数据库的交互,提高查询效率,降低服务器和数据库的压力。

  • 什么样的数据使用缓存?

    经常查询但不常改变的,改变后对结果影响不大的数据。

  • MyBatis缓存分为哪几类?

    一级缓存和二级缓存

  • 如何判断两次Sql是相同的?

    1. 查询的Sql语句相同
    2. 传递的参数值相同
    3. 对结果集的要求相同
    4. 预编译的模板Id相同

MyBatis一级缓存

  • MyBatis一级缓存也叫本地缓存。SqlSession对象中包含一个Executor对象,Executor对象中包含一个PerpetualCache对象,在该对象存放一级缓存数据。
  • 由于一级缓存是在SqlSession对象中,所以只有使用同一个SqlSession对象操作数据库时才能共享一级缓存
  • MyBatis的一级缓存是默认开启的,不需要任何的配置。
测试一级缓存
@Test
public void testCache1() throws IOException {
  InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml");
  SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
  SqlSessionFactory factory = builder.build(is);
  SqlSession session = factory.openSession();


  // 使用同一个SqlSession查询
  UserMapper mapper1 = session.getMapper(UserMapper.class);
  UserMapper mapper2 = session.getMapper(UserMapper.class);


  User user1 = mapper1.findById(1);
  System.out.println(user1.hashCode());
  System.out.println("-------------------------------------------");
  User user2 = mapper2.findById(1);
  System.out.println(user2.hashCode());
}


@Test
public void testCache2() throws IOException {
  InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml");
  SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
  SqlSessionFactory factory = builder.build(is);
  SqlSession session1 = factory.openSession();
  SqlSession session2 = factory.openSession();


  // 使用不同的SqlSession查询
  UserMapper mapper1 = session1.getMapper(UserMapper.class);
  UserMapper mapper2 = session2.getMapper(UserMapper.class);


  User user1 = mapper1.findById(1);
  System.out.println(user1.hashCode());
  System.out.println("-------------------------------------------");
  User user2 = mapper2.findById(1);
  System.out.println(user2.hashCode());
}

MyBatis清空一级缓存

进行以下操作可以清空MyBatis一级缓存:

  1. SqlSession调用close():操作后SqlSession对象不可用,该对象的缓存数据也不可用。
  2. SqlSession调用clearCache()/commit():操作会清空一级缓存数据。
  3. SqlSession调用增删改方法:操作会清空一级缓存数据,因为增删改后数据库发生改变,缓存数据将不准确。
@Test
public void testCache3() throws IOException {
  InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml");
  SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
  SqlSessionFactory factory = builder.build(is);
  SqlSession session = factory.openSession();


  UserMapper mapper1 = session.getMapper(UserMapper.class);
  UserMapper mapper2 = session.getMapper(UserMapper.class);


  User user1 = mapper1.findById(1);
  System.out.println(user1.hashCode());
  //     session.close();
  //     session.clearCache();
  //     session.commit();
  mapper1.delete(2);
  System.out.println("-------------------------------------------");
  User user2 = mapper2.findById(1);
  System.out.println(user2.hashCode());
}

MyBatis二级缓存

  • MyBatis二级缓存也叫全局缓存。数据存放在SqlSessionFactory中,只要是同一个工厂对象创建的SqlSession,在进行查询时都能共享数据。一般在项目中只有一个SqlSessionFactory对象,所以二级缓存的数据是全项目共享的。

  • MyBatis一级缓存存放的是对象,二级缓存存放的是对象的数据。所以要求二级缓存存放的POJO必须是可序列化的,也就是要实现Serializable接口。

  • MyBatis二级缓存默认不开启,手动开启后数据先存放在一级缓存中,只有一级缓存数据清空后,数据才会存到二级缓存中。

    SqlSession调用clearCache()无法将数据存到二级缓存中。

开启二级缓存
  1. POJO类实现Serializable接口。

    public class User implements Serializable {
      private int id;
      private String username;
      private String sex;
      private String address;
    }
    
    
  2. 在MyBatis配置文件添加如下设置:

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

    由于cacheEnabled默认值是true,所以该设置可以省略。

  3. 在映射文件添加<cache />标签,该映射文件下的所有方法都支持二级缓存。

    如果查询到的集合中对象过多,二级缓存只能缓存1024个对象引用。可以通过<cache />标签的size属性修改该数量。

    <cache size="2048"/>
    
  4. 测试二级缓存

    @Test
    public void testCache4() throws IOException {
      InputStream is = Resources.getResourceAsStream("SqlMapConfig.xml");
      SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
      SqlSessionFactory factory = builder.build(is);
      SqlSession session1 = factory.openSession();
      SqlSession session2 = factory.openSession();
    
    
      UserMapper mapper1 = session1.getMapper(UserMapper.class);
      UserMapper mapper2 = session2.getMapper(UserMapper.class);
    
    
      User user1 = mapper1.findById(1);
      System.out.println(user1);
      System.out.println(user1.hashCode());
      // 让一级缓存失效
      session1.commit();
      System.out.println("-------------------------------------------");
    
    
      User user2 = mapper2.findById(1);
      System.out.println(user2);
      System.out.println(user2.hashCode());
    }
    
    
posted @ 2023-11-26 12:42  Gjq-  阅读(31)  评论(0编辑  收藏  举报  来源