飞道的博客

快速掌握 MyBatis 框架(二)

410人阅读  评论(0)

一、前言

快速掌握 MyBatis 框架(一)

1.1 数据库与表

create database if not exists library;
use library;

drop table if exists book;
create table book(
    id int primary key auto_increment,  -- 书籍Id
    bookName varchar(100) not null,     -- 书名
    content varchar(1024) not null,     -- 书籍内容
    authorId int not null,              -- 作者Id
    `state` int default 1               -- 借出状态,默认为1,未借出
);
drop table if exists author;
create table author(
    id int primary key auto_increment,  -- 作者Id
    authorName varchar(100) not null,   -- 作者名字
    age int,                            -- 作者年龄
    nationality varchar(250)            -- 作者国籍
);

1.2 实体类

//书籍类
@Data
public class Book {
   
    private Integer id;
    private String bookName;
    private String content;
    private Integer authorId;
    private Integer state;
    private Author author;
}
//作者类
@Data
public class Author {
   
    private Integer id;
    private String authorName;
    private Integer age;
    private String nationality;
    private List<Book> books;
}

1.3 MyBatisX 插件

在写 MyBatis 代码的时候有一个非常好用的插件——MyBatisX

选择 File->Settings ,然后进行如下操作进行插件的安装

该插件可以实现接口代码和对应的 XML 文件中的代码的跳转

而且写好一个 接口方法后可以在对应的 XML 文件中自动生成代码(出现红下划线,Alt+Enter,选择第一个选项),当然,这样的方式生成的标签不一定是我们想要的


1.4 SQL 日志查看配置

为了查看写好的 SQL ,可以在配置文件中进行配置,如此,在控制台就可以查看 SQL 日志

mybatis:
  configuration:
    log-impl: org.apache.ibatis.logging.stdout.StdOutImpl

二、多表查询

在前面一篇文章中有讲数据库的增加(insert)、删除(delete)、修改(update)、查询(select)操作

增删改这三个操作返回的值都是影响的行数,所以在写 SQL 的 XML 时并不需要指定返回类型,但是如果是查询操作就需要通过 resultType 来设置返回类型,就算是 String 类型,返回值也要设置为 resultType =“java.lang.String”

但是如果进行多表查询,实体类中就会有一个属性为另一个实体类,比如上面的实体类定义中,一本书对应一个作者,想要将作者的完整信息都放在书籍类中。一个作者可以写很多本书,想要将这个作者写的所有书的信息都放在一个 List 中。这样的情况,简简单单通过 resultType 已经没有办法实现,如果只是使用 resultType ,会发现对应的类的属性值为 null(变量 author 和 变量 books 的值为 null)

此时就需要使用 resultMap ,返回一个字典映射

2.1 一对一

一本书对应一个作者的情况

interface

@Mapper
public interface BookMapper {
   
    public Book getBookById(Integer id);
}

XML

BookMapper.xml

<?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.example.demo.Mapper.BookMapper">

    <resultMap id="BaseMap" type="com.example.demo.model.Book">
        <id property="id" column="id"></id>
        <result property="bookName" column="bookName"></result>
        <result property="content" column="content"></result>
        <result property="authorId" column="authorId"></result>
        <result property="state" column="state"></result>
        <association property="author"
                     resultMap="com.example.demo.Mapper.AuthorMapper.BaseMap"
                     columnPrefix="a_">
        </association>
    </resultMap>
    <select id="getBookById" resultMap="BaseMap">
        select b.*,a.id a_id,a.authorName a_authorName,a.age a_age,a.nationality a_nationality
        from book b left join author a
        on b.authorId = a.id where b.id = #{id}
    </select>
</mapper>

