MyBatis
快速了解(入门案例)(Mybatis3)
- 准备数据模型
1
2
3
4
5
6
7
8
9
10
11
12
13
14CREATE 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); - 搭建项目和准备
a.搭建项目
mybatis-base-quickstart-01
b.依赖导入c.实体类1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22<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>1
2
3
4
5
6
7
8
9
10
11
12package cn.xnj.pojo;
public class Employee {
private Integer empId;
private String empName;
private Double empSalary;
//生成 getter | setter | toString 方法
} - 准备Mapper接口和MapperXML文件
MyBatis 框架下,SQL语句编写位置发生改变,从原来的Java类,改成XML或者注解定义!
a.定义mapper接口b.定义xml映射文件1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19package cn.xnj.mapper;
import cn.xnj.pojo.Employee;
/**
* t_emp表对应数据库SQL语句映射接口!
* 接口只规定方法,参数和返回值!
* mapper.xml中编写具体SQL语句!
*/
public interface EmployeeMapper {
/**
* 根据员工id查询员工数据方法
* @param empId 员工id
* @return 员工实体对象
*/
Employee selectEmployeeById(Integer empId);
}
注意此处规范的文件目录格式应该是:src/main/resource/对应mapper接口包路径/xxxMapper.xml
对应mapper接口包路径在idea创建的时候也应该用/
分隔开,这里不能用.
注意:1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<!-- namespace=接口类的全限定名,这样实现对应 -->
<mapper namespace="cn.xnj.mapper.EmployeeMapper">
<!-- 查询使用 select标签
id = 方法名
resultType = 返回值类型
标签内编写SQL语句
-->
<select id="selectEmployeeById" resultType="cn.xnj.pojo.Employee">
<!-- #{empId}代表动态传入的参数,并且进行赋值! -->
select emp_id empId,emp_name empName, emp_salary empSalary from
t_emp where emp_id = #{empId}
</select>
</mapper>- 方法名和SQL的id一致
- 方法返回值和resultType一致
- 方法的参数和SQL的参数一致
- 接口的全类名和映射配置文件的名称空间一致
- 准备MyBatis配置文件
mybatis框架配置文件: 数据库连接信息,性能配置,mapper.xml配置等!
建议命名为mybatis-config.xml
,之后用spring会整合,就不需要该配置文件了1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
<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="cn/xnj/mapper/EmployeeMapper.xml"/>
</mappers>
</configuration> - 运行和测试 说明:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27public class MyBatisTest {
public void testSelectEmployee() throws IOException {
// 1.读取外部配置文件 (mybatis-config.xml)
// ①声明Mybatis全局配置文件的路径
String mybatisConfigFilePath = "mybatis-config.xml";
// ②以输入流的形式加载Mybatis配置文件
InputStream inputStream = Resources.getResourceAsStream(mybatisConfigFilePath);
// 2. 创建SqlSessionFactory
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build (inputStream);
// 3.根据sqlSessionFactory创建sqlSession(每次业务创建一个,用完就释放)
SqlSession session = sessionFactory.openSession();
// 4.根据接口的代理对象(代理技术)调用代理对象的方法,就会查找 mapper接口的方法
EmployeeMapper employeeMapper = session.getMapper(EmployeeMapper.class);
Employee employee = employeeMapper.selectEmployeeById(1);
System.out.println("employee = " + employee);
// 4.关闭SqlSession
//session.commit(); //提交事务 [DQL不需要,其他需要]
session.close(); //关闭会话
}
}- SqlSession:代表Java程序和数据库之间的会话。(HttpSession是Java程序和浏览器之间的会话)
- SqlSessionFactory:是“生产”SqlSession的“工厂”。
- 工厂模式:如果创建某一个对象,使用的过程基本固定,那么我们就可以把创建这个对象的相关代码封装到一个“工厂类”中,以后都使用这个工厂类来“生产”我们需要的对象。
MyBatis基本使用
向SQL语句传参
mybatis日志输出配置
mybatis-config.xml配置文件中的标签和结构如下:
configuration(配置,都写在它下面)
- properties(属性)
- settings(设置)
- typeAliases(类型别名)
- typeHandlers(类型处理器)
- objectFactory(对象工厂)
- plugins(插件)
- environments(环境配置)
- environment(环境变量)
- transactionManager(事务管理器)
- dataSource(数据源)
- databaseIdProvider(数据库厂商标识)
- mappers(映射器)
在mybatis的配置文件使用settings标签设置,输出运过程SQL日志!
日志配置:1
2
3
4
5
6
7
8
9<settings>
<!-- 开启 mybatis的日志输出,
STDOUT_LOGGING -选择使用sysytem进行控制台输出
SLF4J -选择使用选择slf4j输出
JDK_LOGGING -选择使用JDK自带的日志输出
NOP_LOGGING -选择不输出日志
-->
<setting name="logImpl" value="STDOUT_LOGGING"/>
</settings>
#{}形式
Mybatis会将SQL语句中的#{}转换为?
问号占位符。
#{key}
:emp_id = ? ? = 赋值- 可以防止sql注入的问题
- ?只能代替值的位置,不能代替 容器名(标签,列名,sql关键字)
- select * from 表 where 列名=#{传参}
1 | <select id="selectEmployeeById" resultType="cn.xnj.pojo.Employee"> |
${}形式
${}形式传参,底层Mybatis做的是字符串拼接
操作。
- ${key}:字符串拼接
"emp_id=" + id
1 | <select id="selectEmployeeById" resultType="cn.xnj.pojo.Employee"> |
数据输入
这里数据输入具体是指上层方法(例如Service方法)调用Mapper接口时,数据传入的形式。
- 简单类型:只包含一个值的数据类型
- 基本数据类型:int、byte、short、double、……
- 基本数据类型的包装类型:Integer、Character、Double、……
- 字符串类型:String
- 复杂类型:包含多个值的数据类型
- 实体类类型:Employee、User、……
- 集合类型:List、Set、Map、……
- 数组类型:int[]、String[]、……
- 复合类型:List
、实体类中包含集合……
单个简单类型参数
传入单个简单类型,列名=#{key}
,key可以随便取值,但是为了规范,使用参数名!
Mapper接口中抽象方法声明1
2
3public interface EmployeeMapper {
Employee selectEmployeeById(Integer empId);
}
Mapper.xml文件中sql语句1
2
3
4<select id="selectEmployeeById" resultType="cn.xnj.pojo.Employee">
select emp_id empId,emp_name empName, emp_salary empSalary from
t_emp where emp_id = #{empId}
</select>
实体类型参数
传入实体类型 (#{key1},#{key2}),key填对应实体类的属性名即可
实体类1
2
3
4
5
6
7
8public class Employee {
private Integer empId;
private String empName;
private Double empSalary;
}
Mapper接口中抽象方法声明1
2
3
4
5
6
7
8
9
10public interface EmployeeMapper {
/* 插入入员工数据【实体对象】
Employee employee = new Employee();
employee.setEmpName("张三");
employee.setEmpSalary(10000.0);
int i = employeeMapper.insertEmployee(employee);
System.out.println("i = " + i);
*/
int insertEmployee(Employee employee);
}
Mapper.xml文件中sql语句1
2
3
4<insert id="insertEmployee" >
insert into t_emp(emp_name,emp_salary)
values (#{empName},#{empSalary})
</insert>
零散(多个)的简单类型数据
传入多个简单类型 列名1=#{key1} and 列名2=#{key2}
- key的值不能随便写,也不是按参入的形参名写
- 方案一:注解指定,使用
@Param
注解 指定多个简单参数的key [推荐]@Param("value值")
- sql中:
key=value值
- 方案二:mybatis默认机制
arg0 arg1...
形参从左到右依次对应arg0,arg1…- (name,salary) name-> key=arg0 salary->key=arg1
param1 param2...
形参从左到右依次对应param1 param2…- (name,salary) name-> key=param1 salary->key=param2
Mapper接口中抽象方法声明1
2
3
4public interface EmployeeMapper {
//多个参数查找员工数据
Employee selectByEmpNameAndEmpSalary(; String empName, Double empSalary)
}
Mapper.xml文件中sql语句1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
<select id="selectByEmpNameAndEmpSalary" resultType="cn.xnj.pojo.Employee">
<!--方案一 @Param("value值")-->
select emp_id empId,emp_name empName, emp_salary empSalary from
t_emp where emp_name = #{name} and emp_salary = #{salary}
<!--方案二 arg0 arg1...-->
<!--
select emp_id empId,emp_name empName, emp_salary empSalary from
t_emp where emp_name = #{arg0} and emp_salary = #{arg1}
-->
<!--方案三 param1 param2...-->
<!--
select emp_id empId,emp_name empName, emp_salary empSalary from
t_emp where emp_name = #{param1} and emp_salary = #{param2}
-->
</select>
Map类型参数
传入map只需 key=map的key即可
Mapper接口中抽象方法声明1
2
3
4
5
6
7
8
9public interface EmployeeMapper {
/* 传入Map数据来插入员工数据 map(name="员工名",salary=员工薪资)
Map data = new HashMap();
data.put("name","李四");
data.put("salary",9000.0);
int i = employeeMapper.insertEmployeeByMap(data);
*/
int insertEmployeeByMap(Map data);
}
Mapper.xml文件中sql语句1
2
3
4<insert id="insertEmployeeByMap">
insert into t_emp(emp_name,emp_salary)
values (#{name},#{salary})
</insert>
数据输出
数据输出总体上有两种形式:
- 增删改操作返回的受影响行数:直接使用 int 或 long 类型接收即可
- int insert(User user);//添加数据,返回添加的行数
- int deleteById(Integer id);//通过id删除,返回删除的行数
- int updateById(User user);//根据id修改数据,返回修改的行数
- 查询操作的查询结果
- String queryNameById(Integer id);//通过id查询名字
- Double querySalaryByName(String name);//通过姓名查询薪水
我们需要做的是,指定查询的输出数据类型即可!
并且插入场景下,实现主键数据回显示!
单个简单类型
细节:
- select标签,通过resultType指定查询返回值类型!
- resultType = “全限定符 | 别名 | 如果是返回集合类型,写范型类型即可”
- 基本数据类型 int double -> _int _double
- 包装数据类型 Integer Double -> int integer double
- 集合容器类型 Map List HsahMap -> 小写即可 map list hashmap
别名:
- 官网上指定了:https://mybatis.net.cn/configuration.html#typeAliases
- 类型别名可为 Java 类型设置一个缩写名字。 它仅用于 XML 配置,意在降低冗余的全限定
Mapper接口中抽象方法声明1
2
3
4
5
6
7public interface EmployeeMapper {
//通过id查询名字
String queryNameById(Integer id);
//通过姓名查询薪水
Double querySalaryByName(String name);
}
Mapper.xml文件中sql语句1
2
3
4
5
6
7
8
9
10
11
12
13<!--resultType="java.lang.String" 全限定符写法 -->
<!--<select id="queryNameById" resultType="java.lang.String">-->
<!--resultType="String" 别名写法 -->
<select id="queryNameById" resultType="String">
select emp_name empName
from t_emp where emp_id = #{id}
</select>
<select id="querySalaryByName" resultType="_double">
select emp_salary empSalary
from t_emp where emp_name = #{name}
</select>
返回实体类对象
Mapper接口的抽象方法1
Employee selectEmployee(Integer empId);
Mapper.xml配置SQl语句1
2
3
4
5
6
7
8
9<!-- 编写具体的SQL语句,使用id属性唯一的标记一条SQL语句 -->
<!-- resultType属性:指定封装查询结果的Java实体类的全类名 -->
<select id="selectEmployee" resultType="cn.xnj.pojo.Employee">
<!-- Mybatis负责把SQL语句中的#{}部分替换成“?”占位符 -->
<!-- 给每一个字段设置一个别名,让别名和Java实体类中属性名一致 -->
select emp_id empId,emp_name empName,emp_salary empSalary
from t_emp where emp_id=#{maomi}
</select>
因为数据库表中的列的命名和实体类属性的命名规范不同,导致我们需要通过给数据库表字段加别名,让查询结果的每一列都和Java实体类中属性对应起来,才能正确的接收返回结果。
我们可以在mybatis-config.xml全局配置文件中,做如下配置,这样就不用再给字段设置别名了1
2
3
4
5
6
7
8
9<!-- 在全局范围内对Mybatis进行配置 -->
<settings>
<!-- 具体配置 -->
<!-- 从org.apache.ibatis.session.Configuration类中可以查看能使用的配置项 -->
<!-- 将mapUnderscoreToCamelCase属性配置为true,表示开启自动映射驼峰式命名规则 -->
<!-- 规则要求数据库表字段命名方式:单词_单词 -->
<!-- 规则要求Java实体类属性名命名方式:首字母小写的驼峰式命名 -->
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
驼峰命名规范
Java实体类属性名命名方式:首字母小写的驼峰式命名
实体类:Book
- 属性:id,bookId,bookName,createTime,updateTime
数据库表字段命名方式:单词_单词
数据库表:tb_book
- 字段:id,boo_id,book_name,create_time,update_time
返回Map类型
适用于SQL查询返回的各个字段综合起来并不和任何一个现有的实体类对应,没法封装到实体类对象中。能够封装成实体类类型的,就不使用Map类型。
Mapper接口的抽象方法:1
2//查询薪水最高的员工的姓名和薪水
Map<String,Object> selectEmpNameAndMaxSalary();
Mapper.xml的SQL语句:1
2
3
4<select id="selectEmpNameAndMaxSalary" resultType="java.util.Map">
select emp_name 员工姓名, emp_salary 工资
from t_emp where emp_salary = (select max(emp_salary) from t_emp)
</select>
返回List类型
查询结果返回多个实体类对象,希望把多个实体类对象放在List集合中返回。此时不需要任何特殊处理,在resultType属性中还是设置实体类类型即可
Mapper接口中抽象方法1
List<Employee> selectAll();
SQL语句1
2
3
4
5
6
7
8
9<!-- List<Employee> selectAll(); -->
<!-- 集合类型的返回值
返回值是集合,resultType不需要指定集合类型
只需指定泛型即可,如string,比如下面返回集合的泛型就是Employee
-->
<select id="selectAll" resultType="cn.xnj.pojo.Employee">
select emp_id ,emp_name ,emp_salary
from t_emp
</select>
返回主键值
自增长类型主键
当sql表的id字段为自增长(auto_increment)时,执行插入操作时返回生成的主键id的值在sql标签设置
useGeneratedKeys=true keyColumn="主键列的值" keyProperty="接收主键列值的属性"
Mapper接口中抽象方法
1
2// 传入员工数据【实体对象】
int insertEmployee(Employee employee);SQL语句
1
2
3
4<insert id="insertEmployee" useGeneratedKeys="true" keyColumn="emp_id" keyProperty="empId">
insert into t_emp(emp_name,emp_salary)
values (#{empName},#{empSalary})
</insert>测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
public void testInsertEmployee() throws IOException {
String mybatisConfigFilePath = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(mybatisConfigFilePath);
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//传入参数true 开启自动提交事务
SqlSession session = sessionFactory.openSession(true);
EmployeeMapper employeeMapper = session.getMapper(EmployeeMapper.class);
//创建员工对象
Employee employee = new Employee();
employee.setEmpName("李白");
employee.setEmpSalary(8888.88);
System.out.println("插入操作前empID = " + employee.getEmpId());//插入操作前empID = null
int i = employeeMapper.insertEmployee(employee);
System.out.println("插入操作后empID = " + employee.getEmpId());//插入操作后empID = 8
System.out.println("i = " + i);//i = 1
//session.commit(); //提交事务
session.close(); //关闭会话
}
非自增长类型主键
而对于不支持自增型主键的数据库(例如 Oracle)或者字符串类型主键,则可以使用 selectKey 子元素:selectKey 元素将会首先运行,id 会被设置,然后插入语句会被调用!order="before | after"
sql语句是在插入语句之前还是之后执行resultType = "返回值类型"
keyProperty = "查询结果给哪个属性赋值"
使用 selectKey 帮助插入UUID作为字符串类型主键示例:
1
2
3
4
5
6
7<insert id="insertEmployee" >
<selectKey order="BEFORE" resultType="string" keyProperty="tId">
select replace(UUID(),'-','');
</selectKey>
insert into teacher(t_id,t_name)
values (#{tId},#{tName})
</insert>
实体类属性和数据库字段对应关系
别名对应
将字段的别名设置成和实体类属性一致
实体类属性约定:即getXxx()方法、setXxx()方法把方法名中的get或set去掉,首字母小写。1
2
3
4
5
6
7
8
9<!-- 编写具体的SQL语句,使用id属性唯一的标记一条SQL语句 -->
<!-- resultType属性:指定封装查询结果的Java实体类的全类名 -->
<select id="selectEmployee" resultType="cn.xnj.pojo.Employee">
<!-- Mybatis负责把SQL语句中的#{}部分替换成“?”占位符 -->
<!-- 给每一个字段设置一个别名,让别名和Java实体类中属性名一致 -->
select emp_id empId,emp_name empName,emp_salary empSalary from t_emp where emp_id=# {maomi}
</select>全局配置自动识别驼峰式命名规则
在Mybatis全局配置文件(mybatis-config.xml)加入如下配置:1
2
3
4
5
6
7<!-- 使用settings对Mybatis全局进行设置 -->
<settings>
<!-- 将xxx_xxx这样的列名自动映射到xxXxx这样驼峰式命名的属性名 -->
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>之后xxxMapper.xml文件中的SQL语句可以不设置别名
使用ResultMap
使用resultMap标签定义对应关系,再在后面的SQL语句中引用这个对应关系1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20<!-- 专门声明一个resultMap设定column到property之间的对应关系 -->
<resultMap id="EmployeeResultMap" type="cn.xnj.pojo.Employee">
<!-- 使用id标签设置主键列和主键属性之间的对应关系 -->
<!-- column属性用于指定字段名;property属性用于指定Java实体类属性名 -->
<id column="emp_id" property="empId"/>
<!-- 使用result标签设置普通字段和Java实体类属性之间的关系 -->
<result column="emp_name" property="empName"/>
<result column="emp_salary" property="empSalary"/>
</resultMap>
<!-- Employee selectEmployeeByRM(Integer empId); -->
<select id="selectEmployeeByRM" resultMap="EmployeeResultMap">
select emp_id,emp_name,emp_salary from t_emp where emp_id=#{empId}
</select>
Lombok
lombok能帮我们自动生成无参和有参构造方法以及get,set方法等,只需在实体类上方加上对应注解即可
常用注解 | 说明 |
---|---|
@Data |
包含get,set,toString等 |
@AllArgsConstructor |
全参构造函数 |
@NoArgsConstructor |
无参构造函数 |
@ToString |
重写toString方法 |
- 导入依赖
1
2
3
4
5<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.32</version>
</dependency> - 安装插件
lombok
(图标封面是一个红辣椒🌶️)
MyBatis多表映射
大致分为3步
- 使用多表查询语句
- 多表结果承接实体类设计
- 一对一:属性中包含对方对象
- 一对多:属性中包含对方对象集合
- 使用ResultMap完成多表结果映射
多表映射案例
经典关系:
客户和订单,一对多关系,一个客户对应多个订单
订单和客户,一对一关系,一个订单对应一个客户
数据准备
1
2
3
4
5
6
7
8
9CREATE 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');实体类准备
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public class Customer {//客户实体类
private Integer customerId;
private String customerName;
private List<Order> orderList;// 体现的是对多的关系
}
public class Order {//订单实体类
private Integer orderId;
private String orderName;
private Integer customerId;
private Customer customer;// 体现的是对一的关系
}
对一映射
需求说明
根据订单id查询订单,订单里关联客户的信息OrderMapper接口
1
2
3
4
5public interface OrderMapper {
//根据id查询订单,
Order selectOrderById(Integer id);
}OrderMapper.xml配置文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<mapper namespace="cn.xnj.mapper.OrderMapper">
<resultMap id="OrderResultMap" type="cn.xnj.pojo.Order">
<!-- 主键列 -->
<id column="order_id" property="orderId"/>
<!-- 普通列 -->
<result column="order_name" property="orderName"/>
<result column="customer_id" property="customerId"/>
<!-- 一对一关联 -->
<association property="customer" javaType="cn.xnj.pojo.Customer">
<id column="customer_id" property="customerId"/>
<result column="customer_name" property="customerName"/>
</association>
</resultMap>
<select id="selectOrderById" resultMap="OrderResultMap">
select * from t_order join t_customer
on t_order.customer_id = t_customer.customer_id
where order_id = #{id};
</select>
</mapper>junit测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24public class MyBatisTest {
private SqlSession sqlSession;
public void before() throws IOException {
String mybatisConfigFilePath = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(mybatisConfigFilePath);
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build (inputStream);
sqlSession = sessionFactory.openSession(true);
}
public void after(){
sqlSession.close();
}
public void test_01() throws IOException {
OrderMapper mapper = sqlSession.getMapper(OrderMapper.class);
Order order = mapper.selectOrderById(1);
System.out.println(order);
//控制台输出
//Order(orderId=1, orderName=o1, customerId=1, customer=Customer(customerId=1, customerName=c01, orderList=null))
}
}
对多映射
需求说明
根据客户id查询客户信息,客户信息包含其所有订单信息CustomerMapper接口
1
2
3
4
5public interface CustomerMapper {
//根据客户id查询客户信息,客户信息包含用户信息
Customer selectCustomerById(Integer id);
}CustomerMapper.xml配置文件
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
<mapper namespace="cn.xnj.mapper.CustomerMapper">
<resultMap id="CustomerResultMap" type="cn.xnj.pojo.Customer">
<!--主键列-->
<id column="customer_id" property="customerId"/>
<!--普通列-->
<result column="customer_name" property="customerName"/>
<!--一对多关联-->
<collection property="orderList" ofType="cn.xnj.pojo.Order">
<id column="order_id" property="orderId"/>
<result column="order_name" property="orderName"/>
<result column="customer_id" property="customerId"/>
</collection>
</resultMap>
<select id="selectCustomerById" resultMap="CustomerResultMap">
select * from t_customer join t_order
on t_order.customer_id = t_customer.customer_id
where t_customer.customer_id = #{id};
</select>
</mapper>junit测试
1
2
3
4
5
6
7
8
9
public void test_02() throws IOException {
CustomerMapper mapper = sqlSession.getMapper(CustomerMapper.class);
Customer customer = mapper.selectCustomerById(1);
System.out.println(customer);
//控制台输出
//Customer(customerId=1, customerName=c01, orderList=[Order(orderId=1, orderName=o1, customerId=1, customer=null), Order(orderId=2, orderName=o2, customerId=1, customer=null), Order(orderId=3, orderName=o3, customerId=1, customer=null)])
}
MyBatis动态语句
if和where标签
我们现在可以看到很多软件里查询时,筛选条件是可选可不选的,即sql语句是动态的,能根据你传递的值来动态的拼接执行sql
if标签
让我们可以有选择的加入SQL语句的片段。- 这个SQL语句片段是否要加入整个SQL语句,就看if标签判断的结果是否为true
- test属性 内部做比较运行,为true将标签内的sql语句进行拼接
- 判断连接语句:
and | or
- 大小比较:
< <
> >
>= ≥
<= ≤
<if test="salary!= null and salary > 10000.0"></test>
- 判断连接语句:
where标签
会自动去掉“标签体内前面多余的and/or
,自适应添加where关键字
- 防止,当有多个条件时,随机的拼接导致sql语句错误
- 如第一个条件没拼接:
select * from t_emp where and salary=10000.0
的语法错误 - 没有一个条件拼接:
select * from t_emp where
对条件对象为Srting类型时,不仅要判空还需要判断是否为空值
<if test="name !=null and name !=''"></test>
1 | <!-- |
junit测试1
2
3
4
5
6
7
8
public void test_01() throws IOException {
EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
List<Employee> employeeList = mapper.query(null, null);
System.out.println(employeeList);
List<Employee> employeeList1 = mapper.query(null, 10000.0);
System.out.println(employeeList1);
}
set标签
set标签
和where标签类似,set标签能动态管理set语句,并切动态去掉两端多余的逗号
1 | <!-- |
junit测试1
2
3
4
5
6
7
8
9
10
11
public void test_02() throws IOException {
EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
//使用builder模式创建对象,引入lombok依赖,实体类要加上@Builder注解
Employee employee = Employee.builder()
.empId(4)
.empName("xnj")
.empSalary(15000.0)
.build();
mapper.updateEmployeeDynamic(employee);
}
trim标签
使用trim标签控制条件部分两端是否包含某些字符
- prefix属性:指定要动态添加的前缀
- suffix属性:指定要动态添加的后缀
- prefixOverrides属性:指定要动态去掉的前缀,使用“|”分隔有可能的多个值
- suffixOverrides属性:指定要动态去掉的后缀,使用“|”分隔有可能的多个值
1 | <!-- trim 代替 where--> |
choose/when/otherwise标签
在多个分支条件中,仅执行一个。
- 从上到下依次执行条件判断
- 遇到的第一个满足条件的分支会被采纳
- 被采纳分支后面的分支都将不被考虑
- 如果所有的when分支都不满足,那么就执行otherwise分支
- 即:必会执行一个条件,也只会执行一个条件
1 | <!-- List<Employee> selectEmployeeByConditionByChoose(Employee employee) --> |
foreach标签
collection属性:要遍历的集合
- item属性:遍历集合的过程中能得到每一个具体对象,在item属性中设置一个名字,将来通过这个名字引用遍历出来的对象
- separator属性:指定当foreach标签的标签体重复拼接字符串时,各个标签体字符串之间的分隔符
- open属性:指定整个循环把字符串拼好后,字符串整体的前面要添加的字符串
- close属性:指定整个循环把字符串拼好后,字符串整体的后面要添加的字符串
- index属性:这里起一个名字,便于后面引用
- 遍历List集合,这里能够得到List集合的索引值
- 遍历Map集合,这里能够得到Map集合的key
EmployeeMapper接口
1
2
3
4
5
6
7
8
9
10
11
12public interface EmployeeMapper {
// 批量查询
List<Employee> queryBatch(; List<Integer> ids)
// 批量插入
int insertBatch(; List<Employee> employees)
// 批量删除
int deleteBatch(; List<Integer> ids)
// 批量更新
int updateBatch(; List<Employee> employees)
}EmployeeMapper.xml配置文件SQL语句
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
<!-- namespace=接口类的全限定名,这样实现对应 -->
<mapper namespace="cn.xnj.mapper.EmployeeMapper">
<!-- 批量查询 List<Employee> queryBatch(List<Integer> ids) -->
<select id="queryBatch" resultType="cn.xnj.pojo.Employee">
select * from t_emp where emp_id in
<foreach collection="ids" item="id" open="(" close=")" separator=",">
#{id}
</foreach>
</select>
<!-- 批量插入 void insertBatch(List<Employee> employees) -->
<insert id="insertBatch">
insert into t_emp(emp_name,emp_salary)values
<foreach collection="employees" item="employee" separator=",">
(#{employee.empName},#{employee.empSalary})
</foreach>
</insert>
<!-- 批量更新 void updateBatch(List<Employee> employees) -->
<update id="updateBatch">
<foreach collection="employees" item="emp" separator=";">
update t_emp set emp_name=#{emp.empName},emp_salary=#{emp.empSalary} where emp_id=#{emp.empId}
</foreach>
</update>
<!-- 批量删除 void deleteBatch(List<Integer> ids) -->
<delete id="deleteBatch">
delete from t_emp where emp_id in
<foreach collection="ids" item="id" open="(" close=")" separator=",">
#{id}
</foreach>
</delete>
</mapper>junit功能测试
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62public class MyBatisTest {
private SqlSession sqlSession;
public void before() throws IOException {
String mybatisConfigFilePath = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(mybatisConfigFilePath);
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build (inputStream);
sqlSession = sessionFactory.openSession(true);
}
public void after(){
sqlSession.close();
}
//批量查询
public void test_03() throws IOException {
EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
//List<Employee> employeeList = mapper.queryBatch(List.of(1, 2, 3, 4, 5));
List<Integer> ids = new ArrayList<>();
ids.add(1);
ids.add(2);
ids.add(3);
ids.add(4);
ids.add(5);
System.out.println(ids);
List<Employee> employeeList = mapper.queryBatch(ids);
System.out.println(employeeList);
}
// 批量添加
public void test_04() throws IOException {
EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
List<Employee> employees = new ArrayList<>();
employees.add(new Employee("xx1",1000.0));
employees.add(new Employee("xx2",1000.0));
employees.add(new Employee("xx3",1000.0));
employees.add(new Employee("xx4",1000.0));
employees.add(new Employee("xx5",1000.0));
mapper.insertBatch(employees);
}
// 批量修改
public void test_05() throws IOException {
EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
List<Employee> employees = new ArrayList<>();
employees.add(new Employee(10,"xx1",1500.0));
employees.add(new Employee(11,"xx2",1500.0));
employees.add(new Employee(12,"xx3",1500.0));
employees.add(new Employee(13,"xx4",1500.0));
employees.add(new Employee(14,"xx5",1500.0));
mapper.updateBatch(employees);
}
// 批量删除
public void test_06() throws IOException {
EmployeeMapper mapper = sqlSession.getMapper(EmployeeMapper.class);
List<Integer> ids = new ArrayList<>(List.of(10,11,12,13,14));
mapper.deleteBatch(ids);
}
}批量更新时需要注意:
- 上面批量插入的例子本质上是一条SQL语句,而实现批量更新则需要多条SQL语句拼起来,用分号分开。也就是一次性发送多条SQL语句让数据库执行。此时需要在数据库连接信息的URL地址中设置:
jdbc.url=jdbc:mysql:///mybatis-example?allowMultiQueries=true
mapper接口方法定义参数时注意:
- 如果没有给接口中List类型的参数使用@Param注解指定一个具体的名字,那么在collection属性中默认可以使用
collection
或list
来引用这个list集合。这一点可以通过异常信息看出来: Parameter 'empList' not found. Available parameters are [arg0, collection, list]
- 如果没有给接口中List类型的参数使用@Param注解指定一个具体的名字,那么在collection属性中默认可以使用
Sql片段
抽取重复的SQL片段1
2
3
4<!-- 使用sql标签抽取重复出现的SQL片段 -->
<sql id="mySelectSql">
select emp_id,emp_name,emp_age,emp_salary,emp_gender from t_emp
</sql>
引用已抽取的SQL片段1
2<!-- 使用include标签引用声明的SQL片段 -->
<include refid="mySelectSql"/>
Mapper批量映射优化
前面提过一嘴,当你创建XxxMapper.xml
配置文件时,建议目录应该和XxxMapper
一样,如:
- src/main/java/
cn.xnj.mapper.XxxMapper.java
- 在此处创建时,用
.
,idea能自动识别并创建包对应的文件目录结构
- 在此处创建时,用
- src/main/resource
cn/xnj/mapper/XxxMapper.xml
- 在此处创建时,用
.
idea不能识别为多层目录结构,要用/
- 在此处创建时,用
- 这样在打包后能看到,
XxxMapper.java
和XxxMapper.xml
存放在一个同一个位置
这么做不仅仅是口头规范,在配置批量映射时也是要求目录的格式
在mybatis-config.xml
配置文件中,Mapper 配置文件很多时,需要在全局配置文件中一个一个注册,这样太麻烦:
1
2
3
4
5<mappers>
<mapper resource="cn/xnj/mapper/EmployeeMapper.xml"/>
<mapper resource="cn/xnj/mapper/UserMapper.xml"/>
...
</mappers>
现在我们可以直接在mybatis-config.xml
配置文件指定 Mapper 映射文件时,只指定其所在的包:
1
2
3<mappers>
<package name="cn/xnj/mapper"/>
</mappers>
分页插件PageHelper
- pom.xml引入依赖
1
2
3
4
5<dependency>
<groupId>com.github.pagehelper</groupId>
<artifactId>pagehelper</artifactId>
<version>5.1.11</version>
</dependency> 在mybatis-config.xml全局配置文件中添加分页插件
1
2
3
4
5<plugins>
<plugin interceptor="com.github.pagehelper.PageInterceptor">
<property name="helperDialect" value="mysql"/>
</plugin>
</plugins>页插件使用
sql语句中不需要写limit
,结尾不要写;
1
2
3
4<!--分页查询 List<Employee> queryByPage();-->
<select id="queryByPage" resultType="cn.xnj.pojo.Employee">
select * from t_emp
</select>在查询方法中使用分页
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28// 分页查询
public void test_07() throws IOException {
EmployeeMapper employeeMapper = sqlSession.getMapper(EmployeeMapper.class);
//调用之前,先设置分页数据(当前页码,每页显示多少数据)
PageHelper.startPage(1,2);
// 查询Customer对象同时将关联的Order集合查询出来
List<Employee> list = employeeMapper.queryByPage();
//将查询到的数据封装到一个pageInfo的分页实体类当中
PageInfo<Employee> pageInfo = new PageInfo<>(list);
System.out.println("pageInfo = " + pageInfo);
long total = pageInfo.getTotal(); // 获取总记录数
System.out.println("总记录数:total = " + total);
int pages = pageInfo.getPages(); // 获取总页数
System.out.println("总页数:pages = " + pages);
int pageNum = pageInfo.getPageNum(); // 获取当前页码
System.out.println("当前页码:pageNum = " + pageNum);
int pageSize = pageInfo.getPageSize(); // 获取每页显示记录数
System.out.println("每页显示记录数:pageSize = " + pageSize);
List<Employee> employees = pageInfo.getList(); //获取查询页的数据集合
System.out.println("查询页的数据集合:employees = " + employees);
// 遍历集合
employees.forEach(System.out::println);
}
MyBatisX插件
一个实用的插件,当你书写正确时,能自动定位你的mapper接口方法到mapper.xml配置文件中的sql语句
搜索 MyBatisX 并安装(小鸟图标🐦)。