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

目录

一、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 mapperPUBLIC "-//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 fromt_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 configurationPUBLIC "-//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进行方法的调用@Testpublic void test_01() throws IOException {//1.读取外部配置文件(mybatis-config.xml)InputStream ips = Resources.getResourceAsStream("mybatis-config.xml");//2.创建sqlSessionFactorySqlSessionFactory 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 mapperPUBLIC "-//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 configurationPUBLIC "-//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.运行和测试

    @Testpublic void test_02() throws IOException {//1.读取外部配置文件(mybatis-config.xml)InputStream ips = Resources.getResourceAsStream("mybatis-config.xml");//2.创建sqlSessionFactorySqlSessionFactory 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 configurationPUBLIC "-//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 empSalaryfrom 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 empSalaryfrom 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 empSalaryfrom 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 empSalaryfrom 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

测试:

    @Testpublic void test_01() throws IOException {//1.读取外部配置文件InputStream ips = Resources.getResourceAsStream("mybatis-config.xml");//2.创建sqlSessionFactorySqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(ips);//3.获取sqlSessionSqlSession 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 configurationPUBLIC "-//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 configurationPUBLIC "-//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 empSalaryfrom 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">SELECTemp_name 员工姓名,emp_salary 员工工资,(SELECT AVG(emp_salary) FROM t_emp) 部门平均工资FROM t_emp WHERE emp_salary=(SELECT MAX(emp_salary) FROM t_emp)</select>

测试:

    @Testpublic void test_01() throws IOException {//1.读取外部配置文件InputStream ips = Resources.getResourceAsStream("mybatis-config.xml");//2.创建sqlSessionFactorySqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(ips);//3.获取sqlSessionSqlSession 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>

测试:

    @Testpublic void test_02() throws IOException {//1.读取外部配置文件InputStream ips = Resources.getResourceAsStream("mybatis-config.xml");//2.创建sqlSessionFactorySqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(ips);//3.获取sqlSessionSqlSession 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 mapperPUBLIC "-//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>

测试:

    @Testpublic void test_03() throws IOException {//1.读取外部配置文件InputStream ips = Resources.getResourceAsStream("mybatis-config.xml");//2.创建sqlSessionFactorySqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(ips);//3.获取sqlSessionSqlSession sqlSession = sqlSessionFactory.openSession();//4.获取代理mapper对象TeacherMapper mapper = sqlSession.getMapper(TeacherMapper.class);//准备实体对象Teacher teacher = new Teacher();teacher.setTName("zhangsan");//用UUID随机生产主键idString 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>

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

    @Testpublic void test_03() throws IOException {//1.读取外部配置文件InputStream ips = Resources.getResourceAsStream("mybatis-config.xml");//2.创建sqlSessionFactorySqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(ips);//3.获取sqlSessionSqlSession 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 empSalaryfrom 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_salaryfrom 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 mapperPUBLIC "-//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 tcron tor.customer_id = tcr.customer_idwhere tor.order_id = #{id}</select></mapper>

对应关系可以参考下图:

细节:

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

c. mybatis-config.xml 配置文件

<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configurationPUBLIC "-//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.创建sqlSessionFactorySqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(ips);//3.获取sqlSessionsqlSession = sqlSessionFactory.openSession();}@AfterEach  // 每次都测试方法之后,调用的结束方法public void clean(){//5.提交事务或释放资源sqlSession.close();}@Testpublic 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 mapperPUBLIC "-//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 tcrjoin t_order toron 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. 测试:

    @Testpublic 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 mapperPUBLIC "-//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 tcrjoin t_order toron 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 关键字


测试:

    @Testpublic 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. 分页插件使用

    //使用分页插件@Testpublic 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.mzph.cn/web/52792.shtml

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

相关文章

为什么矩阵特征值之和等于主对角线元素之和,特征值乘积等于行列式值

首先给出特征值和特征向量的定义。 设A是n阶矩阵&#xff0c;如果数λ和n维非零向量x使关系式 Axλx &#xff08;1&#xff09; 成…

微信小程序使用canvas画图保存图片到手机相册

微信小程序要实现使用canvas绘制一个图&#xff0c;然后保存到手机相册 **最终效果&#xff1a;**实现生成以下图片 一、初始化canvas // wxml页面设置canvas标签 <canvas style"width: {{windowW}}px; height: {{windowH}}px;" disable-scrolltrue canvas-id&…

C++(2)之Linux多线程服务端编程总结

C之Linux多线程服务端编程读书笔记 Author: Once Day Date: 2023年1月31日/2024年8月23日 一位热衷于Linux学习和开发的菜鸟&#xff0c;试图谱写一场冒险之旅&#xff0c;也许终点只是一场白日梦… 漫漫长路&#xff0c;有人对你微笑过嘛… 全系列文章可参考专栏: Linux实践…

C++:类与对象

一、面向对象编程 (一) 面向过程vs面向对象 面向过程&#xff08;Procedural-Oriented-Programming&#xff0c; POP&#xff09;和面向对象&#xff08;Object-Oriented-Programming&#xff0c;OOP&#xff09;&#xff0c;是两种典型的编程范式&#xff0c;通常是作为划分编…

频带宽度固定,如何突破数据速率的瓶颈?

目录 目录 引言 信道 频带宽度 信噪比 信噪比的重要性 影响信噪比的因素 码元 码元的特点&#xff1a; 码元与比特的关系&#xff1a; 码元的作用&#xff1a; 码元的类型&#xff1a; Question 类比解释&#xff1a; 技术解释&#xff1a; 引言 在现代通信系统中…

OpenAI o1:AI领域的“草莓”革命,华人科学家贡献卓越

最近&#xff0c;科技界的热门明星“草莓”频繁出现在大家的视线中。9月11号&#xff0c;The Information报道称&#xff1a;OpenAI计划在未来两周内推出一款更智能、更昂贵、更谨慎的AI模型&#xff01;网友们对此消息持怀疑态度&#xff0c;认为类似消息屡见不鲜&#xff0c;…

centos8构建nginx1.27.1+BoringSSL+http3+lua+openresty

需要接入http3&#xff0c;索性最新的nginx在构建一波&#xff0c;趟一遍坑 准备工作 1.环境命令安装 yum install GeoIP -y yum install GeoIP-devel -y yum install libmaxminddb-devel -y yum install -y patch wget zlib zlib-devel lftp gcc gcc-c make openssl-devel p…

Pikachu靶场之csrf

CSRF 跨站请求伪造 CSRF入门及靶场实战 - FreeBuf网络安全行业门户 攻击者伪造恶意链接&#xff0c;诱使用户点击&#xff0c;这个链接附带了用户的认证凭据Cookie、Session等&#xff0c;执行操作如转账。 因为带了cookie、session&#xff0c;服务器认为是用户的行为。借用…

待机模式中WKUP上升沿模拟开机与关机

本篇博客重点在于标准库函数的理解与使用&#xff0c;搭建一个框架便于快速开发 目录 前言 待机模式 代码 wkup.h wkup.c main.c 使用注意 前言 建议先阅读下面的博客中待机模式部分。本博客主要分享代码-基于待机模式WKUP引脚的上升沿实现类似长按开机与关机的功能…

二维码的原理以及Java生成二维码【中间带图片】

一、什么是二维码&#xff1a; 二维码 &#xff08;2-dimensional bar code&#xff09;&#xff0c;是用某种特定的几何图形按一定规律在平面&#xff08;二维方向上&#xff09; 分布的黑白相间的图形记录数据符号信息的。 二、常用的码制 Data Matrix, Maxi Code, Aztec,…

看看智慧门诊银医通自助服务方案,如何化解医院患者跑难题

“看病三分钟&#xff0c;排队三小时”&#xff0c;这是许多患者在就医过程中的无奈吐槽。挂号队伍长如龙&#xff0c;看病流程繁琐复杂&#xff0c;缴费窗口人满为患&#xff0c;检查报告等待时间漫长…… 这些就医痛点&#xff0c;不仅让患者身心疲惫&#xff0c;也给医院的管…

基于微信小程序+Java+SSM+Vue+MySQL的宿舍管理系统

作者&#xff1a;计算机学姐 开发技术&#xff1a;SpringBoot、SSM、Vue、MySQL、JSP、ElementUI等&#xff0c;“文末源码”。 专栏推荐&#xff1a;前后端分离项目源码、SpringBoot项目源码、SSM项目源码 系统展示 基于微信小程序JavaSSMVueMySQL的宿舍管理系统【附源码文档…

F1C100S/F1C200S的资料来源说明

文章目录 常用板子开源创客荔枝派榴莲派 我想说是的官网啥资料都没有。但是它的资料又很多&#xff0c;从淘宝或者其他地方能都搜到很多。 http://wiki.lcmaker.com/index.php?titleLC-PI-200S https://github.com/peng-zhihui/Planck-Pi?tabreadme-ov-file#head4 http://do…

使用 PyCharm 新建 Python 项目详解

使用 PyCharm 新建 Python 项目详解 文章目录 使用 PyCharm 新建 Python 项目详解一 新建 Python 项目二 配置环境1 项目存放目录2 Python Interpreter 选择3 创建隔离环境4 选择你的 Python 版本5 选择 Conda executable 三 New Window 打开项目四 目录结构五 程序编写运行六 …

虚拟机Linux+Ubuntu操作系统 如何在虚拟机上安装docker VMPro 2024在线激活资源

一般情况下 不建议在windows系统上安装docker Windows本身就自带一个虚拟机叫WSL 但是不推荐在日常使用的电脑上安装 我们要下一个虚拟机 我们在window上安装docker会被告知WSL内核太老 我们要一个专业的 隔离的虚拟机软件 推荐使用虚拟机 这是我们的虚拟机软件 我们这边…

深入链表的遍历——快慢指针算法(LeetCode——876题)

今天我们一起来学习一下一个快速遍历链表的方法 我们先来看看一道经典的需要遍历链表的题目 &#xff08;题目来自LeetCode&#xff09; 876. 链表的中间结点https://leetcode.cn/problems/middle-of-the-linked-list/ 给你单链表的头结点 head &#xff0c;请你找出并返回链…

网络安全 DVWA通关指南 DVWA Reflected Cross Site Scripting (反射型 XSS)

DVWA Reflected Cross Site Scripting (反射型 XSS) 文章目录 DVWA Reflected Cross Site Scripting (反射型 XSS)XSS跨站原理反射型 LowMediumHighImpossible 参考文献 WEB 安全靶场通关指南 XSS跨站原理 当应用程序发送给浏览器的页面中包含用户提交的数据&#xff0c;但没有…

鼎捷新一代PLM 荣膺维科杯 “2023年度行业优秀产品奖”

近日&#xff0c;由中国高科技行业门户OFweek维科网主办的“全数会2024&#xff08;第五届&#xff09;中国智能制造数字化转型大会暨维科杯工业自动化及数字化行业年度评选颁奖典礼”在深圳隆重举办。这不仅是中国工业自动化及数字化行业的一大品牌盛会&#xff0c;亦是高科技…

如何利用 CSS 渐变实现多样化背景效果

前言 总在平常看到像这样的图片 背景是如何实现的呢 背景效果的多样性和美观性直接影响用户体验。CSS 渐变为设计师提供了一种强大且灵活的方法来创建引人注目的背景。渐变是颜色之间平滑过渡的效果&#xff0c;通过调整渐变类型和设置&#xff0c;你可以轻松实现从简单到复杂…

DataWind将string类型转化为int类型的报错解决

一、现象&#xff1a; toInt64([kernel_wakeup_top_count_str]) 二、日志&#xff1a; 遇到&#xff1a;错误: 直连查询失败&#xff0c;内部异常:<class aeolus.aeolus.libs.exception.aeolus_base_exception.AeolusBaseException>: aeolus/logicQuery/logicQueryMysq…