MENU

浅入深出-Mybatis框架笔记

November 28, 2020 • Read: 434 • 教程学习

Mybatis笔记

一、简介

1.1 什么是mybatis

MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。

MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。

MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO为数据库中的记录。

持久层框架:所谓“持久”就是将数据保存到可掉电式存储设备中以便今后使用,简单的说,就是将内存中的数据保存到关系型数据库、文件系统、消息队列等提供持久化支持的设备中。持久层就是系统中专注于实现数据持久化的相对独立的层面。

常见的持久层框架

  • -Hibernate
  • - MyBatis
  • - TopLink
  • - Guzz
  • - JOOQ
  • - Spring Data
  • - ActiveJDBC

1.2 Mybatis的历史

MyBatis项目继承自iBATIS3.0,其维护团队也包含iBits的初创成员。

2010年5月19日项目创建。当时Apache iBATIS 3.0发布,其开发团队宣布会在新的名字、新的站点中继续开发

2013年11月10日,项目迁移到了GitHub

1.3 为什么要使用Mybatis框架

1.3.1 传统方式JDBC存在的问题

1、使用JDBC访问数据库有大量重复代码(比如注册驱动、获取连接、获取传输器、释放资源等);

2、JDBC自身没有连接池,会频繁的创建连接和关闭连接,效率低;

3、SQL是写死在程序中,一旦修改SQL,需要对类重新编译;

4、对查询SQL执行后返回的ResultSet对象,需要手动处理,有时会特别麻烦;

1.3.2 使用Mybatis的好处

1、Mybatis对JDBC对了封装,可以简化JDBC代码;

2、Mybatis自身支持连接池(也可以配置其他的连接池),因此可以提高程序的效率;

3、Mybatis是将SQL配置在mapper文件中,修改SQL只是修改配置文件,类不需要重新编译。

4、对查询SQL执行后返回的ResultSet对象,Mybatis会帮我们处理,转换成Java对象。

二、使用

2.1 搭建第一个Mybatis项目

下载最新版Jar包,以及MySQL_Jar包放置在项目中,并添加到项目的构建路径;maven项目即添加依赖置于 pom.xml:

<dependency>
    <groupId>org.mybatis</groupId>
    <artifactId>mybatis</artifactId>
    <version>x.x.x</version>
</dependency>

新建资源文件夹resources文件夹,在文件夹中新建mybatis的主配置文件mybatis-config.xml,拷贝官网xml文件到其中:

mybaits-01

主配置文件:包含了对 MyBatis 系统的核心设置,包括获取数据库连接实例的数据源(DataSource)以及决定事务作用域和控制方式的事务管理器

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
  PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
  "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
        <!-----数据库环境---->
  <environments default="development">
    <environment id="development">
            <!----事务类型--->
      <transactionManager type="JDBC"/>
            <!----数据库连接池类型--->
      <dataSource type="POOLED">
                <!---数据库驱动类型---->
        <property name="driver" value="${driver}"/>
                      <!---数据库连接url--->
        <property name="url" value="${url}"/>
                          <!---数据库连接用户名--->
        <property name="username" value="${username}"/>
                              <!---数据库连接密码--->
        <property name="password" value="${password}"/>
      </dataSource>
    </environment>
  </environments>
  <mappers>
    <mapper resource="org/mybatis/example/BlogMapper.xml"/>
  </mappers>
</configuration>

2.1.1 配置文件结构

2.1.2 修改主配置文件配置

<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url" value="jdbc:mysql://localhost:3306/mybatis?useUnicode=true&amp;characterEncoding=utf-8&amp;useSSL=false&amp;serverTimezone = GMT"/>
<property name="username" value="root"/>
<property name="password" value="root"/>

注意:修改数据库名和密码为你的数据库密码!

2.1.3 创建数据库