解释:

  1. select 标签中的 resultMap 的值 “BaseMap” 就是一个标识和上面 resultMap 标签中的 id 属性值 “BaseMap” 是对应的,起什么名字都行,
  2. resultMap 标签中的 type 属性值为映射的实体类的包名加类名
  3. id 标签指的是主键result 标签指的是普通列
  4. property 属性指的是程序中的属性名,column 属性指的是数据库中的字段名。因此,即使程序中的属性名和数据库中的字段名不一致也不要紧,通过这样的映射就不会出错
  5. 由于是一对一的多表查询,使用的是association标签。
    • property属性指的是 Book 类中关联的 Author 类的变量,即 author;
    • resultMap 属性指的是指定关联的结果集映射,将基于该映射配置来组织用户数据,这里关联的就是 AuthorMapper 里的 BaseMap(就是下面的代码);
    • columnPrefix 属性指的是给关联的数据库中的 column 添加一个前缀(如果不添加前缀,当 author 表和 book 表中同时有 id 字段,查询结果时一定会产生覆盖,使得两个 id 的值一样);

AuthorMapper.xml

<?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.example.demo.Mapper.AuthorMapper">
    <resultMap id="BaseMap" type="com.example.demo.model.Author">
        <id property="id" column="id"></id>
        <result property="authorName" column="authorName"></result>
        <result property="age" column="age"></result>
        <result property="nationality" column="nationality"></result>
    </resultMap>
</mapper>

单元测试代码

@SpringBootTest
class BookMapperTest {
   
    @Autowired
    private BookMapper mapper;
    @Test
    void getBookById() {
   
        System.out.println(mapper.getBookById(1));
    }
}

结果显示

2.2 一对多

一对多和一对一的写法大体上是一样的额,不同的是一对一使用的是 association 标签,一对多使用的是 collection 标签

interface

public List<Author> getAuthor(Integer id);

XML

<?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.example.demo.Mapper.AuthorMapper">
    <resultMap id="BaseMap" type="com.example.demo.model.Author">
        <id property="id" column="id"></id>
        <result property="authorName" column="authorName"></result>
        <result property="age" column="age"></result>
        <result property="nationality" column="nationality"></result>
        <collection property="books"
                    resultMap="com.example.demo.Mapper.BookMapper.BaseMap"
                    columnPrefix="b_">
        </collection>
    </resultMap>
    <select id="getAuthor" resultMap="BaseMap">
        select a.*,b.id b_id,b.bookName b_bookName,b.content b_content
        from author a left join book b
        on a.id = b.authorId where a.id = #{id}
    </select>
</mapper> 

关联的BookMapper 中的 BaseMap 在上面一对一的代码中已经写了

单元测试代码

@Test
void getAuthor() {
   
    List<Author> list = mapper.getAuthor(1);
    list.stream().forEach(n-> System.out.println(n));
}

结果展示

三、动态 SQL 使用

为了应对各种各样的需求,就需要动态 SQL 完成不同条件下不同的 SQL 拼接

3.1 < if > 标签

在完善信息的时候,有些信息是必须要填的,有些是非必填的。就像 author 表中作者的信息一样,只有 authorName 是一定要填写的,age 和 nationality 是非必填的,那么在插入数据的时候,就需要应对各种信息插入情况,就需要用 < if > 标签来判断传来的值是否为 null,如果是就不将内容拼接到 SQL 中

interface

public int setAuthor3(Author author);

XML

<insert id="setAuthor3" >
    insert into author(authorName
    <if test="age != null">
        ,age
    </if>
    <if test="nationality != null">
        ,nationality
    </if>
    ) values(#{authorName}
    <if test="age != null">
        ,#{age}
    </if>
    <if test="nationality != null">
        ,#{nationality}
    </if>
    )
</insert>

解释:

  1. 通过 if 标签中的 test属性中的内容(对象中的属性)是否为空来决定是否将其拼接到 SQL 语句中
  2. 需要注意拼接时的逗号以及括号,保证在任何情况下都能组装成正确的 SQL 语句
  3. 由于需要判空,因此创建实体类的时候最好使用包装类,比如使用 Integer 类型而不是 int 类型。因为 int 类型默认值为 0 ,不会为 null,而 0 和 null 还是有很大区别的,使用 int 类型会存在报错的风险

测试代码展示

@Test
void setAuthor3() {
   
    Author author = new Author();
    author.setAuthorName("钱七");
    author.setNationality("马来西亚");
    System.out.println("更新的数据条数:"+mapper.setAuthor3(author));
}

结果显示

3.2 < trim > 标签

