Mybatis搞四下(动态SQL,一对多多对多,延迟加载)

Mybatis搞四下(动态SQL,一对多多对多,延迟加载)

上一篇主要讲了Mybatis的各种强大的参数,标签等,本篇不再赘述标签,主要讲讲动态SQL和复杂的一对多,多对一,多对多查询,还有一个优化性能的延迟加载问题。

动态SQL

动态sql其实就是sql的内容是变化的,根据条件获得不同的sql语句。

(其实主要是where部分变化)

if
choose(when、otherwise)
trim(where、set)
foreach

搭建一个简单的例子,后面的动态SQL实操基于本表

blog.sql

CREATE TABLE `blog` (
  `id` varchar(50) NOT NULL COMMENT '博客id',
  `title` varchar(100) NOT NULL COMMENT '博客标题',
  `author` varchar(30) NOT NULL COMMENT '博客作者',
  `create_time` datetime NOT NULL COMMENT '创建时间',
  `views` int(30) NOT NULL COMMENT '浏览量'
) ENGINE=InnoDB DEFAULT CHARSET=utf8

Blog.java实体类

public class Blog {
    private int id;
    private String title;
    private String author;
    private Date createTime;
    private int views;
    
    //getter,setter,等略
}

<if>标签

这里的标签主要是判断条件

语法上

<if test = "Java对象属性值的判断条件">
	<!--sql语句-->
</if>

if标签实操

<select id="queryBlogIF" parameterType="map" resultType="blog">
    select * from mybatis.blog where 1=1
    <if test="title != null">
        and title = #{title}
    </if>
    <!--满足if条件才会把里面的内容拼接到sql-->
    <if test="author != null">
        and author = #{author}
    </if>
</select>

注:

image-20200923151424627

这里为什么要加1=1?因为如果第一个if不满足单第二个满足,那sql语句就变成了

select * from mybatis.blog where and author = #{author}

显然会报错,所以一定要加上where 1=1

<choose>、<when>、</otherwise>标签

有时候,我们不想使用所有的条件,而只是想从多个条件中选择一个使用。针对这种情况,MyBatis 提供了 choose 元素,它有点像 Java 中的 switch 语句。

还是举例来理解

<select id="queryBlogChoose" resultType="blog" parameterType="map">
        select * from mybatis.blog
        <where>
            <choose>
                <when test="title != null">
                    title = #{title}
                </when>
                <when test="author != null">
                    and author = #{author}
                </when>
                <otherwise>
                    and views = 6666
                </otherwise>
            </choose>
        </where>
    </select>

这里可以看到when里有两个参数,title和author,如果传了title就按title来,如果传了author就按author来,如果两者都不传,就按照otherwise里咱指定的返回

image-20200923155913750

<where>标签

<where>标签主要是用来包含多个<if>标签的,当多个if成立,<where>会自动增加一个where关键字并且去掉多余if中and、or等

where实操

<select id="queryBlogIF" parameterType="map" resultType="blog">
        select * from mybatis.blog
        <where>
<!--            <include refid="if-title-author"></include>-->
            <if test="title != null">
                title = #{title}
            </if>
            <if test="author != null">
                and author = #{author}
            </if>
        </where>
    </select>

如果传两个参数(title和author),则sql语句是这样

image-20200923152421605

如果只传一个参数(author),则

image-20200923152527223

<set>标签

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

上代码!

<update id="updateBlog" parameterType="map">
        update mybatis.blog
        <set>
            <if test="title != null">
                title = #{title},
            </if>
            <if test="author != null">
                author = #{author}
            </if>
        </set>
        where id = #{id}
    </update>

可以看到这里我是想更新指定id的title和author吧

title和author都指定的情况:

image-20200923160550560

title和author只指定一个呢?

image-20200923160638311

咋样?不会这还看不懂吧

<trim>标签