CREATE TABLE `student`  (
`id` int(20) NOT NULL AUTO_INCREMENT,
`name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
`tid` int(20) NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = MyISAM AUTO_INCREMENT = 5 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

INSERT INTO `student` VALUES (1, '张三', 1);
INSERT INTO `student` VALUES (2, '李四', 1);
INSERT INTO `student` VALUES (3, '王武', 1);
INSERT INTO `student` VALUES (4, '张散散', 1);

CREATE TABLE `teacher`  (
`id` int(20) NOT NULL AUTO_INCREMENT,
`name` varchar(255) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL,
PRIMARY KEY (`id`) USING BTREE
) ENGINE = MyISAM AUTO_INCREMENT = 2 CHARACTER SET = utf8 COLLATE = utf8_general_ci ROW_FORMAT = Dynamic;

INSERT INTO `teacher` VALUES (1, 'hresh');

2.1.4 新建POJO类

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

2.1.5 新建数据访问对象(DAO)接口

新建IStudentDao接口

public interface IStudentDao {
   List<Student> queryStudent();
}

2.1.6 创建接口映射文件

新建IAuthorDao.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">

<!-- namespace需要绑定接口的全限定名-->
<mapper namespace="cn.icelo.demo.dao.IStudentDao">
    <!-- id 接口中的方法,resultType 查询返回的类型,需要写全限定名-->
    <select id="queryStudent" resultType="cn.icelo.demo.pojo.Student">
        <!-- SQL语句-->
    select * from student
  </select>
</mapper>

每一个接口的映射文件都需要在主配置文件(mybatis-config.xml)中注册,修改如下内容:

<mappers>
    <!-- 这里使用的是package方式,详情见配置文件解析 -->
    <package name="cn.icelo.demo.dao"/>
</mappers>

2.1.7 编写测试类

package cn.icelo.demo.test;


import cn.icelo.demo.dao.IStudentDao;
import cn.icelo.demo.pojo.Student;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;

import java.io.IOException;
import java.io.InputStream;
import java.util.List;

/**
 * @author:icelo
 * @date: 2020/11/24
 * @time: 14:49
 */
public class App {

    private static SqlSessionFactory factory;
    private static SqlSession session;
    private static IStudentDao studentDao;

    /**
     * 官网中构建Sqlsession的方法,
     * <p>
     * 每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为核心的。
     * SqlSessionFactory 的实例可以通过 SqlSessionFactoryBuilder 获得。
     * 而 SqlSessionFactoryBuilder 则可以从 XML 配置文件或一个预先配置的
     * Configuration 实例来构建出 SqlSessionFactory 实例。
     */
    private static void init() {
        String resource = "mybatis-config.xml";
        try {
            InputStream inputStream = Resources.getResourceAsStream(resource);
            factory = new SqlSessionFactoryBuilder().build(inputStream);
            session = factory.openSession();
            studentDao = session.getMapper(IStudentDao.class);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }

    /**
     * 关闭session所需要的资源
     */
    private static void clear() {
        if (session != null) {
            session.commit();
            session.close();
        }
    }

    public static void main(String[] args) {
        init();
        List<Student> students = studentDao.queryStudent();
        for (Student student : students) {
            System.out.println(student);
        }
        clear();
    }
}

2.1.8 调试

mybatis-02

2.1.9可能遇到的问题

  • 配置文件没有注册

在测试类中确保resource的引入的mybatis-config.xml主配置文件路径正确

  • 绑定接口错误

在映射文件中绑定的namespace一定为接口的全限定名一个接口对应一个映射文件

  • 方法名不对

在映射文件中的<select>标签下的id是在接口中的方法,方法名唯一

  • 返回类型不对

select语句下记得返回类型,即resultType属性,为查询返回的类的全限定名; 注意,如果返回的是集合,那应该设置为集合包含的类型,而不是集合本身的类型。

三、CRUD操作

使用Mybatis进行一些基本的增删改查操作

3.1 select

<select id="queryStudent" resultType="cn.icelo.demo.pojo.Student">
  select * from student
</select>
  • id:在命名空间中唯一的标识符,可以被用来引用这条语句。

通常在每一条SQL语句中都需要这个参数,和映射文件对应的接口类中方法名一致

如上面的 queryStudent在它对应的接口中有一个未实现的接口方法!

public interface IStudentDao {
/**
    * 查询所有学生
    * @return 查询到的学生集合
    */
   List<Student> queryStudent();
}
  • resultType:期望从这条语句中返回结果的类全限定名或别名。 注意,如果返回的是集合,那应该设置为集合包含的类型,而不是集合本身的类型。

select的SQL语句中特别重要不要忘记返回类型,一般是POJO包中的实体类的全限定名,

3.1.1 需要带参的select查询语句

在查询语句中需要根据条件查询,如下面:查询id为3的学生的信息:

// IStudentDao.java文件中
/**
 * 根据id查询学生
 * @param id 传入的id
 * @return 返回的学生
 */

Student queryById(int id);
<!--IStudentDao.xml文件-->
<select id="queryById" resultType="cn.icelo.demo.pojo.Student">
    select  * from  student where id= #{id}
</select>
// 调用方法
Student student = studentDao.queryById(3);

在MyBatis 创建一个预处理语句(PreparedStatement)参数,使用#{xxx}来代替JDBC中的?,在测试类中使用代理类的方法调用接口中的函数,并传入参数;一般的查询语句不需要parameterType;因为这个属性是可选的,它可以通过类型处理器(TypeHandler)推断出具体传入语句的参数,默认值为未设置(unset)。

3.1.2 模糊查询的三种方法

在mybaits中如何处理模糊查询呢?以下我这里总结三个方法

  • 直接传参法:

以查询学生表中“张”姓的学生为例

定义接口方法:

/**
* 模糊查询
* @param  name 需要传入的名字
* @return 学生集合
*/
List<Student>  queryStudentLike(String name);

映射文件中的SQL语句:

<select id="queryStudentLike" resultType="cn.icelo.demo.pojo.Student">
  select * from student where name  like #{name}
</select>

调用方法:

String name="张";
List<Student> students = studentDao.queryStudentLike("%"+name+"%");
for (Student student : students) {
  System.out.println(student);
}

查询结果:

Student(id=1, name=张三, tid=1)
Student(id=5, name=张无忌, tid=2)
Student(id=6, name=张三丰, tid=2)

思路:查询的关键字keyword,在代码中拼接好要查询的格式,如%name%,然后直接作为参数传入 mapper.xml的映射文件中。

  • CONCAT() 函数:

在MySQL中有一个比较常用的函数,CONCAT()函数能够将多个字符串连接成一个字符串

接口中的方法:

/**
 * 模糊查询
 * @param  name 需要传入的名字
 * @return 学生集合
 */
List<Student>  queryStudentLike(String name);

映射文件中的SQL语句:

<select id="queryStudentLike" resultType="cn.icelo.demo.pojo.Student">
    select * from student where name  like CONCAT('%',#{name},'%')
</select>

调用方法:

String name="张";
List<Student> students = studentDao.queryStudentLike(name);

查询结果:

Student(id=1, name=张三, tid=1)
Student(id=5, name=张无忌, tid=2)
Student(id=6, name=张三丰, tid=2)

思路:在SQL语句中使用CONCAT()函数,在调用函数处传入需要查询的值

注意:在调用CONCAT()函数时,应该用英文逗号,分割开,需要拼接的字符串应该用''来包含,其中的#{}OGNL表达式不用被包含,函数名必须大写

  • Mybatis的bind

在Mybatis的OGNL表达式中,bind 元素允许创建一个变量,并将其绑定到当前的上下文。其可以用于模糊查询。

接口中的方法:

/**
 * 模糊查询
 * @param  name 需要传入的名字
 * @return 学生集合
 */
List<Student>  queryStudentLike(String name);

映射文件中的SQL语句:

<select id="queryStudentLike" resultType="cn.icelo.demo.pojo.Student">
    <bind name="pattern" value="'%'+name+'%'"/>
    select * from student where name  like #{pattern}
</select>

调用方法:

String name="张";
List<Student> students = studentDao.queryStudentLike(name);

查询结果:

Student(id=1, name=张三, tid=1)
Student(id=5, name=张无忌, tid=2)
Student(id=6, name=张三丰, tid=2)

思路:用bind在<select>内定义一个变量,属性name用作替换like后面的值,属性value是需要被替换部分的字符串拼接

注意:在value中使用''单引号包裹需要拼接的字符串,需要传入的变量不需要使用单引号包裹!

3.2 insert

在mybatis中使用插入语句,如下面:插入一个老师到数据库中:

// ITeacherDao.java文件中
/**
* 添加老师
* @param  t 需要添加的老师对象
* @return 添加的结果
*/
int insertTeacher(Teacher t);
<!--ITeacherDao.xml文件-->
<mapper namespace="cn.icelo.demo.dao.ITeacherDao">
    <insert id="insertTeacher">
      insert into teacher (id,`name`) values (#{id},#{name})
    </insert>
</mapper>
// 调用方法
Teacher t=new Teacher();    //构建Teacher对象
t.setId(2);        // 设定参数
t.setName("icelo");
int result = teacherDao.insertTeacher(t);
System.out.println(result==1?"添加成功!":"添加失败!");

3.2.1 多行数据插入的使用

在mybatis中如果你的数据库还支持多行插入, 你也可以传入一个Student 数组或集合,并返回自动生成的主键。

/**
 * 插入多条学生数据
 * @param students 需要插入的学生数据集合
 * @return 返回的结果
 */
int insertStudentMore(List<Student> students);
<insert id="insertStudentMore" parameterType="java.util.List">
    insert into student (id,name,tid) values
    <foreach item="item" collection="list" separator=",">
        (#{item.id}, #{item.name},#{item.tid})
    </foreach>
</insert>
Student s1=new Student();
s1.setId(7);
s1.setName("赵四");
s1.setTid(2);
Student s2=new Student();
s2.setId(8);
s2.setName("赵高");
s2.setTid(2);
List<Student> students=new ArrayList<>();
students.add(s1);
students.add(s2);
int result = studentDao.insertStudentMore(students);

多条数据插入时使用了Mybatis中的OGNL表达式foreach的使用;后面会介绍相关内容!这里的返回类型是数据库中的影响的行数

3.3 update

使用mybatis语句完成数据库中的修改的操作,如下面:修改id为4的学生的姓名为朱晓明

/**
 * 修改学生
 * @param student 需要修改的学生
 * @return 修改的结果
 */
// IStudentDao.java
int updateStudent(Student student);
<!--IStudentDao.xml-->
<update id="updateStudent">
    update student set id=#{id},name=#{name},tid=#{tid} where id= #{id}
</update>
//调用方法
Student s1=new Student();
s1.setId(4);
s1.setName("朱晓明");
s1.setTid(2);
int result = studentDao.updateStudent(s1);

在update语句中可以类比insert方法,

3.4 delete

使用mybatis语句完成数据库中的删除的操作,如下面:删除id为8的学生

/**
 * 删除学生
 * @param id 学生的id
 * @return 结果
 */
int deleteStudent(int id);
<delete id="deleteStudent">
    delete from student where id=#{id}
</delete>
// 调用方法
int result = studentDao.deleteStudent(8);  
System.out.println(result);

3.5 万能的Map

在mybaits中还可以使用Map的方法来实现多个参数的传递,这是一种不规范的使用,map就意味着可读性的下降,所以这不是推荐的方式

  • 假设实体类或者数据库中的表的字段过多,应当考虑使用Map,可以不用把表的属性全写出来,只要写需要的属性。

如果在开发过程,实体类的字段很多,在SQL语句中可以使用在使用Map传递参数,然后直接在SQL中取出key即可

接口方法:

/**
*@parame map 用户的数据存在map中
*@return 结果
**/
int insertUser(Map<String, Object> map);

映射文件中的SQL:

<!--通过map插入,字段的名字就可以随便取了-->
<insert id="insertUser" parameterType="map">
 insert into user(id,name,pwd) values (#{userId},#{userName},#{password})
</insert>

调用方法:

UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
Map<String, Object> map = new HashMap<String, Object>();
map.put("userId", 5);
map.put("userName", "icelo");
map.put("password", "123456");
userMapper.insertUser(map);

四、配置文件详解

在mybatis中最重要的就是配置文件了,它会深深影响 MyBatis 行为的设置和属性信息。上面我们列出了主配置文件的 文件结构我们在编写主配置文件时,需要按照上面文件结构顺序编写。我们这里主要分析几个比较常用配置项。

4.1 properties

我们可以使用这个配置项在外部进行配置一些数据库相关的参数,比如:Driverurl

在resources目录下新建一个文件名为db.properties,在其中配置好数据库相关参数如下所示:

driver=com.mysql.jdbc.Driver
url=jdbc:mysql://localhost:3306/mybaitsmybaits?useSSL=true
username=root
password=root

在主配置文件mybatis-config.xml中使用properties文件,如下所示:

<properties resource="db.properties" />
    ...
<property name="driver" value="${driver}" />
<property name="url" value="${url}" />
<property name="username" value="${username}" />
<property name="password" value="${password}" />
    ...

在properties中有resource属性可以配置,主要是要引入的外部文件位置

<properties resource="db.properties" />
  <property name="username" value="dev_user"/>
  <property name="password" value="F2Fa3!33TYyg"/>
</properties>

同样可以使用property标签在主配置文件中配置数据库的相关信息,当外部文件和主配置文件中同时存在时,将会优先使用外部文件,亦可一部分在外,一部分在内。

4.2 settings

设置名描述有效值默认值
cacheEnabled全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。true \falsetrue
lazyLoadingEnabled延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置 fetchType 属性来覆盖该项的开关状态。true \falsefalse
aggressiveLazyLoading开启时,任一方法的调用都会加载该对象的所有延迟加载属性。 否则,每个延迟加载属性会按需加载(参考 lazyLoadTriggerMethods)。true \falsefalse (在 3.4.1 及之前的版本中默认为 true)
multipleResultSetsEnabled是否允许单个语句返回多结果集(需要数据库驱动支持)。true \falsetrue
useColumnLabel使用列标签代替列名。实际表现依赖于数据库驱动,具体可参考数据库驱动的相关文档,或通过对比测试来观察。true \falsetrue
useGeneratedKeys允许 JDBC 支持自动生成主键,需要数据库驱动支持。如果设置为 true,将强制使用自动生成主键。尽管一些数据库驱动不支持此特性,但仍可正常工作(如 Derby)。true \falseFalse
autoMappingBehavior指定 MyBatis 应如何自动映射列到字段或属性。 NONE 表示关闭自动映射;PARTIAL 只会自动映射没有定义嵌套结果映射的字段。 FULL 会自动映射任何复杂的结果集(无论是否嵌套)。NONE, PARTIAL, FULLPARTIAL
autoMappingUnknownColumnBehavior指定发现自动映射目标未知列(或未知属性类型)的行为。NONE: 不做任何反应WARNING: 输出警告日志('org.apache.ibatis.session.AutoMappingUnknownColumnBehavior' 的日志等级必须设置为 WARNFAILING: 映射失败 (抛出 SqlSessionException)NONE, WARNING, FAILINGNONE
defaultExecutorType配置默认的执行器。SIMPLE 就是普通的执行器;REUSE 执行器会重用预处理语句(PreparedStatement); BATCH 执行器不仅重用语句还会执行批量更新。SIMPLE REUSE BATCHSIMPLE
defaultStatementTimeout设置超时时间,它决定数据库驱动等待数据库响应的秒数。任意正整数未设置 (null)
defaultFetchSize为驱动的结果集获取数量(fetchSize)设置一个建议值。此参数只可以在查询设置中被覆盖。任意正整数未设置 (null)
defaultResultSetType指定语句默认的滚动策略。(新增于 3.5.2)FORWARD_ONLY \SCROLL_SENSITIVE \SCROLL_INSENSITIVE \DEFAULT(等同于未设置)未设置 (null)
safeRowBoundsEnabled是否允许在嵌套语句中使用分页(RowBounds)。如果允许使用则设置为 false。true \falseFalse
safeResultHandlerEnabled是否允许在嵌套语句中使用结果处理器(ResultHandler)。如果允许使用则设置为 false。true \falseTrue
mapUnderscoreToCamelCase是否开启驼峰命名自动映射,即从经典数据库列名 A_COLUMN 映射到经典 Java 属性名 aColumn。true \falseFalse
localCacheScopeMyBatis 利用本地缓存机制(Local Cache)防止循环引用和加速重复的嵌套查询。 默认值为 SESSION,会缓存一个会话中执行的所有查询。 若设置值为 STATEMENT,本地缓存将仅用于执行语句,对相同 SqlSession 的不同查询将不会进行缓存。SESSION \STATEMENTSESSION
jdbcTypeForNull当没有为参数指定特定的 JDBC 类型时,空值的默认 JDBC 类型。 某些数据库驱动需要指定列的 JDBC 类型,多数情况直接用一般类型即可,比如 NULL、VARCHAR 或 OTHER。JdbcType 常量,常用值:NULL、VARCHAR 或 OTHER。OTHER
lazyLoadTriggerMethods指定对象的哪些方法触发一次延迟加载。用逗号分隔的方法列表。equals,clone,hashCode,toString
defaultScriptingLanguage指定动态 SQL 生成使用的默认脚本语言。一个类型别名或全限定类名。org.apache.ibatis.scripting.xmltags.XMLLanguageDriver
defaultEnumTypeHandler指定 Enum 使用的默认 TypeHandler 。(新增于 3.4.5)一个类型别名或全限定类名。org.apache.ibatis.type.EnumTypeHandler
callSettersOnNulls指定当结果集中值为 null 的时候是否调用映射对象的 setter(map 对象时为 put)方法,这在依赖于 Map.keySet() 或 null 值进行初始化时比较有用。注意基本类型(int、boolean 等)是不能设置成 null 的。true \falsefalse
returnInstanceForEmptyRow当返回行的所有列都是空时,MyBatis默认返回 null。 当开启这个设置时,MyBatis会返回一个空实例。 请注意,它也适用于嵌套的结果集(如集合或关联)。(新增于 3.4.2)true \falsefalse
logPrefix指定 MyBatis 增加到日志名称的前缀。任何字符串未设置
logImpl指定 MyBatis 所用日志的具体实现,未指定时将自动查找。SLF4J \LOG4J \LOG4J2 \JDK_LOGGING \COMMONS_LOGGING \STDOUT_LOGGING \NO_LOGGING未设置
proxyFactory指定 Mybatis 创建可延迟加载对象所用到的代理工具。CGLIB \JAVASSISTJAVASSIST (MyBatis 3.3 以上)
vfsImpl指定 VFS 的实现自定义 VFS 的实现的类全限定名,以逗号分隔。未设置
useActualParamName允许使用方法签名中的名称作为语句参数名称。 为了使用该特性,你的项目必须采用 Java 8 编译,并且加上 -parameters 选项。(新增于 3.4.1)true \falsetrue
configurationFactory指定一个提供 Configuration 实例的类。 这个被返回的 Configuration 实例用来加载被反序列化对象的延迟加载属性值。 这个类必须包含一个签名为static Configuration getConfiguration() 的方法。(新增于 3.2.3)一个类型别名或完全限定类名。未设置
shrinkWhitespacesInSql从SQL中删除多余的空格字符。请注意,这也会影响SQL中的文字字符串。 (新增于 3.5.5)true \falsefalse
defaultSqlProviderTypeSpecifies an sql provider class that holds provider method (Since 3.5.6). This class apply to the type(or value) attribute on sql provider annotation(e.g. @SelectProvider), when these attribute was omitted.A type alias or fully qualified class nameNot set

MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为,这里直接使用官网文档,参考使用!

4.3 typeAliases

为了降低在映射文件中的全限定名的冗余,主配置文件提供为Java类型设置一个别名

在mybatis中提供了三种设置别名的方法:

  • 使用类的全限定名

typeAliases中可以配置很多typeAlias,使用这中方式需要指定类的全限定名;

<typeAlias type="cn.icelo.demo.pojo.Student" alias="stu" ></typeAlias>

tpye中设置类的全限定名,在alias中设置想要使用的别名,这个别名可以用在使用到cn.icelo.demo.pojo.Student的地方

使用:

<select id="queryById" resultType="stu">
 select  * from  student where id= #{id}
</select>

resultType中直接使用上面的别名替代

  • 使用包扫描方式

typeAliases中可以直接配置一个包中的实体类的别名,在使用时直接使用类名即可;

<typeAliases >
<package name="cn.i    <select id="queryStudent" resultType="STUDENT">
 select * from student
</select>celo.demo.pojo"/>
</typeAliases>

使用:

<select id="queryStudent" resultType="STUDENT">
 select * from student
</select>

注意:在使用时不区分大小写;

  • 使用注解的方式

在mybatis中还提供了一种使用注解的方式来为类取别名的方式;这种方法不能独立存在,必须依托上面的两种方式:

<typeAliases>
  <package name="cn.icelo.demo.pojo"/>
</typeAliases>
@Alias("stu")
public class Student {
private int id;
private String name;
private int tid;
}

也就是说,在上面两种方法存在下,会优先使用注解的方法为别名

推荐使用第一种;

4.4 mappers

在主配置文件中,mapper是极其重要的,每一个要使用的映射文件都需要在主配置文件中绑定注册,主要是使用三种方式注册:

  • resource

使用这个方法是比较复杂的,所有的映射文件的都必须放置在resources目录下,但为了方便管理,我们经常在resources目录下构建一个和src一样的目录结构,这样为了能够让类和我们的映射文件对应起来:

mybatis-04

使用这个方式可以不需要让映射文件和对应的接口类的名字一致,可以DIY其文件名;但在IDEA中使用maven构建项目会存在xml文件不能编译的问题,解决办法:

首先在pom文件中找到<bulid></build>节点,改为下面代码

<build>  
<resources>  
 <!-- mapper.xml文件在java目录下 -->
 <resource>  
   <directory>src/main/java</directory>  
     <includes>  
       <include>**/*.xml</include>  
     </includes>  
 </resource>  
 <!-- mapper.xml文件在resources目录下-->
 <resource>
     <directory>src/main/resources</directory> 
 </resource>
</resources>  
</build>
  • package

使用package的方式可以为一整个包中的映射文件绑定注册,将映射文件放置接口包中,可以将包内的映射器接口实现全部注册为映射器

mybaits-05

<mappers>
<package name="cn.icelo.demo.dao"/>
</mappers>

这种方式是非常推荐的,但注意:映射文件的文件名必须要和接口类中保持一致,不能随意DIY

  • class

Mapper中可以使用接口的全限定名的方式为映射文件注册,当时有这中方式时,mybatis会扫描所有的资源目录和对应接口类下的包,找到和接口类的名字相对应的映射文件,为其绑定注册。所以使用这个方式,不能DIY其文件名,必须和接口类文件一致,文件位置只能在资源目录和对应接口所在的包中;

<mappers>
<mapper class="cn.icelo.demo.dao.IStudentDao"></mapper>
<mapper class="cn.icelo.demo.dao.ITeacherDao"></mapper>
</mappers>

在Java项目中项目编译后不同的源码文件夹会被合并到bin目录下,形成类路径。不同的源码文件夹下的同名的包实际上是同一个包,因为编译后,包中的文件都在同一个文件夹下。

五、映射文件

5.1 参数

5.1.1单个参数问题

当传入单个参数时#{}内的名字可以任意,它只是一个占位符的作用,注意在其前面的id是你需要参入参数的对应数据库中列

/**
 * 根据id查询学生
 * @param id 传入的id
 * @return 返回的学生
 */

Student queryById(int id);
<select id="queryById" resultType="stu">
    select  * from  student where id= #{id}
</select>

5.1.2 多个参数问题

这里使用一个业务来说明问题:查询姓名为“张三”,老师编号为1号的学生(三种方法)

接口方法:

/**
 * 查询学生姓名为”张三",教师编号为1号的学生
 * @param name 学生姓名
 * @param tid 教师编号
 * @return 学生对象
 */
Student queryStudentByTid(String name,int tid);
<!--配置映射文件-->
<select id="queryStudentByTid" resultType="stu">
    select * from student where name=#{name} and tid=#{tid}
</select>
// 调用方法
Student student = studentDao.queryStudentByTid("张三",1);

查询结果:

Caused by: org.apache.ibatis.binding.BindingException: Parameter 'name' not found. Available parameters are [arg1, arg0, param1, param2]
    at org.apache.ibatis.binding.MapperMethod$ParamMap.get(MapperMethod.java:212)
    at org.apache.ibatis.reflection.wrapper.MapWrapper.get(MapWrapper.java:45)
    at org.apache.ibatis.reflection.MetaObject.getValue(MetaObject.java:122)
    at org.apache.ibatis.executor.BaseExecutor.createCacheKey(BaseExecutor.java:219)
    at org.apache.ibatis.executor.CachingExecutor.createCacheKey(CachingExecutor.java:146)
    at org.apache.ibatis.executor.CachingExecutor.query(CachingExecutor.java:88)
    at org.apache.ibatis.session.defaults.DefaultSqlSession.selectList(DefaultSqlSession.java:147)
    ... 7 more

调试错误:Parameter 'name' not found. Available parameters are [arg1, arg0, param1, param2]当传入多个参数时,mybatis要求使用arg1, arg0... 或者param1, param2对参数进行编号!

  • 修改映射文件如下:
<select id="queryStudentByTid" resultType="stu">
    select * from student where name=#{arg0} and tid=#{arg1}
</select>

或者:

<select id="queryStudentByTid" resultType="stu">
    select * from student where name=#{param1} and tid=#{param2}
</select>

注意:使用arg0开始,param1开始

  • 使用map传参:

向上面万能的map所介绍的一样,可以将需要传入的参数都可以封装成一个map对象,Map<String,Oboject>,在需要传入参数时String类型为#{}中所对应,后面的Object为需要传入的参数。

接口类:

/**
 * 查询学生姓名为张三老师编号为1的学生
 * @param map 需要传入的参数map集合
 * @return 查询到的学生对象
 */
Student queryStudentByTid(Map<String ,Object> map);

映射配置文件:

<select id="queryStudentByTid" parameterType="map" resultType="stu">
    select * from student where name=#{name } and tid=#{tid}
</select>

调用方法:

Map<String,Object> map=new HashMap<>();
map.put("name","张三");
map.put("tid",1);
Student student = studentDao.queryStudentByTid(map);
  • 使用参数注解方式:

在接口方法中使用@Param注解为所需要的参数标识,在映射文件中就能使用了,如下:

接口类:

/**
 * 查询学生姓名为张三老师编号为1的学生
 * @param name 学生姓名
 * @param tid  老师编号
 * @return 学生对象
 */
Student queryStudentByTid(@Param("name") String name,@Param("tid") int tid);

映射文件:

<select id="queryStudentByTid" parameterType="map" resultType="stu">
    select * from student where name=#{name} and tid=#{tid}
</select>

调用方法:

Student student = studentDao.queryStudentByTid("张三",1);

注意:使用这个方式需保证@Param中的标识和#{}一致,在接口方法中的@Param和参数用空格分隔,没有逗号

5.1.3 数组参数问题

使用数组的情况一般是批量插入,向上面例子差不多,需要使用到foreachOGNL表达式,现在有一个业务,要查询id为1,3,5的学生:

接口类:

/**
 * 查询id为1,3,5的学生
 * @param ids 需要查询的学生数组
 * @return 学生集合对象
 */
List<Student> queryStudentByIds(List<Integer> ids);

映射文件:

<select id="queryStudentByIds" resultType="stu">
    select  * from student where id in
    <foreach collection="list" item="item" separator="," open="(" close=")">
       #{item}
    </foreach>
</select>
List<Integer> ids=new ArrayList<>();
ids.add(1);
ids.add(3);
ids.add(5);
List<Student> students = studentDao.queryStudentByIds(ids);

注意:

  • 如果传入的是单参数且参数类型是一个List的时,collection属性值为list .
  • 如果传入的是单参数且参数类型是一个array数组时,collection的属性值为array .

5.2 结果映射

resultMap是mybatis中最重要最强大的元素

我们在查询一个学生对象时如果数据库中表的字段与Student类的属性名称一致,我们就可以使用resultType来直接放回一个Student返回。但是如果在数据库中的字段和类中属性名称不一致时,我们就可以使用resultMap来做结果集映射了。

如果Student类保持不表,在数据库中用sid代替id,然后查询所有的学生

<select id="queryStudent" resultType="stu">
select * from student
</select>

查询结果:·

Student(id=0, name=张三, tid=1)
Student(id=0, name=李四, tid=1)
Student(id=0, name=王武, tid=1)
Student(id=0, name=朱晓明, tid=2)
Student(id=0, name=张无忌, tid=2)
Student(id=0, name=张三丰, tid=2)
Student(id=0, name=赵四, tid=2)

不会报错,但是在查询的数据中,所有学生的id0这显然是不合理的,所以我们需要使用resultMap

我们需要在映射配置文件中编写结果集映射,如下所示:

映射配置文件:

<resultMap id="stuMap" type="stu" >
    <id column="sid" property="id"></id>
</resultMap>

column和数据库中的字段相对应,property和实体类中的属性相对应,即可完成映射。在resultMap中一般用<id></id>表示数据库中的主键的映射,使用<result></result>表示一般字段的映射。

5.3 高级结果映射

在数据库中包含着一对多、一对一的关系。比如说一个人和他的身份证就是一对一的关系,但是他和他的银行卡就是一对多的关系。我们的生活中存在着很多这样的场景。在数据库中会存在几种不同的关系,如下:

mybatis-06

  • 单向和双向问题

主要有一对一,一对多,多对多这三种情况,但是每一种又分为单向和双向,先说这个一对多这种关系来讲,比如有学生和老师,一个老师中有多个学生,从老师方面来看,是一对多关系,而多个学生在一个老师班上,是多对一关系,那么如果我们的业务需求只需要通过老师查找到所有的学生,那么我们就只需要进行单向一对多的映射,如果我们需要通过学生来查询出对应的老师,那么我们就需要进行单向多对一的映射,而如果我们这两个业务需求都需要实现,也就是不管从哪一方进行查找,都需要能够找到对方,那么此时就应该编写双向一对多或者双向多对一(双向一对多和双向多对一是一样的意思)。所以,不管是编写哪一种,都是根据业务需求来进行决策的。这就是单向和双向的意思。

  • 什么是多对多

多对多就是不管从哪一方看,都是一对多,那么该关系就是多对多。比如学生跟选修课之间,从学生方看,一个学生能选多门选修课,一对多关系,从选修课之间,一门选修课可以被多个学生选择,也是一对多关系,那么学生跟选修课就是多对多关系。多对多关系之间都会由第三张表来表示这种关系。而不会相互设置外键。

5.3.1 多对一

首先我们先来创建一个多对一的情况,在学生属性中加上一个属性Teacher,这里只是改变了实体类,数据库中的字段不变,如下:

实体类:

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

    /**
     * 学生需要关联一个老师
     * 
     */
    private Teacher teacher;
}

主要业务是,查询所有学生的信息,当然也包括老师的信息;

接口类:

// IStudnetDao接口
/**
 * 查询所有的学生信息
 * @return 学生集合
 */
List<Student> getStudent();

思路一:查询所有的学上信息,根据查询出的学生的tid,寻找对应的老师

SQL语句:

# 第一步:查询出所有学生
select * from student;
# 第二步:根据查询到的学生tid,查询到对应的老师
select * from teacher where id=#{tid};

有点类似SQL的子查询的语句分开写;那么在映射文件该如何写呢?

我们可以先写好根据学生的tid查询老师这个部分;

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

这个select语句是在Student的映射文件中写的,那么在Teacher的接口中需不需要写这个方法的接口呢?
答案:不需要,当要调用dao或者mapper里面调用这个teacher的时候才需要定义方法,如果在SQL中使用子查询时不需要

查询所有学生的SQL语句:

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

那么这resultMap中需要将两个查询结合起来,那应该怎么写呢?

我们可以先把Student中简单属性要映射的关系写好,然后处理复杂的,如下:

<resultMap id="StudentTeacher" type="student">
    <!--        先把简单的属性写好-->
    <result property="id" column="id"/>
    <result property="name" column="name"/>

    <!--  Teacher 属性该如何写?-->
    <result property="Teacher" column="???"
</resultMap>

在Mybatis中,复杂类型,我们需要单独处理,处理原则:

  • 对象:association
  • 集合:collection

完整的resultMap就是:

<resultMap id="StudentTeacher" type="student">
    <!--        先把简单的属性写好-->
    <result property="id" column="id"/>
    <result property="name" column="name"/>

    <!--  Teacher 属性该如何写?-->
    <association property="teacher" column="tid" javaType="Teacher" select="getTeacher"/>
</resultMap>

association中,

  • property表示的是Student类中Teacher类型属性的名称,不可随意写,(特别是不能写成类型名,如:Teacher
  • column表示的是两个表需要建立的连接的字段名,也就是查询Teacher的后面的id的赋值
  • javaType即表示propertyJava类型,
  • select表示的是该映射文件中的SQL查询,对应的是Teacher查询的id

思路二:根据数据库中的多表查询的复杂SQL语句方法。

SQL语句:

# 查询所有的结果
select * from student s,teacher t where s.tid=t.id;

或者,查询我们需要的SQL字段,改写为下面的SQL

select s.id sid,s.name sname, t.name tname from student s,teacher t where s.tid=t.id;

映射文件:

<select id="getStudent" resultMap="???">
    select s.id sid,s.name sname, t.id tid, t.name tname from student s,teacher t where s.tid=t.id;
</select>

那么这resultMap中需要将查到的结果进行映射,可以先写好,简单的属性,如下:

<resultMap id="StudentTeacher" type="student">
    <result property="id" column="sid"/>
    <result property="name" column="sname"/>
</resultMap>

这里的column为数据库中查询的字段,注意为该别名后的别名

依照我们上面的处理原则,则完整的resultMap为:

<resultMap id="StudentTeacher" type="student">
    <result property="id" column="sid"/>
    <result property="name" column="sname"/>
    <association property="teacher" javaType="Teacher">
        <result property="id" column="tid"/>
        <result property="name" column="tname"/>
    </association>
</resultMap>

Teacher依然是一个对象,应该使用association,在里面继续使用<result>标签写上Teacher对应的属性和对应的数据库字段。取别名的按照别名写。

注意:若查询的过程中,有字段中的全为空,或者全为0,请将resultMap中的字段写完整。

5.3.2 一对多

我们再来创建一个一对多的情况,去掉上面学生属性中的Teacher,在Teacher中加上List<Student>属性。这样从老师角度看就是一对多了。这里同样只是改变了实体类,数据库中的字段不变,如下:

学生类:

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

老师类:

@Data
public class Teacher {
    private int id;
    private String name;
   
    /**
     * 一个老师关联上多个学生,一个list数组
     */
    private List<Student> students;
}

接口类:

public interface ITeacherDao {
    /**
     * 查询所有老师信息,同样包括老师信息下的学生集合
     * @return
     */
    List<Teacher> getTeacher(int id);
}

*思路一:先查询出指定老师,根据老师的id查出学生集合。

SQL语句:

select * from teacher where id=2

然后根据老师的id查询对应的学生集合

select * from student where tid=2;

映射文件:

<resultMap id="TeacherStudent" type="teacher">
    <result property="id" column="id"/>
    <result property="name" column="name"/>
    <collection property="students" column="id" javaType="ArrayList" ofType="Student" select="getStudent" />
</resultMap>

同样是复杂属性,我们需要复杂处理,这里使用的是集合,集合中泛型信息,我们使用ofType获取。

思路二:使用复杂的SQL语句处理,对结果进行映射。

SQL语句:

select s.id sid ,s.name sname, s.tid tid ,t.name tname  from student s,teacher t where s.tid=t.id and t.id=2

映射文件:

<select id="getTeacher" resultMap="???">
select s.id sid ,s.name sname, s.tid tid ,t.id id,t.name tname from student s,teacher t where s.tid=t.id and t.id=#{id}
</select>

我们需要使用resultMap对查询的结果进行映射,如下:

<resultMap id="TeacherStudent" type="Teacher">
    <!-- 先写出简单属性的映射 -->
    <result property="id" column="id"/>
    <result property="name" column="tname" />
    <!-- 复杂属性单独处理 -->
    <collection property="students"  javaType="ArrayList" ofType="student">
        <result property="id" column="sid"/>
        <result property="name" column="sname"/>
        <result property="tid" column="tid"/>
    </collection>
</resultMap>

这里的javaType可写可不写,mybatis通常能够做出相对应的类型推断,ofTyppe为集合中的类型

5.4 缓存

MyBatis 内置了一个强大的事务性查询缓存机制,它可以非常方便地配置和定制。默认情况下,只启用了本地的会话缓存,它仅仅对一个会话中的数据进行缓存。

5.4.1 一级缓存

在mybatis中是默认开启了一级缓存,一级缓存是SqlSession级别的,即,同一个SqlSession ,多次调用同一个Mapper和同一个方法的同一个参数,只会进行一次数据库查询,然后把数据缓存到缓冲中,以后直接先从缓存中取出数据,不会直接去查数据库。如果是相同的SQL语句,会优先命中一级缓存,避免直接对数据库进行查询,提高性能。具体执行过程如下图所示。mybatis-06

但是不同的SqlSession对象,因为不同的SqlSession都是相互隔离的,所以相同的Mapper、参数和方法,他还是会再次发送到SQL到数据库去执行,返回结果。

我们这里用根据学生id查询学生来举例,如下:

Student s1=studentDao.getStudent(1);
System.out.println(s1);
Student s2=studentDao.getStudent(1);
System.out.println(s2);
System.out.println(s1==s2);

查询结果:

查询结果

当使用同一条SQL语句完成两次查询的时候,mybatis只进行一次查询,第二次查询直接使用上一次查询的结果,两个结果返回的对象一样。

问题:哪些因素会使一级缓存失效?

  • 通过同一个SqlSession执行更新操作时,这个更新操作不仅仅指代update操作,还指insertdelete操作;
  • 事务提交时会删除一级缓存;
  • 事务回滚时也会删除一级缓存;
  • 开启两个SqlSession,在sqlSession1中查询数据,使一级缓存生效,在sqlSession2中更新数据库,验证一级缓存只在数据库会话内部共享。

总结:

  1. MyBatis一级缓存的生命周期和SqlSession一致。
  2. MyBatis一级缓存内部设计简单,只是一个没有容量限定的HashMap,在缓存的功能性上有所欠缺。
  3. MyBatis的一级缓存最大范围是SqlSession内部,有多个SqlSession或者分布式的环境下,数据库写操作会引起脏数据,建议设定缓存级别为Statement。

5.4.2 二级缓存

二级缓存是mapper级别的缓存,它的实现机制跟一级缓存差不多,也是基于PerpetualCacheHashMap本地存储。作用域为mappernamespace,可以自定义存储,比如EhcacheMybatis的二级缓存是跨Session的,每个Mapper享有同一个二级缓存域。

二级缓存是需要配置来开启的,在Mapper映射文件中加上:

<mapper namespace="cn.icelo.demo.dao.ITeacherDao">
    <cache/>
    ...
</mapper>

编写不同的Sqlsession来测试,如下:

SqlSession sqlSession1 = factory.openSession(); // 创建一个Sqlsession1
IStudentDao studentDao1 = sqlSession1.getMapper(IStudentDao.class);
Student student1 = studentDao1.getStudent(1); // SqlSession1查询的学生对象
System.out.println(student1);
sqlSession1.close(); // 关闭SqlSession1
SqlSession sqlSession2 = factory.openSession(); // 创建一个SqlSession2
IStudentDao studentDao2 = sqlSession2.getMapper(IStudentDao.class);
Student student2 = studentDao2.getStudent(1);// Sqlsession2查询的学生对象
sqlSession2.close(); // 关闭Sqlsession2
System.out.println(student2);

调试结果:

Exception in thread "main" org.apache.ibatis.cache.CacheException: Error serializing object.  Cause: java.io.NotSerializableException: cn.icelo.demo.pojo.Student
    at org.apache.ibatis.cache.decorators.SerializedCache.serialize(SerializedCache.java:95)
    at org.apache.ibatis.cache.decorators.SerializedCache.putObject(SerializedCache.java:56)
    at org.apache.ibatis.cache.decorators.LoggingCache.putObject(LoggingCache.java:49)
    at org.apache.ibatis.cache.decorators.SynchronizedCache.putObject(SynchronizedCache.java:43)
    at org.apache.ibatis.cache.decorators.TransactionalCache.flushPendingEntries(TransactionalCache.java:116)
    at org.apache.ibatis.cache.decorators.TransactionalCache.commit(TransactionalCache.java:99)
    at org.apache.ibatis.cache.TransactionalCacheManager.commit(TransactionalCacheManager.java:44)
    at org.apache.ibatis.executor.CachingExecutor.close(CachingExecutor.java:61)
    at org.apache.ibatis.session.defaults.DefaultSqlSession.close(DefaultSqlSession.java:263)
    at cn.icelo.demo.test.App.main(App.java:57)
Caused by: java.io.NotSerializableException: cn.icelo.demo.pojo.Student
    at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1184)
    at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
    at java.util.ArrayList.writeObject(ArrayList.java:766)
    at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
    at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
    at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
    at java.lang.reflect.Method.invoke(Method.java:498)
    at java.io.ObjectStreamClass.invokeWriteObject(ObjectStreamClass.java:1140)
    at java.io.ObjectOutputStream.writeSerialData(ObjectOutputStream.java:1496)
    at java.io.ObjectOutputStream.writeOrdinaryObject(ObjectOutputStream.java:1432)
    at java.io.ObjectOutputStream.writeObject0(ObjectOutputStream.java:1178)
    at java.io.ObjectOutputStream.writeObject(ObjectOutputStream.java:348)
    at org.apache.ibatis.cache.decorators.SerializedCache.serialize(SerializedCache.java:91)
    ... 9 more

注意:在使用二级缓存中,一定要在使用对象实体类上实现Serializable接口,如下:

public class Student implements Serializable {
    private int id;
    private String name;
    private int tid;
}

六、动态SQL语句

5.1 if

if是mybatis中的判断函数,这个有点类似Java语言中if语句,在mybatis中一般和test一起使用,来看一个简单的例子:

/**
 * 查询指定学生姓名
 * @param name 学生的姓名
 * @return 学生对象
 */
List<Student> getStudentByName(@Param("name") String name);

这里面的name可以为null,代表不传值,即代表查询所有学生。

映射文件:

<select id="getStudentByName" resultType="student">
    select * from student
    <if test="name!=null and name !=''">
        where name =#{name}
    </if>
</select>

调用函数:

IStudentDao studentDao= sqlSession.getMapper(IStudentDao.class);
List<Student> students = studentDao.getStudentByName(null);
for (Student student : students) {
    System.out.println(student);
}

注意:在test中写上if的判断条件

5.2 choose (when, otherwise)

choose有点类似于Java中的switch,常常配合whenotherwise一起来使用。我们来看一个简单的例子:

接口类:

/**
 * 查询学生
 * @param id 学生的id
  * @param tid 学生老师id
 * @param name 学生的名字
 * @return 学生对象
 */
List<Student> getStudentByName(@Param("id") int id,@Param("tid") int tid,@Param("name") String name);

映射文件:

<select id="getStudentByName" resultType="student">
    SELECT * FROM student WHERE 1=1
    <choose>
        <when test="id!=0">
            and 5 > #{id}
        </when>
        <when test="tid!=0">
            and tid=#{tid}
        </when>
        <otherwise>
            and name like CONCAT('%',#{name},'%')
        </otherwise>
    </choose>
</select>

调用类:

IStudentDao studentDao= sqlSession.getMapper(IStudentDao.class);
List<Student> students = studentDao.getStudentByName(0,0,"张");
for (Student student : students) {
    System.out.println(student);
}

结果:

Student(id=1, name=张三, tid=1)
Student(id=5, name=张无忌, tid=2)
Student(id=6, name=张三丰, tid=2)

在查询条件中,如果用户传来了id,那么我就查询该小于5的条件,如果用户传来了tid,那么我就我们添加tid的查询条件,最后如果用户任何一个查询条件都没有添加进来,那么默认查询条件就是模糊查询包含“张”的所有数据。

这里需要添加一个where 1=1的来消除后面and……的,比较麻烦,使用where即比较方便多了

5.3 where

上面例子用where改写为下面语句

<select id="getStudentByName" resultType="student">
    SELECT * FROM student
    <where>
        <choose>
            <when test="id!=0">
                and 5 > #{id}
            </when>
            <when test="tid!=0">
                and tid=#{tid}
            </when>
            <otherwise>
                and name like CONCAT('%',#{name},'%')
            </otherwise>
        </choose>
    </where>
</select>

这样,只有where元素中有条件成立,才会将where关键字组装到SQL中,这样就比前一种方式简单许多。where能够智能处理where后面的查询条件,如果一个查询条件都没有,就不会拼接where子句,如果有查询条件,会智能去掉第一个and

5.5 foreach

当传入一个数组时使用,foreach元素的属性主要有 itemindexcollectionopenseparatorclose

item表示集合中每一个元素进行迭代时的别名,
index指 定一个名字,用于表示在迭代过程中,每次迭代到的位置,
open表示该语句以什么开始,
separator表示在每次进行迭代之间以什么符号作为分隔 符,
close表示以什么结束。

需要注意点:

  1. 如果传入的是单参数且参数类型是一个List的时候,collection属性值为list
  2. 如果传入的是单参数且参数类型是一个array数组的时候,collection的属性值为array
  3. 如果传入的参数是多个的时候,我们就需要把它们封装成一个Map了,当然单参数也可以

具体实例:多行数据插入的使用


Author:icelo

Date:2020年11月28日

博客地址:冰洛博客

版权声明:文章转载请注明来源,如有侵权请联系删除!

Archives QR Code Tip
QR Code for this page
Tipping QR Code