目录
一、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 |
String | string |
Byte | byte |
Character | char (since 3.5.10) |
Character | character (since 3.5.10) |
Long | long |
Short | short |
Integer | int |
Integer | integer |
Double | double |
Float | float |
Boolean | boolean |
Date | date |
BigDecimal | decimal |
BigDecimal | bigdecimal |
BigInteger | biginteger |
Object | object |
Date[] | date[] |
BigDecimal[] | decimal[] |
BigDecimal[] | bigdecimal[] |
BigInteger[] | biginteger[] |
Object[] | object[] |
Map | map |
HashMap | hashmap |
List | list |
ArrayList | arraylist |
Collection | collection |
Iterator | iterator |
不用死记,规则如下:
基本数据类型: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 > 0">
and emp_salary = #{salary}
</if>
</where>
</select>
细节:
① if 标签可以通过 test 属性进行判断,如果为 true,就将标签内的 sql 语句进行拼接。
test 属性中的判断语句:key 比较符号 值(且:and 或:or)
大于和小于可以直接写符号(< >),但不推荐,因为低版本的 mybatis 可能会识别成标签的开始或者结束符号。推荐使用实体符号(大于:> 小于:< )
② 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 > 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 > 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 的操作,多表查询依然需要我们自己编写!