SSM框架学习(三、MyBatis实践:提高持久层数据处理效率)

news2024/9/22 12:20:43

目录

一、Mybatis简介

1.简介

2.持久层框架对比

3.快速入门(基于Mybatis3方式)

4.ibatis方式的实现和原理

5.ibatis与mybatis之间的关系

二、Mybatis基本使用 

1.向 sql 语句传参

(1)mybatis日志输出配置

(2)#{ key } 和 ${ key } 

 2.数据输入

(1)单个简单类型参数

(2)实体类类型参数 

(3)零散的简单类型数据

(4)Map类型参数

 3.数据输出

(1)单个简单类型

(2)返回实体类对象

(3)返回 Map 类型

(4)返回 List 类型

(5)返回主键值

Ⅰ. 自增长类型主键

Ⅱ. 非自增长类型主键 

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

三、Mybatis 多表映射

1.概念

(1)实体类设计方案

 (2)多表映射案例准备

2.对一映射

3.对多映射

4.多表映射优化

四、MyBatis动态语句

1. if 和 where标签 

2. set 标签

3. trim标签(了解)

4. choose/when/otherwise 标签

5. foreach标签

6. sql 片段提取

五、Mybatis 高级扩展

1. Mapper 批量映射优化

2. 插件和分页插件 PageHelper

(1)插件机制

(2)PageHelper 插件使用

 3. 逆向工程和MybatisX插件

(1)ORM思维介绍

(2)逆向工程

 (3)逆向工程插件 MyBatisX 使用


一、Mybatis简介

1.简介

MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作。

MyBatis 可以通过简单的 XML 或注解来配置和映射原始类型、接口和 Java POJO(Plain Old Java Objects,普通老式 Java 对象)为数据库中的记录。

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

2.持久层框架对比

JDBC:

        SQL 夹杂在Java代码中耦合度高,导致硬编码内伤

        维护不易且实际开发需求中 SQL 有变化,频繁修改的情况多见

        代码冗长,开发效率低

Hibernate 和 JPA:

        操作简便,开发效率高

        程序中的长难复杂 SQL 需要绕过框架

        内部自动生成的 SQL,不容易做特殊优化

        基于全映射的全自动框架,大量字段的 POJO 进行部分映射时比较困难。

        反射操作太多,导致数据库性能下降

MyBatis:

        轻量级,性能出色

        SQL 和 Java 编码分开,功能边界清晰。Java代码专注业务、SQL语句专注数据

        开发效率稍逊于 Hibernate,但是完全能够接收

开发效率:Hibernate>Mybatis>JDBC

运行效率:JDBC>Mybatis>Hibernate

3.快速入门(基于Mybatis3方式)

a.准备数据模型

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);
INSERT INTO `t_emp`(emp_name,emp_salary) VALUES("jerry",666.66);
INSERT INTO `t_emp`(emp_name,emp_salary) VALUES("andy",777.77);

b.项目搭建和准备

① 项目搭建

② 依赖导入

<dependencies>
  <!-- mybatis依赖 -->
  <dependency>
      <groupId>org.mybatis</groupId>
      <artifactId>mybatis</artifactId>
      <version>3.5.11</version>
  </dependency>

  <!-- MySQL驱动 mybatis底层依赖jdbc驱动实现,本次不需要导入连接池,mybatis自带! -->
  <dependency>
      <groupId>mysql</groupId>
      <artifactId>mysql-connector-java</artifactId>
      <version>8.0.25</version>
  </dependency>

  <!--junit5测试-->
  <dependency>
      <groupId>org.junit.jupiter</groupId>
      <artifactId>junit-jupiter-api</artifactId>
      <version>5.3.1</version>
  </dependency>
</dependencies>

③ 实体类准备

public class Employee {

    private Integer empId;

    private String empName;

    private Double empSalary;

    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;
    }
}

 c.准备Mapper接口和MapperXML文件

MyBatis 框架下,SQL语句编写位置发生改变,从原来的Java类,改成XML或者注解定义!

推荐在XML文件中编写SQL语句,让用户能更专注于 SQL 代码,不用关注其他的JDBC代码。

Mybatis 中的 Mapper 接口相当于以前的 Dao。但是区别在于,Mapper 仅仅只是建接口即可,我们不需要提供实现类,具体的SQL写到对应的Mapper文件。

① 定义mapper接口

package com.mihoyo.mapper;
import com.mihoyo.pojo.Employee;

public interface EmployMapper {
    //根据id查询员工信息
    Employee queryById(Integer id);
}

② 定义mapper.xml 

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-mapper.dtd">

<!-- namespace = mapper 对应接口类的全限定名 -->
<mapper namespace="com.mihoyo.mapper.EmployeeMapper">

    <!-- 查询使用 select标签
         每个标签对应一个方法,是方法的实现
     -->
    <select id="queryById" resultType="com.mihoyo.pojo.Employee">
        <!-- #{id}代表动态传入的参数,并且进行赋值! -->
        select emp_id empId,emp_name empName, emp_salary empSalary from
        t_emp where emp_id = #{id}
    </select>
</mapper>

注意: 

① 四个一致:

        方法名和 SQL 的 id 一致

        方法返回值和 resultType 一致

        方法的参数和 SQL 的参数一致

        接口的全类名和映射配置文件的名称空间一致

mapper 接口不能方法重载!因为虽然 java 语法不会出现问题,但 mapper.xml 无法识别,它是根据方法名进行识别的。

③ mapper.xml 一般写在 resource 目录下,这样编译后 maven 会打包到类路径(target/classes)中。

d.准备MyBatis配置文件

mybatis框架配置文件: 数据库连接信息,性能配置,mapper.xml配置等。

习惯上命名为 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.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis-example"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>

    <mappers>
        <!-- Mapper注册:指定Mybatis映射文件的具体位置 -->
        <!-- mapper标签:配置一个具体的Mapper映射文件 -->
        <!-- resource属性:指定Mapper映射文件的实际存储位置,这里需要使用一个以类路径根目录为基准的相对路径 -->
        <!--    对Maven工程的目录结构来说,resources目录下的内容会直接放入类路径,所以这里我们可以以resources目录为基准 -->
        <mapper resource="mappers/EmployeeMapper.xml"/>
    </mappers>

</configuration>

e.运行和测试

public class MybatisTest {
    //使用 mybatis 提供的api进行方法的调用
    @Test
    public void test_01() throws IOException {
        //1.读取外部配置文件(mybatis-config.xml)
        InputStream ips = Resources.getResourceAsStream("mybatis-config.xml");
        //2.创建sqlSessionFactory
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(ips);
        //3.根据创建sqlSessionFactory创建sqlSession(每次业务创建一个,用完就释放)
        SqlSession sqlSession = sqlSessionFactory.openSession();
        //4.获取接口的代理对象(通过代理技术),调用代理对象的方法,就会查找mapper接口的方法
        EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
        Employee employee = mapper.queryById(1);
        System.out.println("employee = "+ employee);
        //5.提交事务(非查询语句)和释放资源
        //sqlSession.commit();
        sqlSession.close();
    }
}

说明:

SqlSession:代表Java 程序和数据库之间的会话。(HttpSession是Java程序和浏览器之间的会话)

SqlSessionFactory:是 “生产” SqlSession 的 “工厂”。

