8-Mybatis高级配置

14 篇文章 0 订阅

第八章、Mybatis高级配置

1. MyBatis的映射文件高级

  • cache – 该命名空间的缓存配置。
  • cache-ref – 引用其它命名空间的缓存配置。
  • resultMap – 描述如何从数据库结果集中加载对象,是最复杂也是最强大的元素。
  • sql – 可被其它语句引用的可重用语句块。

1.1 SQL代码片段

该元素可以用来定义可重用的 SQL 代码片段,以便在其它语句中使用。

1.1.1 基础的SQL片段

通过<sql>标签定义代码片段

<!--定义所有的列名-->
<sql id="fullColumns">id, title, description, pid, image</sql>

<!--定义一个基础查询-->
<sql id="baseSelect">
    select
    <include refid="fullColumns"></include>
    from section
</sql>

通过<include>标签引入定义的代码片段

<select id="selectByPrimaryKey" resultType="Section">
    <include refid="baseSelect"></include>
    where id = #{id}
</select>


<select id="selectAll" resultType="Section">
    <include refid="baseSelect"></include>
</select>

1.1.2 带参数的SQL片段(了解)

<sql id="baseSectionSelect">
    select
    <include refid="fullColumns"></include>
    from section where title like '%${key}%'
</sql>
<select id="selectAll" resultType="Section">
    <include refid="baseSectionSelect">
        <property name="key" value=""/>       
    </include>
</select>

1.2 参数设置

参数的类型

原始类型(基本数据类型及包装类)

引用数据类型:简单类型(String,Date)和复杂类型(自定义类型-实体类)

参数的使用

#{参数名,javaType=int,jdbcType=NUMERIC,typeHandler=TypeHandler} 完整形式
#{参数名} 简写形式(最常用)

1.2.1 MyBatis的参数分类

简单类型:直接使用值的数据类型

/**
 * 根据主键删除
 * @param id
 * @return
 */
int deleteByPrimaryKey(int id);
<!--简单的参数数据类型,通常省略掉parameterType由MyBatis的自动推断-->
<delete id="deleteByPrimaryKey" parameterType="int">
    delete from section where id = #{id}
</delete>

复杂类型:使用属性值的数据类型

/**
 * 添加一个数据到数据库中
 * @param section
 * @return 返回受影响的行数
 */
