MyBatis核心功能一
本文最后更新于:3 年前
引言
MyBatis作为一个强大的持久层框架,提供了面向接口编程的方法,通过动态代理技术,自动地生成接口实现,本文将对这一特点进行详细介绍。
面向接口编程
简介
面向接口编程是一种常用的设计模式,在MyBatis中,可以定义一个Java接口,其中的每个方法对应于SQL映射文件中的一个SQL语句。MyBatis利用动态代理技术在运行时自动生成这些接口的实现类。开发者只需调用接口方法,即可执行对应的SQL操作。这种方法将接口定义与具体的SQL操作分离,使得代码更加整洁、易于管理和扩展。
原理
- 接口定义:首先定义一个接口,声明所需的数据库操作,如插入、查询、更新和删除。
- 映射文件:为接口中的每个方法提供相应的SQL语句映射。这些映射定义在Mapper文件中,或通过注解直接在接口方法上指定。
- SqlSessionFactory:通过配置信息,MyBatis构建一个
SqlSessionFactory
实例,这是创建SQL会话的工厂。 - SqlSession:从
SqlSessionFactory
获取SqlSession
,它是执行SQL命令的实际对象。 - 动态代理:当调用接口方法时,MyBatis会通过动态代理拦截这些调用,并根据方法名称匹配Mapper文件或注解中定义的 SQL 语句,执行数据库操作。
优点
- 解耦合:接口和SQL映射的分离使得业务逻辑和数据库操作代码解耦,提高了代码的可维护性。
- 简洁性:开发者只需通过调用接口方法来执行数据库操作,无需关心底层的SQL详情,代码更简洁。
- 易于维护:SQL语句集中管理,在Mapper文件或接口注解中修改SQL语句即可,无需修改业务代码。
- 灵活性:易于实现和切换不同的数据库操作逻辑,只需更改SQL映射即可,不影响业务逻辑层。
- 类型安全:由于使用Java接口,所有的操作都是类型安全的,减少了运行时错误。
示例
以
tt_vehicle
为示例表,定义DAO
接口VehicleMapper.class
,这个接口包含了的各种操作如下1
2
3
4
5
6
7
8
9
10
11
12
13public interface VehicleMapper {
void addVehicle(VehiclePO vehiclePO);
void deleteVehicle(long id);
VehiclePO getVehicleById(long id);
List<VehiclePO> getAllVehicles();
void updateVehicle(VehiclePO vehiclePO);
}定义对应的Mapper文件
VehicleMapper.xml
,接口与Mapper文件之间通过namespace
进行关联(Mapper
文件的namespace
=接口的全限定类名),SQL语句标签的id
=接口方法名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<?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="space.yangtao.mapper.VehicleMapper">
<insert id="addVehicle" parameterType="space.yangtao.domain.po.VehiclePO">
insert into tt_vehicle (product_name, vin, production_date, guide_price)
values (#{productName}, #{vin}, #{productionDate}, #{guidePrice})
</insert>
<delete id="deleteVehicle" parameterType="long">
delete from tt_vehicle where id = #{id}
</delete>
<select id="getVehicleById" resultType="space.yangtao.domain.po.VehiclePO">
select id as id,
product_name as productName,
vin as vin,
production_date as productionDate,
guide_price as guidePrice
from tt_vehicle
where id = #{id}
</select>
<select id="getAllVehicles" resultType="space.yangtao.domain.po.VehiclePO">
select id as id,
product_name as productName,
vin as vin,
production_date as productionDate,
guide_price as guidePrice
from tt_vehicle
</select>
<update id="updateVehicle" parameterType="space.yangtao.domain.po.VehiclePO">
update tt_vehicle
set product_name = #{productName},
vin = #{vin},
production_date = #{productionDate},
guide_price = #{guidePrice}
where id = #{id}
</update>
</mapper>使用时,不再通过
SqlSession
对象调用Mapper文件中的方法,而是通过SqlSession
获取到对应的接口的代理对象,通过调用代理对象的方法,实现对Mapper文件中对应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
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77@TestMethodOrder(MethodOrderer.OrderAnnotation.class)
public class VehicleMapperTest {
private static final Logger log = LoggerFactory.getLogger(VehicleMapperTest.class);
@Test
@Order(1)
public void testAddVehicle() {
log.info("--- testAddVehicle start ---");
VehiclePO audiA6L = new VehiclePO()
.setProductName("Audi A6L")
.setVin("LVIN1")
.setProductionDate(LocalDate.now())
.setGuidePrice(new BigDecimal("450000"));
VehiclePO bwm530Li = new VehiclePO()
.setProductName("BMW 530Li")
.setVin("LVIN2")
.setProductionDate(LocalDate.now())
.setGuidePrice(new BigDecimal("600000"));
VehiclePO benzE300L = new VehiclePO()
.setProductName("Benz E300L")
.setVin("LVIN3")
.setProductionDate(LocalDate.now())
.setGuidePrice(new BigDecimal("500000"));
SqlSession session = SqlSessionUtil.getSession();
session.getMapper(VehicleMapper.class).addVehicle(audiA6L);
session.getMapper(VehicleMapper.class).addVehicle(bwm530Li);
session.getMapper(VehicleMapper.class).addVehicle(benzE300L);
session.commit();
log.info("--- testAddVehicle end ---");
}
@Test
@Order(2)
public void testGetVehicleById() {
log.info("--- testGetVehicleById start ---");
SqlSession session = SqlSessionUtil.getSession();
VehiclePO vehiclePO = session.getMapper(VehicleMapper.class).getVehicleById(1L);
System.out.println(vehiclePO);
log.info("--- testGetVehicleById end ---");
}
@Test
@Order(3)
public void testUpdateVehicle() {
log.info("--- testUpdateVehicle start ---");
VehiclePO vehiclePO = new VehiclePO()
.setId(1L)
.setProductName("Audi A6L")
.setVin("LVIN1")
.setProductionDate(LocalDate.now())
.setGuidePrice(new BigDecimal("450000"));
SqlSession session = SqlSessionUtil.getSession();
session.getMapper(VehicleMapper.class).updateVehicle(vehiclePO);
session.commit();
log.info("--- testUpdateVehicle end ---");
}
@Test
@Order(4)
public void testDeleteVehicle() {
log.info("--- testDeleteVehicle start ---");
SqlSession session = SqlSessionUtil.getSession();
session.getMapper(VehicleMapper.class).deleteVehicle(1L);
session.commit();
log.info("--- testDeleteVehicle end ---");
}
@Test
@Order(5)
public void testGetAllVehicles() {
log.info("--- testGetAllVehicles start ---");
SqlSession session = SqlSessionUtil.getSession();
session.getMapper(VehicleMapper.class).getAllVehicles().forEach(System.out::println);
log.info("--- testGetAllVehicles end ---");
}
}运行程序,控制台输出日志如下
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
62
63
64
65
66
67
68
69
70
7121:01:57.345 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Opening JDBC Connection
21:01:58.177 [main] DEBUG o.apache.ibatis.datasource.pooled.PooledDataSource - Created connection 34073107.
21:01:58.177 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@207ea13]
21:01:58.179 [main] DEBUG space.yangtao.mapper.VehicleMapper.addVehicle - ==> Preparing: insert into tt_vehicle (product_name, vin, production_date, guide_price) values (?, ?, ?, ?)
21:01:58.197 [main] DEBUG space.yangtao.mapper.VehicleMapper.addVehicle - ==> Parameters: Audi A6L(String), LVIN1(String), 2024-07-18(LocalDate), 450000(BigDecimal)
21:01:58.204 [main] DEBUG space.yangtao.mapper.VehicleMapper.addVehicle - <== Updates: 1
21:01:58.213 [main] DEBUG space.yangtao.mapper.VehicleMapper.addVehicle - ==> Preparing: insert into tt_vehicle (product_name, vin, production_date, guide_price) values (?, ?, ?, ?)
21:01:58.214 [main] DEBUG space.yangtao.mapper.VehicleMapper.addVehicle - ==> Parameters: BMW 530Li(String), LVIN2(String), 2024-07-18(LocalDate), 600000(BigDecimal)
21:01:58.215 [main] DEBUG space.yangtao.mapper.VehicleMapper.addVehicle - <== Updates: 1
21:01:58.215 [main] DEBUG space.yangtao.mapper.VehicleMapper.addVehicle - ==> Preparing: insert into tt_vehicle (product_name, vin, production_date, guide_price) values (?, ?, ?, ?)
21:01:58.215 [main] DEBUG space.yangtao.mapper.VehicleMapper.addVehicle - ==> Parameters: Benz E300L(String), LVIN3(String), 2024-07-18(LocalDate), 500000(BigDecimal)
21:01:58.216 [main] DEBUG space.yangtao.mapper.VehicleMapper.addVehicle - <== Updates: 1
21:01:58.216 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Committing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@207ea13]
21:01:58.218 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@207ea13]
21:01:58.218 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@207ea13]
21:01:58.219 [main] DEBUG o.apache.ibatis.datasource.pooled.PooledDataSource - Returned connection 34073107 to pool.
21:01:58.219 [main] INFO space.yangtao.client.VehicleMapperTest - --- testAddVehicle end ---
21:01:58.223 [main] INFO space.yangtao.client.VehicleMapperTest - --- testGetVehicleById start ---
21:01:58.224 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Opening JDBC Connection
21:01:58.224 [main] DEBUG o.apache.ibatis.datasource.pooled.PooledDataSource - Checked out connection 34073107 from pool.
21:01:58.224 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@207ea13]
21:01:58.225 [main] DEBUG space.yangtao.mapper.VehicleMapper.getVehicleById - ==> Preparing: select id as id, product_name as productName, vin as vin, production_date as productionDate, guide_price as guidePrice from tt_vehicle where id = ?
21:01:58.226 [main] DEBUG space.yangtao.mapper.VehicleMapper.getVehicleById - ==> Parameters: 1(Long)
21:01:58.235 [main] TRACE space.yangtao.mapper.VehicleMapper.getVehicleById - <== Columns: id, productName, vin, productionDate, guidePrice
21:01:58.236 [main] TRACE space.yangtao.mapper.VehicleMapper.getVehicleById - <== Row: 1, Audi A6L, LVIN1, 2024-07-18, 450000.00
21:01:58.238 [main] DEBUG space.yangtao.mapper.VehicleMapper.getVehicleById - <== Total: 1
VehiclePO(id=1, productName=Audi A6L, vin=LVIN1, productionDate=2024-07-18, guidePrice=450000.00)
21:01:58.240 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@207ea13]
21:01:58.240 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@207ea13]
21:01:58.240 [main] DEBUG o.apache.ibatis.datasource.pooled.PooledDataSource - Returned connection 34073107 to pool.
21:01:58.240 [main] INFO space.yangtao.client.VehicleMapperTest - --- testGetVehicleById end ---
21:01:58.241 [main] INFO space.yangtao.client.VehicleMapperTest - --- testUpdateVehicle start ---
21:01:58.242 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Opening JDBC Connection
21:01:58.242 [main] DEBUG o.apache.ibatis.datasource.pooled.PooledDataSource - Checked out connection 34073107 from pool.
21:01:58.242 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@207ea13]
21:01:58.242 [main] DEBUG space.yangtao.mapper.VehicleMapper.updateVehicle - ==> Preparing: update tt_vehicle set product_name = ?, vin = ?, production_date = ?, guide_price = ? where id = ?
21:01:58.242 [main] DEBUG space.yangtao.mapper.VehicleMapper.updateVehicle - ==> Parameters: Audi A6L(String), LVIN1(String), 2024-07-18(LocalDate), 450000(BigDecimal), 1(Long)
21:01:58.243 [main] DEBUG space.yangtao.mapper.VehicleMapper.updateVehicle - <== Updates: 1
21:01:58.243 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Committing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@207ea13]
21:01:58.244 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@207ea13]
21:01:58.244 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@207ea13]
21:01:58.244 [main] DEBUG o.apache.ibatis.datasource.pooled.PooledDataSource - Returned connection 34073107 to pool.
21:01:58.244 [main] INFO space.yangtao.client.VehicleMapperTest - --- testUpdateVehicle end ---
21:01:58.245 [main] INFO space.yangtao.client.VehicleMapperTest - --- testDeleteVehicle start ---
21:01:58.245 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Opening JDBC Connection
21:01:58.245 [main] DEBUG o.apache.ibatis.datasource.pooled.PooledDataSource - Checked out connection 34073107 from pool.
21:01:58.245 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@207ea13]
21:01:58.246 [main] DEBUG space.yangtao.mapper.VehicleMapper.deleteVehicle - ==> Preparing: delete from tt_vehicle where id = ?
21:01:58.246 [main] DEBUG space.yangtao.mapper.VehicleMapper.deleteVehicle - ==> Parameters: 1(Long)
21:01:58.247 [main] DEBUG space.yangtao.mapper.VehicleMapper.deleteVehicle - <== Updates: 1
21:01:58.247 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Committing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@207ea13]
21:01:58.249 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@207ea13]
21:01:58.250 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@207ea13]
21:01:58.250 [main] DEBUG o.apache.ibatis.datasource.pooled.PooledDataSource - Returned connection 34073107 to pool.
21:01:58.250 [main] INFO space.yangtao.client.VehicleMapperTest - --- testDeleteVehicle end ---
21:01:58.251 [main] INFO space.yangtao.client.VehicleMapperTest - --- testGetAllVehicles start ---
21:01:58.251 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Opening JDBC Connection
21:01:58.251 [main] DEBUG o.apache.ibatis.datasource.pooled.PooledDataSource - Checked out connection 34073107 from pool.
21:01:58.252 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Setting autocommit to false on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@207ea13]
21:01:58.252 [main] DEBUG space.yangtao.mapper.VehicleMapper.getAllVehicles - ==> Preparing: select id as id, product_name as productName, vin as vin, production_date as productionDate, guide_price as guidePrice from tt_vehicle
21:01:58.253 [main] DEBUG space.yangtao.mapper.VehicleMapper.getAllVehicles - ==> Parameters:
21:01:58.253 [main] TRACE space.yangtao.mapper.VehicleMapper.getAllVehicles - <== Columns: id, productName, vin, productionDate, guidePrice
21:01:58.253 [main] TRACE space.yangtao.mapper.VehicleMapper.getAllVehicles - <== Row: 2, BMW 530Li, LVIN2, 2024-07-18, 600000.00
21:01:58.253 [main] TRACE space.yangtao.mapper.VehicleMapper.getAllVehicles - <== Row: 3, Benz E300L, LVIN3, 2024-07-18, 500000.00
21:01:58.253 [main] DEBUG space.yangtao.mapper.VehicleMapper.getAllVehicles - <== Total: 2
VehiclePO(id=2, productName=BMW 530Li, vin=LVIN2, productionDate=2024-07-18, guidePrice=600000.00)
VehiclePO(id=3, productName=Benz E300L, vin=LVIN3, productionDate=2024-07-18, guidePrice=500000.00)
21:01:58.255 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Resetting autocommit to true on JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@207ea13]
21:01:58.255 [main] DEBUG org.apache.ibatis.transaction.jdbc.JdbcTransaction - Closing JDBC Connection [com.mysql.cj.jdbc.ConnectionImpl@207ea13]
21:01:58.255 [main] DEBUG o.apache.ibatis.datasource.pooled.PooledDataSource - Returned connection 34073107 to pool.
21:01:58.255 [main] INFO space.yangtao.client.VehicleMapperTest - --- testGetAllVehicles end ---由日志可以看出,SqlSession通过接口的动态代理对象已经实现了对Mapper文件中的SQL执行,并且在编码时分层清晰且可维护性高。
占位符
场景一
VehicleMapper.xml
中定义两个测试方法,都是以VIN
为条件,分别用#{}
和${}
传递参数
1 |
|
VehicleMapper.class
中定义对应的接口方法
1 |
|
运行测试程序
1 |
|
控制台输出日志如下
1 |
|
由日志可知,方法执行testD
方法时报错了,从日志的SQL
条件看,两条语句中,使用#{}
语句将#{}
替换为了?
,并通过预处理语句设置参数值,而${}
语句不使用预处理语句,而是直接将${}
替换为了参数值,SQL语句中对于字符串参数没有加引号''
,因此报错。
场景二
VehicleMapper.xml
中定义两个测试方法,查询参数为排序方式
1 |
|
VehicleMapper.class
中定义对应的接口方法
1 |
|
运行测试程序
1 |
|
控制台输出日志如下
1 |
|
由日志可知,方法在执行testG
方法时报错了,从日志的SQL
看,使用${}
传参的语句正确的实现了预想的功能,而#{}
传参的语句使用了预处理,将MySQL关键字asc
当成了参数,导致执行报错。
总结
预处理与直接替换
#{}
:使用预处理,安全地绑定变量。${}
:直接进行字符串替换,存在SQL
注入的风险。
应用场景
#{}
:对于大多数需要动态数据绑定的场景。${}
:可用于那些动态构建SQL
语句的场景,如动态表名或动态列名或排序方式,但必须非常小心处理变量值,确保安全性。
SQL 注入防御
#{}
:因为使用了预处理和参数绑定,能有效防止SQL
注入。${}
:由于缺乏预处理,用户输入如果不被适当清理和限制,可能会引起SQL
注入问题。
取别名
简介
在 MyBatis 中,<typeAliases>
标签用于定义Java类型的别名,以便在配置文件和映射文件中简化对Java类的引用。通过使用别名,可以避免在XML配置中反复写出长的类名,从而使配置文件更加简洁易读。
作用
- 简化类名:为长的完全限定类名(FQCN)提供一个简短的名称,使得配置文件中的引用更加简洁。
- 提高可读性:通过使用别名,配置文件的可读性得到提高,配置更加直观。
- 配置灵活性:允许开发者自定义别名,根据项目的需要,映射任意的类名。
使用方式
在mybatis-config.xml
中,在<typeAliases>
标签下取别名,别名可以在Mapper
文件中使用
1 |
|
注意:
- 别名默认不区分大小写,但可以在设置中,通过
caseSensitiveAliases
开启别名大小写敏感。 - 如果不同的类使用相同的简单名称,自动扫描可能会引发冲突,此时需要为冲突的类指定具体的别名。
- 使用别名时,确保别名的唯一性和一致性,避免在项目中引起混淆。
namespace
不能取别名。
获取主键
在许多开发场景中,插入一条数据库后,通常会希望的到新插入行自动生成的主键值,MyBatis中通过设置Mapper
文件中对应的insert
标签即可实现,如
1 |
|
useGeneratedKeys
:是否使用自动生成的主键keyProperty
:生成的主键值赋值给对象的哪个属性
运行测试程序
1 |
|
控制台输出日志如下
1 |
|
由日志可知新插入数据的主键ID
为4,经核查也与数据库中对应的主键ID
一致。
拓展:本方式适用于支持自动生成主键的数据库(如MySQL的AUTO_INCREMENT
)。对于不支持自动生成主键的数据库,需要使用selectKey
标签来获取主键。
参数处理
创建测试表tt_student
,同时插入3条测试数据
1 |
|
简单类型自动转换
- 基本数据类型:byte、short、int、long、float、double、char
- 基本数据类型包装类:Byte、Short、Integer、Long、Float、Double、Character
- 字符串类型:String
- 日期:java.util.Date、java.sql.Date、java.time.LocalDate、java.time.LocalDateTime
在Mapper文件StudentMapper.xml
中定义表tt_student
对应的方法
1 |
|
编写对应的接口方法
1 |
|
运行测试程序
1 |
|
控制台输出日志如下
1 |
|
根据日志可知,MyBatis自身有类型推断机制,对于简单数据类型和常用的时间日期类型可自动转换映射。
除了类型推断,还可以在Mapper中通过parameterType
指定参数类型,如
通过全限定类名指定类型
1
2
3<select id="getById" resultType="space.yangtao.domain.po.StudentPO" parameterType="java.lang.Long">
select * from tt_student where id = #{id}
</select>通过MyBatis的默认别名转换
1
2
3
4
5
6
7
8
9<!-- java.lang.String类型的默认别名为string -->
<select id="getByName" resultType="space.yangtao.domain.po.StudentPO" parameterType="string">
select * from tt_student where name = #{name}
</select>
<!-- Java基本数据类型double的默认别名为_double -->
<select id="getByHeight" resultType="space.yangtao.domain.po.StudentPO" parameterType="_double">
select * from tt_student where height = #{height}
</select>在
#{}
中通过属性javaType
指定Java的参数类型,通过属性jdbcType
指定MySQL表字段的参数类型1
2
3<select id="getByGender" resultType="space.yangtao.domain.po.StudentPO">
select * from tt_student where gender = #{gender, javaType=Character, jdbcType=C23 AR}
</select>123456789 -
对象或Map类型参数
在MyBatis Mapper中还支持将所有的参数都封装到一个实体对象或Map数据结构中,其中对象属性名或Map的key必须与#{param}
中的参数名一致。
在Mapper中定义查询语句
1 |
|
定义对应的接口方法
1 |
|
运行测试程序
1 |
|
控制台输出日志如下
1 |
|
由日志可知,Mapper已正确接收接口传递的参数。
多参数
在mapper中定义查询方法如下,其中参数占位符为#{id}
和#{name}
1 |
|
定义对应的接口方法
1 |
|
运行测试程序
1 |
|
控制台输出日志如下
1 |
|
程序运行出现异常,由日志可知异常原因是没有找到名为id
的参数,支持的参数有arg1
、arg0
、param1
、param2
,因此修改Mapper文件如下
1 |
|
再次运行,控制台输出日志如下
1 |
|
由日志可知,方法执行成功,查找到了需要的数据,此外arg0
和arg1
还可以分别替换为param1
和param2
,如
1 |
|
之所以要通过指定的参数,是因为MyBatis底层会自动将多个简单参数封装到一个Map型数据结构中,逻辑类似于
1 |
|
@Param命名参数
上面提到的多参数,MyBatis默认创建key为arg0... param1...
的Map型数据结构,MyBatis提供了@Param
注解,用于指定Mapper接口参数的名称,方便在SQl语句中引用这些参数。
定义SQL如下,其中参数占位符为#{id}
和#{name}
1 |
|
定义对应的接口方法
1 |
|
运行测试程序
1 |
|
控制台输出日志如下
1 |
|
由日志可知,@Param
已经将MyBatis默认的参数命名替换成了id
和name
。
除此之外,@Param
还可以应用于实体对象以及Map类型参数,甚至还可以使接口同时支持多个这种复杂参数。
在Mapper中指定了id对应参数为#{map1.id}
,name对应参数为#{map2.name}
1 |
|
对应的接口方法中也指定参数的@Param
值
1 |
|
测试测试程序
1 |
|
控制台输出日志如下
1 |
|
由日志可知,@Param
注解支持为多个参数进行不同的命名。
在实际开发中,对于复杂参数,应明确使用@Param
参数注解,特别是在团队协作和代码审查时,有助于避免参数混淆。
返回值处理
单个对象
单个对象可以通过实体对象或Map接收Mapper SQL的返回值。
在mapper中定义查询方法
1 |
|
定义对应的接口方法
1 |
|
运行测试程序
1 |
|
控制台输出日志如下
1 |
|
由日志可知,实体类和Map结构都能接收单行数据。
多个对象
如果SQL查询可能存在多行,则在接口中需要通过集合来接收,如List数据结构
在mapper定义查询方法
1 |
|
定义对应的接口方法
1 |
|
运行测试程序
1 |
|
控制台输出日志如下
1 |
|
由日志可知,对于多行数据,Java的接口方法可以使用集合类数据结构接收,如List,List的泛型可为实体类或Map。
Map<Object, Map<String, Object>>
在日常中可能会遇到需要将SQL查询的结果封装成Map<Object, Map<String, Object>>这种结构,其中Map的key为指定列的值,value为行。
例如将列id作为查询结果的key,行封装为Map作为value
mapper中定义方法
1 |
|
定义对应的接口方法,此时需要使用@MapKey
注解定义返回结果的key值取的列名称(此处为id
)
1 |
|
运行测试程序
1 |
|
控制台输出日志如下
1 |
|
由日志可知,@MapKey
注解可以将查询的结果封装成Map<Object, Map<String, Object>>结构对象。
resultMap
前面提到过,如果数据库中的列名与实体对象中的字段名对应不上,可以在Mapper文件中为SQL字段取别名。除此之外,还可以通过resultMap
进行结果映射
在Mapper中定义resultMap
以及对应的查询方法,并在标签resultMap
中将数据库字段create_time
映射到了实体类的createTime
属性
1 |
|
定义对应的接口方法
1 |
|
运行测试程序
1 |
|
控制台输出日志如下
1 |
|
由日志可知,方法的返回结果createTime字段正确取值为SQL中create_time字段的值,resultMap已成功为两个字段进行映射。
驼峰命名自动映射
如果MyBatis开启了驼峰命名自动自动映射、且实体类属性符合驼峰命名规范、SQL字段符合单词小写、单词之间采用下划线分割,那么Java实体类中的属性能与SQL的字段进行映射。
全局配置文件mybatis-config.xml
的setting
标签中开启命名自动映射
1 |
|
Mapper中对应的SQL为
1 |
|
对应的接口方法为
1 |
|
此时实体类属性createTime与SQL字段create_time符合驼峰命名自动映射条件
运行测试程序
1 |
|
控制台输出日志如下
1 |
|
由日志可知,驼峰命名自动映射已为实体属性createTime与数据库字段create_time进行了映射。
总结
MyBatis面向接口编程不仅提高了开发效率,也提升了代码的可维护性和可扩展性。通过将SQL操作封装在接口中,MyBatis使得SQL语句的管理变得更加集中和系统化,极大地降低了数据库操作的复杂性。本文详细说明了如何有效地利用MyBatis提供的这一功能,是理解和应用MyBatis核心功能的重要资源。
本博客所有文章除特别声明外,均采用 CC BY-SA 4.0 协议 ,转载请注明出处!