Mybatis(上)

一、 前言

1、总体技术体系

①单一架构

一个项目,一个工程,导出为一个war包,在一个Tomcat上运行。也叫all in one。

②分布式架构

一个项目,拆分成很多个模块,每个模块是一个工程。每一个工程都是运行在自己的Tomcat上。模块之间可以互相调用。每一个模块内部可以看成是一个单一架构的应用。

2、框架的概念

框架=jar包+配置文件

3、Mybatis历史

MyBatis最初是Apache的一个开源项目iBatis, 2010年6月这个项目由Apache Software Foundation迁移到了Google Code。随着开发团队转投Google Code旗下, iBatis3.x正式更名为MyBatis。代码于2013年11月迁移到Github。

iBatis一词来源于“internet”和“abatis”的组合,是一个基于Java的持久层框架。 iBatis提供的持久层框架包括SQL Maps和Data Access Objects(DAO)。

4、Mybatis下载地址

https://github.com/mybatis/mybatis-3

5、Mybatis特性

  • MyBatis支持定制化SQL、存储过程以及高级映射
  • MyBatis避免了几乎所有的JDBC代码和手动设置参数以及结果集解析操作
  • MyBatis可以使用简单的XML或注解实现配置和原始映射;将接口和Java的POJO(Plain Ordinary Java Object,普通的Java对象)映射成数据库中的记录
  • Mybatis是一个半自动的ORM(Object Relation Mapping)框架

6、和其它持久化层技术对比

  • JDBC
    • SQL 夹杂在Java代码中耦合度高,导致硬编码内伤
    • 维护不易且实际开发需求中 SQL 有变化,频繁修改的情况多见
    • 代码冗长,开发效率低
  • Hibernate 和 JPA
    • 操作简便,开发效率高
    • 程序中的长难复杂 SQL 需要绕过框架
    • 内部自动生产的 SQL,不容易做特殊优化
    • 基于全映射的全自动框架,大量字段的 POJO 进行部分映射时比较困难。
    • 反射操作太多,导
  • MyBatis
    • 轻量级,性能出色
    • SQL 和 Java 编码分开,功能边界清晰。Java代码专注业务、SQL语句专注数据
    • 开发效率稍逊于HIbernate,但是完全能够接收

二、 Mybatis基本用法

第一节 HelloWorld

1、物理建模


CREATE DATABASE `mybatis-example`;

USE `mybatis-example`;

CREATE TABLE `t_emp`(
emp_id INT AUTO_INCREMENT,
emp_name CHAR(100),
emp_salary DOUBLE(10,5),
PRIMARY KEY(emp_id)
);

INSERT INTO `t_emp`(emp_name,emp_salary) VALUES("tom",200.33);

2、逻辑建模

①创建Maven module

②创建Java实体类

实体类是和现实世界中某一个具体或抽象的概念对应,是软件开发过程中,为了管理现实世界中的数据而设计的模型。

实体类的多个不同的叫法:

domain:领域模型

entity:实体

POJO:Plain Old Java Object

Java bean:一个Java类

/**
 * 和数据库表t_emp对应的实体类
 * emp_id INT AUTO_INCREMENT
 * emp_name CHAR(100)
 * emp_salary DOUBLE(10,5)
 *
 * Java的实体类中,属性的类型不要使用基本数据类型,要使用包装类型。因为包装类型可以赋值为null,表示空,而基本数据类型不可以。
 */
public class Employee {
    
    private Integer empId;
    
    private String empName;
    
    private Double empSalary;
    
    public Employee() {
    
    }
    
    public Integer getEmpId() {
        return empId;
    }
    
    public void setEmpId(Integer empId) {
        this.empId = empId;
    }
    
    public String getEmpName() {
        return empName;
    }
    
    public void setEmpName(String empName) {
        this.empName = empName;
    }
    
    public Double getEmpSalary() {
        return empSalary;
    }
    
    public void setEmpSalary(Double empSalary) {
        this.empSalary = empSalary;
    }
    
    @Override
    public String toString() {
        return "Employee{" +
                "empId=" + empId +
                ", empName='" + empName + '\'' +
                ", empSalary=" + empSalary +
                '}';
    }
    
    public Employee(Integer empId, String empName, Double empSalary) {
        this.empId = empId;
        this.empName = empName;
        this.empSalary = empSalary;
    }
}

3、搭建框架开发环境

①导入依赖

<dependencies>
    <!-- Mybatis核心 -->
    <dependency>
        <groupId>org.mybatis</groupId>
        <artifactId>mybatis</artifactId>
        <version>3.5.7</version>
    </dependency>
    
    <!-- junit测试 -->
    <dependency>
        <groupId>junit</groupId>
        <artifactId>junit</artifactId>
        <version>4.12</version>
        <scope>test</scope>
    </dependency>
    
    <!-- MySQL驱动 -->
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
        <version>5.1.3</version>
    </dependency>
    
    <!-- 数据源 -->
    <dependency>
        <groupId>com.alibaba</groupId>
        <artifactId>druid</artifactId>
        <version>1.0.31</version>
    </dependency>
</dependencies>

②准备配置文件

[1]Mybatis全局配置文件

习惯上命名为mybatis-config.xml,这个文件名仅仅只是建议,并非强制要求。将来整合Spring之后,这个配置文件可以省略,所以大家操作时可以直接复制、粘贴。

<?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表示配置Mybatis的开发环境,可以配置多个环境,在众多具体环境中,使用default属性指定实际运行时使用的环境。default属性的取值是environment标签的id属性的值。 -->
    <environments default="development">
        <!-- environment表示配置Mybatis的一个具体的环境 -->
        <environment id="development">
    
            <!-- Mybatis的内置的事务管理器 -->
            <transactionManager type="JDBC"/>
    
            <!-- 配置数据源 -->
            <dataSource type="POOLED">
    
                <!-- 建立数据库连接的具体信息 -->
                <property name="driver" value="com.mysql.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis-example"/>
                <property name="username" value="root"/>
                <property name="password" value="atguigu"/>
            </dataSource>
        </environment>
    </environments>
    
    <mappers>
        <!-- Mapper注册:指定Mybatis映射文件的具体位置 -->
        <!-- mapper标签:配置一个具体的Mapper映射文件 -->
        <!-- resource属性:指定Mapper映射文件的实际存储位置,这里需要使用一个以类路径根目录为基准的相对路径 -->
        <!--    对Maven工程的目录结构来说,resources目录下的内容会直接放入类路径,所以这里我们可以以resources目录为基准 -->
        <mapper resource="mappers/EmployeeMapper.xml"/>
    </mappers>
</configuration>

注意:配置文件存放的位置是src/main/resources目录下。

[2]Mybatis映射文件

相关概念:ORMObject Relationship Mapping)对象关系映射。

  • 对象:Java的实体类对象
  • 关系:关系型数据库
  • 映射:二者之间的对应关系
Java概念 数据库概念
属性 字段/列
对象 记录/行