工厂模式:如果创建某一个对象,使用的过程基本固定,那么我们就可以把创建这个对象的相关代码封装到一个 “工厂类” 中,以后都使用这个工厂类来 “生产” 我们需要的对象。


SqlSession 和 HttpSession 区别:

        HttpSession:工作在Web服务器上,属于表述层。

                                代表浏览器和Web服务器之间的会话。

        SqlSession:不依赖Web服务器,属于持久化层。

                                代表Java程序和数据库之间的会话。

4.ibatis方式的实现和原理

a.准备数据模型

use `mybatis-example`;

create table student(
    sid int primary key  auto_increment,
    sname varchar(20)
);

INSERT INTO `student` (sid, sname) VALUES (1, "tom");
INSERT INTO `student` (sid, sname) VALUES (2, "jerry");

 b.准备实体类

public class Student {
    private Integer sid;
    private String sname;

    public Student() {
    }

    public Student(Integer sid, String sname) {
        this.sid = sid;
        this.sname = sname;
    }
    public Integer getSid() {
        return sid;
    }
    public void setSid(Integer sid) {
        this.sid = sid;
    }
    public String getSname() {
        return sname;
    }
    public void setSname(String sname) {
        this.sname = sname;
    }

    public String toString() {
        return "Student{sid = " + sid + ", sname = " + sname + "}";
    }
}

 c.定义mapper.xml (不用定义mapper接口)

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-mapper.dtd">

<!-- ibatis方式进行数据库操作:
        1.不用写接口
        2.直接创建mapper.xml文件,内部编写sql语句
        3.namespace 无要求,随意声明一个字符串即可
        4.内部通过增删改查标签声明sql语句
-->
<mapper namespace="xx.yy">

    <!--  id 也无任何要求,随意声明即可-->
    <select id="kkk" resultType="com.mihoyo.pojo.Student">
        <!-- #{id}代表动态传入的参数,并且进行赋值! -->
        select * from student where sid = #{id}
    </select>
</mapper>

d.准备MyBatis配置文件

<?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.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis-example"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>

    <mappers>
        <!-- Mapper注册:指定Mybatis映射文件的具体位置 -->
        <!-- mapper标签:配置一个具体的Mapper映射文件 -->
        <!-- resource属性:指定Mapper映射文件的实际存储位置,这里需要使用一个以类路径根目录为基准的相对路径 -->
        <!--    对Maven工程的目录结构来说,resources目录下的内容会直接放入类路径,所以这里我们可以以resources目录为基准 -->
        <mapper resource="mappers/StudentMapper.xml"/>
    </mappers>

</configuration>

e.运行和测试

    @Test
    public void test_02() throws IOException {
        //1.读取外部配置文件(mybatis-config.xml)
        InputStream ips = Resources.getResourceAsStream("mybatis-config.xml");
        //2.创建sqlSessionFactory
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(ips);
        //3.根据创建sqlSessionFactory创建sqlSession(每次业务创建一个,用完就释放)
        SqlSession sqlSession = sqlSessionFactory.openSession();
        //4.使用sqlSession提供的增删改查方法进行数据库操作
        //参数1:sql标签对应的标识(id 或者 namespace.id)
        //参数2:执行sql语句传入的参数
        Student stu = sqlSession.selectOne("xx.yy.kkk", 1);
        System.out.println("Student = " + stu);
        //5.提交事务(非查询语句)和释放资源
        //sqlSession.commit();
        sqlSession.close();
    }

 注意:

ibatis 方式的缺点:

        a. sql 语句标签对应的字符串标识太过随意,容易出现错误

        b. 执行 sql 语句传入的参数只能传一个,多个参数需要整合成一个 Map 集合中

        c. 返回值类型需要自己指定,不会提示,默认是 Object 类型

② sqlSession 提供了增删改查方法,但该方法并不是帮我们生成 sql 语句,而是帮我们查找对应的 sql 语句标签,再交给 mybatis 执行。

5.ibatis与mybatis之间的关系

Mybatis 会先使用 jdk 动态代理技术,生成一个代理对象。

① 代理对象 根据 接口的全限定符 和 方法名,内部拼接成 "接口的全限定符.方法名",再去调用 ibatis 对应的方法,去查找对应的 sql 语句标签,进行执行。

所以 mapper.xml 会严格限定 namespace 和 id,因为拼接后要根据它们去查找对应的 sql 标签。

这样就确保了 查找 sql 语句标签的过程不会出现错误!

② 代理对象也会将传入的参数进行整合,然后给 ibatis 对应的方法传入整合后的参数。

这样也优化了参数传递!

所以:mybatis 底层依然调用 ibatis,只不过底层进行了封装,有一套固定的模式!

二、Mybatis基本使用 

1.向 sql 语句传参

(1)mybatis日志输出配置

mybatis配置文件设计标签和顶层结构如下:

我们可以在 mybatis 的配置文件使用 settings 标签设置,输出运过程SQL日志!

通过查看日志,我们可以判定#{} 和 ${}的输出效果!

settings 设置项:

logImpl

指定 MyBatis 所用日志的具体实现,未指定时将自动查找。

SLF4J

LOG4J(3.5.9 起废弃)

LOG4J2

JDK_LOGGING

COMMONS_LOGGING

STDOUT_LOGGING

NO_LOGGING

未设置

日志配置: 

<?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>

    <settings>
        <!-- 开启了 mybatis 的日志输出功能,选择使用System进行控制台输出 -->
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>

    ...
</configuration>

(2)#{ key } 和 ${ key } 

#{ key }占位符 + 赋值
${ key }字符串拼接

 #{ key } 形式:

 ${ key } 形式:

细节:

① 推荐使用 #{ key } 的形式,可以防止 SQL注入

② 存在即合理!#{ key } 只能替代值的位置,对于列名,容器名,关键字,都无法替代。

     比如:select * from 表 where 动态列名 = 动态值

     动态列名只能使用 ${ columnName },动态值可以使用 #{ columnValue }

所以:动态值,使用 #{ key }

           动态列名,容器名,关键字,使用 ${ key }

 2.数据输入

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

简单类型:只包含一个值的数据类型(单值类型) 

        基本数据类型:int、byte、short、double、……

        基本数据类型的包装类型:Integer、Character、Double、……

        字符串类型:String

复杂类型:包含多个值的数据类型(多值类型)

        实体类类型:Employee、Department、……

        集合类型:List、Set、Map、……

        数组类型:int[]、String[]、……

        复合类型:List<Employee>、实体类中包含集合……

(1)单个简单类型参数

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

    //根据id删除员工信息
    int deleteById(Integer id);

    //根据工资查询员工信息
    List<Employee> queryBySalary(Double salary);

 SQL语句:

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

    <select id="queryBySalary" resultType="com.mihoyo.pojo.Employee">
        select emp_id empId,emp_name empName,emp_salary empSalary
        from t_emp where emp_salary= #{salary}
    </select>

单个简单类型参数,在#{} 中可以随意命名,但是没有必要。通常还是使用和接口方法参数同名

(2)实体类类型参数 

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

    //插入员工数据(实体对象)
    int insertEmp(Employee employee);

SQL语句:

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

当传入的是一个实体对象,key = 属性名

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

(3)零散的简单类型数据

