mybatis动态SQL之foreach、模糊查询、分页

这篇博客详细探讨了Mybatis的动态SQL特性,包括foreach标签在批量查询中的应用,如何进行模糊查询,以及如何实现高效分页。通过具体的示例,展示了在mapper.xml中如何配置动态SQL,以及在遇到问题时如何解决,例如字段命名不一致导致的空对象问题。同时,文章提到了Mybatis内置分页的局限性,并给出了增强型分页的解决方案。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

前言

之前我们讲过了mybatis的sql语句是由我们手动编写的,但是我们的mybatis逆向工程生成的xxMapper.xml中会自动生成一部分动态的sql

什么叫做动态SQL?

根据用户提供的参数,动态决定查询语句依赖的查询条件或SQL语句的内容。

这篇博客主要讲述mybatis的动态SQL以及其中的分页。

测试mybatis的自生动态sql

首先我们根据service类创建一个测试类
在这里插入图片描述
勾上setUp和tearDown,再选勾上自己要测试的方法
在这里插入图片描述

将测试类准备好:

package com.zengjing.service;

import com.zengjing.mapper.BookMapper;
import com.zengjing.model.Book;
import com.zengjing.service.impl.BookServiceImpl;
import com.zengjing.util.SessionUtil;
import org.apache.ibatis.session.SqlSession;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;

/**
 * @author zengjing
 * @site https://blog.youkuaiyun.com/JiangHu_Java
 * @create  2020-10-21 14:38
 */
public class IBookServiceTest {

    private IBookService bookService;
    private SqlSession sqlSession;


    @Test
    public void deleteByPrimaryKey() {
        this.bookService.deleteByPrimaryKey(12);
    }

    @Test
    public void updateByPrimaryKey() {
        Book book=new Book();
        book.setBookId(12);
        book.setBookName("新神雕侠侣5555");
        book.setBookPrice((float) 38);
        book.setBookBrief("略ovo");
        this.bookService.updateByPrimaryKey(book);
    }

    @Test
    public void insert() {
        Book book=new Book();
        book.setBookId(13);
        book.setBookName("测试1");
        book.setBookPrice((float) 14);
        book.setBookBrief("略");
        this.bookService.insert(book);
    }


    @Test
    public void selectByPrimaryKey() {
        Book book = this.bookService.selectByPrimaryKey(1);
        System.out.println(book);
    }


    @Before
    public void setUp() throws Exception {
        BookServiceImpl bookService=new BookServiceImpl();
        sqlSession = SessionUtil.openSession();
        BookMapper mapper = sqlSession.getMapper(BookMapper.class);
        bookService.setBookMapper(mapper);
        this.bookService=bookService;
    }

    @After
    public void tearDown() throws Exception {
        sqlSession.commit();
        sqlSession.close();
    }

}

再看看生成的xxMapper.xml中自动生成的动态sql:

