12. Mybatis 多表查询 & 动态 SQL

目录

[1. 数据库字段和 Java 对象不一致](#1. 数据库字段和 Java 对象不一致)

[2. 多表查询](#2. 多表查询)

[3. 动态 SQL 使用](#3. 动态 SQL 使用)

[4. 标签](#4. 标签)

[5. 标签](#5. 标签)

[6. 标签](#6. 标签)

[7. 标签](#7. 标签)

[8. 标签](#8. 标签)

[9. 通过注解实现](#9. 通过注解实现)

[9.1 查找所有数据](#9.1 查找所有数据)

[9.2 通过 id 查找](#9.2 通过 id 查找)


1. 数据库字段和 Java 对象不一致

我们先来看一下数据库中的数据:

接下来,我们在之前代码的基础上修改字段的名称:

java 复制代码
/**
 * 数据库字段和 Java 对象不完全一致
 */
@Data
public class User {
    private Integer id;
    private String name;
    private String pwd;
    private String photo;
    private Date createtime;
    private Date updatetime;
}
java 复制代码
@Slf4j
@SpringBootTest
class UserMapperTest {
    @Autowired
    private UserMapper userMapper;
    @Test
    void queryAll() {
        List<User> users = userMapper.queryAll();
        log.info(users.toString());
    }

    @BeforeEach
    void setUp() {
        log.info("before...");
    }

    @AfterEach
    void tearDown() {
        log.info("after...");
    }
}

可以看到能够获取数据,但是对应字段的值为 null 了:

因为数据库的字段命名规则和 Java 的命名规则不一致,数据库命名:字母小写,以下划线分割;Java 属性命名:小驼峰,第一个英文单词首字母小写,其他英文单词首字母大写。

一个 xml 文件中,可以存在多个 resultMap,只需要 id 不同即可:

java 复制代码
List<User> queryAllMap();
XML 复制代码
 <resultMap id="BaseMap" type="com.example.demo.model.User">
        <id property="id" column="id"></id>
        <result property="name" column="username"></result>
        <result property="pwd" column="password"></result>
    </resultMap>
    <select id="queryAllMap" resultMap="BaseMap">
        select * from userinfo
    </select>
java 复制代码
@Test
    void queryAllMap() {
        List<User> users = userMapper.queryAllMap();
        log.info(users.toString());
    }

此时,我们可以看到成功查询到数据:

那么具体的关系如下图标注所示:

2. 多表查询

我们再新建一张表:

sql 复制代码
-- 创建⽂章表
drop table if exists articleinfo;
create table articleinfo(
 id int primary key auto_increment,
 title varchar(100) not null,
 content text not null,
 createtime datetime default now(),
 updatetime datetime default now(),
 uid int not null,
 rcount int not null default 1,
 `state` int default 1
)default charset 'utf8mb4';

如下图所示:

添加文章的数据:

sql 复制代码
-- ⽂章添加测试数据
insert into articleinfo(title,content,uid)
 values('Java','Java正⽂',1);

文章添加数据后,如下图所示:

首先是 SQL 语句的多表查询:

sql 复制代码
select * from articleinfo ta 
left join userinfo tb on ta.uid = tb.id;

接下来通过 Mybatis 实现:

首先新建 ArticleInfo 类:

java 复制代码
@Data
public class ArticleInfo {
    private Integer id;
    private String title;
    private String content;
    private Date createtime;
    private Date updatetime;
    private Integer rcount;
    private User user;
}

新建 ArticleMapper.xml 文件:

XML 复制代码
<resultMap id="BaseMap" type="com.example.demo.model.ArticleInfo">
        <id property="id" column="id"></id>
        <result property="title" column="title"></result>
        <result property="content" column="content"></result>
        <result property="createtime" column="createtime"></result>
        <result property="updatetime" column="updatetime"></result>
        <association property="user" resultMap="com.example.demo.mapper.UserMapper.BaseMap"></association>
    </resultMap>
    <select id="queryArticle" resultMap="BaseMap">
        select *
        from articleinfo ta
        left join userinfo tb on ta.uid = tb.id
    </select>

添加测试类:

java 复制代码
@Slf4j
@SpringBootTest
class ArticleMapperTest {
    @Autowired
    private ArticleMapper articleMapper;
    @Test
    void queryArticle() {
        List<ArticleInfo> articleInfos = articleMapper.queryArticle();
        log.info(articleInfos.toString());
    }
}

运行结果如下图所示:

我们可以看到上述方式的结果显示的不够完整且需要输入的 SQL 语句过多。


接下来,我们通过另一种方式来实现:

java 复制代码
List<ArticleInfo> queryArticle2();

在 ArticleMapper.xml 文件中添加以下配置:

XML 复制代码
<resultMap id="BaseMap2" type="com.example.demo.model.ArticleInfo">
        <id property="id" column="id"></id>
        <result property="title" column="title"></result>
        <result property="content" column="content"></result>
        <result property="createtime" column="createtime"></result>
        <result property="updatetime" column="updatetime"></result>
        <result property="userId" column="userid"></result>
        <result property="username" column="username"></result>
    </resultMap>
    <select id="queryArticle2" resultMap="BaseMap2">
        select
        ta.*,
        tb.id as userid,
        tb.username as username
        from articleinfo ta
        left join userinfo tb on ta.uid = tb.id
    </select>

添加测试类:

java 复制代码
@Test
    void queryArticle2() {
        List<ArticleInfo> articleInfos = articleMapper.queryArticle2();
        log.info(articleInfos.toString());
    }

查询数据如下图所示:

需要注意,在实际的开发中,要尽量避免使用 *,无论数据库中有多少字段都需要一一罗列出来

如下所示:

XML 复制代码
<resultMap id="BaseMap2" type="com.example.demo.model.ArticleInfo">
        <id property="id" column="id"></id>
        <result property="title" column="title"></result>
        <result property="content" column="content"></result>
        <result property="createtime" column="createtime"></result>
        <result property="updatetime" column="updatetime"></result>
        <result property="userId" column="userid"></result>
        <result property="username" column="username"></result>
    </resultMap>
    <select id="queryArticle2" resultMap="BaseMap2">
        select
        ta.id as id,
        ta.title as title,
        ta.content as content,
        ta.createtime as createtime,
        ta.updatetime as updatetime,
        tb.id as userid,
        tb.username as username
        from articleinfo ta
        left join userinfo tb on ta.uid = tb.id
    </select>

3. 动态 SQL 使用

在实际的应用中,并不是每个信息都是必填的,也就是动态 SQL根据输入参数的不同动态的拼接 SQL。

我们先来看一下表中现有的数据:

接下来,我们插入数据:

java 复制代码
void insert(ArticleInfo articleInfo);
XML 复制代码
<insert id="insert">
        insert into articleinfo(title,content,uid,state)values (#{title},#{content},#{userId},#{state})
    </insert>
java 复制代码
 @Test
    void insert() {
        ArticleInfo articleInfo = new ArticleInfo();
        articleInfo.setTitle("测试文章");
        articleInfo.setContent("测试文章内容");
        articleInfo.setUserId(1);
        articleInfo.setState(null);
        articleMapper.insert(articleInfo);
    }

可以看到,上面我们是自行将 state 的值设置为了 null,那么如果我们没有给这个字段赋值呢?

修改 XML 文件:

XML 复制代码
 <insert id="insert">
        insert into articleinfo(title,content,uid)values (#{title},#{content},#{userId})
    </insert>

可以看到当我们没有对 state 赋值时,进行了自动默认赋值为1:

那么,这显然是不符合我们的预期的,我们想要实现的是当用户没有输入数据时,应该为默认值;输入数据时,显示为输入的数据。此时就需要用到标签了。

4. <if> 标签

我们的目标是根据用户输入的情况,动态拼接 SQL。

java 复制代码
void insertByCondition(ArticleInfo articleInfo);
XML 复制代码
<insert id="insertByCondition">
        insert into articleinfo(title,content,uid
        <if test="state!=null">
            ,state
        </if>
        )
        values

        (#{title},#{content},#{userId}
        <if test="state!=null">
            ,#{state}
        </if>
        )
    </insert>
java 复制代码
@Test
    void insertByCondition() {
        ArticleInfo articleInfo = new ArticleInfo();
        articleInfo.setTitle("测试文章2");
        articleInfo.setContent("测试文章内容2");
        articleInfo.setUserId(1);
        articleMapper.insert(articleInfo);
    }

由于我们并没有设置 state 的状态,因此默认为1:

接下来我们设置 state 为0:

java 复制代码
@Test
    void insertByCondition() {
        ArticleInfo articleInfo = new ArticleInfo();
        articleInfo.setTitle("测试文章3");
        articleInfo.setContent("测试文章内容3");
        articleInfo.setUserId(1);
        articleInfo.setState(0);
        articleMapper.insert(articleInfo);
    }

当我们需要对多个字段应用 if 标签时,会存在报错:

如果统一把逗号放在字段前面,当第一个字段为 null 时,整个 SQL 的最前面就会多一个逗号;如果统一把逗号放在字段后面,当最后一个字段为 null 时,整个 SQL 的最后面会多一个逗号。

5. <trim> 标签

上面的插入数据功能,如果所有字段都是非必填项,就考虑使用标签结合标签,对多个字段都采取动态生成的方式。 标签中有如下属性:

  • prefix:表示整个语句块,以prefix的值作为前缀
  • suffix:表示整个语句块,以suffix的值作为后缀
  • prefixOverrides:表示整个语句块要去除掉的前缀
  • suffixOverrides:表示整个语句块要去除掉的后缀

使用 <trim> 标签:

XML 复制代码
<insert id="insertByCondition">
        insert into articleinfo
        <trim prefix="(" suffix=")" prefixOverrides="," suffixOverrides=",">
            <if test="title!=null">
                title,
            </if>
            <if test="content!=null">
                content,
            </if>
            <if test="userId!=null">
                uid,
            </if>
            <if test="state!=null">
                state
            </if>
        </trim>
        values
        <trim prefix="(" suffix=")" prefixOverrides="," suffixOverrides=",">
            <if test="title!=null">
                #{title},
            </if>
            <if test="content!=null">
                #{content},
            </if>
            <if test="userId!=null">
                #{content},
            </if>
            <if test="state!=null">
                #{state},
            </if>
        </trim>
    </insert>

可以看到此时能够正确执行:

6. <where> 标签

当我们需要使用 where 语句进行条件筛选时:

java 复制代码
List<ArticleInfo> queryBycondition(@Param("uid") Integer uid,@Param("state")Integer state);
XML 复制代码
<select id="queryBycondition" resultType="com.example.demo.model.ArticleInfo">
        select * from articleinfo
        where
        <if test="uid!=null">
            uid = #{uid}
        </if>
        <if test="state!=null">
            and state=#{state}
        </if>
    </select>
java 复制代码
@Test
    void queryBycondition() {
        List<ArticleInfo> articleInfos = articleMapper.queryBycondition(1,1);
        log.info(articleInfos.toString());
    }

可以看到成功执行:

此时我们修改代码如下:

java 复制代码
 @Test
    void queryBycondition() {
        List<ArticleInfo> articleInfos = articleMapper.queryBycondition(1,null);
        log.info(articleInfos.toString());
    }

依然可以成功执行:

当我们修改代码如下时:

java 复制代码
 @Test
    void queryBycondition() {
        List<ArticleInfo> articleInfos = articleMapper.queryBycondition(null,1);
        log.info(articleInfos.toString());
    }

产生报错信息:

添加语句 1 = 1,继续修改代码如下:

XML 复制代码
<select id="queryBycondition" resultType="com.example.demo.model.ArticleInfo">
        select * from articleinfo
        where 1 = 1
        <if test="uid!=null">
            and uid = #{uid}
        </if>
        <if test="state!=null">
            and state=#{state}
        </if>
    </select>

此时可以看到成功执行:


接下来,我们使用 where 标签实现以上功能。

在 XML 文件中,添加以下语句:

XML 复制代码
<select id="queryBycondition" resultType="com.example.demo.model.ArticleInfo">
        select * from articleinfo
        <where>
            <if test="uid!=null">
                and uid = #{uid}
            </if>
            <if test="state!=null">
                and state=#{state}
            </if>
        </where>
    </select>

可以已经生成了 where 并且去掉了 and :

当两个字段均为 null 时,可以看到直接去掉了 where:

java 复制代码
@Test
    void queryBycondition() {
        List<ArticleInfo> articleInfos = articleMapper.queryBycondition(null,null);
        log.info(articleInfos.toString());
    }

综上,我们可以知道 where 标签具有以下作用:

  1. 生成 where 关键字
  2. 去除多余的 and
  3. 如果没有 where 条件,就不会生成 where 关键字

7. <set> 标签

java 复制代码
void updateByCondition(@Param("uid") Integer uid,@Param("state")Integer state);
XML 复制代码
<update id="updateByCondition">
        update articleinfo
        set
        <if test="uid!=null">
            uid = #{uid},
        </if>
        <if test="state!=null">
            state = #{state},
        </if>
    </update>
java 复制代码
@Test
    void updateByCondition() {
        articleMapper.updateByCondition(1,null);
    }

运行后,产生以下报错:

接下来,我们使用 set 标签:

XML 复制代码
<update id="updateByCondition">
        update articleinfo
        <set>
            <if test="uid!=null">
                uid = #{uid},
            </if>
            <if test="state!=null">
                state = #{state},
            </if>
        </set>
    </update>

运行成功:

综上,我们可以看到 set 标签的作用:

  1. 生成 set 关键字
  2. 去除最后一个逗号(也可以使用 trim 标签)

8. <foreach> 标签

对集合进行遍历时可以使用该标签。标签有如下属性:

  • collection:绑定方法参数中的集合,如 List,Set,Map或数组对象
  • item:遍历时的每⼀个对象
  • open:语句块开头的字符串
  • close:语句块结束的字符串
  • separator:每次遍历之间间隔的字符串

因此,我们来通过 foreach 标签实现以下目标:

接下来我们通过代码实现:

java 复制代码
void batchDelete(List<Integer> ids);
XML 复制代码
<delete id="batchDelete">
        delete from articleinfo where id in
        <foreach collection="list" open="(" close=")" separator="," item="id">
            #{id}
        </foreach>
    </delete>
java 复制代码
@Test
    void batchDelete() {
        List<Integer> ids = Arrays.asList(2,3,4,5,6,10,11);
        articleMapper.batchDelete(ids);
    }

可以看到成功运行:

表中相应的数据也删除了:

注意:

还需要注意的是 collection 也可以是参数的名称:

9. 通过注解实现

Mybatis 的实现有两种方式:

  • xml
  • 注解

9.1 查找所有数据

接下来,我们来看一下如何通过注解来实现:

java 复制代码
@Mapper
public interface UserMapper2 {
    @Select("select * from userinfo")
    List<User> queryAll();
}
java 复制代码
@Slf4j
@SpringBootTest
class UserMapper2Test {
    @Autowired
    private UserMapper2 userMapper2;
    @Test
    void queryAll() {
       List<User> userList = userMapper2.queryAll();
       log.info(userList.toString());
    }
}

运行结果如下:

9.2 通过 id 查找

java 复制代码
@Mapper
public interface UserMapper2 {

    @Select("select * from userinfo where id = #{uid}")
    User queryById(Integer aaa);
}
java 复制代码
@Slf4j
@SpringBootTest
class UserMapper2Test {
    @Autowired
    private UserMapper2 userMapper2;

    @Test
    void queryById() {
        User user = userMapper2.queryById(1);
        log.info(user.toString());
    }
}

可以看到运行结果如下:

一个项目中,注解和 XML 的方式可以并存,对于简单的 SQL 使用注更加方便,但是对于动态 SQL 注解写起来非常麻烦。


相关推荐
ok!ko2 小时前
设计模式之原型模式(通俗易懂--代码辅助理解【Java版】)
java·设计模式·原型模式
2402_857589362 小时前
“衣依”服装销售平台:Spring Boot框架的设计与实现
java·spring boot·后端
吾爱星辰3 小时前
Kotlin 处理字符串和正则表达式(二十一)
java·开发语言·jvm·正则表达式·kotlin
ChinaDragonDreamer3 小时前
Kotlin:2.0.20 的新特性
android·开发语言·kotlin
IT良3 小时前
c#增删改查 (数据操作的基础)
开发语言·c#
哎呦没3 小时前
大学生就业招聘:Spring Boot系统的架构分析
java·spring boot·后端
Kalika0-04 小时前
猴子吃桃-C语言
c语言·开发语言·数据结构·算法
_.Switch4 小时前
Python Web 应用中的 API 网关集成与优化
开发语言·前端·后端·python·架构·log4j
编程、小哥哥4 小时前
netty之Netty与SpringBoot整合
java·spring boot·spring
代码雕刻家4 小时前
课设实验-数据结构-单链表-文教文化用品品牌
c语言·开发语言·数据结构