<?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属性:在Mybatis全局范围内找到一个具体的Mapper配置 -->
<!-- 引入接口后,为了方便通过接口全类名来找到Mapper配置文件,所以通常将namespace属性设置为接口全类名 -->
<mapper namespace="com.atguigu.mybatis.dao.EmployeeMapper">

    <!-- 编写具体的SQL语句,使用id属性唯一的标记一条SQL语句 -->
    <!-- resultType属性:指定封装查询结果的Java实体类的全类名 -->
    <select id="selectEmployee" resultType="com.atguigu.mybatis.entity.Employee">
        <!-- Mybatis负责把SQL语句中的#{}部分替换成“?”占位符,在#{}内部还是要声明一个见名知意的名称 -->
        select emp_id empId,emp_name empName,emp_salary empSalary from t_emp where emp_id=#{empId}
    </select>
</mapper>

注意:EmployeeMapper.xml所在的目录要和mybatis-config.xml中使用mapper标签配置的一致。

5、junit测试代码

@Test
public void testSelectEmployee() throws IOException {
    
    // 1.创建SqlSessionFactory对象
    // ①声明Mybatis全局配置文件的路径
    String mybatisConfigFilePath = "mybatis-config.xml";
    
    // ②以输入流的形式加载Mybatis配置文件
    InputStream inputStream = Resources.getResourceAsStream(mybatisConfigFilePath);
    
    // ③基于读取Mybatis配置文件的输入流创建SqlSessionFactory对象
    SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
    
    // 2.使用SqlSessionFactory对象开启一个会话
    SqlSession session = sessionFactory.openSession();
    
    // 3.根据Mapper配置文件的名称空间+SQL语句的id找到具体的SQL语句
    // 格式是:名称空间.SQL语句的id
    String statement = "com.atguigu.mybatis.dao.EmployeeMapper.selectEmployee";
    
    // 要传入SQL语句的参数
    Integer empId = 1;
    
    // 执行SQL语句
    Object result = session.selectOne(statement, empId);
    
    System.out.println("o = " + result);
    
    // 4.关闭SqlSession
    session.close();
}

说明:

  • SqlSession:代表Java程序和数据库之间的会话(事务级别)。(HttpSession是Java程序和浏览器之间的会话)
  • SqlSessionFactory:是“生产”SqlSession的“工厂”。
  • 工厂模式:如果创建某一个对象,使用的过程基本固定,那么我们就可以把创建这个对象的相关代码封装到一个“工厂类”中,以后都使用这个工厂类来“生产”我们需要的对象。

第二节 HelloWorld强化

1、加入日志

①目的

在Mybatis工作过程中,通过打印日志的方式,将要执行的SQL语句打印出来。

②操作

[1]加入依赖
<!-- log4j日志 -->
<dependency>
    <groupId>log4j</groupId>
    <artifactId>log4j</artifactId>
    <version>1.2.17</version>
</dependency>
[2]加入log4j的配置文件

支持XML和properties属性文件两种形式。无论使用哪种形式,文件名是固定的(二选一):

  • log4j.xml
  • log4j.properties
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE log4j:configuration SYSTEM "log4j.dtd">
    
<log4j:configuration xmlns:log4j="http://jakarta.apache.org/log4j/">
    
    <appender name="STDOUT" class="org.apache.log4j.ConsoleAppender">
        <param name="Encoding" value="UTF-8" />
        <layout class="org.apache.log4j.PatternLayout">
            <param name="ConversionPattern" value="%-5p %d{MM-dd HH:mm:ss,SSS} %m  (%F:%L) \n" />
        </layout>
    </appender>
    <logger name="java.sql">
        <level value="debug" />
    </logger>
    <logger name="org.apache.ibatis">
        <level value="info" />
    </logger>
    <root>
        <level value="debug" />
        <appender-ref ref="STDOUT" />
    </root>
</log4j:configuration>

③日志的级别

FATAL(致命)>ERROR(错误)>WARN(警告)>INFO(信息)>DEBUG(调试)

从左到右打印的内容越来越详细

例如:FATAL级别,只打印致命信息,其他信息不会打印。

④STDOUT

是standard output的缩写,意思是标准输出。对于Java程序来说,打印到标准输出就是打印到控制台。

⑤打印效果

2、关联外部属性文件

①需求

在实际开发时,同一套代码往往会对应多个不同的具体服务器环境。使用的数据库连接参数也不同。为了更好的维护这些信息,我们建议把数据库连接信息提取到Mybatis全局配置文件外边。

②做法

创建jdbc.properties配置文件


wechat.dev.driver=com.mysql.jdbc.Driver
wechat.dev.url=jdbc:mysql://192.168.198.100:3306/mybatis-example
wechat.dev.username=root
wechat.dev.password=atguigu
    
wechat.test.driver=com.mysql.jdbc.Driver
wechat.test.url=jdbc:mysql://192.168.198.150:3306/mybatis-example
wechat.test.username=root
wechat.test.password=atguigu
    
wechat.product.driver=com.mysql.jdbc.Driver
wechat.product.url=jdbc:mysql://192.168.198.200:3306/mybatis-example
wechat.product.username=root
wechat.product.password=atguigu

在Mybatis全局配置文件中指定外部jdbc.properties文件的位置

<properties resource="jdbc.properties"/>

在需要具体属性值的时候使用${key}格式引用属性文件中的键

<dataSource type="POOLED">
    
    <!-- 建立数据库连接的具体信息(引用了外部属性文件中的数据) -->
    <property name="driver" value="${wechat.dev.driver}"/>
    <property name="url" value="${wechat.dev.url}"/>
    <property name="username" value="${wechat.dev.username}"/>
    <property name="password" value="${wechat.dev.password}"/>
    
</dataSource>

3、用上Mapper接口

Mybatis中的Mapper接口相当于以前的Dao。但是区别在于,Mapper仅仅是接口,我们不需要提供实现类。

①思路

②调整junit代码

public class ImprovedMybatisTest {
    
    private SqlSession session;
    
    // junit会在每一个@Test方法前执行@Before方法
    @Before
    public void init() throws IOException {
         session = new SqlSessionFactoryBuilder()
                 .build(
                         Resources.getResourceAsStream("mybatis-config.xml"))
                 .openSession();
    }
    
    // junit会在每一个@Test方法后执行@After方法
    @After
    public void clear() {
        session.commit();
        session.close();
    }
    
}

③完成Mapper接口

public interface EmployeeMapper {
    
    Employee selectEmployee(Integer empId);
        
}
  • 方法名和SQL的id一致
  • 方法返回值和resultType一致
  • 方法的参数和SQL的参数一致
  • 接口的全类名和映射配置文件的名称空间一致

④最终的junit测试方法

@Test
public void testUsrMapperInterface() {
    
    // 1.根据EmployeeMapper接口的Class对象获取Mapper接口类型的对象
    EmployeeMapper employeeMapper = session.getMapper(EmployeeMapper.class);
        
    // 2.调用EmployeeMapper接口的方法完成对数据库的操作
    Emp emp = employeeMapper.selectEmployee(1L);
    
    // 3.打印查询结果
    System.out.println("emp = " + emp);
}

4、增删改操作

① insert

SQL语句