方案一:使用 mybatis 默认机制,形参从左到右依次对应:arg0 / param1, arg1 / param2 ...

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

    //根据员工姓名和工资查询员工信息
    List<Employee> queryByNameAndSalary(String name,Double salary);

 SQL语句:

    <select id="queryByNameAndSalary" resultType="com.mihoyo.pojo.Employee">
        select emp_id empId,emp_name empName,emp_salary empSalary
        from t_emp where emp_name = #{arg0} and emp_salary = ${arg1}
    </select>

或者

    <select id="queryByNameAndSalary" resultType="com.mihoyo.pojo.Employee">
        select emp_id empId,emp_name empName,emp_salary empSalary
        from t_emp where emp_name = #{param1} and emp_salary = ${param2}
    </select>

方案二:使用 @Param 注解指定(推荐)

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

    //根据员工姓名和工资查询员工信息
    List<Employee> queryByNameAndSalary(@Param("eName") String name, @Param("eSalary") Double salary);

 SQL语句:

    <select id="queryByNameAndSalary" resultType="com.mihoyo.pojo.Employee">
        select emp_id empId,emp_name empName,emp_salary empSalary
        from t_emp where emp_name = #{eName} and emp_salary = ${eSalary}
    </select>

(4)Map类型参数

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

    //插入员工数据,传入一个map(name=员工名,salary=员工薪水)
    int insertEmpMap(Map data);

SQL语句:

    <insert id="insertEmpMap">
        insert into t_emp(emp_name,emp_salary) values(#{name},#{salary})
    </insert>

当传入的是 map 类型的参数时,key = map 中的 key

测试:

    @Test
    public void test_01() throws IOException {
        //1.读取外部配置文件
        InputStream ips = Resources.getResourceAsStream("mybatis-config.xml");
        //2.创建sqlSessionFactory
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(ips);
        //3.获取sqlSession
        SqlSession sqlSession = sqlSessionFactory.openSession();
        //4.获取代理mapper对象
        EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
        Map<String, Object> paramMap = new HashMap<>();
        //存入键值对
        paramMap.put("name", "zhangsan");
        paramMap.put("salary", 123.45);
        int result = mapper.insertEmpMap(paramMap);
        System.out.println("result = " + result);
        //5.提交事务或释放资源
        sqlSession.close();
    }

 3.数据输出

数据输出总体上有两种形式:

        增删改操作:默认返回的是受影响行数,直接使用 int 或 long 类型接收即可

        查询操作:查询结果的返回值类型可能不确定

我们需要做的是,指定查询的输出数据类型,并且插入场景下,实现主键数据回显示。

(1)单个简单类型

Mapper 接口中的抽象方法:

    //dml语句(插入,修改,删除)-->返回受影响的行数
    int deleteById(Integer id);

    //根据员工的id查询员工姓名
    String queryNameById(Integer id);

    //根据员工的id查询员工工资
    Double querySalaryById(Integer id);

SQL语句:

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

    <select id="queryNameById" resultType="java.lang.String">
        select emp_name from t_emp where emp_id = #{id}
    </select>

    <select id="querySalaryById" resultType="double">
        select emp_salary from t_emp where emp_id = #{id}
    </select>

select 标签,通过 resultType 指定查询返回值类型。

resultType 的取值有两种:类的全限定符类的别名

mybatis 给我们常用的 Java 数据类型,都提供了别名,如下:

映射的类型别名
byte_byte
char_char (since 3.5.10)
char_character (since 3.5.10)
long_long
short_short
int_int
int_integer
double_double
float_float
boolean_boolean
Stringstring
Bytebyte
Characterchar (since 3.5.10)
Charactercharacter (since 3.5.10)
Longlong
Shortshort
Integerint
Integerinteger
Doubledouble
Floatfloat
Booleanboolean
Datedate
BigDecimaldecimal
BigDecimalbigdecimal
BigIntegerbiginteger
Objectobject
Date[]date[]
BigDecimal[]decimal[]
BigDecimal[]bigdecimal[]
BigInteger[]biginteger[]
Object[]object[]
Mapmap
HashMaphashmap
Listlist
ArrayListarraylist
Collectioncollection
Iteratoriterator

不用死记,规则如下:

        基本数据类型:int   double  ->  _int   _double

        包装数据类型:Integer   Double  ->  int / integer  double

        集合容器类型:Map   List   HashMap  ->  map   list   hashmap(小写即可)


Test:如果返回的类型,java 没有提供相应的别名,怎么办?

可以自定义别名,或 使用类的全限定符。

扩展(给自定义类定义别名):

① 给单独的类定义别名:

<?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>

    <!-- 定义别名 -->
    <typeAliases>
        <typeAlias type="com.mihoyo.pojo.Employee" alias="employee"/>
    </typeAliases>

    ...
</configuration>

② 批量定义别名

<?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>
    
    <!-- 批量定义别名 -->
    <typeAliases>
        <package name="com.mihoyo.pojo"/>
    </typeAliases>

    ...
</configuration>

批量将包下的类给于别名,别名就是首字母小写的类名(如 Employee --> employee)

批量定义别名后(前提条件),不想使用批量的别名,可以使用 @Alias 注解重新定义别名

@Alias("emp")
public class Employee {
    ...
}

(2)返回实体类对象

Mapper 接口的抽象方法:

    //返回单个自定义实体类型
    Employee queryById(Integer id);

SQL语句:

    <select id="queryById" resultType="com.mihoyo.pojo.Employee">
        <!-- 给每一个字段设置一个别名,让别名和Java实体类中属性名一致 -->
        select emp_id empId,emp_name empName,emp_salary empSalary
        from t_emp where emp_id = ${empId}
    </select>

返回实体类对象时,resultType = 实体类型即可

注意:

当返回实体类对象时,存在一个默认要求:列名和属性名要一 一映射,因为底层会根据映射对应的属性名存入对象。

所以,sql 语句中需要给每个数据库表字段起别名

但这种方式过于麻烦,我们可以增加全局配置自动识别对应关系:

<!-- 在全局范围内对Mybatis进行配置 -->
<settings>

  <!-- 将mapUnderscoreToCamelCase属性配置为true,表示开启自动映射驼峰式命名规则
          规则要求数据库表字段命名方式:单词_单词
          规则要求Java实体类属性名命名方式:首字母小写的驼峰式命名
  -->
  <setting name="mapUnderscoreToCamelCase" value="true"/>

</settings>

在 Mybatis 全局配置文件中,进行如上配置,select语句中可以不给字段设置别名。

可以自动将字段名(XXX_YYY)变为属性名(xxxYyy)。

(3)返回 Map 类型

SQL查询返回的各个字段综合起来并不和任何一个现有的实体类对应,没法封装到实体类对象中,就可以用map集合进行存储,key = 查询的列名,value = 查询到的值

Mapper 接口的抽象方法:

    //查询部门的最高工资和平均工资
    Map<String,Object> selectEmpNameAndMaxSalary();

SQL语句:

    <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>

测试:

    @Test
    public void test_01() throws IOException {
        //1.读取外部配置文件
        InputStream ips = Resources.getResourceAsStream("mybatis-config.xml");
        //2.创建sqlSessionFactory
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(ips);
        //3.获取sqlSession
        SqlSession sqlSession = sqlSessionFactory.openSession();
        //4.获取代理mapper对象
        EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
        Map<String, Object> resultMap = mapper.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);
        }
        //5.提交事务或释放资源
        sqlSession.close();
    }