<?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.zengjing.mapper.BookMapper" >
  <resultMap id="BaseResultMap" type="com.zengjing.model.Book" >
    <constructor >
      <idArg column="book_id" jdbcType="INTEGER" javaType="java.lang.Integer" />
      <arg column="book_name" jdbcType="VARCHAR" javaType="java.lang.String" />
      <arg column="book_price" jdbcType="REAL" javaType="java.lang.Float" />
      <arg column="book_brief" jdbcType="VARCHAR" javaType="java.lang.String" />
    </constructor>
  </resultMap>
  <sql id="Base_Column_List" >
    book_id, book_name, book_price, book_brief
  </sql>
  <select id="selectByPrimaryKey" resultMap="BaseResultMap" parameterType="java.lang.Integer" >
    select 
    <include refid="Base_Column_List" />
    from t_book
    where book_id = #{bookId,jdbcType=INTEGER}
  </select>
  <delete id="deleteByPrimaryKey" parameterType="java.lang.Integer" >
    delete from t_book
    where book_id = #{bookId,jdbcType=INTEGER}
  </delete>
  <insert id="insert" parameterType="com.zengjing.model.Book" >
    insert into t_book (book_id, book_name, book_price, 
      book_brief)
    values (#{bookId,jdbcType=INTEGER}, #{bookName,jdbcType=VARCHAR}, #{bookPrice,jdbcType=REAL}, 
      #{bookBrief,jdbcType=VARCHAR})
  </insert>
  <insert id="insertSelective" parameterType="com.zengjing.model.Book" >
    insert into t_book
    <trim prefix="(" suffix=")" suffixOverrides="," >
      <if test="bookId != null" >
        book_id,
      </if>
      <if test="bookName != null" >
        book_name,
      </if>
      <if test="bookPrice != null" >
        book_price,
      </if>
      <if test="bookBrief != null" >
        book_brief,
      </if>
    </trim>
    <trim prefix="values (" suffix=")" suffixOverrides="," >
      <if test="bookId != null" >
        #{bookId,jdbcType=INTEGER},
      </if>
      <if test="bookName != null" >
        #{bookName,jdbcType=VARCHAR},
      </if>
      <if test="bookPrice != null" >
        #{bookPrice,jdbcType=REAL},
      </if>
      <if test="bookBrief != null" >
        #{bookBrief,jdbcType=VARCHAR},
      </if>
    </trim>
  </insert>
  <update id="updateByPrimaryKeySelective" parameterType="com.zengjing.model.Book" >
    update t_book
    <set >
      <if test="bookName != null" >
        book_name = #{bookName,jdbcType=VARCHAR},
      </if>
      <if test="bookPrice != null" >
        book_price = #{bookPrice,jdbcType=REAL},
      </if>
      <if test="bookBrief != null" >
        book_brief = #{bookBrief,jdbcType=VARCHAR},
      </if>
    </set>
    where book_id = #{bookId,jdbcType=INTEGER}
  </update>
  <update id="updateByPrimaryKey" parameterType="com.zengjing.model.Book" >
    update t_book
    set book_name = #{bookName,jdbcType=VARCHAR},
      book_price = #{bookPrice,jdbcType=REAL},
      book_brief = #{bookBrief,jdbcType=VARCHAR}
    where book_id = #{bookId,jdbcType=INTEGER}
  </update>
</mapper>

我们首先找到我们熟悉的标签:select,insert,update,delete,可以看到这四个标签都是有的,为什么会有?其实这个映射文件里的这些标签都是根据对应的mapper接口里的方法生成的。

这些sql的标签的id都是mapper接口里方法的方法名 ,带的参数参数名也是一致的,如果是带的对象的话,则会使用id标签来对对象的属性一个一个判断要执行的字段是哪些:

package com.zengjing.mapper;

import com.zengjing.model.Book;

public interface BookMapper {
    int deleteByPrimaryKey(Integer bookId);

    int insert(Book record);

    int insertSelective(Book record);

    Book selectByPrimaryKey(Integer bookId);

    int updateByPrimaryKeySelective(Book record);

    int updateByPrimaryKey(Book record);
}

我们以此来看这些标签,select:


  <resultMap id="BaseResultMap" type="com.zengjing.model.Book" >
    <constructor >
      <idArg column="book_id" jdbcType="INTEGER" javaType="java.lang.Integer" />
      <arg column="book_name" jdbcType="VARCHAR" javaType="java.lang.String" />
      <arg column="book_price" jdbcType="REAL" javaType="java.lang.Float" />
      <arg column="book_brief" jdbcType="VARCHAR" javaType="java.lang.String" />
    </constructor>
  </resultMap>
  
  <sql id="Base_Column_List" >
    book_id, book_name, book_price, book_brief
  </sql>
  
  <select id="selectByPrimaryKey" resultMap="BaseResultMap" parameterType="java.lang.Integer" >
    select 
    <include refid="Base_Column_List" />
    from t_book
    where book_id = #{bookId,jdbcType=INTEGER}
  </select>

其中resultMap是用来存放我们查询的结果集的,sql标签存放了我们查询的字段。

测试:

在这里插入图片描述
查询结果:

在这里插入图片描述
insert:

 <insert id="insertSelective" parameterType="com.zengjing.model.Book" >
    insert into t_book
    <trim prefix="(" suffix=")" suffixOverrides="," >
      <if test="bookId != null" >
        book_id,
      </if>
      <if test="bookName != null" >
        book_name,
      </if>
      <if test="bookPrice != null" >
        book_price,
      </if>
      <if test="bookBrief != null" >
        book_brief,
      </if>
    </trim>
    <trim prefix="values (" suffix=")" suffixOverrides="," >
      <if test="bookId != null" >
        #{bookId,jdbcType=INTEGER},
      </if>
      <if test="bookName != null" >
        #{bookName,jdbcType=VARCHAR},
      </if>
      <if test="bookPrice != null" >
        #{bookPrice,jdbcType=REAL},
      </if>
      <if test="bookBrief != null" >
        #{bookBrief,jdbcType=VARCHAR},
      </if>
    </trim>
  </insert>

其中使用了trim来进行括号以及最后的逗号的去除,测试:

在这里插入图片描述

update测试:

在这里插入图片描述
delect测试:

在这里插入图片描述

注意,测试前需要增加一段pom.xml中的配置文件:

  <resources>
      <!--解决mybatis-generator-maven-plugin运行时没有将XxxMapper.xml文件放入target文件夹的问题-->
      <resource>
        <directory>src/main/java</directory>
        <includes>
          <include>**/*.xml</include>
        </includes>
      </resource>
      <!--解决mybatis-generator-maven-plugin运行时没有将jdbc.properites文件放入target文件夹的问题-->
      <resource>
        <directory>src/main/resources</directory>
        <includes>
          <include>jdbc.properties</include>
          <include>*.xml</include>
        </includes>
      </resource>
    </resources>

添加位置,build标签中:

在这里插入图片描述

foreach标签

作用:foreach 遍历集合,批量查询、通常用于in关键字

我们首先在mapper.java类中增加要的方法:


    /**
     * 加Param注解是因为形参需要在mapper.xml文件中使用
     * @param BookIds
     * @return
     */
    List<Book> selectBooksIn(@Param("BookIds") List BookIds);

然后我们在mapper.xml文件中增加foreach标签:

resultType是用来定义一个返回结果集对象的全限定名或者别名。如果接收参数是一个集合,那么这里定义的就是集合中可以包含的类型,而并不是集合本身。

parameterType是用来定义传递参数的类型。

collection是用来定义传递参数也就是数据源的名称,item是循环集合中的对象别名,open是用来开头的;close是加在结束的,separator是每次循环间加的。

  <!--foreach标签使用-->
  <select id="selectBooksIn" resultType="com.zengjing.model.Book" parameterType="java.util.List" >
    select * from t_book where bookId in
    <foreach collection="BookIds" item="bid" open="(" close=")" separator=",">
      #{bid}
    </foreach>
  </select>

然后我们在service类中增加相应的方法:

IBookService增加:

List<Book> selectBooksIn(List BookIds);

实现类BookServiceImpl增加:

 @Override
    public List<Book> selectBooksIn(List BookIds) {  return bookMapper.selectBooksIn(BookIds); }

然后我们测试:

    @Test
    public void selectBooksIn(){
        List list=new ArrayList();
        list.add(1);
        list.add(3);
        list.add(11);
        List<Book> books=this.bookService.selectBooksIn(list);
        System.out.println(books.size());
        for (Book b:books
             ) {
            System.out.println(b);
        }
    }

在这里插入图片描述

遇到的小bug

博主在测试foreach标签时,出现了一个bug:明明查到了结果,但是集合当中的对象显示为空,这是因为博主之前在数据库当中字段命名的方法是加了下划线,类似book_id,但是mybatis逆向工程生成的实体类字段名是bookId,导致两边字段不一致,我用一个book的集合来装的时候,他识别不出来是实体类的book,就变成了null,其实是有结果的。
在这里插入图片描述

模糊查询

模糊查询一共有三种拼接sql语句的方法:

#{…}

#会在前后加单引号[']

${…}

$则直接插入值

concat

拼接函数

我们一般都会采用#,因为$有sql注入的危险,而concat又过于麻烦。

我们依次测试:

首先准备好工具类:

package com.zengjing.util;

/**
 * @author zengjing
 * @site https://blog.youkuaiyun.com/JiangHu_Java
 * @create  2020-10-26 19:56
 */
public class StringUtils {
    public static String toLikeStr(String str){
        return "%"+str+"%";
    }
}

mapper.java中增加方法:

    List<Book> selectBookLike1(@Param("bookName") String bookName);
    List<Book> selectBookLike2(@Param("bookName") String bookName);
    List<Book> selectBookLike3(@Param("bookName") String bookName);

mapper.xml中的事务,具体差异就是体现在这里,由#插入的值直接写参数名就行了,而$需要加单引号,且前面也说了sql注入的危险,不懂的可以去博主上篇博客看看,而concat确实比较前两者稍显麻烦

  <!--模糊查询的三种拼接sql语句的方法-->
  <select id="selectBookLike1"  resultType="com.zengjing.model.Book" parameterType="java.lang.String">
      select * from t_book where bookName like #{bookName}
  </select>

  <select id="selectBookLike2"  resultType="com.zengjing.model.Book" parameterType="java.lang.String">
      select * from t_book where bookName like '${bookName}'
  </select>

  <select id="selectBookLike3"  resultType="com.zengjing.model.Book" parameterType="java.lang.String">
      select * from t_book where bookName like concat(concat('%',#{bookName}),'%')
  </select>

IBookService增加:

 	List<Book> selectBookLike1(String bookName);
    List<Book> selectBookLike2(String bookName);
    List<Book> selectBookLike3(String bookName);

实现类BookServiceImpl增加:

    @Override
    public List<Book> selectBookLike1(String bookName) {
        return bookMapper.selectBookLike1(bookName);
    }

    @Override
    public List<Book> selectBookLike2(String bookName) {
        return bookMapper.selectBookLike2(bookName);
    }

    @Override
    public List<Book> selectBookLike3(String bookName) {
        return bookMapper.selectBookLike3(bookName);
    }

IBookServiceTest测试代码:

@Test
    public void selectBookLike() {
        String bname = "的";
        //第一种
        //List<Book> books =this.bookService.selectBookLike1(StringUtils.toLikeStr(bname));
        //第二种
        //List<Book> books =this.bookService.selectBookLike2(StringUtils.toLikeStr(bname));
        //第三种
        List<Book> books =this.bookService.selectBookLike3(bname);
        for (Book b : books) {
            System.out.println(b);
        }
    }

测试结果:

在这里插入图片描述

分页

其实mybatis中是自带分页的,那么为什么要将分页特意做一遍?

Mybatis的分页功能很弱,它是基于内存的分页(查出所有记录再按偏移量offset和边界limit取结果),在大数据量的情况下这样的分页基本上是没有用的。

意思就是,mybatis中的分页是将数据库的数据全都查询并保存到内存当中,然后我们需要的时候根据页数将数据取出来,那么当数据过多的时候,不仅查询时间慢,而且那么多的数据保存到内存当中可能导致的问题也会有很多,所以我们需要将分页特意做一遍。

首先导入依赖

    <dependency>
      <groupId>com.github.pagehelper</groupId>
      <artifactId>pagehelper</artifactId>
      <version>5.1.2</version>
    </dependency>

在mybatis.cfg.xml的运行环境前配置

在这里插入图片描述

 <plugins>
        <!-- 配置分页插件PageHelper, 4.0.0以后的版本支持自动识别使用的数据库 -->
        <plugin interceptor="com.github.pagehelper.PageInterceptor"></plugin>
    </plugins>

Bookmapper.java中增加分页的方法

List<Map> listPager(Map map);

BookMapper.xml中增加标签

  <select id="listPager" resultType="java.util.Map">
      select * from t_book where bookName like #{bookName}
  </select>

IBookService增加方法

List<Map> listPager(Map map, PageBean pageBean);

IBookService的实现类BookServiceImpl增加方法,分页最主要的代码处理就在这里,就是PageHelper处理了分页的请求,我们先测试成功的结果

 @Override
    public List<Map> listPager(Map map, PageBean pageBean) {
        if(pageBean !=null && pageBean.isPagination()){
            PageHelper.startPage(pageBean.getPage(),pageBean.getRows());
        }
        List<Map> list =bookMapper.listPager(map);
        if(pageBean !=null && pageBean.isPagination()){
            PageInfo pageInfo=new PageInfo(list);
            System.out.println("总记录数:"+pageInfo.getTotal());
            System.out.println("当前页:"+pageInfo.getPageNum());
            System.out.println("页大小:"+pageInfo.getPageSize());
            pageBean.setTotal(pageInfo.getTotal()+"");
            System.out.println("总页数:" + pageBean.getMaxPage());
        }
        return list;
    }

测试代码:

    @Test
    public void listPager() {
        Map map=new HashMap();
        map.put("bookName","%%");

        PageBean pageBean =new PageBean();
        pageBean.setPage(1);
        List<Map> list=this.bookService.listPager(map,pageBean);
        for (Map m:list
             ) {
            System.out.println(m);
        }


    }

测试结果:

在这里插入图片描述
假如我们将BookServiceImpl的listPager中的PageHelper处理那一行代码注释掉再运行
在这里插入图片描述
测试结果,可以看到传递到数据库的sql语句没有了后面的limit
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值