<insert id="insertEmployee">
    <!-- 现在在这条SQL语句中,#{}中的表达式需要被用来从Emp emp实体类中获取emp_name的值、emp_salary的值 -->
    <!-- 而我们从实体类中获取值通常都是调用getXxx()方法 -->
    <!-- 而getXxx()方法、setXxx()方法定义了实体类的属性 -->
    <!-- 定义属性的规则是:把get、set去掉,剩下部分首字母小写 -->
    <!-- 所以我们在#{}中使用getXxx()方法、setXxx()方法定义的属性名即可 -->
    insert into t_emp(emp_name,emp_salary) values(#{empName},#{empSalary})
</insert>

Java代码中的Mapper接口:


public interface EmployeeMapper {
    
    Employee selectEmployee(Integer empId);
    
    int insertEmployee(Employee employee);
}

Java代码中的junit测试:

@Test
public void testSaveEmployee() {
    
    EmployeeMapper employeeMapper = session.getMapper(EmployeeMapper.class);
    
    // 创建要保存到数据库的对象
    Employee employee = new Employee();
    
    // 给实体类对象设置具体属性值
    employee.setEmpName("jerry");
    employee.setEmpSalary(5000.33);
    
    // 执行保存操作
    int result = employeeMapper.insertEmployee(employee);
    
    // 打印受影响的行数
    System.out.println("result = " + result);
}

②delete

SQL语句

<delete id="deleteEmployee">
        delete from t_emp where emp_id=#{empId}
    </delete>

Java代码中的Mapper接口:

public interface EmployeeMapper {
    
    Employee selectEmployee(Integer empId);
    
    int insertEmployee(Employee employee);
    
    int deleteEmployee(Integer empId);
}

Java代码中的junit测试:


@Test
public void testRemoveEmployee() {
    
    EmployeeMapper employeeMapper = session.getMapper(EmployeeMapper.class);
    
    int result = employeeMapper.deleteEmployee(1);
    
    System.out.println("result = " + result);
}

③update

SQL语句:


<update id="updateEmployee">
    update t_emp set emp_name=#{empName},emp_salary=#{empSalary} where emp_id=#{empId}
</update>

Java代码中的Mapper接口:

public interface EmployeeMapper {
    
    Employee selectEmployee(Integer empId);
    
    int insertEmployee(Employee employee);
    
    int deleteEmployee(Integer empId);
    
    int updateEmployee(Employee employee);
}

Java代码中的junit测试:

@Test
public void testUpdateEmployee() {
    
    EmployeeMapper employeeMapper = session.getMapper(EmployeeMapper.class);
    
    Employee employee = new Employee(2, "AAAAAA", 6666.66);
    
    int result = employeeMapper.updateEmployee(employee);
    
    System.out.println("result = " + result);
}

第三节 给SQL语句传参

1、#{}方式

Mybatis会在运行过程中,把配置文件中的SQL语句里面的#{}转换为“?”占位符,发送给数据库执行。

配置文件中的SQL:

<delete id="deleteEmployeeById">
    delete from t_emp where emp_id=#{empId}
</delete>

实际执行的SQL:

delete from t_emp where emp_id=?

2、${}方式

将来会根据${}拼字符串

①SQL语句


<select id="selectEmployeeByName" resultType="com.atguigu.mybatis.entity.Employee">
    select emp_id empId,emp_name empName,emp_salary empSalary from t_emp where emp_name like '%${empName}%'
</select>

②Mapper接口

注意:由于Mapper接口中方法名是作为SQL语句标签的id,不能重复,所以Mapper接口中不能出现重名的方法不允许重载

public interface EmployeeMapper {
    
    Employee selectEmployee(Integer empId);
    
    Employee selectEmployeeByName(@Param("empName") String empName);
    
    int insertEmployee(Employee employee);
    
    int deleteEmployee(Integer empId);
    
    int updateEmployee(Employee employee);
}

③junit测试

@Test
public void testDollar() {
    
    EmployeeMapper employeeMapper = session.getMapper(EmployeeMapper.class);
    
    Employee employee = employeeMapper.selectEmployeeByName("r");
    
    System.out.println("employee = " + employee);
}

④实际打印的SQL

select emp_id empId,emp_name empName,emp_salary empSalary from t_emp where emp_name like '%r%'

⑤应用场景举例

在SQL语句中,数据库表的表名不确定,需要外部动态传入,此时不能使用#{},因为数据库不允许表名位置使用问号占位符,此时只能使用${}。

其他情况,只要能用#{}肯定不用${},避免SQL注入。

第四节 数据输入

1、Mybatis总体机制概括

2、概念说明

这里数据输入具体是指上层方法(例如Service方法)调用Mapper接口时,数据传入的形式。

  • 简单类型:只包含一个值的数据类型
    • 基本数据类型:int、byte、short、double、……
    • 基本数据类型的包装类型:Integer、Character、Double、……
    • 字符串类型:String
  • 复杂类型:包含多个值的数据类型
    • 实体类类型:Employee、Department、……
    • 集合类型:List、Set、Map、……
    • 数组类型:int[]、String[]、……
    • 复合类型:List、实体类中包含集合……

3、单个简单类型参数

①Mapper接口中抽象方法的声明

Employee selectEmployee(Integer empId);

Mybatis规定,如果传入的是简单类型,且只有一个参数。如果动态sql中需要用到该参数,判断该参数是否为空,该参数的名称必须为"value"。

<select id="findPage" parameterType="string" resultType="travelGroup">
        select * from t_travelgroup
        <where>
            <if test="value != null and value.length>0">
                code =#{value} or name like "%"#{value}"%" or helpCode=#{value}
            </if>
        </where>
    </select>

②SQL语句

<select id="selectEmployee" resultType="com.atguigu.mybatis.entity.Employee">
    select emp_id empId,emp_name empName,emp_salary empSalary from t_emp where emp_id=#{empId}
</select>

4、实体类类型参数

①Mapper接口中抽象方法的声明

int insertEmployee(Employee employee);

②SQL语句

<insert id="insertEmployee">
    insert into t_emp(emp_name,emp_salary) values(#{empName},#{empSalary})
</insert>

③对应关系

④结论

Mybatis会根据#{}中传入的数据,加工成getXxx()方法,通过反射在实体类对象中调用这个方法,从而获取到对应的数据。填充到#{}这个位置。

5、零散的简单类型数据

①Mapper接口中抽象方法的声明

int updateEmployee(@Param("empId") Integer empId,@Param("empSalary") Double empSalary);

②SQL语句

  <update id="updateEmployee">
        update t_emp set emp_salary=#{empSalary} where emp_id=#{empId}
    </update>

③对应关系

6、Map类型参数

①Mapper接口中抽象方法的声明


int updateEmployeeByMap(Map<String, Object> paramMap);

②SQL语句

 <update id="updateEmployeeByMap">
        update t_emp set emp_salary=#{empSalaryKey} where emp_id=#{empIdKey}
    </update>

③junit测试

@Test
public void testUpdateEmpNameByMap() {
    
    EmployeeMapper mapper = session.getMapper(EmployeeMapper.class);
    
    Map<String, Object> paramMap = new HashMap<>();
    
    paramMap.put("empSalaryKey", 999.99);
    paramMap.put("empIdKey", 5);
    
    int result = mapper.updateEmployeeByMap(paramMap);
    
    System.out.println("result = " + result);
}

④对应关系

#{}中写Map中的key

⑤使用场景

有很多零散的参数需要传递,但是没有对应的实体类类型可以使用。使用@Param注解一个一个传入又太麻烦了。所以都封装到Map中。

第五节 数据输出

1、返回单个简单类型数据

①Mapper接口中的抽象方法

int selectEmpCount();

②SQL语句

 <select id="selectEmpCount" resultType="int">
        select count(*) from t_emp
    </select>

③junit测试

 @Test
    public void testEmpCount() {
    
        EmployeeMapper employeeMapper = session.getMapper(EmployeeMapper.class);
    
        int count = employeeMapper.selectEmpCount();
    
        System.out.println("count = " + count);
    }

2、返回实体类对象

①Mapper接口的抽象方法


Employee selectEmployee(Integer empId);

②SQL语句


<!-- 编写具体的SQL语句,使用id属性唯一的标记一条SQL语句 -->
<!-- resultType属性:指定封装查询结果的Java实体类的全类名 -->
<select id="selectEmployee" resultType="com.atguigu.mybatis.entity.Employee">
    <!-- Mybatis负责把SQL语句中的#{}部分替换成“?”占位符 -->
    <!-- 给每一个字段设置一个别名,让别名和Java实体类中属性名一致 -->
    select emp_id empId,emp_name empName,emp_salary empSalary from t_emp where emp_id=#{maomi}
</select>

通过给数据库表字段加别名,让查询结果的每一列都和Java实体类中属性对应起来。

③增加全局配置自动识别对应关系

在全局配置文件mybatis-config.xml做了下面的配置,select语句中可以不给字段设置别名


<!-- 在全局范围内对Mybatis进行配置 -->
<settings>
    <!-- 具体配置 -->
    <!-- 从org.apache.ibatis.session.Configuration类中可以查看能使用的配置项 -->
    <!-- 将mapUnderscoreToCamelCase属性配置为true,表示开启自动映射驼峰式命名规则 -->
    <!-- 规则要求数据库表字段命名方式:单词_单词 -->
    <!-- 规则要求Java实体类属性名命名方式:首字母小写的驼峰式命名 -->
    <setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>

3、返回Map类型

适用于SQL查询返回的各个字段综合起来并不和任何一个现有的实体类对应,没法封装到实体类对象中。能够封装成实体类类型的,就不使用Map类型

①Mapper接口的抽象方法

Map<String,Object> selectEmpNameAndMaxSalary();

②SQL语句


<!-- Map<String,Object> selectEmpNameAndMaxSalary(); -->
<!-- 返回工资最高的员工的姓名和他的工资 -->
<select id="selectEmpNameAndMaxSalary" resultType="map">
        SELECT
            emp_name 员工姓名,
            emp_salary 员工工资,
            (SELECT AVG(emp_salary) FROM t_emp) 部门平均工资
        FROM t_emp WHERE emp_salary=(
            SELECT MAX(emp_salary) FROM t_emp
        )
</select>

③junit测试

 @Test
    public void testQueryEmpNameAndSalary() {
    
        EmployeeMapper employeeMapper = session.getMapper(EmployeeMapper.class);
    
        Map<String, Object> resultMap = employeeMapper.selectEmpNameAndMaxSalary();
    
        Set<Map.Entry<String, Object>> entrySet = resultMap.entrySet();
    
        for (Map.Entry<String, Object> entry : entrySet) {
            String key = entry.getKey();
            Object value = entry.getValue();
            System.out.println(key + "=" + value);
        }
    }

4、返回List类型

查询结果返回多个实体类对象,希望把多个实体类对象放在List集合中返回。此时不需要任何特殊处理,在resultType属性中还是设置实体类类型即可。

①Mapper接口中抽象方法


List<Employee> selectAll();

②SQL语句

  <!-- List<Employee> selectAll(); -->
    <select id="selectAll" resultType="com.atguigu.mybatis.entity.Employee">
        select emp_id empId,emp_name empName,emp_salary empSalary
        from t_emp
    </select>

③junit测试

 @Test
    public void testSelectAll() {
    
        EmployeeMapper employeeMapper = session.getMapper(EmployeeMapper.class);
    
        List<Employee> employeeList = employeeMapper.selectAll();
    
        for (Employee employee : employeeList) {
            System.out.println("employee = " + employee);
        }
    
    }

5、返回自增主键

①使用场景

例如:保存订单信息。需要保存Order对象和List<OrderItem>。其中,OrderItem对应的数据库表,包含一个外键,指向Order对应表的主键。

在保存List<OrderItem>的时候,需要使用下面的SQL:

insert into t_order_item(item_name,item_price,item_count,order_id) values(...)

这里需要用到的order_id,是在保存Order对象时,数据库表以自增方式产生的,需要特殊办法拿到这个自增的主键值。至于,为什么不能通过查询最大主键的方式解决这个问题,参考下图:

② Mapper接口中的抽象方法

int insertEmployee(Employee employee);

③ SQL语句

<!-- int insertEmployee(Employee employee); -->
<!-- useGeneratedKeys属性字面意思就是“使用生成的主键” -->
<!-- keyProperty属性可以指定主键在实体类对象中对应的属性名,Mybatis会将拿到的主键值存入这个属性 -->
<insert id="insertEmployee" useGeneratedKeys="true" keyProperty="empId">
    insert into t_emp(emp_name,emp_salary)
    values(#{empName},#{empSalary})
</insert>

④ junit测试

@Test
public void testSaveEmp() {
    
    EmployeeMapper employeeMapper = session.getMapper(EmployeeMapper.class);
    
    Employee employee = new Employee();
        
    employee.setEmpName("john");
    employee.setEmpSalary(666.66);
    
    employeeMapper.insertEmployee(employee);
    
    System.out.println("employee.getEmpId() = " + employee.getEmpId());
    
}

⑤ 注意

Mybatis是将自增主键的值设置到实体类对象中,而不是以Mapper接口方法返回值的形式返回。接口中的返回值是受影响的行数

⑥ 不支持自增主键的数据库

而对于不支持自增型主键的数据库(例如 Oracle),则可以使用 selectKey 子元素:selectKey 元素将会首先运行,id 会被设置,然后插入语句会被调用

<insert id="insertEmployee" 
        parameterType="com.atguigu.mybatis.beans.Employee"  
            databaseId="oracle">
        <selectKey order="BEFORE" keyProperty="id" 
                                       resultType="integer">
            select employee_seq.nextval from dual 
        </selectKey>    
        insert into orcl_employee(id,last_name,email,gender) values(#{id},#{lastName},#{email},#{gender})
</insert>

或者是


<insert id="insertEmployee" 
        parameterType="com.atguigu.mybatis.beans.Employee"  
            databaseId="oracle">
        <selectKey order="AFTER" keyProperty="id" 
                                         resultType="integer">
            select employee_seq.currval from dual 
        </selectKey>    
    insert into orcl_employee(id,last_name,email,gender) values(employee_seq.nextval,#{lastName},#{email},#{gender})
</insert>

6、数据库表字段和实体类属性对应关系

① 别名

将字段的别名设置成和实体类属性一致。


<!-- 编写具体的SQL语句,使用id属性唯一的标记一条SQL语句 -->
<!-- resultType属性:指定封装查询结果的Java实体类的全类名 -->
<select id="selectEmployee" resultType="com.atguigu.mybatis.entity.Employee">
    <!-- Mybatis负责把SQL语句中的#{}部分替换成“?”占位符 -->
    <!-- 给每一个字段设置一个别名,让别名和Java实体类中属性名一致 -->
    select emp_id empId,emp_name empName,emp_salary empSalary from t_emp where emp_id=#{maomi}
</select>

关于实体类属性的约定:

getXxx()方法、setXxx()方法把方法名中的get或set去掉,首字母小写。

②全局配置自动识别驼峰式命名规则

在Mybatis全局配置文件加入如下配置:

<!-- 使用settings对Mybatis全局进行设置 -->
<settings>
    <!-- 将xxx_xxx这样的列名自动映射到xxXxx这样驼峰式命名的属性名 -->
    <setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>

SQL语句中可以不使用别名

<!-- Employee selectEmployee(Integer empId); -->
<select id="selectEmployee" resultType="com.atguigu.mybatis.entity.Employee">
    select emp_id,emp_name,emp_salary from t_emp where emp_id=#{empId}
</select>

③使用resultMap

使用resultMap标签定义对应关系,再在后面的SQL语句中引用这个对应关系

<!-- 专门声明一个resultMap设定column到property之间的对应关系 -->
<resultMap id="selectEmployeeByRMResultMap" type="com.atguigu.mybatis.entity.Employee">
    
    <!-- 使用id标签设置主键列和主键属性之间的对应关系 -->
    <!-- column属性用于指定字段名;property属性用于指定Java实体类属性名 -->
    <id column="emp_id" property="empId"/>
    
    <!-- 使用result标签设置普通字段和Java实体类属性之间的关系 -->
    <result column="emp_name" property="empName"/>
    <result column="emp_salary" property="empSalary"/>
</resultMap>
    
<!-- Employee selectEmployeeByRM(Integer empId); -->
<select id="selectEmployeeByRM" resultMap="selectEmployeeByRMResultMap">
    select emp_id,emp_name,emp_salary from t_emp where emp_id=#{empId}
</select>

三、关联关系

第一节 概念

1、关联关系概念说明

①数量关系

主要体现在数据库表中

  • 一对一

    夫妻关系,人和身份证号

  • 一对多

    用户和用户的订单,锁和钥匙

  • 多对多

    老师和学生,部门和员工

②关联关系的方向

主要体现在Java实体类中

  • 双向:双方都可以访问到对方
    • Customer:包含Order的集合属性
    • Order:包含单个Customer的属性
  • 单向:双方中只有一方能够访问到对方
    • Customer:不包含Order的集合属性,访问不到Order
    • Order:包含单个Customer的属性

2、创建模型

①创建实体类

public class Customer {
    
    private Integer customerId;
    private String customerName;
    private List<Order> orderList;// 体现的是对多的关系

public class Order {
    
    private Integer orderId;
    private String orderName;
    private Customer customer;// 体现的是对一的关系

②创建数据库表插入测试数据

CREATE TABLE `t_customer` (
     `customer_id` INT NOT NULL AUTO_INCREMENT, 
     `customer_name` CHAR(100), 
     PRIMARY KEY (`customer_id`) 
);
CREATE TABLE `t_order` ( 
    `order_id` INT NOT NULL AUTO_INCREMENT, 
    `order_name` CHAR(100), 
    `customer_id` INT, 
    PRIMARY KEY (`order_id`) 
); 
INSERT INTO `t_customer` (`customer_name`) VALUES ('c01');
INSERT INTO `t_order` (`order_name`, `customer_id`) VALUES ('o1', '1'); 
INSERT INTO `t_order` (`order_name`, `customer_id`) VALUES ('o2', '1'); 
INSERT INTO `t_order` (`order_name`, `customer_id`) VALUES ('o3', '1'); 

实际开发时,一般在开发过程中,不给数据库表设置外键约束。

原因是避免调试不方便。

一般是功能开发完成,再加外键约束检查是否有bug。

第二节 对一

1、创建OrderMapper接口

public interface OrderMapper {
    
    Order selectOrderWithCustomer(Integer orderId);
    
}

2、创建OrderMapper.xml配置文件

<!-- 创建resultMap实现“对一”关联关系映射 -->
<!-- id属性:通常设置为这个resultMap所服务的那条SQL语句的id加上“ResultMap” -->
<!-- type属性:要设置为这个resultMap所服务的那条SQL语句最终要返回的类型 -->
<resultMap id="selectOrderWithCustomerResultMap" type="com.atguigu.mybatis.entity.Order">

    <!-- 先设置Order自身属性和字段的对应关系 -->
    <id column="order_id" property="orderId"/>
    <result column="order_name" property="orderName"/>

    <!-- 使用association标签配置“对一”关联关系 -->
    <!-- property属性:在Order类中对一的一端进行引用时使用的属性名 -->
    <!-- javaType属性:一的一端类的全类名 -->
    <association property="customer" javaType="com.atguigu.mybatis.entity.Customer">
        <!-- 配置Customer类的属性和字段名之间的对应关系 -->
        <id column="customer_id" property="customerId"/>
        <result column="customer_name" property="customerName"/>
    </association>

</resultMap>

<!-- Order selectOrderWithCustomer(Integer orderId); -->
<select id="selectOrderWithCustomer" resultMap="selectOrderWithCustomerResultMap">
    SELECT order_id,order_name,c.customer_id,customer_name
    FROM t_order o
    LEFT JOIN t_customer c
    ON o.customer_id=c.customer_id
    WHERE o.order_id=#{orderId}
</select>

3、在Mybatis全局配置文件中注册Mapper配置文件

<!-- 注册Mapper配置文件:告诉Mybatis我们的Mapper配置文件的位置 -->
<mappers>
    <!-- 在mapper标签的resource属性中指定Mapper配置文件以“类路径根目录”为基准的相对路径 -->
    <mapper resource="com/atguigu/mybatis/mapper/OrderMapper.xml"/>
</mappers>

4、junit测试程序


SqlSession session;
    @Before
    public void before() throws IOException {
       sqlSession = new SqlSessionFactoryBuilder().build(
                Resources.getResourceAsStream("mybatis-config.xml")
        ).openSession();
    }
    @After
    public void after(){
        sqlSession.commit();
        sqlSession.close();
    }

@Test
public void testRelationshipToOne() {
    OrderMapper orderMapper = session.getMapper(OrderMapper.class);
    
    // 查询Order对象,检查是否同时查询了关联的Customer对象
    Order order = orderMapper.selectOrderWithCustomer(2);
    System.out.println("order = " + order);
}

5、关键词

在“对一”关联关系中,我们的配置比较多,但是关键词就只有:associationjavaType

第三节 对多

1、创建Mapper接口

public interface CustomerMapper {
    
    Customer selectCustomerWithOrderList(Integer customerId);
    
}

2、创建CustomerMapper.xml配置文件

注意:不要忘记在Mybatis全局配置文件中注册

3、配置关联关系和SQL语句

<!-- 配置resultMap实现从Customer到OrderList的“对多”关联关系 -->
<resultMap id="selectCustomerWithOrderListResultMap"
           type="com.atguigu.mybatis.entity.Customer">
    
    <!-- 映射Customer本身的属性 -->
    <id column="customer_id" property="customerId"/>
    <result column="customer_name" property="customerName"/>
    
    <!-- collection标签:映射“对多”的关联关系 -->
    <!-- property属性:在Customer类中,关联“多”的一端的属性名 -->
    <!-- ofType属性:集合属性中元素的类型 -->
        <collection property="orderList" ofType="com.atguigu.mybatis.entity.Order">
        <!-- 映射Order的属性 -->
        <id column="order_id" property="orderId"/>
        <result column="order_name" property="orderName"/>
    
    </collection>
    
</resultMap>
    
<!-- Customer selectCustomerWithOrderList(Integer customerId); -->
<select id="selectCustomerWithOrderList" resultMap="selectCustomerWithOrderListResultMap">
    SELECT c.customer_id,c.customer_name,o.order_id,o.order_name
    FROM t_customer c
    LEFT JOIN t_order o
    ON c.customer_id=o.customer_id
    WHERE c.customer_id=#{customerId}
</select>

对应关系可以参考下图:

4、junit测试


@Test
public void testRelationshipToMulti() {
    
    CustomerMapper customerMapper = session.getMapper(CustomerMapper.class);
    
    // 查询Customer对象同时将关联的Order集合查询出来
    Customer customer = customerMapper.selectCustomerWithOrderList(1);
    
    System.out.println("customer.getCustomerId() = " + customer.getCustomerId());
    System.out.println("customer.getCustomerName() = " + customer.getCustomerName());
    
    List<Order> orderList = customer.getOrderList();
    for (Order order : orderList) {
        System.out.println("order = " + order);
    }
    
}

5、关键词

在“对多”关联关系中,同样有很多配置,但是提炼出来最关键的就是:“collection”和“ofType

第四节 分步查询

1、概念和需求

为了实现延迟加载,对Customer和Order的查询必须分开,分成两步来做,才能够实现。为此,我们需要单独查询Order,也就是需要在Mapper配置文件中,单独编写查询Order集合数据的SQL语句。

2、对一

① 编写OrderMapper的抽象方法

Order selectOrderWithCustomerTwoStep(Integer orderId);

② 编写OrderMapper.xml

 <!-- Order selectOrderWithCustomerTwoStep(Integer orderId); -->
    <!-- 分步查询时,查询Order就只查Order -->
    <select id="selectOrderWithCustomerTwoStep" resultMap="selectOrderWithCustomerTwoStepResultMap">
        select order_id,order_name,customer_id from t_order
        where order_id=#{orderId}
    </select>

	
	 <resultMap id="selectOrderWithCustomerTwoStepResultMap" type="com.atguigu.mybatis.entity.Order">

        <!-- 第一部分:映射Order自己的属性 -->
        <id column="order_id" property="orderId"/>
        <result column="order_name" property="orderName"/>
        <result column="customer_id" property="customerId"/>

        <!-- 第二部分:映射Customer -->
        <!-- select属性:定位到另外一条专门查询Customer的SQL语句 -->
        <!-- column属性:指定用来给查询Customer的SQL语句传参的字段 -->
        <association
                property="customer"
                column="customer_id"
                select="com.atguigu.mybatis.mapper.CustomerMapper.selectCustomerById"/>

    </resultMap>

③ 编写CustomerMapper.xml

<!-- 定位到当前SQL语句的方式:com.atguigu.mybatis.mapper.CustomerMapper.selectCustomerById -->
    <!-- 这条SQL语句仅仅是根据id查询Customer -->
    <select id="selectCustomerById" resultType="com.atguigu.mybatis.entity.Customer">
        select customer_id,customer_name from t_customer where customer_id=#{custom                                                                                erId}
    </select>

④ junit

 @Test
    public void testQueryOrderWithCustomerTwoStep() {

        OrderMapper orderMapper = session.getMapper(OrderMapper.class);

        Integer orderId = 1;

        // 查询Order对象
        Order order = orderMapper.selectOrderWithCustomerTwoStep(orderId);

        // 打印Order对象本身信息
        System.out.println("order = " + order);

        // 通过Order对象获取关联的Customer对象
        Customer customer = order.getCustomer();

        System.out.println("customer = " + customer);
    }

3、对多

① 编写CustomerMapper的抽象方法

Customer selectCustomerWithOrderTowStep(Integer customerId);

② 编写CustomerMapper.xml

<!-- Customer selectCustomerWithOrderTowStep(Integer customerId); -->
    <select id="selectCustomerWithOrderTowStep" resultMap="selectCustomerWithOrderTowStepResultMap">
        select customer_id,customer_name from t_customer where customer_id=#{customerId}
    </select>

	 <resultMap id="selectCustomerWithOrderTowStepResultMap" type="com.atguigu.mybatis.entity.Customer">
        <!-- 第一部分:映射Customer自己的对应关系 -->
        <id column="customer_id" property="customerId"/>
        <result column="customer_name" property="customerName"/>

        <!-- 第二部分:映射关联关系 -->
        <!-- property属性:指定在Customer实体类中建立关联关系的orderList属性 -->
        <!-- column属性:用来给select属性指定的SQL语句传参数 -->
        <!-- select属性:指定根据customerId查询Order集合的SQL语句 -->
        <collection property="orderList"
                    column="customer_id"
                    select="com.atguigu.mybatis.mapper.OrderMapper.selectOrderListByCustomerId"/>

    </resultMap>

③ 编写OrderMapper.xml

 <!-- 根据customerId查询Order集合 -->
    <select id="selectOrderListByCustomerId" resultType="com.atguigu.mybatis.entity.Order">
        select order_id,order_name,customer_id from t_order
        where customer_id=#{customerId}
    </select>

④ junit

 @Test
    public void testQueryCustomerWithOrderTowStep() throws InterruptedException {

        CustomerMapper customerMapper = session.getMapper(CustomerMapper.class);

        Integer customerId = 1;

        Customer customer = customerMapper.selectCustomerWithOrderTowStep(customerId);

        // 测试延迟加载功能时不要打印Customer对象整体,这样没效果
        // System.out.println("customer = " + customer);
        Integer customerId1 = customer.getCustomerId();
        String customerName = customer.getCustomerName();
        System.out.println("customerId1 = " + customerId1);
        System.out.println("customerName = " + customerName);

        TimeUnit.SECONDS.sleep(3);

        List<Order> orderList = customer.getOrderList();

        for (Order order : orderList) {
            System.out.println("order = " + order);
        }

    }

⑤ 各个要素之间的对应关系

第五节 延迟加载

1、概念

查询到Customer的时候,不一定会使用Order的List集合数据。如果Order的集合数据始终没有使用,那么这部分数据占用的内存就浪费了。对此,我们希望不一定会被用到的数据,能够在需要使用的时候再去查询。

例如:对Customer进行1000次查询中,其中只有15次会用到Order的集合数据,那么就在需要使用时才去查询能够大幅度节约内存空间。

延迟加载的概念:对于实体类关联的属性到需要使用时才查询。也叫懒加载

2、配置

①较低版本

在Mybatis全局配置文件中配置settings

<!-- 使用settings对Mybatis全局进行设置 -->
<settings>
    <!-- 开启延迟加载功能:需要配置两个配置项 -->
    <!-- 1、将lazyLoadingEnabled设置为true,开启懒加载功能 -->
    <setting name="lazyLoadingEnabled" value="true"/>

    <!-- 2、将aggressiveLazyLoading设置为false,关闭“积极的懒加载” -->
    <setting name="aggressiveLazyLoading" value="false"/>
</settings>

官方文档中对aggressiveLazyLoading属性的解释:

When enabled, an object with lazy loaded properties will be loaded entirely upon a call to any of the lazy properties.Otherwise, each property is loaded on demand.

②较高版本

<!-- Mybatis全局配置 -->
<settings>
    <!-- 开启延迟加载功能 -->
    <setting name="lazyLoadingEnabled" value="true"/>
</settings>

3、修改junit测试


@Test
public void testSelectCustomerWithOrderList() throws InterruptedException {
    
    CustomerMapper mapper = session.getMapper(CustomerMapper.class);
    
    Customer customer = mapper.selectCustomerWithOrderList(1);
    
    // 这里必须只打印“customerId或customerName”这样已经加载的属性才能看到延迟加载的效果
    // 这里如果打印Customer对象整体则看不到效果
    System.out.println("customer = " + customer.getCustomerName());
    
    // 先指定具体的时间单位,然后再让线程睡一会儿
    TimeUnit.SECONDS.sleep(5);
    
    List<Order> orderList = customer.getOrderList();
    
    for (Order order : orderList) {
        System.out.println("order = " + order);
    }
}

效果:刚开始先查询Customer本身,需要用到OrderList的时候才发送SQL语句去查询

DEBUG 11-30 11:25:31,127 ==>  Preparing: select customer_id,customer_name from t_customer where customer_id=?   (BaseJdbcLogger.java:145) 
DEBUG 11-30 11:25:31,193 ==> Parameters: 1(Integer)  (BaseJdbcLogger.java:145) 
DEBUG 11-30 11:25:31,314 <==      Total: 1  (BaseJdbcLogger.java:145) 
customer = c01
DEBUG 11-30 11:25:36,316 ==>  Preparing: select order_id,order_name from t_order where customer_id=?   (BaseJdbcLogger.java:145) 
DEBUG 11-30 11:25:36,316 ==> Parameters: 1(Integer)  (BaseJdbcLogger.java:145) 
DEBUG 11-30 11:25:36,321 <==      Total: 3  (BaseJdbcLogger.java:145) 
order = Order{orderId=1, orderName='o1'}
order = Order{orderId=2, orderName='o2'}
order = Order{orderId=3, orderName='o3'}

4、关键词总结

我们是在“对多”关系中举例说明延迟加载的,在“对一”中配置方式基本一样。

关联关系 配置项关键词 所在配置文件
对一 association标签/javaType属性 Mapper配置文件中的resultMap
对多 collection标签/ofType属性 Mapper配置文件中的resultMap
对一分步 association标签/select属性 Mapper配置文件中的resultMap
对多分步 collection标签/select属性 Mapper配置文件中的resultMap
延迟加载[低] lazyLoadingEnabled设置为true
aggressiveLazyLoading设置为false
Mybatis全局配置文件中的settings
延迟加载[高] lazyLoadingEnabled设置为true Mybatis全局配置文件中的settings

第四章 动态SQL

第一节 简介

Mybatis框架的动态SQL技术是一种根据特定条件动态拼装SQL语句的功能,它存在的意义是为了解决拼接SQL语句字符串时的痛点问题。

One of the most powerful features of MyBatis has always been its Dynamic SQL capabilities. If you have any experience with JDBC or any similar framework, you understand how painful it is to conditionally concatenate strings of SQL together, making sure not to forget spaces or to omit a comma at the end of a list of columns. Dynamic SQL can be downright painful to deal with.

MyBatis的一个强大的特性之一通常是它的动态SQL能力。如果你有使用JDBC或其他相似框架的经验,你就明白条件地串联SQL字符串在一起是多么的痛苦,确保不能忘了空格或在列表的最后省略逗号。动态SQL可以彻底处理这种痛苦。

第二节 if和where标签

<!-- List<Employee> selectEmployeeByCondition(Employee employee); -->
<select id="selectEmployeeByCondition" resultType="com.atguigu.mybatis.entity.Employee">
    select emp_id,emp_name,emp_salary from t_emp
    
    <!-- where标签会自动去掉“标签体内前面、后面多余的and/or” -->
    <where>
        <!-- 使用if标签,让我们可以有选择的加入SQL语句的片段。这个SQL语句片段是否要加入整个SQL语句,就看if标签判断的结果是否为true -->
        <!-- 在if标签的test属性中,可以访问实体类的属性,不可以访问数据库表的字段 -->
        <if test="empName != null">
            <!-- 在if标签内部,需要访问接口的参数时还是正常写#{} -->
            or emp_name=#{empName}
        </if>
        <if test="empSalary &gt; 2000">
            or emp_salary>#{empSalary}
        </if>
        <!--
         第一种情况:所有条件都满足 WHERE emp_name=? or emp_salary>?
         第二种情况:部分条件满足 WHERE emp_salary>?
         第三种情况:所有条件都不满足 没有where子句
         -->
    </where>
</select>

第三节 set标签

1、相关业务需求举例

实际开发时,对一个实体类对象进行更新。往往不是更新所有字段,而是更新一部分字段。此时页面上的表单往往不会给不修改的字段提供表单项。

<form action="" method="">
    
    <input type="hidden" name="userId" value="5232" />
    
    年  龄:<input type="text" name="userAge" /><br/>
    性  别:<input type="text" name="userGender" /><br/>
    坐  标:<input type="text" name="userPosition" /><br/>
    <!-- 用户名:<input type="text" name="userName" /><br/>   -->
    <!-- 余  额:<input type="text" name="userBalance" /><br/>-->
    <!-- 等  级:<input type="text" name="userGrade" /><br/>  -->
    
    <button type="submit">修改</button>
    
</form>

例如上面的表单,如果服务器端接收表单时,使用的是User这个实体类,那么userName、userBalance、userGrade接收到的数据就是null。

如果不加判断,直接用User对象去更新数据库,在Mapper配置文件中又是每一个字段都更新,那就会把userName、userBalance、userGrade设置为null值,从而造成数据库表中对应数据被破坏。

此时需要我们在Mapper配置文件中,对update语句的set子句进行定制,此时就可以使用动态SQL的set标签。

2、实际配置方式

<!-- void updateEmployeeDynamic(Employee employee) -->
<update id="updateEmployeeDynamic">
    update t_emp
    <!-- set emp_name=#{empName},emp_salary=#{empSalary} -->
    <!-- 使用set标签动态管理set子句,并且动态去掉两端多余的逗号 -->
    <set>
        <if test="empName != null">
            emp_name=#{empName},
        </if>
        <if test="empSalary &lt; 3000">
            emp_salary=#{empSalary},
        </if>
    </set>
    where emp_id=#{empId}
    <!--
         第一种情况:所有条件都满足 SET emp_name=?, emp_salary=?
         第二种情况:部分条件满足 SET emp_salary=?
         第三种情况:所有条件都不满足 update t_emp where emp_id=?
            没有set子句的update语句会导致SQL语法错误
     -->
</update>

第四节 trim标签

使用trim标签控制条件部分两端是否包含某些字符

  • prefix属性:指定要动态添加的前缀
  • suffix属性:指定要动态添加的后缀
  • prefixOverrides属性:指定要动态去掉的前缀,使用“|”分隔有可能的多个值
  • suffixOverrides属性:指定要动态去掉的后缀,使用“|”分隔有可能的多个值
<!-- List<Employee> selectEmployeeByConditionByTrim(Employee employee) -->
<select id="selectEmployeeByConditionByTrim" resultType="com.atguigu.mybatis.entity.Employee">
    select emp_id,emp_name,emp_age,emp_salary,emp_gender
    from t_emp
    
    <!-- prefix属性指定要动态添加的前缀 -->
    <!-- suffix属性指定要动态添加的后缀 -->
    <!-- prefixOverrides属性指定要动态去掉的前缀,使用“|”分隔有可能的多个值 -->
    <!-- suffixOverrides属性指定要动态去掉的后缀,使用“|”分隔有可能的多个值 -->
    <!-- 当前例子用where标签实现更简洁,但是trim标签更灵活,可以用在任何有需要的地方 -->
    <trim prefix="where" suffixOverrides="and|or">
        <if test="empName != null">
            emp_name=#{empName} and
        </if>
        <if test="empSalary &gt; 3000">
            emp_salary>#{empSalary} and
        </if>
        <if test="empAge &lt;= 20">
            emp_age=#{empAge} or
        </if>
        <if test="empGender=='male'">
            emp_gender=#{empGender}
        </if>
    </trim>
</select>

第五节 choose/when/otherwise标签

在多个分支条件中,仅执行一个。

<!-- List<Employee> selectEmployeeByConditionByChoose(Employee employee) -->
<select id="selectEmployeeByConditionByChoose" resultType="com.atguigu.mybatis.entity.Employee">
    select emp_id,emp_name,emp_salary from t_emp
    where
    <choose>
        <when test="empName != null">emp_name=#{empName}</when>
        <when test="empSalary &lt; 3000">emp_salary &lt; 3000</when>
        <otherwise>1=1</otherwise>
    </choose>
    
    <!--
     第一种情况:第一个when满足条件 where emp_name=?
     第二种情况:第二个when满足条件 where emp_salary < 3000
     第三种情况:两个when都不满足 where 1=1 执行了otherwise
     -->
</select>

第六节 foreach标签

1、批量插入

 /**
     * 批量插入
     */
    void batchInsert(@Param("stuList") List<StudentDemo> stuList);

<!--
    collection属性:要遍历的集合
    item属性:遍历集合的过程中能得到每一个具体对象,在item属性中设置一个名字,将来通过这个名字引用遍历出来的对象
    separator属性:指定当foreach标签的标签体重复拼接字符串时,各个标签体字符串之间的分隔符
    open属性:指定整个循环把字符串拼好后,字符串整体的前面要添加的字符串
    close属性:指定整个循环把字符串拼好后,字符串整体的后面要添加的字符串
    index属性:这里起一个名字,便于后面引用
        遍历List集合,这里能够得到List集合的索引值
        遍历Map集合,这里能够得到Map集合的key
 -->
<!-- void batchInsert(@Param("stuList")List<StudentDemo> stuList);-->
    <insert id="batchInsert">
        insert into t_studentdemo(stu_name)
        <foreach collection="stuList" item="stu" separator="," open="values">
            (#{stu.stuName})
        </foreach>
    </insert>
 /**
     * 测试批量删除
     */

    @Test
    public void test03(){
        StudentDemoMapper studentDemoMapper = sqlSession.getMapper(StudentDemoMapper.class);
        List<StudentDemo> stuList = new ArrayList<StudentDemo>();

        for (int i = 1; i <11 ; i++) {
            StudentDemo studentDemo = new StudentDemo(null, "jack" + i);
            stuList.add(studentDemo);
        }
        studentDemoMapper.batchInsert(stuList);
    }

2、批量更新

上面批量插入的例子本质上是一条SQL语句,而实现批量更新则需要多条SQL语句拼起来,用分号分开。也就是一次性发送多条SQL语句让数据库执行。此时需要在数据库连接信息的URL地址中设置:

    atguigu.dev.url=jdbc:mysql://192.168.198.100:3306/mybatis0922?allowMultiQueries=true
/**
     * 批量更新
     */
    void batchUpdate(@Param("stuList") List<StudentDemo> stuList);

对应的foreach标签如下:


 <!-- void batchUpdate(@Param("stuList") List<StudentDemo> stuList);-->
    <update id="batchUpdate">
        <foreach collection="stuList" item="stu" separator=";">
            update t_studentdemo set stu_name = #{stu.stuName} where stu_id = #{stu.stuId}
        </foreach>
    </update>
/**
     * 测试批量更新
     */
    @Test
    public void test04(){
        StudentDemoMapper studentDemoMapper = sqlSession.getMapper(StudentDemoMapper.class);
        List<StudentDemo> stuList = new ArrayList<StudentDemo>();

        for (int i = 1; i <11 ; i++) {
            StudentDemo studentDemo = new StudentDemo(i, "jack" + (i+1));
            stuList.add(studentDemo);
        }
        studentDemoMapper.batchUpdate(stuList);
    }

3、关于foreach标签的collection属性

如果没有给接口中List类型的参数使用@Param注解指定一个具体的名字,那么在collection属性中默认可以使用collection或list来引用这个list集合。这一点可以通过异常信息看出来:


Parameter 'empList' not found. Available parameters are [collection, list]

在实际开发中,为了避免隐晦的表达造成一定的误会,建议使用@Param注解明确声明变量的名称,然后在foreach标签的collection属性中按照@Param注解指定的名称来引用传入的参数。

第七节 sql标签

1、抽取重复的SQL片段

<!-- 使用sql标签抽取重复出现的SQL片段 -->
    <sql id="mySelectSql">
        select emp_id,emp_name,emp_age,emp_salary,emp_gender from t_emp
    </sql>

2、引用已抽取的SQL片段

<!-- 使用include标签引用声明的SQL片段 -->
        <include refid="mySelectSql"/>

posted @ 2021-07-01 18:12  沙滩拾贝  阅读(74)  评论(0编辑  收藏  举报