int insert(Section section);
<!--在SQL语句的拼接中,直接使用属性名获取值,parameterType可以省略由MyBatis的自动推断 -->
<insert id="insert" parameterType="Section"
        useGeneratedKeys="true" keyProperty="id">
    insert into section (id, title, description, pid, image)
    values (default, #{title}, #{description}, #{pid}, #{image})
</insert>

1.2.2 多个参数

多个参数,mybatis在实现时,会使用list集合保存

参数在使用时,通过有序的名称来获取值

两种方式:

第一种:arg0, arg1,arg2, …

第二种:param1, param2, param3, …

List<Section> selectByTitleAndPid(String title, int pid);
<!--多个参数时,parameterType属性通常省略-->
<select id="selectByTitleAndPid" parameterType="list"  resultType="Section">
    select * from section where title = #{param1} and pid = #{param2}
</select>

使用注解给参数命名

使用 @Param(“名称”)

List<Section> selectByTitleAndPid(@Param("title") String title, @Param("pid") int pid);
<select id="selectByTitleAndPid" resultType="Section">
    select * from section where title = #{title} and pid = #{pid}
</select>

注意事项 :一般情况下如果参数的个数超过3个,请使用对象封装。

1.2.3 字符串替换(面试中常问)

替换SQL语句中的特殊字符。

常见的特殊字符

#{ 参数名 }:会使用 ? 来替换,用于设置SQL语句的参数占位符

${ 参数名 }:会使用参数名对应的内容来替换,通常用于拼接SQL语句。(慎用 - SQL注入式攻击)

1.3 结果映射ResultMap

resultMap 元素是 MyBatis 中最重要最强大的元素。它可以让你从 90% 的 JDBC ResultSets 数据提取代码中解放出来,并在一些情形下允许你进行一些 JDBC 不支持的操作。实际上,在为一些比如连接的复杂语句编写映射代码的时候,一份 resultMap 能够代替实现同等功能的数千行代码。ResultMap 的设计思想是,对简单的语句做到零配置,对于复杂一点的语句,只需要描述语句之间的关系就行了。

1.3.1 直接映射Map

会把数据库中查询的每一行数据映射成为一个Map,列名作为key,对应列的值作为value

实际开发尽量少用。

Map selectOneMap(int id);

List<Map> selectMap();
<select id="selectOneMap" resultType="map">
    select * from section where id = #{id}
</select>

<select id="selectMap" resultType="map">
    select * from section
</select>

1.3.2 使用ResultMap映射实体类

当属性名和列名不一致时,需要实现属性名和列名的映射

1.3.2.1 传统方式,使用SQL语句as别名
<select id="selectSections" resultType="Section" >
    select
        id, title, description, pid, image as image
    from section
</select>
1.3.2.1 使用ResultMap标签,实现映射
<resultMap id="BaseMap" type="Section" autoMapping="true">
    <!--实现主键映射-->
    <id property="id" column="id"></id>
    <!--实现其余属性映射(通常映射名称不一样的)-->
    <result property="image" column="image"></result>
</resultMap>
<select id="selectSections" resultMap="BaseMap">
    select
        id, title, description, pid, image
    from section
</select>

1.3.3 ResultMap的高级映射

映射有级联关系的对象

package com.xuetang9.mybatis.demo.entity;

import lombok.Data;


@Data
public class Customer {
    private String account;
    private String password;
    private String name;
}
package com.xuetang9.mybatis.demo.entity;

import lombok.Data;


@Data
public class Topic {
    private Integer id;
    private Integer sid;
    private String account;
    private String title;
	// 关联了一个Customer类型的对象属性
    private Customer author;
}
<resultMap id="BaseResultMap" type="Topic" autoMapping="true">

    <!--配置一个对象属性-->
    <association property="author" javaType="Customer" columnPrefix="cus_">
        <id property="account" column="account"></id>
        <result property="name" column="name"></result>
        <result property="password" column="password"></result>
    </association>
</resultMap>

<select id="selectByPrimaryKey" resultMap="BaseResultMap">
    select
        t.*,
        c.account as cus_account,
        c.password as cus_password,
        c.name as cus_name
    from topic t
    inner join customer c on t.account = c.account
    where t.id = #{id}
</select>

映射有级联关系的集合

package com.xuetang9.mybatis.demo.entity;

import lombok.Data;

import java.util.List;

@Data
public class Section {

    private Integer id;

    private String title;

    private String description;

    private Integer pid;

    private String image;

    // 版块下面关联多个主贴
    private List<Topic> topics;

}
package com.xuetang9.mybatis.demo.entity;

import lombok.Data;


public class Topic {
    private Integer id;
    private Integer sid;
    private String account;
    private String title;

    private Customer author;
}
<resultMap id="BaseMap" type="Section" autoMapping="true">
    <!--实现主键映射-->
    <id property="id" column="id"></id>
    <!--实现其余属性映射(通常映射名称不一样的)-->
    <result property="image" column="image"></result>

    <!--配置一个集合的信息-->
    <collection property="topics" ofType="Topic" columnPrefix="t_">
        <id property="id" column="id"></id>
        <result property="title" column="title"></result>
    </collection>
</resultMap>

<select id="selectByPrimaryKey" resultMap="BaseMap">
    select
    s.*,
    t.id as t_id,
    t.title as t_title
    from section s
    inner join topic t on s.id = t.sid
    where s.id = #{id}
</select>

集合中含有复杂类型

注意sql语句的前缀中,需要给两次前缀t_表示Topict_cus_ 表示Topic中的Customer

<resultMap id="sectionMapTopicList" type="Section" autoMapping="true">
        <!-- 需要注意父级对象的id需要显示的写出来。 可以进行唯一数据的区别 -->
    <id property="id" column="id"/>
    <collection property="topics" ofType="Topic" columnPrefix="t_">
        <id property="id" column="id"/>
        <result property="title" column="title"/>
        <result property="content" column="content"/>
        <association property="author" javaType="Customer" columnPrefix="cus_">
            <!-- 实际查找的列 cus_id -->
            <id property="id" column="id"/>
            <result property="account" column="account"/>
            <result property="name" column="name"/>
        </association>
    </collection>
</resultMap>

<!-- 查询板块以及板块下的所有帖子 -->
<select id="selectWithTopicByPrimaryKey" resultMap="sectionMapTopicList">
    select s.*,t.id as t_id,
    t.title as t_title,
    t.content as t_content,
    c.id as t_cus_id,
    c.account as t_cus_account,
    c.name as t_cus_name
    from section as s
    inner join topic as t on t.section_id = s.id
    inner join customer as c on c.id = t.customer_id
    where s.id = #{sid}
</select>

1.4 MyBatis缓存

缓存:把物理介质中数据,读取到内存中,以便于后续操作的访问。针对查询。

MyBatis中的缓存分为两种:

第一种:一级缓存,又称为本地缓存,数据是缓存在SqlSession中。只要SqlSession关闭缓存就会清除。

第二种:二级缓存,可以使用 缓存 中间件, 数据是缓存在SelSessionFactory级别。

1.4.1 一级缓存

List<Topic> selectLocalCache();
<select id="selectLocalCache" resultType="Topic">
    select * from topic
</select>
@Test
public void selectLocalCache() {
    // 演示一级缓存
    List<Topic> topics = topicMapper. selectLocalCache();

    // 手动清除缓存
    sqlSession.clearCache();

    topicMapper. selectLocalCache();
}

1.4.2 二级缓存

在映射文件中添加 <cache/>

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

    <!--简单基础缓存-->
    <cache/>

    <resultMap id="BaseResultMap" type="Topic" autoMapping="true">

        <!--配置一个对象属性-->
        <association property="author" javaType="Customer" columnPrefix="cus_">
            <id property="account" column="account"></id>
            <result property="name" column="name"></result>
            <result property="password" column="password"></result>
        </association>
    </resultMap>

    <select id="selectByPrimaryKey" resultMap="BaseResultMap">
        select
            t.*,
            c.account as cus_account,
            c.password as cus_password,
            c.name as cus_name
        from topic t
        inner join customer c on t.account = c.account
        where t.id = #{id}
    </select>

    <select id="selectLocalCache" resultType="Topic">
        select * from topic
    </select>
</mapper>
1.4.2.1 自定义二级缓存
package com.xuetang9.mybatis.demo.cache;

import org.apache.ibatis.cache.Cache;

import java.util.HashMap;
import java.util.Map;

public class MyCache implements Cache {

    /**
     * 使用一个Map用来保存需要缓存的数据(可以换成 缓存数据库 Redis)
     */
    private Map<Object,Object> cacheMap = new HashMap<>();

    private String id;

    public MyCache(String id){
        this.id = id;
    }

    @Override
    public String getId() {
        return this.id;
    }

    @Override
    public void putObject(Object key, Object value) {
        System.out.println("缓存数据..............");
        System.out.println(key);
        System.out.println(value);
        System.out.println("缓存数据..............");
        // 保存对象到缓存中
        cacheMap.put(key,value);
    }

    @Override
    public Object getObject(Object key) {
        System.out.println("取出数据");
        // 从缓存中取出数据
        return cacheMap.get(key);
    }

    @Override
    public Object removeObject(Object key) {
        // 从缓存中移除数据
        return cacheMap.remove(key);
    }

    @Override
    public void clear() {
        // 清空缓存
        cacheMap.clear();
    }

    @Override
    public int getSize() {
        // 获取缓存数据的条数
        return cacheMap.size();
    }
}
<cache type="com.xuetang9.mybatis.demo.cache.MyCache"/>
1.4.2.2 使用Redis实现自定义二级缓存
package com.xuetang9.framework.mybatisadvanceconfig.cache;

import org.apache.ibatis.cache.Cache;
import redis.clients.jedis.Jedis;

import java.io.*;
import java.util.ArrayList;
import java.util.List;

public class CacheRedis implements Cache {
    /**
     * 大师兄外网IP
     */
    private String host = "192.168.5.134";
    /**
     * Redis默认端口
     */
    private int port = 6379;

    private Jedis conn = new Jedis(host,port);

    private String id;
    /**
     * 所有的键
     */
    private List<Object> keys = new ArrayList<>();

    public CacheRedis(String id){
        this.id = id;
    }

    @Override
    public String getId() {
        return id;
    }

    /**
     * 对象转化为字节数组
     * @param obj
     * @return
     */
    public byte[] toByteArray(Object obj){
        try(ByteArrayOutputStream byteOut = new ByteArrayOutputStream();
            ObjectOutputStream objOut = new ObjectOutputStream(byteOut);
        ){
            objOut.writeObject(obj);
            objOut.flush();
            return byteOut.toByteArray();
        }catch (IOException e){
            e.printStackTrace();
        }
        return null;
    }

    /**
     * 将字节数组转化为对象
     * @param arr
     * @return
     */
    public Object byteArrayToObj(byte [] arr){
        try(ByteArrayInputStream in = new ByteArrayInputStream(arr);
            ObjectInputStream objIn = new ObjectInputStream(in);
        ){
            return objIn.readObject();
        }catch (IOException|ClassNotFoundException e){
            e.printStackTrace();
        }
        return null;
    }

    @Override
    public void putObject(Object key, Object value) {
        if(key instanceof  String && value instanceof  String ){
            conn.set((String)key,(String)value);
            keys.add(key);
        }else{
            conn.set(toByteArray(key),toByteArray(value));
            keys.add(toByteArray(key));
        }
    }

    @Override
    public Object getObject(Object key) {
        if(key instanceof  String){
            return conn.get((String)key);
        }else{
            byte[] bytes = conn.get(toByteArray(key));
            return byteArrayToObj(bytes);
        }
    }

    @Override
    public Object removeObject(Object key) {
        if(key instanceof  String){
            conn.del((String)key);
            keys.remove(key);
        }else{
            conn.del(toByteArray(key));
            keys.remove(toByteArray(key));
        }
        return null;
    }

    @Override
    public void clear() {
        keys.forEach(key->{
            if(key instanceof  String){
                conn.del((String)key);
            }else{
                conn.del((byte[])key);
            }
        });
    }

    @Override
    public int getSize() {
        return keys.size();
    }
}

<cache type="com.xuetang9.framework.mybatisadvanceconfig.cache.CacheRedis" />

1.4.3 cache标签的属性

<cache
  eviction="FIFO"
  flushInterval="60000"
  size="512"
  readOnly="true"/>
  • eviction:缓存清除策略
    • LRU – 最近最少使用:移除最长时间不被使用的对象。
    • FIFO – 先进先出:按对象进入缓存的顺序来移除它们。
    • SOFT – 软引用:基于垃圾回收器状态和软引用规则移除对象。
    • WEAK – 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象。
  • flushInterval:缓存刷新的间隔时间
  • size:缓存的对象引用(变量地址)的个数,默认是1024
  • readOnly:是否是只读缓存。

1.5 动态SQL语句

动态 SQL 是 MyBatis 的强大特性之一。如果你使用过 JDBC 或其它类似的框架,你应该能理解根据不同条件拼接 SQL 语句有多痛苦,例如拼接时要确保不能忘记添加必要的空格,还要注意去掉列表最后一个列名的逗号。利用动态 SQL,可以彻底摆脱这种痛苦。

使用动态 SQL 并非一件易事,但借助可用于任何 SQL 映射语句中的强大的动态 SQL 语言,MyBatis 显著地提升了这一特性的易用性。

1.5.1 动态SQL元素

  • if
  • choose (when, otherwise)
  • trim (where, set)
  • foreach
  • bind
1.5.1.1 if标签
/**
 * 根据条件查询
 * @param section
 * @return
 */
List<Section> selectByCondition(Section section);
<select id="selectByCondition" parameterType="Section" resultMap="BaseMap">
    select * from section where 1 = 1
    <if test="id != null">
        and id = #{id}
    </if>
    <if test="title != null and title != ''">
        and title = #{title}
    </if>
    <if test="description != null">
        and description = #{description}
    </if>
    <if test="pid != null">
        and pid = #{pid}
    </if>
    <if test="image != null">
        and image = #{image}
    </if>
</select>
1.5.1.2 choose、when、otherwise
List<Section> selectByProperty(@Param("prop") String prop,@Param("value") Object value);
<select id="selectByProperty" resultType="Section">
        select
        id, title, description, pid, image
        from section where 1 = 1
        <choose>
            <when test="prop == 'id'">
                and id = #{value}
            </when>
            <when test="prop == 'title'">
                and title = #{value}
            </when>
            <when test="prop == 'pid'">
                and pid = #{value}
            </when>
        </choose>
    </select>
1.5.1.3 trim、where、set

解决条件拼接的固定部分

<select id="selectByProperty" resultMap="BaseMap">
    select
        id, title, description, pid, image
    from section
    <where>
        <choose>
            <when test="prop == 'id'">
                and id = #{value}
            </when>
            <when test="prop == 'title'">
                and title = #{value}
            </when>
            <when test="prop == 'pid'">
                and pid = #{value}
            </when>
        </choose>
    </where>
</select>
<update id="update" parameterType="Section">
    update section
    <set>
        <if test="title != null">
            title = #{title},
        </if>
        <if test="description != null">
            description = #{description},
        </if>
        <if test="pid != null">
            pid = #{pid},
        </if>
        <if test="image != null">
            image = #{image},
        </if>
    </set>
    where id = #{id}
</update>
<select id="selectByCondition" parameterType="Section" resultMap="BaseMap">
    select * from section
    <trim prefix="where" prefixOverrides="and | or">

        <if test="id != null">
            and id = #{id}
        </if>
        <if test="title != null and title != ''">
            and title = #{title}
        </if>
        <if test="description != null">
            and description = #{description}
        </if>
        <if test="pid != null">
            and pid = #{pid}
        </if>
        <if test="image != null">
            and image = #{image}
        </if>
    </trim>
</select>
1.5.1.4 foreach

collection属性,填写的是需要遍历的参数的名称

默认情况下:

  • 数组类型的参数:名称是 array, arg0
  • 集合类型的参数:名称是 list, param1
  • 填写@Param中的名称

foreach在循环遍历大量的数据是:使用 ${} 比 #{} 速度要快。

  • 使用${}是,只解析一次参数,就完成了SQL的生成。

  • 使用#{}时,会先把#{}换成?号,然后再执行参数的设置。

<select id="selectByPrimarykeys" resultMap="BaseMap">
    select * from section
    where id in
    <foreach collection="list" item="id" separator="," open="(" close=")">
        #{id}
    </foreach>
</select>
@Test
public void selectByPrimarykeys() {
    long count = 1000000;
    List<Integer> list = new ArrayList<>();
    for (int i = 0; i < count; i++) {
        list.add(i);
    }
    long start = System.currentTimeMillis();
    System.out.println("执行查询:" + start);
    List<Section> sections = sectionMapper.selectByPrimarykeys(list);
    long end = System.currentTimeMillis();
    System.out.println("执行查询结束:" + end);
    System.out.println("时间差:" + (end - start));
}
1.5.1.5 bind标签

创建一个变量,并将其绑定到当前的上下文

原始方式的模糊查询

@Test
public void selectLikeCondition() {

    Section sectionCondition =new Section();
    // 在代码中构建模糊查询的条件
    sectionCondition.setTitle("%区%");
    List<Section> sections = sectionMapper.selectLikeCondition(sectionCondition);

}
<select id="selectLikeCondition" parameterType="Section" resultMap="BaseMap">
    select * from section
    <where>
        <if test="id != null">
            and id = #{id}
        </if>
        <if test="title != null and title != ''">
            and title like #{title}
        </if>
        <if test="description != null">
            and description like #{description}
        </if>
        <if test="pid != null">
            and pid = #{pid}
        </if>
        <if test="image != null">
            and image like #{image}
        </if>
    </where>
</select>

使用bind标签改造

@Test
public void selectLikeCondition() {
    Section sectionCondition =new Section();
    sectionCondition.setTitle("区");
    List<Section> sections = sectionMapper.selectLikeCondition(sectionCondition);
}
<select id="selectLikeCondition" parameterType="Section" resultMap="BaseMap">
    select * from section
    <where>
        <if test="id != null">
            and id = #{id}
        </if>
        <if test="title != null and title != ''">
            <bind name="titlePattern" value=" '%'+title+'%' "/>
            and title like #{titlePattern}
        </if>
        <if test="description != null">
            and description like #{description}
        </if>
        <if test="pid != null">
            and pid = #{pid}
        </if>
        <if test="image != null">
            and image like #{image}
        </if>
    </where>
</select>

2. MyBatis的注解开发

使用Java API简化配置文件

常用注解

  • @Insert
  • @Delete
  • @Update
  • @Select
/**
 * 查询全部
 * @return
 */
@Select("select * from section")
@ResultMap("BaseMap")
List<Section> selectAll();


/**
 * 根据条件查询
 * @param section
 * @return
 */
@Select({"<script>",
         "select * from section",
         "  <where>",
         "    <if test='title != null'>and title=#{title}</if>",
         "    <if test='pid != null'>and pid=#{pid}</if>",
         "    <if test='id != null'>and id=#{id}</if>",
         "    <if test='image != null'>and image=#{image}</if>",
         "  </where>",
         "</script>"})
@Results(id="BaseMap",value = {
    @Result(id=true,property = "id", column = "id"),
    @Result(property = "image", column = "image"),
})
List<Section> selectByCondition(Section section);
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值