这个标签主要是在你觉得where不好用的时候,通过自定义trim来定制where 的功能,同样,set也可以用它来指定。也可以单独拿出来用,可以自定义字符串截取规则,举几个简单的例子

<trim prefix="1=1" suffix="" suffixOverrides="AND | OR" prefixOverrides=""></trim>

prefix:在trim标签内sql语句加上前缀。

suffix:在trim标签内sql语句加上后缀。

prefixOverrides:指定去除多余的前缀内容

suffixOverrides:指定去除多余的后缀内容,如:suffixOverrides=",",去除trim标签内sql语句多余的后缀","。

<foreach>标签

foreach:循环Java中的数组,list集合的,主要用在sql语句的in语句中

foreach实操

<!--
        select * from mybatis.blog where 1=1 and (id=1 or id = 2 or id=3)
    -->
    <select id="queryBlogForeach" parameterType="map" resultType="blog">
        select * from mybatis.blog

        <where>
            <foreach collection="ids" item="id" open="and (" close=")" separator="or">
                id = #{id}
            </foreach>
        </where>

    </select>

可以看到这个foreach标签里面元素很多,这里把测试类代码块一并附上,一起讲解

@Test
    public void queryBlogForeach(){
        SqlSession sqlSession = MybatisUtils.getSqlSession();
        BlogMapper mapper = sqlSession.getMapper(BlogMapper.class);
        HashMap map = new HashMap();
        ArrayList<Integer> ids = new ArrayList<>();
        ids.add(1);
        ids.add(2);
        ids.add(3);
        map.put("ids",ids);
        List<Blog> blogs = mapper.queryBlogForeach(map);
        for (Blog blog : blogs) {
            System.out.println(blog);
        }
        sqlSession.close();
    }
foreach参数
  • collection:表示接口中方法参数的类型,数组就是Array,集合就是list
  • item:自定义,表示数组或者集合的成员变量
  • open:循环开始时的字符
  • close:循环结束时的字符
  • separator:分隔符

image-20200923154344964

SQL片段拼接–<include>

有时候我们可能会碰到sql重复的地方,复制太多也没意思,这个时候是不是考虑一下把代码块提出来复用?确实有这个功能(拿上面where的例子直接改)

步骤:先使用<sql id=“自定义唯一名称”>sql语句</sql>

在使用<include refid=“自定义唯一名称”/>调用

1、抽取SQL中公共部分

<sql id="if-title-author">
        <if test="title != null">
            title = #{title}
        </if>
        <if test="author != null">
            and author = #{author}
        </if>
    </sql>

2、在需要调用的地方使用<include>标签

<select id="queryBlogIF" parameterType="map" resultType="blog">
        select * from mybatis.blog
        <where>
            <include refid="if-title-author"></include>
        </where>
    </select>

注意事项:

  • 最好基于单表来定义SQL片段!
  • 不要存在where标签

一对多、多对一、多对多、一对一索性全讲讲看吧

一对一查询

我们现在手上有两张表是一对一的关系,并且我们想同时查出来,一般我们会怎么做?把两张表的属性都放在一个类里?也不是不可以,但是那样的代码冗余度就上去了,不太好。现在举个例子,如果我们有一个员工表和一个部门表,一个员工对应一个部门。显然要把department包含在employee里,来做做看。

SQL