(4)返回 List 类型

Mapper 接口中抽象方法:

    //查询工资高于传入值的员工姓名
    List<String> queryNameBySalary(Double salary);

    //查询所有员工信息
    List<Employee> queryAll();

SQL语句:

    <select id="queryNameBySalary" resultType="string">
        select emp_name from t_emp where emp_salary > #{ salary }
    </select>

    <select id="queryAll" resultType="employee">
        select * from t_emp
    </select>

返回的是 List 集合类型,此时不需要任何特殊处理,在 resultType 属性中指定泛型类型即可。

原理:

Mybatis 底层调用的是 ibatis,查询共有两个方法:selectOne 和 selectList,而 SelectOne 底层也是调用 selectList,所以底层本质全是按照 List 集合进行查找的

(5)返回主键值

Ⅰ. 自增长类型主键

Mapper 接口中的抽象方法:

    //员工插入
    int insertEmp(Employee employee);

SQL语句:

    <!-- 自增主键回显:
            useGeneratedKeys="true" 使用数据库自增的主键
            keyColumn="emp_id"      主键的列名
            keyProperty="empId"     接收主键值的属性名
    -->
    <insert id="insertEmp" useGeneratedKeys="true" keyColumn="emp_id" keyProperty="empId">
        insert into t_emp(emp_name,emp_salary) value(#{empName},#{empSalary})
    </insert>

测试:

    @Test
    public void test_02() throws IOException {
        //1.读取外部配置文件
        InputStream ips = Resources.getResourceAsStream("mybatis-config.xml");
        //2.创建sqlSessionFactory
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(ips);
        //3.获取sqlSession
        SqlSession sqlSession = sqlSessionFactory.openSession();
        //4.获取代理mapper对象
        EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
        //准备实体对象
        Employee employee = new Employee();
        employee.setEmpName("zhangsan");
        employee.setEmpSalary(999.0);
        
        System.out.println("插入前,empId = " + employee.getEmpId());
        //插入
        mapper.insertEmp(employee);
        System.out.println("插入后,empId = " + employee.getEmpId());
        //5.提交事务或释放资源
        sqlSession.close();
    }

运行结果:

Ⅱ. 非自增长类型主键 

a.准备数据库

create table teacher(
    t_id varchar(64) primary key ,
    t_name varchar(20)
)

b. 准备实体类

public class Teacher {
    private String tId;
    private String tName;

    public Teacher() {
    }

    public Teacher(String tId, String tName) {
        this.tId = tId;
        this.tName = tName;
    }

    public String getTId() {
        return tId;
    }

    public void setTId(String tId) {
        this.tId = tId;
    }

    public String getTName() {
        return tName;
    }
    
    public void setTName(String tName) {
        this.tName = tName;
    }

    public String toString() {
        return "Teacher{tId = " + tId + ", tName = " + tName + "}";
    }
}

Mapper 接口中的抽象方法:

public interface TeacherMapper {
    //插入老师信息
    int insertTeacher(Teacher teacher);
}

SQL语句:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.mihoyo.mapper.TeacherMapper">
    
    <insert id="insertTeacher">
        insert into teacher(t_id, t_name) values (#{tId},#{tName})
    </insert>
    
</mapper>

测试:

    @Test
    public void test_03() throws IOException {
        //1.读取外部配置文件
        InputStream ips = Resources.getResourceAsStream("mybatis-config.xml");
        //2.创建sqlSessionFactory
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(ips);
        //3.获取sqlSession
        SqlSession sqlSession = sqlSessionFactory.openSession();
        //4.获取代理mapper对象
        TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class);
        //准备实体对象
        Teacher teacher = new Teacher();
        teacher.setTName("zhangsan");
        //用UUID随机生产主键id
        String id= UUID.randomUUID().toString().replace("-","");
        teacher.setTId(id);
        //插入
        mapper.insertTeacher(teacher);
        //5.提交事务或释放资源
        sqlSession.close();
    }

上述数据库的主键是一个字符串类型的数据,每次插入新数据都需要书写一段代码来生成主键值,过于麻烦(需要自己维护主键)。

改进方案:Mybatis 可以帮助我们对主键进行维护,无需自己生成主键值。

    <insert id="insertTeacher">
        <!-- 插入前,先指定一段sql语句,生成一个主键值
                order="BEFORE | AFTER" 表示该sql语句是在插入之前还是插入之后执行
                resultType              返回值类型
                keyProperty="tId"       查询结果给哪个属性赋值
        -->
        <selectKey order="BEFORE" resultType="string" keyProperty="tId">
            select replace(uuid(),'-','')
        </selectKey>

        insert into teacher(t_id, t_name) values (#{tId},#{tName})
    </insert>

此时,测试代码中无需再自行维护主键:

    @Test
    public void test_03() throws IOException {
        //1.读取外部配置文件
        InputStream ips = Resources.getResourceAsStream("mybatis-config.xml");
        //2.创建sqlSessionFactory
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(ips);
        //3.获取sqlSession
        SqlSession sqlSession = sqlSessionFactory.openSession();
        //4.获取代理mapper对象
        TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class);
        //准备实体对象
        Teacher teacher = new Teacher();
        teacher.setTName("zhangsan");

        System.out.println("插入前,tId = " + teacher.getTId());
        //插入
        mapper.insertTeacher(teacher);
        System.out.println("插入后,tId = " + teacher.getTId());
        //5.提交事务或释放资源
        sqlSession.close();
    }

 运行结果:

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

方案一:别名对应

    <select id="queryById" resultType="com.mihoyo.pojo.Employee">
        select emp_id empId,emp_name empName,emp_salary empSalary
        from t_emp where emp_id = ${empId}
    </select>

方案二:全局配置自动识别驼峰式命名规则

<!-- 使用settings对Mybatis全局进行设置 -->
<settings>

  <!-- 将xxx_xxx这样的列名自动映射到xxXxx这样驼峰式命名的属性名 -->
  <setting name="mapUnderscoreToCamelCase" value="true"/>

</settings>

SQL语句中可以不使用别名:

    <select id="queryById" resultType="com.mihoyo.pojo.Employee">
        select emp_id,emp_name,emp_salary
        from t_emp where emp_id = ${empId}
    </select>

还可以直接使用:select * 

方案三:使用 resultMap 自定义映射

    <!-- 声明resultMap标签,自定义映射规则
            id标识:<select resultMap="标识"
            type: 返回值类型
    -->
    <resultMap id="eMap" type="employee">
        <!--
            id:主键映射关系
            result:普通字段的映射关系
            column属性用于指定字段名;property属性用于指定Java实体类属性名
        -->
        <id column="emp_id" property="empId"/>
        <result column="emp_name" property="empName"/>
        <result column="emp_salary" property="empSalary"/>
    </resultMap>

    <select id="queryById" resultMap="eMap">
        select emp_id,emp_name,emp_salary from t_emp where emp_id = ${empId}
        <!-- 或 select * from t_emp where emp_id = ${empId} -->
    </select>

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

三、Mybatis 多表映射

1.概念

开发中更多的是多表查询需求,这种情况我们如何让进行处理?

我们的学习目标:

        ① 多表查询语句使用

        ② 多表结果承接实体类设计

        ③ 使用ResultMap完成多表结果映射

(1)实体类设计方案

数据库中,多表关系是双向查看的关系:一对一,一对多,多对多

而在 Java 实体类设计时,多表关系是单向查看的关系:

对一:一个订单对应一个客户

//客户实体
public class Customer {

  private Integer customerId;
  private String customerName;

}

//订单实体
public class Order {

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

}  

实体类设计:对一关系下,类中只要包含单个对方对象类型属性即可。

对多:一个客户对应多个订单

//订单实体
public class Order {

  private Integer orderId;
  private String orderName;
  
}

//客户实体
public class Customer {

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

实体类设计:对多关系下,类中只要包含对方类型集合属性即可!


注意:

① 只有真实发生多表查询时,才需要设计和修改实体类,否则不提前设计和修改实体类。

② 无论多少张表联查,实体类设计都是两两考虑。

③ 在查询映射的时候,只需要关注本次查询相关的属性!例如:查询订单和对应的客户,就不要关注客户中的订单集合。

 (2)多表映射案例准备

数据库:

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。


实体类设计:

@Data    //    lombok
public class Customer {

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

@Data
public class Order {
    private Integer orderId;
    private String orderName;
    private Integer customerId;//外键
    private Customer customer;// 体现的是对一的关系
}

2.对一映射

需求:根据ID查询订单,以及订单关联的用户的信息

a. OrderMapper接口

public interface OrderMapper {
    //根据订单id查询订单信息和订单所对应的客户
    Order queryOrderById(Integer id);//订单信息中包含客户
}

b. OrderMapper.xml配置文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.mihoyo.mapper.OrderMapper">
    <resultMap id="orderMap" type="order">
        <!-- 第一层映射 order对象属性 -->
        <id column="order_id" property="orderId"/>
        <result column="order_name" property="orderName"/>
        <result column="customer_id" property="customerId"/>
        <!-- 第二层映射 customer对象属性
                association :给对一的对象属性赋值
                property:对象属性名
                javaType:对象类型
        -->
        <association property="customer" javaType="customer">
            <id column="customer_id" property="customerId"/>
            <result column="customer_name" property="customerName"/>
        </association>
    </resultMap>

    <select id="queryOrderById" resultMap="orderMap">
        select * from t_order tor join t_customer tcr
        on tor.customer_id = tcr.customer_id
        where tor.order_id = #{id}
    </select>

</mapper>

对应关系可以参考下图:

细节:

 resultType 只支持一层映射,而 resultMap 可以支持深层映射(可以多层嵌套 association)。

c. mybatis-config.xml 配置文件

<?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>

    <settings>
        <!-- 开启了 mybatis 的日志输出功能,选择使用System进行控制台输出 -->
        <setting name="logImpl" value="STDOUT_LOGGING"/>
    </settings>

    <!-- 批量起别名 -->
    <typeAliases>
        <package name="com.mihoyo.pojo"/>
    </typeAliases>

    <!-- environments表示配置Mybatis的开发环境,可以配置多个环境(开发、测试、生产环境)。
        在众多具体环境中,使用default属性指定实际运行时使用的环境。
        default属性的取值是environment标签的id属性的值。
    -->
    <environments default="development">
        <!-- environment表示配置Mybatis的一个具体的环境 -->
        <environment id="development">
            <!-- Mybatis的内置的事务管理器(取值:JDBC | MANAGED )
                    JDBC:自动开启事务
                    MANAGED:不会自动开启事务
            -->
            <transactionManager type="JDBC"/>
            <!-- 配置数据源(取值:POOLED | UNPOOLED)
                    POOLED:mybatis会提供一个连接池,帮我们维护(性能没有第三方专门的连接池好)
                    UNPOOLED:每次都新建或者释放连接-->
            <dataSource type="POOLED">
                <!-- 建立数据库连接的具体信息 -->
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/mybatis-example"/>
                <property name="username" value="root"/>
                <property name="password" value="123456"/>
            </dataSource>
        </environment>
    </environments>

    <mappers>
        <!-- Mapper注册:指定Mybatis映射文件的具体位置 -->
        <!-- mapper标签:配置一个具体的Mapper映射文件 -->
        <!-- resource属性:指定Mapper映射文件的实际存储位置,这里需要使用一个以类路径根目录为基准的相对路径 -->
        <!--    对Maven工程的目录结构来说,resources目录下的内容会直接放入类路径,所以这里我们可以以resources目录为基准 -->
        <mapper resource="mappers/OrderMapper.xml"/>
    </mappers>

</configuration>

d. 测试

public class MybatisTest {
    private SqlSession sqlSession;

    @BeforeEach // 每次都测试方法之前,先走的初始化方法
    public void init() throws IOException {
        //1.读取外部配置文件
        InputStream ips = Resources.getResourceAsStream("mybatis-config.xml");
        //2.创建sqlSessionFactory
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(ips);
        //3.获取sqlSession
        sqlSession = sqlSessionFactory.openSession();
    }

    @AfterEach  // 每次都测试方法之后,调用的结束方法
    public void clean(){
        //5.提交事务或释放资源
        sqlSession.close();
    }

    @Test
    public void testToOne() {
        //根据id查询订单和对应的客户
        OrderMapper mapper = sqlSession.getMapper(OrderMapper.class);
        Order order = mapper.queryOrderById(1);
        System.out.println(order);
        System.out.println(order.getCustomer());
    }
}

3.对多映射

需求:查询所有客户和客户关联的订单信息

a. CustomerMapper 接口:

public interface CustomerMapper {
    //查询所有客户信息以及客户对应的订单信息
    List<Customer> queryCustomerAll();
}

b. CustomerMapper.xml 文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.mihoyo.mapper.CustomerMapper">
    <resultMap id="customerMap" type="customer">
        <id column="customer_id" property="customerId"/>
        <result column="customer_name" property="customerName"/>
        <!-- 给对多的集合对象属性赋值
                property:集合属性名
                ofType:集合的泛型类型
         -->
        <collection property="orderList" ofType="Order">
            <id column="order_id" property="orderId"/>
            <result column="order_name" property="orderName"/>
            <result column="customer_id" property="customerId"/>
        </collection>
    </resultMap>

    <select id="queryCustomerAll" resultMap="customerMap">
        select * from t_customer tcr
        join t_order tor
        on tcr.customer_id=tor.customer_id
    </select>
</mapper>

对应关系可以参考下图:

c. Mybatis 全局注册Mapper文件 

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

d. 测试:

    @Test
    public void testToMany(){
        CustomerMapper mapper = sqlSession.getMapper(CustomerMapper.class);
        List<Customer> customers = mapper.queryCustomerAll();
        System.out.println("customers = " + customers);

        for (Customer customer : customers) {
            List<Order> orderList = customer.getOrderList();
            System.out.println("orderList = " + orderList);
        }

    }

总结:

关联关系配置项关键词所在配置文件和具体位置
对一association标签 / javaType属性 / property属性Mapper配置文件中的resultMap标签内
对多collection标签 / ofType属性 / property属性Mapper配置文件中的resultMap标签内

4.多表映射优化

setting属性属性含义可选值默认值
autoMappingBehavior

指定 MyBatis 应如何自动映射列到字段或属性。

NONE 表示关闭自动映射

PARTIAL 只会自动映射没有定义嵌套结果映射的字段(对于只有一层的 result 标签,可以自动映射)

FULL 会自动映射任何复杂的结果集(无论是否多少层,都自动映射)

NONE, PARTIAL,

FULL

PARTIAL

将 autoMappingBehavior 设置为 full,进行多表 resultMap 映射的时候,可以省略符合列和属性命名映射规则的 result 标签。

注意:

只有 列名=属性名,或者开启驼峰映射(XXX_YYY -> xxxYyy)的 reult 标签才能自动映射。


修改 mybati-sconfig.xml:

    <settings>
        <!-- 将mapUnderscoreToCamelCase属性配置为true,表示开启自动映射驼峰式命名规则 -->
        <setting name="mapUnderscoreToCamelCase" value="true"/>
        <!--开启resultMap自动映射 -->
        <setting name="autoMappingBehavior" value="FULL"/>
    </settings>

此时,CustomerMapper.xml 中的自定义映射可省略为:

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
        PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
        "https://mybatis.org/dtd/mybatis-3-mapper.dtd">

<mapper namespace="com.mihoyo.mapper.CustomerMapper">
    <resultMap id="customerMap" type="customer">
        <id column="customer_id" property="customerId"/>
        <collection property="orderList" ofType="Order">
            <id column="order_id" property="orderId"/>
        </collection>
    </resultMap>

    <select id="queryCustomerAll" resultMap="customerMap">
        select * from t_customer tcr
        join t_order tor
        on tcr.customer_id=tor.customer_id
    </select>
</mapper>

四、MyBatis动态语句

经常遇到很多按照很多查询条件进行查询的情况,比如智联招聘的职位搜索等。

其中经常出现很多条件不取值的情况,在后台应该如何完成最终的SQL语句呢? 

动态 SQL 是 MyBatis 的强大特性之一!

1. if 和 where标签 

需求:根据多个条件查询员工信息,但条件可能为空

 Mapper 接口中的抽象方法:

public interface EmployeeMapper {
    //根据员工的姓名和工资查询员工信息
    List<Employee> query(@Param("name") String name, @Param("salary") Double salary);
}

SQL语句:

    <select id="query" resultType="employee">
        select * from t_emp
            <where>
                <if test="name!=null">
                    emp_name = #{name}
                </if>
                <if test="salary !=null and salary &gt; 0">
                    and emp_salary = #{salary}
                </if>
            </where>
    </select>

细节:

① if 标签可以通过 test 属性进行判断,如果为 true,就将标签内的 sql 语句进行拼接。

     test 属性中的判断语句:key  比较符号  值(且:and    或:or)

     大于和小于可以直接写符号(< >),但不推荐,因为低版本的 mybatis 可能会识别成标签的开始或者结束符号。推荐使用实体符号(大于:&gt;        小于:&lt; )

② where 标签的作用:

        a.只要 where 内部有任何一个 if 满足,自动添加 where 关键字,否则不会添加 where 关键字

        b.自动去掉条件中多余的 and 和 or 关键字


测试:

    @Test
    public void test_01() {
        //根据id查询订单和对应的客户
        EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
        List<Employee> list = mapper.query(null, 100d);
        System.out.println("list = " + list);
    }

2. set 标签

 Mapper 接口中的抽象方法:

    //根据员工id更新员工数据(要求name和salary不为空时才更新)
    int update(Employee employee);

 SQL语句:

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

细节:

set 标签的作用:

        a. 自动添加 set 关键字

        b. 自动去掉多余的逗号(,)

注意:

update 方法中,必须保证至少一个 if 满足。因为如果都不满足,不管有没有 set 关键字,语法都是错的!

3. trim标签(了解)

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

prefix属性指定要动态添加的前缀
suffix属性指定要动态添加的后缀
prefixOverrides属性指定要动态去掉的前缀,使用“|”分隔有可能的多个值
suffixOverrides属性指定要动态去掉的后缀,使用“|”分隔有可能的多个值

例如,前两个案例中的 SQL 语句可修改为:

    <select id="query" resultType="employee">
        select * from t_emp
        <trim prefix="where" prefixOverrides="and|or">
            <if test="name!=null">
                emp_name = #{name}
            </if>
            <if test="salary !=null and salary &gt; 0">
                and emp_salary = #{salary}
            </if>
        </trim>
    </select>

    <update id="update">
        update t_emp
        <trim prefix="set" suffixOverrides=",">
            <if test="empName!=null">
                emp_name = #{empName},
            </if>
            <if test="empSalary !=null">
                emp_salary = #{empSalary}
            </if>
        </trim>
        where emp_id = #{empId}
    </update>

4. choose/when/otherwise 标签

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

从上到下依次执行条件判断,遇到的第一个满足条件的分支会被采纳,被采纳分支后面的分支都将不被考虑。

如果所有的when分支都不满足,那么就执行otherwise分支。(类似于 switch-case-default)。


 Mapper 接口中的抽象方法:

    /*
        根据两个条件查询
            如果name不为空,根据name查询
            如果name为空,salary不为空,根据salary查询
            都为空,查询全部
    */
    List<Employee> queryChoose(@Param("name") String name, @Param("salary") Double salary);

 SQL语句:

    <select id="queryChoose" resultType="employee">
        select * from t_emp where
        <choose>
            <when test="name!=null">
                emp_name = #{name}
            </when>
            <when test="salary !=null">
                and emp_salary = #{salary}
            </when>
            <otherwise>1=1</otherwise>
        </choose>
    </select>

5. foreach标签

 Mapper 接口中的抽象方法:

    //根据id进行批量查询
    List<Employee> queryBatch(@Param("ids") List<Integer> ids);

    //批量插入
    int insertBatch(@Param("list") List<Employee> employeeList);

    //批量更新
    int updateBatch(@Param("list") List<Employee> employeeList);

 SQL语句:

    <select id="queryBatch" resultType="employee">
        select * from t_emp where emp_id in
        <!--
            collection属性:要遍历的集合
            item属性:遍历集合的过程中能得到每一个具体对象,在item属性中设置一个名字,将来通过这个名字引用遍历出来的对象
            separator属性:每个遍历项之间的分隔符
            open属性:遍历之前要添加的字符串
            close属性:遍历之后要追加的字符串
            index属性:这里起一个名字,便于后面引用
                遍历List集合,这里能够得到List集合的索引值
                遍历Map集合,这里能够得到Map集合的key
         -->
            <foreach collection="ids" open="(" separator="," close=")" item="id">
                #{id}
            </foreach>
    </select>

    <insert id="insertBatch">
        insert into t_emp(emp_name,emp_salary) values
            <foreach collection="list" separator="," item="employee">
                (#{employee.empName},#{employee.empSalary})
            </foreach>
    </insert>
    
    <update id="updateBatch">
        <foreach collection="list" item="employee" separator=";">
            update t_emp set emp_name = #{employee.empName},emp_salary = #{employee.empSalary}
            where emp_id = #{employee.empId}
        </foreach>
    </update>

注意:

① 上述批量查询和批量插入,本质上都是一条 sql 语句执行。

而批量更新,事实上是一次性执行多条 sql 语句,中间用分号隔开。

当一次性发送多条SQL语句让数据库执行,此时需要在数据库连接信息的URL地址中设置:

<property name="url" value="jdbc:mysql://localhost:3306/mybatis-example?allowMultiQueries=true"/>

 ② 关于 foreach 标签的 collection 属性:

如果没有给接口中 List 类型的参数使用 @Param 注解指定一个具体的名字,那么默认可以使用collection 或 list 或 arg0 来引用要遍历的集合。

但在实际开发中,为了避免隐晦的表达造成一定的误会,建议使用 @Param 注解明确声明变量的名称,然后 collection 属性使用注解指定的名称。

6. sql 片段提取

    <!-- 抽取重复的 sql 片段 -->
    <sql id="selectSql">
        select * from t_emp
    </sql>


    <select id="query" resultType="employee">
        <!--引用 sql 片段-->
        <include refid="selectSql"/>
            <where>
                <if test="name!=null">
                    emp_name = #{name}
                </if>
                <if test="salary !=null and salary &gt; 0">
                    and emp_salary = #{salary}
                </if>
            </where>
    </select>

五、Mybatis 高级扩展

1. Mapper 批量映射优化

Mapper 配置文件很多时,在全局配置文件中一个一个注册太麻烦,我们希望可以批量注册。

配置方式:

<mappers>
    <!-- 指定 Mapper 接口 和 Mapper.xml 打包后所在的包 -->
    <package name="com.mihoyo.mapper"/>
</mappers>

注意:

批量 mapper 配置文件指定,package = “Mapper 接口 和 Mapper.xml 打包后共同所在的包”

所以,必要满足 2 个要求:

        ① 要求 Mapper 接口 和 Mapper.xml 的命名必须相同

        ② 要求 Mapper 接口 和 Mapper.xml 的所在的包名必须相同(最终打包的位置相同)

                方案一:xml 文件也加入到接口所在包,并且 pom 文件中在 build 中配置 resources,来防止我们资源导出失败的问题

    <build>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.properties</include>
                    <include>**/*.xml</include>
                </includes>
                <filtering>false</filtering>
            </resource>
        </resources>
    </build>

                方案二:resource 文件夹也创建和 mapper 接口一样的文件夹结构

细节:resources 目录下创建多级目录,用 / 分割,而不是 . (即 com/mihoyo/mapper )。

2. 插件和分页插件 PageHelper

(1)插件机制

MyBatis 对插件进行了标准化的设计,并提供了一套可扩展的插件机制。

插件可以在用于语句执行过程中进行拦截,并允许通过自定义处理程序来拦截和修改 SQL 语句、映射语句的结果等。

具体来说,MyBatis 的插件机制包括以下三个组件:

Interceptor(拦截器):定义一个拦截方法 intercept,该方法在执行 SQL 语句、执行查询、查询结果的映射时会被调用。

Invocation(调用):实际上是对被拦截的方法的封装,封装了Object target、Method method 和 Object[] args 这三个字段。

InterceptorChain(拦截器链):对所有的拦截器进行管理,包括将所有的链接成一条链,并在执行 SQL 语句时按顺序调用。

(2)PageHelper 插件使用

PageHelper 是 MyBatis 中比较著名的分页插件,它提供了多种分页方式(例如 MySQL 和 Oracle 分页方式),支持多种数据库,并且使用非常简单。

a. pom.xml 引入依赖

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

b. mybatis-config.xml配置分页插件

    <!-- mybatis内部配置插件,进行sql语句拦截-->
    <plugins>
        <plugin interceptor="com.github.pagehelper.PageInterceptor">
            <!-- 分页插件对应的数据库类型 -->
            <property name="helperDialect" value="mysql"/>
        </plugin>
    </plugins>

c. mapper接口 和 mapper.xml 

public interface EmployeeMapper {
    List<Employee> queryList();
}
<mapper namespace="com.mihoyo.mapper.EmployeeMapper">

    <select id="queryList" resultType="employee">
        <!-- 正常编写sql语句即可,不要使用 ; 结尾 -->
        select * from t_emp where emp_salary > 100
    </select>
</mapper>

注意:sql 语句末尾不用加 ; 

d. 分页插件使用

    //使用分页插件
    @Test
    public void test_01() {
        EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
        //调用方法之前,先设置分页数据(当前是第几页,每页显示多少数据)
        PageHelper.startPage(2,2);
        List<Employee> list = mapper.queryList();
        //将查询数据封装到一个 PageInfo 的分页实体类中
        PageInfo<Employee> pageInfo = new PageInfo<>(list);
        //从 pageInfo 中获取分页的数据
        List<Employee> list1 = pageInfo.getList();//获取当前页的数据
        System.out.println("list1 = " + list1);

        int pages = pageInfo.getPages();//获取总页数
        System.out.println("pages = " + pages);

        long total = pageInfo.getTotal();//获取总条数
        System.out.println("total = " + total);

        int pageNum = pageInfo.getPageNum();//获取当前页数
        System.out.println("pageNum = " + pageNum);

        int pageSize = pageInfo.getPageSize();//获取每页容量
        System.out.println("pageSize = " + pageSize);
        //...
    }

注意:不能将两条查询装到一个分页区(PageInfo)。

运行结果:

 3. 逆向工程和MybatisX插件

(1)ORM思维介绍

ORM(Object-Relational Mapping,对象-关系映射)是一种将数据库和面向对象编程语言中的对象之间进行转换的技术。

它将对象和关系数据库的概念进行映射,最后我们就可以通过方法调用进行数据库操作。

最终,让我们可以使用面向对象思维进行数据库操作

ORM 框架的两种方式:

半自动 ORM :通常需要程序员手动编写 SQL 语句或者配置文件,将实体类和数据表进行映射,还需要手动将查询的结果集转换成实体对象。

全自动 ORM :将实体类和数据表进行自动映射,使用 API 进行数据库操作时,ORM 框架会自动执行 SQL 语句并将查询结果转换成实体对象,程序员无需再手动编写 SQL 语句和转换代码。

(2)逆向工程

MyBatis 的逆向工程是一种自动化生成持久层代码和映射文件的工具,它可以根据数据库表结构和设置的参数生成对应的实体类、Mapper.xml 文件、Mapper 接口等代码文件,简化了开发者手动生成的过程。逆向工程使开发者可以快速地构建起 DAO 层,并快速上手进行业务开发。

简单来说:逆向工程使得半自动 orm 框架,也能实现单表的 crud 自动生成,向全自动 orm 迈进!

注意:逆向工程只能生成单表 crud 的操作,多表查询依然需要我们自己编写! 

 (3)逆向工程插件 MyBatisX 使用

MyBatisX 是一个 MyBatis 的代码生成插件,可以通过简单的配置和操作快速生成 MyBatis Mapper、pojo 类和 Mapper.xml 文件。

步骤:

① 安装插件

② 使用 IntelliJ IDEA连接数据库

a. 连接数据库

 

 b. 填写信息

c. 展示库表

 

d. 逆向工程使用

 

③ 查看生成结果

 

注意:逆向工程插件 MybatisX 只能生成单表 crud 的操作,多表查询依然需要我们自己编写!

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2139062.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

小程序开发设计-第一个小程序:创建小程序项目④

上一篇文章导航&#xff1a; 小程序开发设计-第一个小程序&#xff1a;安装开发者工具③-CSDN博客https://blog.csdn.net/qq_60872637/article/details/142219152?spm1001.2014.3001.5501 须知&#xff1a;注&#xff1a;不同版本选项有所不同&#xff0c;并无大碍。 一、创…

主播和礼品检测系统源码分享

主播和礼品检测检测系统源码分享 [一条龙教学YOLOV8标注好的数据集一键训练_70全套改进创新点发刊_Web前端展示] 1.研究背景与意义 项目参考AAAI Association for the Advancement of Artificial Intelligence 项目来源AACV Association for the Advancement of Computer V…

SSH软链接后门从入门到应急响应

目录 1. 软链接与SSH软链接后门介绍 1.1 软链接介绍 1.2 SSH软连接后门介绍 2. 如何在已拿下控制权限的主机创建后门 2.1 使用root账户并执行ssh软链接后门命令&#xff1a; 2.2 连接软链接后门测试&#xff1a; 2.3 创建其它账户 ssh软连接后门命令 3. 如何进行应急…

笋丁网页自动回复机器人V3.0.0免授权版源码

笋丁网页机器人一款可设置自动回复&#xff0c;默认消息&#xff0c;调用自定义api接口的网页机器人。 此程序后端语言使用Golang&#xff0c;内存占用最高不超过30MB&#xff0c;1H1G服务器流畅运行。仅支持Linux服务器部署&#xff0c;不支持虚拟主机&#xff0c;请悉知&…

七. 部署YOLOv8检测器-quantization-analysis

目录 前言0. 简述1. 案例运行2. 补充说明3. 量化分析4. 探讨总结下载链接参考 前言 自动驾驶之心推出的 《CUDA与TensorRT部署实战课程》&#xff0c;链接。记录下个人学习笔记&#xff0c;仅供自己参考 本次课程我们来学习课程第七章—部署YOLOv8检测器&#xff0c;一起来学习…

无限边界:现代整合安全如何保护云

尽管云计算和远程工作得到广泛采用&#xff0c;零信任网络也稳步推广&#xff0c;但边界远未消失。相反&#xff0c;它已被重新定义。就像数学分形的边界一样&#xff0c;现代网络边界现在无限延伸到任何地方。 不幸的是&#xff0c;传统工具在现代无限边界中效果不佳。现代边…

优化算法(三)—模拟退火算法(附MATLAB程序)

模拟退火算法&#xff08;Simulated Annealing, SA&#xff09;是一种基于概率的优化算法&#xff0c;旨在寻找全局最优解。该算法模拟金属退火过程中的物质冷却过程&#xff0c;逐渐降低系统的“温度”以达到全局优化的效果。它特别适用于解决复杂的组合优化问题。 一、模拟退…

深度学习笔记(6)文本分类

深度学习笔记&#xff08;6&#xff09;文本分类 文章目录 深度学习笔记&#xff08;6&#xff09;文本分类一、文本分析与关键词提取1.关键概念1.停用词2 Tf-idf&#xff1a;关键词提取 3.相似度 二、文本分析案例1.数据处理2.分词&#xff1a;实用结巴分词器3.清洗4.TF-IDF5.…

FastText 和 Faiss 的初探了解

概览 大模型目前已经是如火如荼的程度&#xff0c;各个大厂都有推出面向大众的基础大模型&#xff0c;同时诸多行业也有在训练专有大模型&#xff0c;而大模型的发展由来却是经过多年从文本检索生成、深度学习、自然语言处理&#xff0c;在Transformer架构出来后&#xff0c;才…

win11下面graphviz的用法

安装 安装graphviz 2.38版本 控制面板在变量path中增加E:\software\Graphviz\bin example.dot代码 digraph SignalPathway {node [fontname"SimHei"];edge [fontname"SimHei"];// 定义节点形状node [shapecircle];// 定义节点CellA [label"细胞 A&…

第 13 章 兵马未动,粮草先行——InnoDB 统计数据是如何收集的

表的统计数据&#xff1a;SHOW TABLE STATUS LIKE table_name; 索引的统计数据&#xff1a;SHOW INDEX FROM table_name; 13.1 两种不同的统计数据存储方式 InnoDB 提供了两种存储统计数据的方式&#xff1a; 永久性的统计数据。存储在磁盘上&#xff0c;服务器重启之后还在…

nvm安装并配置全局缓存文件

nvm下载&#xff0c;最新版为 1.1.12&#xff1a;Releases coreybutler/nvm-windows GitHub 下载exe&#xff0c;选择指定位置安装即可&#xff0c;安装及配置参考链接&#xff1a;window下安装并使用nvm&#xff08;含卸载node、卸载nvm、全局安装npm&#xff09;-CSDN博客 …

SpringBoot教程(安装篇) | RabbitMQ的安装

SpringBoot教程&#xff08;安装篇&#xff09; | RabbitMQ的安装 一、下载RabbitMQ&#xff08;windows版本&#xff09;1. 先下载 RabbitMQ2. 再下载Erlang3. 开始安装 Erlang4. 为Erlang配置环境变量5、验证安装6. 开始安装 RabbitMQ7. 启用RabbitMQ的管理插件&#xff08;图…

学习整理vue前端框架项目目录结构的含义

学习整理vue前端框架项目目录结构的含义 1、目录结构2、结构含义 1、目录结构 2、结构含义

C++STL~~deque

文章目录 deque的概念deque的使用deque的练习总结 deque的概念 deque(双端队列)&#xff1a;是一种序列容器、是一种双开口的"连续"空间的数据结构&#xff0c;双开口的含义是&#xff1a;可以在头尾两端进行插入和删除操作&#xff0c;且时间复杂度为O(1)&#xff…

F12抓包12:Performance(性能)前端性能分析

课程大纲 使用场景: ① 前端界面加载性能测试。 ② 导出性能报告给前端开发。 复习&#xff1a;后端(接口)性能分析 ① 所有请求耗时时间轴&#xff1a;“网络”&#xff08;Network&#xff09; - 概览。 ② 单个请求耗时&#xff1a;“网络”&#xff08;Network&#xf…

FIB对芯片反向技术的贡献

目前由于国内在模拟集成电路设计领域的研究较为薄弱&#xff0c;芯片逆向分析便成为大多数模拟集成电路工程师基础实际模拟电路积累经验的有效途径&#xff0c;IC反向设计也成为推动国内集成电路设计进步的有效手段。在IC逆向分析与设计服务中&#xff0c;主要用FBI对IC线路进行…

计算机二级office操作技巧——Excel篇

文章目录 函数公式总结写在前面五大基本函数sum求和函数average求平均函数max求最大值函数min求最小值函数count求个数函数 rank排名函数if逻辑判断函数条件求个数函数countif单条件求个数函数countifs多条件求个数函数 条件求和函数sumifs多条件求和函数sumproduct乘积求和函数…

【学习笔记】线段树合并

前言 一般来说&#xff0c;线段树会有 O ( n ) O(n) O(n) 个节点。但是有的时候&#xff0c;整棵线段树就只进行了一次插入操作&#xff0c;这样只会有 O ( l o g n ) O(logn) O(logn) 个节点。 处理树上问题时&#xff0c;我们有时需要把儿子的信息合并到父亲节点。这个时候…

松理解数据库并发调度与可串行性

‍ 前言 在数据库系统中&#xff0c;多个事务的并发执行是不可避免的。然而&#xff0c;并发执行可能导致数据不一致的情况。为了解决这个问题&#xff0c;数据库管理系统&#xff08;DBMS&#xff09;使用调度策略来控制事务的执行顺序。本文将简洁地介绍可串行化调度这一概…