该标签是配合 if 标签进行使用,试想极端情况,所有的参数都是非必传的,那么在不知道哪个参数是第一个,哪个参数是最后一个的情况下,一定会有多出来的逗号,trim 标签就可以解决这样的问题

trim 标签的属性

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

interface

public int setAuthor4(Author author);

XML

<insert id="setAuthor4">
    insert into author
    <trim prefix="(" suffix=")" prefixOverrides=",">
        <if test="authorName != null">
            ,authorName
        </if>
        <if test="age != null">
            ,age
        </if>
        <if test="nationality != null">
            ,nationality
        </if>
    </trim>
    values
    <trim prefix="(" suffix=")" suffixOverrides=",">
        <if test="authorName != null">
            #{authorName},
        </if>
        <if test="age != null">
            #{age},
        </if>
        <if test="nationality != null">
            #{nationality},
        </if>
    </trim>
</insert>

注意:如果 trim 标签中的内容没有一条条件成立 ,就不会执行 trim 语句的内容了,包括其属性也不会生效。因此如果有必传参数,就往 trim 标签中添加必传参数;如果没有任何必传参数,前缀后缀相关属性就在 trim 中设置

单元测试代码

@Test
void setAuthor4() {
   
    Author author = new Author();
    author.setAuthorName("朱八");
    author.setAge(66);
    System.out.println("更新的数据条数:"+mapper.setAuthor4(author));
}

结果显示

3.3 < where > 标签

传入一个对象,根据属性进行 where 条件查询,只要对象中的属性不为 null 就是查询条件

interface

public List<Book> getBookByIdOrAuthorId(Book book);

XML

<select id="getBookByIdOrAuthorId" resultType="com.example.demo.model.Book">
    select * from book
    <where>
        <if test="id != null">
            id=#{id}
        </if>
        <if test="authorId != null">
            and authorId=#{authorId}
        </if>
    </where>
</select>

解释:

  1. where 标签自带 where 关键字,并且会自动去除多余的 and
  2. 可以使用 < trim prefix=“where” prefixOverrides=“and” > 替换

单元测试代码

@Test
void getBookByIdOrAuthorId() {
   
    Book book = new Book();
    book.setAuthorId(1);
    List<Book> list = mapper.getBookByIdOrAuthorId(book);
    list.stream().forEach(n-> System.out.println(n));
}

结果显示

3.4 < set > 标签

传入一个对象,根据属性进行更新用户的数据,比如根据传入的对象的 id ,修改其他不为 null 的属性

interface

public int updateBook(Book book);

XML

<update id="updateBook">
    update book 
    <set>
        <if test="bookName != null">
            bookName=#{bookName},
        </if>
        <if test="content != null">
            content=#{content},
        </if>
        <if test="authorId != null">
            authorId=#{authorId}
        </if>
    </set>
    where id=#{id}
</update>

解释:

  1. set 标签自带 set 关键字,会自动去除多余的逗号
  2. 可以使用 < trim prefix=“set” suffixOverrides=“,”> 替换

单元测试代码

@Test
void updateBook() {
   
    Book book = new Book();
    book.setId(1);
    book.setContent("更新内容~");
    System.out.println("更新数据的条数:"+mapper.updateBook(book));
}

结果显示

3.5 < foreach > 标签

需要对一个集合遍历时使用该标签

foreach 标签属性

  • collection:绑定方法参数中的集合(List、Set、Map、数组…)
  • item:用于指定遍历时的每一个对象
  • open:整个语句块开头的字符串
  • close:整个语句块结束的字符串
  • separator:遍历元素之间间隔的字符串

interface

public int deleteByIdList(List<Integer> list);

XML

<delete id="deleteByIdList">
    delete from book where id in
    <foreach collection="list" item="bookId" open="(" close=")" separator=",">
        #{bookId}
    </foreach>
</delete>

单元测试代码

@Test
void deleteByIdList() {
   
    List<Integer> list = new ArrayList<>();
    list.add(1);
    list.add(3);
    System.out.println("更新数据的条数:"+mapper.deleteByIdList(list));
}

结果显示

完~~~


转载:https://blog.csdn.net/weixin_46103589/article/details/125499725
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场