CREATE TABLE `employee` (
  `id` int(10) NOT NULL AUTO_INCREMENT,
  `lastName` varchar(50) DEFAULT NULL,
  `email` varchar(50) DEFAULT NULL,
  `gender` int(10) DEFAULT NULL,
  `birth` datetime DEFAULT NULL,
  `department` int(10) DEFAULT NULL,
  PRIMARY KEY (`id`),
  UNIQUE KEY `did` (`department`),
  CONSTRAINT `did` FOREIGN KEY (`department`) REFERENCES `department` (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=1006 DEFAULT CHARSET=utf8


CREATE TABLE `department` (
  `id` int(10) NOT NULL AUTO_INCREMENT,
  `departmentName` varchar(100) NOT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=106 DEFAULT CHARSET=utf8

先在Department实体类里整活

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Department {
    private Integer id;
    private String departmentName;
}

没有lombok的乖乖alt+insert哦

Employee里

@Data
@AllArgsConstructor
@NoArgsConstructor
public class Employee {
    private Integer id;
    private String lastName;
    private Integer departmentId;

    private Department departmentinfo;

}

在这里面最后一行直接把department信息导入。

接口里来一行就行

image-20200923165841769

接下来比较复杂的东西来了,在EmployeeMapper里

<select id="listEmployeeAndDept" resultMap="EmployeeAndDeptMap">
        select e.id,e.lastName,d.id,d.departmentName from employee e inner join department d on e.department = d.id
    </select>
    <resultMap id="EmployeeAndDeptMap" type="Employee">
        <result column="id" property="id"/>
        <result column="lastName" property="lastName"/>
        <association property="departmentinfo" javaType="Department">
            <result column="id" property="id"/>
            <result column="departmentName" property="departmentName"/>
        </association>
    </resultMap>

写一个测试类执行以后得到这个

image-20200923165946177

这里一对一就拿下

多对一查询

多对多我们用老师和学生的关系,其实跟一对一有类似之处,大家细看

image-20200923170651595

SQL

CREATE TABLE `student` (
  `id` int(10) NOT NULL,
  `name` varchar(30) DEFAULT NULL,
  `tid` int(10) DEFAULT NULL,
  PRIMARY KEY (`id`),
  KEY `fktid` (`tid`),
  CONSTRAINT `fktid` FOREIGN KEY (`tid`) REFERENCES `teacher` (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

CREATE TABLE `teacher` (
  `id` int(10) NOT NULL,
  `name` varchar(30) DEFAULT NULL,
  PRIMARY KEY (`id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8

数据自己插一下哈

实体类Stu、Ter

@Data
public class Student {
    private int id;
    private String name;

    //学生关联一个老师
    private Teacher teacher;
}
@Data
public class Teacher {
    private int id;
    private String name;
}

StudentMapper接口

public interface StudentMapper {
    //查询所有学生的信息,根据查询出来的学生tid,寻找出对应的老师
    public List<Student> getStudent();
    public List<Student> getStudent2();
}

Mapper主要配Student,Teacher不用搞实质内容

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.feng.dao.StudentMapper">

    <select id="getStudent2" resultMap="StudentTeacher2">
        select s.id sid,s.name sname,t.name tname,t.id tid
        from student s,teacher t
        where s.tid = t.id
    </select>
    <resultMap id="StudentTeacher2" type="Student">
        <result property="id" column="sid"/>
        <result property="name" column="sname"/>
        <association property="teacher" javaType="Teacher" >
            <result property="name" column="tname"/>
            <result property="id" column="tid"/>
        </association>
    </resultMap>


    <select id="getStudent" resultMap="StudentTeacher">
        select * from student;
    </select>
    <resultMap id="StudentTeacher" type="Student">
        <result property="id" column="is"/>
        <result property="name" column="name"/>
        <association property="teacher" column="tid" javaType="Teacher" select="getTeacher"/>

    </resultMap>


    <select id="getTeacher" resultType="Teacher">
        select * from teacher where id = #{id}
    </select>

</mapper>

按照查询嵌套处理

<!--
    思路:
        1. 查询所有的学生信息
        2. 根据查询出来的学生的tid,寻找对应的老师!  子查询
    -->

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

<resultMap id="StudentTeacher" type="Student">
    <result property="id" column="id"/>
    <result property="name" column="name"/>
    <!--复杂的属性,我们需要单独处理 对象: association 集合: collection -->
    <association property="teacher" column="tid" javaType="Teacher" select="getTeacher"/>
</resultMap>

<select id="getTeacher" resultType="Teacher">
    select * from teacher where id = #{id}
</select>

按照结果嵌套处理

<!--按照结果嵌套处理-->
<select id="getStudent2" resultMap="StudentTeacher2">
    select s.id sid,s.name sname,t.name tname
    from student s,teacher t
    where s.tid = t.id;
</select>

<resultMap id="StudentTeacher2" type="Student">
    <result property="id" column="sid"/>
    <result property="name" column="sname"/>
    <association property="teacher" javaType="Teacher">
        <result property="name" column="tname"/>
    </association>
</resultMap>

一对多查询

一对多就是多对一反过来,就是咱老师对学生呗~

还用上面的例子

实体类

@Data
public class Student {
    private int id;
    private String name;
    private int tid;
}
@Data
public class Teacher {
    private int id;
    private String name;
    //一个老师拥有多个学生
    private List<Student> students;
}

按照结果嵌套处理

    <!--按结果嵌套查询-->
    <select id="getTeacher" resultMap="TeacherStudent">
        select s.id sid, s.name sname, t.name tname,t.id tid
        from student s,teacher t
        where s.tid = t.id and t.id = #{tid}
    </select>

    <resultMap id="TeacherStudent" type="Teacher">
        <result property="id" column="tid"/>
        <result property="name" column="tname"/>
        <!--复杂的属性,我们需要单独处理 对象: association 集合: collection
        javaType="" 指定属性的类型!
        集合中的泛型信息,我们使用ofType获取
        -->
        <collection property="students" ofType="Student">
            <result property="id" column="sid"/>
            <result property="name" column="sname"/>
            <result property="tid" column="tid"/>
        </collection>
    </resultMap>

按照查询嵌套处理

<select id="getTeacher2" resultMap="TeacherStudent2">
    select * from mybatis.teacher where id = #{tid}
</select>

<resultMap id="TeacherStudent2" type="Teacher">
    <collection property="students" javaType="ArrayList" ofType="Student" select="getStudentByTeacherId" column="id"/>
</resultMap>

<select id="getStudentByTeacherId" resultType="Student">
    select * from mybatis.student where tid = #{tid}
</select>

多对多查询

多对多其实和一对多差不多的原理,都是利用collection标签,就是在collection标签里面再嵌套collection标签就可以实现多对多的查询,在这里就不在举例了。

多多一一小结

  1. 关联 - association 【多对一】
  2. 集合 - collection 【一对多】
  3. javaType & ofType
    1. JavaType 用来指定实体类中属性的类型
    2. ofType 用来指定映射到List或者集合中的 pojo类型,泛型中的约束类型!

注意点:

  • 保证SQL的可读性,尽量保证通俗易懂
  • 注意一对多和多对一中,属性名和字段的问题!
  • 如果问题不好排查错误,可以使用日志 , 建议使用 Log4j

延迟加载

这里上面多对一里面的Collection里,我们外部查询触发了内部查询(使用association也有这个问题),在性能要求高的情况下显然不行,非常浪费资源,MyBatis官方也不建议我们使用这种方式。

我们拿刚才的连表查询中,来测试懒加载

select * from student

select * from teacher where id = #{id}

未使用懒加载时,

image-20200923193114985

使用延迟加载(懒加载)

这个功能默认关闭的,首先我们要在mybatis-config.xml里打开懒加载的开关~

<setting name="lazyLoadingEnabled" value="true"/>

然后在association或者collection标签中加入fetchType

image-20200923193158102

此时我们再打断点一步一步看,

image-20200923193545302

再进入下一层循环

image-20200923193649356

其实到这一步,不难发现懒加载的功能了

懒加载小结

懒加载其实就是按需加载,当我们需要什么信息的时候再去查,而不是一次性把所有的东西全查了,将复杂的关联查询分解成单表查询,然后通过单表查询的结果去关联查询

  • 4
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 2
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

上上签i

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

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

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

打赏作者

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

抵扣说明:

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

余额充值