
笔记 Mybatis/plus
总体概述
JDBC
- SQL 夹杂在Java代码中耦合度高,导致硬编码内伤
- 维护不易且实际开发需求中 SQL 有变化,频繁修改的情况多见
- 代码冗长,开发效率低
Hibernate 和 JPA
- 操作简便,开发效率高
- 程序中的长难复杂 SQL 需要绕过框架
- 内部自动生成的 SQL,不容易做特殊优化
- 基于全映射的全自动框架,大量字段的 POJO 进行部分映射时比较困难。
- 反射操作太多,导致数据库性能下降
MyBatis
- 轻量级,性能出色
- SQL 和 Java 编码分开,功能边界清晰。Java代码专注业务、SQL语句专注数据
开发效率:Hibernate>Mybatis>JDBC
运行效率:JDBC>Mybatis>Hibernate
基本使用
向SQL语句传参
#{}形式:Mybatis会将SQL语句中的#{}转换为问号占位符。
${}形式:Mybatis做的是字符串拼接操作,直接拼上去。
通常不会采用${}的方式传值。实际开发中,能用#{}实现的,肯定不用${}。
示例
假设我们有一个用户输入的参数username
,我们想要根据这个参数来查询数据库中的用户信息。
使用${}
(不推荐)
1 | <select id="selectUserByUsername" resultType="com.example.User"> |
如果用户输入的username
是' OR '1'='1
,那么生成的SQL语句将会是:
1 | SELECT * FROM users WHERE username = '' OR '1'='1' |
这个SQL语句将会返回所有用户的信息,因为'1'='1
始终为真。
使用#{}
(推荐)
1 | <select id="selectUserByUsername" resultType="com.example.User"> |
如果用户输入的username
是' OR '1'='1
,那么生成的SQL语句将会是:
1 | SELECT * FROM users WHERE username = '' OR '1'='1' |
但是,由于使用了预处理语句,数据库会将' OR '1'='1
视为纯数据,而不是SQL语句的一部分,因此只会返回username
为' OR '1'='1
的用户信息(如果没有这样的用户,将返回空结果)。
通过使用#{}
,我们可以有效地防止SQL注入攻击,确保应用程序的安全性。
数据输入
这里数据输入具体是指上层方法(例如Service方法)调用Mapper接口时,数据传入的形式。
- 简单类型:只包含一个值的数据类型
- 基本数据类型:int、byte、short、double、……
- 基本数据类型的包装类型:Integer、Character、Double、……
- 字符串类型:String
- 复杂类型:包含多个值的数据类型
- 实体类类型:Employee、Department、……
- 集合类型:List、Set、Map、……
- 数组类型:int[]、String[]、……
- 复合类型:List
、实体类中包含集合……
单个简单类型参数
Mapper接口中抽象方法的声明
1 | Employee selectEmployee(Integer empId); |
SQL语句
1 | <select id="selectEmployee" resultType="com.atguigu.mybatis.entity.Employee"> |
单个简单类型参数,在#{}中通常和接口方法参数同名。
多个简单类型参数
零散的多个简单类型参数需要特殊处理,使用(@Param("empId")
标记,否则Mybatis无法识别自定义名称:
Mapper接口中抽象方法的声明
1 | int updateEmployee(; Integer empId, Double empSalary) |
SQL语句
1 | <update id="updateEmployee"> |
实体类类型参数
Mapper接口中抽象方法的声明
1 | int insertEmployee(Employee employee); |
SQL语句
1 | <insert id="insertEmployee"> |
Mybatis会根据#{}中传入的数据,加工成getXxx()方法,通过反射在实体类对象中调用这个方法,从而获取到对应的数据。填充到#{}解析后的问号占位符这个位置。
Map类型参数
Mapper接口中抽象方法的声明
1 | int updateEmployeeByMap(Map<String, Object> paramMap); |
SQL语句
1 | <update id="updateEmployeeByMap"> |
#{}中写Map中的key。
数据输出
数据输出总体上有两种形式:
- 增删改操作返回的受影响行数:直接使用 int 或 long 类型接收即可
- 查询操作的查询结果
我们需要做的是,指定查询的输出数据类型,在插入场景下,实现主键数据回显。
单个简单类型
Mapper接口中的抽象方法
1 | int selectEmpCount(); |
SQL语句
1 | <select id="selectEmpCount" resultType="int"> |
实体类对象
Mapper接口的抽象方法
1 | Employee selectEmployee(Integer empId); |
SQL语句
1 | <!-- 编写具体的SQL语句,使用id属性唯一的标记一条SQL语句 --> |
通过给数据库表字段加别名,让查询结果的每一列都和Java实体类中属性对应起来。
在 Mybatis 全局配置文件中,做了下面的配置,select语句中可以不给字段设置别名
1 | <!-- 在全局范围内对Mybatis进行配置 --> |
Map类型
适用于SQL查询返回的各个字段综合起来并不和任何一个现有的实体类对应,没法封装到实体类对象中。能够封装成实体类类型的,就不使用Map类型。
Mapper接口的抽象方法
1 | Map<String,Object> selectEmpNameAndMaxSalary(); |
SQL语句
1 | <!-- Map<String,Object> selectEmpNameAndMaxSalary(); --> |
主键
自增长类型主键
Mapper接口中的抽象方法
1 | int insertEmployee(Employee employee); |
1 | <!-- int insertEmployee(Employee employee); --> |
Mybatis是将自增主键的值设置到实体类对象中,而不是以Mapper接口方法返回值的形式返回。
非自增长类型主键
而对于不支持自增型主键的数据库(例如 Oracle)或者字符串类型主键,则可以使用 selectKey 子元素:selectKey 元素将会首先运行,id 会被设置,然后插入语句会被调用!
使用 selectKey
帮助插入UUID作为字符串类型主键示例:
1 | <insert id="insertUser" parameterType="User"> |
MapperXML标签总结
MyBatis 的真正强大在于它的语句映射,SQL 映射文件只有很少的几个顶级元素(按照应被定义的顺序列出):
insert
– 映射插入语句。update
– 映射更新语句。delete
– 映射删除语句。select
– 映射查询语句。
select标签:
MyBatis 在查询和结果映射做了相当多的改进。一个简单查询的 select 元素是非常简单:
1 | <select id="selectPerson" |
这个语句名为 selectPerson,接受一个 int(或 Integer)类型的参数,并返回一个 HashMap 类型的对象,其中的键是列名,值便是结果行中的对应值。
注意参数符号:#{id} ${key}
MyBatis 创建一个预处理语句(PreparedStatement)参数,在 JDBC 中,这样的一个参数在 SQL 中会由一个“?”来标识,并被传递到一个新的预处理语句中,就像这样:
1 | // 近似的 JDBC 代码,非 MyBatis 代码... |
select 元素允许你配置很多属性来配置每条语句的行为细节:
属性 | 描述 |
---|---|
id |
在命名空间中唯一的标识符,可以被用来引用这条语句。 |
resultType |
期望从这条语句中返回结果的类全限定名或别名。 注意,如果返回的是集合,那应该设置为集合包含的类型,而不是集合本身的类型。 resultType 和 resultMap 之间只能同时使用一个。 |
resultMap |
对外部 resultMap 的命名引用。结果映射是 MyBatis 最强大的特性,如果你对其理解透彻,许多复杂的映射问题都能迎刃而解。 resultType 和 resultMap 之间只能同时使用一个。 |
timeout |
这个设置是在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。默认值为未设置(unset)(依赖数据库驱动)。 |
statementType |
可选 STATEMENT,PREPARED 或 CALLABLE。这会让 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement,默认值:PREPARED。 |
insert, update 和 delete标签:
数据变更语句 insert,update 和 delete 的实现非常接近:
1 | <insert |
属性 | 描述 |
---|---|
id |
在命名空间中唯一的标识符,可以被用来引用这条语句。 |
timeout |
这个设置是在抛出异常之前,驱动程序等待数据库返回请求结果的秒数。默认值为未设置(unset)(依赖数据库驱动)。 |
statementType |
可选 STATEMENT,PREPARED 或 CALLABLE。这会让 MyBatis 分别使用 Statement,PreparedStatement 或 CallableStatement,默认值:PREPARED。 |
useGeneratedKeys |
(仅适用于 insert 和 update)这会令 MyBatis 使用 JDBC 的 getGeneratedKeys 方法来取出由数据库内部生成的主键(比如:像 MySQL 和 SQL Server 这样的关系型数据库管理系统的自动递增字段),默认值:false。 |
keyProperty |
(仅适用于 insert 和 update)指定能够唯一识别对象的属性,MyBatis 会使用 getGeneratedKeys 的返回值或 insert 语句的 selectKey 子元素设置它的值,默认值:未设置(unset )。如果生成列不止一个,可以用逗号分隔多个属性名称。 |
keyColumn |
(仅适用于 insert 和 update)设置生成键值在表中的列名,在某些数据库(像 PostgreSQL)中,当主键列不是表中的第一列的时候,是必须设置的。如果生成列不止一个,可以用逗号分隔多个属性名称。 |
多表映射
MyBatis 思想是:数据库不可能永远是你所想或所需的那个样子。 我们希望每个数据库都具备良好的第三范式或 BCNF 范式,可惜它们并不都是那样。 如果能有一种数据库映射模式,完美适配所有的应用程序查询需求,那就太好了,而 ResultMap 就是 MyBatis 就是完美答案。
实体类设计方案
多表关系回顾:(双向查看)
一对一
夫妻关系,人和身份证号
一对多| 多对一
用户和用户的订单,锁和钥匙
多对多
老师和学生,部门和员工
实体类设计关系(查询):(单向查看)
对一 : 夫妻一方对应另一方,订单对应用户都是对一关系
实体类设计:对一关系下,类中只要包含单个对方对象类型属性即可!
例如:
1 | public class Customer { |
对多: 用户对应的订单,讲师对应的学生或者学生对应的讲师都是对多关系:
实体类设计:对多关系下,类中只要包含对方类型集合属性即可!
1 | public class Customer { |
多表结果实体类设计技巧:
对一,属性中包含对方对象。
对多,属性中包含对方对象集合。
只有真实发生多表查询时,才需要设计和修改实体类,否则不提前设计和修改实体类。
无论多少张表联查,实体类设计都是两两考虑。在查询映射的时候,只需要关注本次查询相关的属性。
例如:查询订单和对应的客户,就不要关注客户中的订单集合。
多表映射总结
关联关系 | 配置项关键词 | 所在配置文件和具体位置 |
---|---|---|
对一 | association标签/javaType属性/property属性 | Mapper配置文件中的resultMap标签内 |
对多 | collection标签/ofType属性/property属性 | Mapper配置文件中的resultMap标签内 |
动态SQL
if/where标签
使用动态 SQL 最常见情景是根据条件包含 where / if 子句的一部分。比如:
1 | <!-- List<Employee> selectEmployeeByCondition(Employee employee); --> |
set标签
1 | <!-- void updateEmployeeDynamic(Employee employee) --> |
choose/when/otherwise标签
在多个分支条件中,仅执行一个。
- 从上到下依次执行条件判断
- 遇到的第一个满足条件的分支会被采纳
- 被采纳分支后面的分支都将不被考虑
- 如果所有的when分支都不满足,那么就执行otherwise分支
1 | <!-- List<Employee> selectEmployeeByConditionByChoose(Employee employee) --> |
foreach标签
基本用法。用批量插入举例
1 | <!-- |
批量更新时需要注意,上面批量插入的例子本质上是一条SQL语句,而实现批量更新则需要多条SQL语句拼起来,用分号分开。也就是一次性发送多条SQL语句让数据库执行。此时需要在数据库连接信息的URL地址中设置:
1 | atguigu.dev.url=jdbc:mysql:///mybatis-example?allowMultiQueries=true |
对应的foreach标签如下:
1 | <!-- int updateEmployeeBatch(@Param("empList") List<Employee> empList) --> |
关于foreach标签的collection属性
如果没有给接口中List类型的参数使用@Param注解指定一个具体的名字,那么在collection属性中默认可以使用collection或list来引用这个list集合。这一点可以通过异常信息看出来:
1 | Parameter 'empList' not found. Available parameters are [arg0, collection, list] |
在实际开发中,为了避免隐晦的表达造成一定的误会,建议使用@Param注解明确声明变量的名称,然后在foreach标签的collection属性中按照@Param注解指定的名称来引用传入的参数。
sql片段
抽取重复的SQL片段
1 | <!-- 使用sql标签抽取重复出现的SQL片段 --> |
引用已抽取的SQL片段
1 | <!-- 使用include标签引用声明的SQL片段 --> |
扩展
Mapper批量映射优化
Mapper 配置文件很多时,在全局配置文件中一个一个注册太麻烦,希望有一个办法能够一劳永逸。
Mybatis 允许在指定 Mapper 映射文件时,只指定其所在的包:
1 | <mappers> |
此时这个包下的所有 Mapper 配置文件将被自动加载、注册,比较方便。
资源创建要求:
- Mapper 接口和 Mapper 配置文件名称一致
- Mapper 接口:EmployeeMapper.java
- Mapper 配置文件:EmployeeMapper.xml
- Mapper 配置文件放在 Mapper 接口所在的包内
- 可以将mapperxml文件放在mapper接口所在的包!
- 可以在sources下创建mapper接口包一致的文件夹结构存放mapperxml文件
ORM思维
ORM(Object-Relational Mapping,对象-关系映射)是一种将数据库和面向对象编程语言中的对象之间进行转换的技术。它将对象和关系数据库的概念进行映射,可以通过方法调用进行数据库操作,最终,让我们可以使用面向对象思维进行数据库操作。
ORM 框架通常有半自动和全自动两种方式。
- 半自动 ORM 通常需要程序员手动编写 SQL 语句或者配置文件,将实体类和数据表进行映射,还需要手动将查询的结果集转换成实体对象。
- 全自动 ORM 则是将实体类和数据表进行自动映射,使用 API 进行数据库操作时,ORM 框架会自动执行 SQL 语句并将查询结果转换成实体对象,程序员无需再手动编写 SQL 语句和转换代码。
下面是半自动和全自动 ORM 框架的区别:
- 映射方式:半自动 ORM 框架需要程序员手动指定实体类和数据表之间的映射关系,通常使用 XML 文件或注解方式来指定;全自动 ORM 框架则可以自动进行实体类和数据表的映射,无需手动干预。
- 查询方式:半自动 ORM 框架通常需要程序员手动编写 SQL 语句并将查询结果集转换成实体对象;全自动 ORM 框架可以自动组装 SQL 语句、执行查询操作,并将查询结果转换成实体对象。
- 性能:由于半自动 ORM 框架需要手动编写 SQL 语句,因此程序员必须对 SQL 语句和数据库的底层知识有一定的了解,才能编写高效的 SQL 语句;而全自动 ORM 框架通过自动优化生成的 SQL 语句来提高性能,程序员无需进行优化。
- 学习成本:半自动 ORM 框架需要程序员手动编写 SQL 语句和映射配置,要求程序员具备较高的数据库和 SQL 知识;全自动 ORM 框架可以自动生成 SQL 语句和映射配置,程序员无需了解过多的数据库和 SQL 知识。
常见的半自动 ORM 框架包括 MyBatis 等;常见的全自动 ORM 框架包括 Hibernate、Spring Data JPA、MyBatis-Plus 等。
基本操作
MyBatis整合步骤:
1. 导入依赖:在您的Spring Boot项目的构建文件(如pom.xml)中添加MyBatis和数据库驱动的相关依赖。例如,如果使用MySQL数据库,您需要添加MyBatis和MySQL驱动的依赖。
2. 配置数据源:在`application.properties`或`application.yml`中配置数据库连接信息,包括数据库URL、用户名、密码、mybatis的功能配置等。
3. 创建实体类:创建与数据库表对应的实体类。
4. 创建Mapper接口:创建与数据库表交互的Mapper接口。
5. 创建Mapper接口SQL实现: 可以使用mapperxml文件或者注解方式
6. 创建程序启动类
7. 注解扫描:在Spring Boot的主应用类上添加`@MapperScan`注解,用于扫描和注册Mapper接口。
8. 使用Mapper接口:在需要使用数据库操作的地方,通过依赖注入或直接实例化Mapper接口,并调用其中的方法进行数据库操作。
配置类:
1 | server: |
这里的mapper接口在XML文件里实现。
位置:resources/mapper/UserMapper.xml
1 |
|
然后启动类加一个接口扫描。
1 | //mapper接口扫描配置 |
AOP整合配置
依赖导入:
1 | <dependency> |
直接使用aop注解即可:
1 |
|
声明式事务配置
依赖导入:
1 | <dependency> |
注:SpringBoot项目会自动配置一个 DataSourceTransactionManager,所以我们只需在方法(或者类)加上 @Transactional 注解,就自动纳入 Spring 的事务管理了
1 |
|