本文主要通过以下几个方面来系统的介绍 MyBatis 框架:
参考资料:
零、MyBatis简单介绍
MyBatis 是一个基于 Java 的持久化框架 ,支持定制化 SQL
(自行编写 SQL
语句维护数据库)、存储过程及高级映射。它几乎避免了所有的 JDBC 代码和获取结果集。
与其他持久化层技术的对比
JDBC:SQL
语句于业务语句耦合度高,不易更改维护;代码冗长
JPA/Hibernate:操作便捷,开发效率高;但是因为 SQL
语句是自动生成的,不容易做特殊优化
MyBatis:轻量级,SQL
与 Java 编码解耦合,易于维护;开发效率稍逊于 Hibernate。
一、搭建 MyBatis 环境
1. 开发环境
IDE:IntellJ IDEA ver.2021.3
构建工具:Maven ver.3.8.5
JDK 版本:jdk 1.8
MySQL 版本:MySQL ver.8.0.28
2. 使用 Maven 构建项目
引入依赖
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 <dependencies > <dependency > <groupId > org.mybatis</groupId > <artifactId > mybatis</artifactId > <version > 3.5.7</version > </dependency > <dependency > <groupId > junit</groupId > <artifactId > junit</artifactId > <version > 4.12</version > <scope > test</scope > </dependency > <dependency > <groupId > mysql</groupId > <artifactId > mysql-connector-java</artifactId > <version > 5.1.3</version > </dependency > </dependencies >
3. 创建实体类与数据库表
(1) 创建实体类
在 MyBaits
框架中,所有实体类我们都习惯性的将它们放在 pojo
这个包下。
例:User.java
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 package fr.gdai.mybatis.pojo;public class User { private int id; private String name; private String password; private int age; private String gender; private String email; public User () {} public User (int id, String name, String password, int age, String gender, String email) { this .id = id; this .name = name; this .password = password; this .age = age; this .gender = gender; this .email = email; } public int getId () {return id;} public String getName () {return name;} public String getPassword () {return password;} public int getAge () {return age;} public String getGender () {return gender;} public String getEmail () {return email;} public void setId (int id) {this .id = id;} public void setName (String name) {this .name = name;} public void setPassword (String password) {this .password = password;} public void setAge (int age) {this .age = age;} public void setGender (String gender) {this .gender = gender;} public void setEmail (String email) {this .email = email;} @Override public String toString () { return "User{" + "id=" + id + ", name='" + name + '\'' + ", password='" + password + '\'' + ", age=" + age + ", gender='" + gender + '\'' + ", email='" + email + '\'' + '}' ; } }
(2) 根据实体类创建数据库表结构
1 mysql -h [数据库的主机名] -u [数据库用户名] -P [端口] -p
1 2 3 4 5 6 7 8 9 10 create database mybatis_test if not exists default charset utf8;use mybatis_test; create table t_user ( id int auto_increment primary key comment '用户ID' , username varchar (20 ) comment '用户名' , password varchar (20 ) comment '用户密码' , age int comment '用户年龄' , gender varchar (1 ) comment '用户性别' , email varchar (20 ) comment '用户邮箱' ) comment 'test' ;
4. 创建 MyBatis 核心配置文件
以下给出一个 mybatis-config.xml
模版:
注意 :
在 <environments>
标签内设置连接数据库的环境时,我们使用了外挂的 jdbc.properties
文件来配置数据库连接的参数 ,所以我们需要将这个文件使用 <properties>
标签加载
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 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "https://mybatis.org/dtd/mybatis-3-config.dtd" > <configuration > <properties resource ="" /> <typeAliases > <typeAlias type ="需要设置别名的类的全类名,如<fr.gdai.mybatis.pojo.User>" alias ="别名" /> <package name ="需要设置别名的类所在的包,如<fr.gdai.mybatis.pojo>" /> </typeAliases > <environments default ="development" > <environment id ="development" > <transactionManager type ="JDBC" /> <dataSource type ="POOLED" > <property name ="driver" value ="${jdbc.driver}" /> <property name ="url" value ="${jdbc.url}" /> <property name ="username" value ="${jdbc.name}" /> <property name ="password" value ="${jdbc.password}" /> </dataSource > </environment > </environments > <mappers > <mapper resource ="需要引入的映射文件,如<fr/gdai/mybatis/mapper/xxxMapper.xml>" /> <package name ="需要引入的映射文件所在包的名字,如<fr.gdai.mybatis.mapper>" /> </mappers > </configuration >
jdbc.properties
1 2 3 4 jdbc.driver =com.mysql.jdbc.Driver jdbc.url =jdbc:mysql://localhost:3306/mybatis_test jdbc.name =root jdbc.password =root
5. 配置并使用 log4j
日志
(1) 添加 maven
依赖
1 2 3 4 5 <dependency > <groupId > log4j</groupId > <artifactId > log4j</artifactId > <version > 1.2.17</version > </dependency >
(2) 配置 log4j
的配置文件
以下给出一个 log4j.xml
模版:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE log4j :configuration SYSTEM "log4j.dtd" > <log4j:configuration xmlns:log4j ="http://jakarta.apache.org/log4j/" > <appender name ="STDOUT" class ="org.apache.log4j.ConsoleAppender" > <param name ="Encoding" value ="UTF-8" /> <layout class ="org.apache.log4j.PatternLayout" > <param name ="ConversionPattern" value ="%-5p %d{MM-dd HH:mm:ss,SSS} %m (%F:%L) \n" /> </layout > </appender > <logger name ="java.sql" > <level value ="debug" /> </logger > <logger name ="org.apache.ibatis" > <level value ="info" /> </logger > <root > <level value ="debug" /> <appender-ref ref ="STDOUT" /> </root > </log4j:configuration >
6. 创建 mapper
接口
MyBatis
中的 mapper
接口相当于以前的 dao
层。但是区别在于,mapper
仅仅是接口 ,不需要提供实现类 ,实现由 MyBatis 框架提供。
1 2 3 4 5 6 package fr.gdai.mybatis.mapper;public interface UserMapper { int insertUser () ; }
7. 创建 mapper
接口的 .xml
映射文件
(1) 对象关系映射
相关概念:ORM (Object Relationship Mapping)
对象关系映射。
对象 → \to → Java 的实体类对象
关系 → \to → 关系型数据库
映射 → \to → 二者之间的对应关系
Java概念
数据库概念
类
表
属性
字段/列
对象
记录/行
(2) .xml
映射文件
.xml
映射文件用于编写 SQL
语句 ,以访问、操作表中的数据 ;
.xml
映射文件应该存放在如下目录(按照 Maven
的规范),同时需要与 mapper
接口处于同一包下 (即 mapper
接口的全类名 和映射文件的命名空间 namespace
保持一致 )
1 $PROJECT_ROOT/src/main/resources/
表所对应的实体类的类名+Mapper.xml
,因此一个映射文件对应一个实体类,对应一张表的操作。例如:
表名
对应的实体类
实体类的 mapper
接口
mapper
映射文件
t_user
User.java
UserMapper.java
UserMapper.xml
以下给出一个 XxxMapper.xml
模版:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <?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 ="mapper接口的全类名" > <select id ="mapper接口的方法名" > </select > <delete id ="mapper接口里的方法名" > </delete > <update id ="mapper接口里的方法名" > </update > <insert id ="insertUser" > insert into t_user values(null, 'gdai', 'gdai', 23, '男', 'gdai@gdai.com') </insert > </mapper >
8. 与数据库建立连接并测试
(1) 与数据库建立连接:SqlSessionUtils
SqlSession
:代表 Java 程序和数据库之间的会话 (类比 HttpSession
是Java 程序和浏览器之间的会话)
SqlSessionFactory
:是“生产” SqlSession
的“工厂”
了解了以上两点,我们就可以在如下目录内新建一个工具类 SqlSessionUtils.java
(如下所示),通过调用静态方法 getSqlSession():SqlSession
来获得一个 SqlSession
类(Java 程序和数据库之间的会话)
1 $PROJECT_ROOT/src/main/java/项目包名/utils
SqlSessionUtils.java
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 package fr.gdai.mybatis.utils;import org.apache.ibatis.io.Resources;import org.apache.ibatis.session.SqlSession;import org.apache.ibatis.session.SqlSessionFactory;import org.apache.ibatis.session.SqlSessionFactoryBuilder;import java.io.IOException;import java.io.InputStream;public class SqlSessionUtils { public static SqlSession getSqlSession () { SqlSession sqlSession = null ; try { InputStream inputStream = Resources.getResourceAsStream("myBatis-config.xml" ); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder ().build(inputStream); sqlSession = sqlSessionFactory.openSession(true ); } catch (IOException e) { e.printStackTrace(); } return sqlSession; } }
(2) 测试连接
a. MyBatis
自动通过代理模式 创建 XxxMapper
接口的代理实现类对象
1 XxxMapper xxxMapper = sqlSession.getMapper(XxxMapper.class);
b. 调用 XxxMapper
接口中的方法,就可以根据 XxxMapper
的全类名 匹配 .xml
映射文件,通过调用的方法名 匹配映射文件中的 SQL
标签 <id>
,并执行标签中的 SQL
语句
1 Object result = xxxMapper._functions();
insertUser()
的完整演示:
1 2 3 4 5 6 @Test public void testInsertUser () { SqlSession sqlSession = SqlSessionUtils.getSqlSession(); UserMapper userMapper = sqlSession.getMapper(UserMapper.class); assert 1 == userMapper.insertUser(); }
二、核心配置文件详解
0 标签的顺序
核心配置文件中的标签必须按照固定的顺序 :(有的标签可以不写,但顺序一定不能更改)
1 <properties > , <settings > , <typeAliases > , <typeHandlers > , <objectFactory > , <objectWrapperFactory > , <reflectorFactory > , <plugins > , <environments > , <databaseIdProvider > , <mappers >
以下给出一个详细的核心配置文件 mybatis-config.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 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 78 79 80 <?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "https://mybatis.org/dtd/mybatis-3-config.dtd" > <configuration > <properties resource ="properties文件名" /> <typeAliases > <typeAlias type ="需要设置别名的类的全类名,如<fr.gdai.mybatis.pojo.User>" alias ="别名" /> <package name ="需要设置别名的类所在的包,如<fr.gdai.mybatis.pojo>" /> </typeAliases > <environments default ="development" > <environment id ="development" > <transactionManager type ="JDBC" /> <dataSource type ="POOLED" > <property name ="driver" value ="${jdbc.driver}" /> <property name ="url" value ="${jdbc.url}" /> <property name ="username" value ="${jdbc.name}" /> <property name ="password" value ="${jdbc.password}" /> </dataSource > </environment > </environments > <mappers > <mapper resource ="需要引入的映射文件,如<fr/gdai/mybatis/mapper/xxxMapper.xml>" /> <package name ="需要引入的映射文件所在包的名字,如<fr.gdai.mybatis.mapper>" /> </mappers > </configuration >
1 <properties>
标签
1 <properties resource ="xxx.properties" > </properties >
引入 .properties
文件,此时就可以 ${属性名}
的方式访问 .properties
文件中属性值。
resource
:.properties
文件的地址
注意 ⚠️:
需要注意不同的 .properties
文件中同名的属性名会冲突
2 <settings>
标签
这是 MyBatis
中全局设置 ,它们会改变 MyBatis
的运行时行为 。 下表描述了设置中常用的设置的含义、默认值等。详情跳转到 MyBatis
官方中文开发文档 - settings
。
设置名
描述
有效值
默认值
cacheEnabled
全局性地开启或关闭所有映射器配置文件中已配置的任何缓存。
true|false
true
lazyLoadingEnabled
延迟加载的全局开关。当开启时,所有关联对象都会延迟加载。 特定关联关系中可通过设置 fetchType
属性来覆盖该项的开关状态(lazy | eager
)。
true|false
false
aggressiveLazyLoading
当开启时,任何方法的调用都会加载该对象的所有属性。 否则,每个属性会按需加载
true|false
false
mapUnderscoreToCamelCase
是否开启驼峰命名自动映射,即从经典数据库列名 a_column
映射到经典 Java 属性名 aColumn
。
true|false
false
3 <typeAliases>
标签
1 2 3 4 5 6 <typeAliases > <typeAlias type ="需要设置别名的类的全类名,如<fr.gdai.mybatis.pojo.User>" alias ="别名" /> <package name ="需要设置别名的类所在的包,如<fr.gdai.mybatis.pojo>" /> </typeAliases >
MyBatis
中默认的别名
别名
映射的类型
_byte
byte
_char
(since 3.5.10)
char
_character
(since 3.5.10)
char
_long
long
_short
short
_int
int
_integer
int
_double
double
_float
float
_boolean
boolean
string
String
byte
Byte
char
(since 3.5.10)
Character
character
(since 3.5.10)
Character
long
Long
short
Short
int
Integer
integer
Integer
double
Double
float
Float
boolean
Boolean
date
Date
decimal
BigDecimal
bigdecimal
BigDecimal
biginteger
BigInteger
object
Object
date[]
Date[]
decimal[]
BigDecimal[]
bigdecimal[]
BigDecimal[]
biginteger[]
BigInteger[]
object[]
Object[]
map
Map
hashmap
HashMap
list
List
arraylist
ArrayList
collection
Collection
iterator
Iterator
9 <environments>
标签
9.1 <environments>
1 2 3 4 5 6 7 8 <environments default ="" > <environment id ="" > <transactionManager type ="" /> <dataSource type ="" > <property name ="" value ="" /> </dataSource > </environment > </environments >
9.2 <environment>
<environment>
:设置具体 的连接数据库的环境信息
属性:id
:设置环境的唯一标识 ,可通过 <environments>
标签中的 default
设置某一个环境的id,表示默认使用的环境
<transactionManager>
:设置事务管理方式
属性:type
:设置事务管理方式,type="JDBC|MANAGED"
type="JDBC"
:设置当前环境的事务管理都必须手动处理
type="MANAGED"
:设置事务被管理,例如 Spring 来管理
<dataSource>
:设置数据源
属性:type
:设置数据源的类型,type="POOLED|UNPOOLED|JNDI"
type="POOLED"
:使用数据库连接池 ,即会将创建的连接进行缓存,下次使用可以从缓存中直接获取,不需要重新创建
type="UNPOOLED"
:不使用数据库连接池,即每次使用连接都需要重新创建
type="JNDI"
:调用上下文中的数据源
11 <mappers>
标签
注意 ⚠️:
当以包为单位 ,将包下所有的映射文件引入核心配置文件时需要保证
mapper
接口和 mapper
映射文件必须在相同的包下
mapper
接口要和 mapper
映射文件的名字一致
1 2 3 4 5 6 7 8 9 10 11 <mappers > <mapper resource ="XxxMapper.xml" /> <package name ="com.atguigu.mybatis.mapper" /> </mappers >
首先,我们需要告诉 MyBatis 到哪里去找到这些语句。可以使用如下方法:
相对于类路径 的资源引用
1 <mapper resource ="相对路径/XxxMapper.xml" />
完全限定资源定位符(包括 file:///
形式的 URL)
1 <mapper url ="file:///绝对路径/XxxMapper.xml" />
类名
1 <mapper class ="全类名.XxxMapper" />
包名
1 <package name ="需要引入的映射文件所在包的名字" />
三、MyBatis 中的基本使用
在 MyBatis 框架中,我们需要做数据持久层 (DAO
)即可。可以通过两种方式实现:
注解(以注解的形式在接口中配置 SQL
)
配置文件(将 SQL
配置在与接口对应的 .xml
文件中)
注意 ⚠️: 在以后的实验代码中,我会将注释和配置文件悉数给出,后不再另做说明*
0 简单查询
0.1 查询返回一个实体类对象
如果查询出的数据只有一条 ,可以通过
实体类对象 接收
List 集合 接收
Map 集合 接收,结果以属性名 为 key
,属性值 为 value
1 2 3 4 5 6 7 8 9 10 11 12 13 package fr.gdai.mybatis.mapper;import fr.gdai.mybatis.pojo.User;public interface UserMapper { @Select("select * from t_user where id=1") User getUserById1 () ; @Select("select * from t_user where id=1") List<User> getUserById1InList () ; @Select("select * from t_user where id=1") Map<String, Object> getUserById1InMap () ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <select id ="getUserById1" resultType ="User" > select * from t_user where id=1 </select > <select id ="getUserById1InList" resultType ="User" > select * from t_user where id=1 </select > <select id ="getUserById1InMap" resultType ="map" > select * from t_user where id=1 </select >
0.2 查询返回实体类集合
如果查询出的数据有多条 ,一定不能用实体类对象接收,会抛异常 TooManyResultsException
,可以通过:
实体类类型 的 List 集合 接收
Map 类型 的 List 集合 接收
在 mapper
接口的方法上添加 @MapKey
注解(将某一个属性作为 Map
类型的集合的 key
)
形如:{{1={id=1,name=gdai}, {2={id=2,name=tom}}}
1 2 3 4 5 6 7 8 9 10 11 12 13 14 package fr.gdai.mybatis.mapper;import fr.gdai.mybatis.pojo.User;public interface UserMapper { @Select("select * from t_user") List<User> getAllUsersInList () ; @Select("select * from t_user") List<Map<String, Object>> getAllUsersInListMap () ; @Select("select * from t_user") @MapKey("id") Map<String, Map<String, Object>> getAllUsersInMapMap () ; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <select id ="getAllUsers" resultType ="User" > select * from t_user </select > <select id ="getAllUsersInListMap" resultType ="map" > select * from t_user </select > <select id ="getAllUsersInMapMap" resultType ="map" > select * from t_user </select >
注意 ⚠️ :
查询的标签 <select>
必须设置属性 resultType
或 resultMap
,用于设置实体类和数据库表的映射关系
resultType
:自动映射,用于属性名和表中字段名一致 的情况
resultMap (@Results(@Result() ...))
:自定义映射,用于一对多或多对一 或字段名和属性名不一致 的情况
当查询的数据为多条时,不能使用实体类作为返回值,只能使用集合,否则会抛出异常 TooManyResultsException
;但是若查询的数据只有一条,可以使用实体类或集合作为返回值
1 获取参数值的两种方式(重点)
MyBatis
获取参数值的两种方式:${}
和 #{}
${}
的本质就是字符串拼接 ,若为字符串类型 或日期类型 的字段进行赋值时,需要手动加单引号 ;
#{}
的本质就是占位符赋值 ,不需要手动加单引号 ,但是在如下特殊 SQL
操作时 ,需要分情况讨论 。
2 单个字面量类型的参数
1 2 3 4 5 6 7 8 package fr.gdai.mybatis.mapper;import fr.gdai.mybatis.pojo.User;public interface UserMapper { @Select("select * from t_user where username = #{username}") User selectUserByUsername (String username) ; }
1 2 3 4 <select id ="getUserByUsername" resultType ="User" > select * from t_user where username = #{username} </select >
1 2 3 4 <select id ="getUserByUsername" resultType ="User" > select * from t_user where username = '${username}' </select >
1 2 3 4 5 6 7 @Test public void testGetUserByUsername () { SqlSession sqlSession = SqlSessionUtils.getSqlSession(); UserMapper userMapper = sqlSession.getMapper(UserMapper.class); User user = userMapper.selectUserByUsername("gdai" ); System.out.println(user.toString()); }
3 多个字面量类型的参数
3.1 MyBatis
默认提供的 Map
集合
若 mapper
接口中的方法参数为多个 时,此时 MyBatis
会自动 将这些参数放在一个 Map 集合 :
以 arg0, arg1, ...
为键 ,以参数为值 ;
以 param1, param2, ...
为键 ,以参数为值 ;
因此只需要通过 ${}
和 #{}
访问 Map
集合的键 就可以获取相对应的值 ,注意 ${}
需要手动加单引号 。
使用 arg
或者 param
都行,要注意的是,arg
是从 arg0
开始的,param
是从 param1
开始的
1 2 3 4 5 6 7 8 9 package fr.gdai.mybatis.mapper;import fr.gdai.mybatis.pojo.User;public interface UserMapper { @Select("select * from t_user where username = #{arg0} and password = #{arg1}") User selectUserByIdAndPassword (String username, String password) ; }
1 2 3 4 <select id ="selectUserByUsernameAndPassword" resultType ="User" > select * from t_user where username = #{arg0} and password = #{arg1} </select >
1 2 3 4 <select id ="selectUserByUsernameAndPassword" resultType ="User" > select * from t_user where username = '${param1}' and password = '${param2}' </select >
1 2 3 4 5 6 7 @Test public void testSelectUserByUsernameAndPassword () { SqlSession sqlSession = SqlSessionUtils.getSqlSession(); UserMapper userMapper = sqlSession.getMapper(UserMapper.class); User user = userMapper.selectUserByIdAndPassword("admin" ,"admin" ); System.out.println(user.toString()); }
3.2 手动创建 Map
集合
上面我们使用了 MyBatis
默认提供的 Map
集合,我们也可以手动创建 Map
集合 ,将这些数据放在 Map
中只需要通过 ${}
和 #{}
访问 Map
集合的键 就可以获取相对应的值 ,注意 ${}
需要手动加单引号 。
1 2 3 4 5 6 7 8 9 package fr.gdai.mybatis.mapper;import fr.gdai.mybatis.pojo.User;import java.util.Map;public interface UserMapper { @Select("select * from t_user where username = #{username} and password = #{password}") User selectUserByUsernameAndPassword (Map<String, Object> map) ; }
1 2 3 4 <select id ="selectUserByUsernameAndPassword" resultType ="User" > select * from t_user where username = #{username} and password = #{password} </select >
1 2 3 4 5 6 7 8 9 10 11 @Test public void testSelectUserByUsernameAndPassword () { Map<String, Object> map = new HashMap <>(); map.put("username" , "gdai" ); map.put("password" , "admin" ); SqlSession sqlSession = SqlSessionUtils.getSqlSession(); UserMapper userMapper = sqlSession.getMapper(UserMapper.class); User user = userMapper.selectUserByUsernameAndPassword(map); System.out.println(user.toString()); }
4 实体类类型的参数
若 mapper
接口中的方法参数为实体类对象 时此时可以使用 ${}
和 #{}
,通过访问实体类对象中的属性名获取属性值 ,注意 ${}
需要手动加单引号.
1 2 3 4 5 6 7 8 package fr.gdai.mybatis.mapper;import fr.gdai.mybatis.pojo.User;public interface UserMapper { @Insert("insert into t_user values(#{id}, #{username}, #{password}, #{age}, #{gender}, #{email})") int insertUserByEntity (User u1) ; }
1 2 3 4 <insert id ="insertUserByEntity" > insert into t_user values(#{id}, #{username}, #{password}, #{age}, #{gender}, #{email}) </insert >
1 2 3 4 5 6 7 @Test public void testInsertUserByEntity () { User user = new User (3 ,"tom" , "123456" , 22 , "男" , "tom@tom.com" ); SqlSession sqlSession = SqlSessionUtils.getSqlSession(); UserMapper userMapper = sqlSession.getMapper(UserMapper.class); userMapper.insertUserByEntity(user); }
5 使用 @Param
标识参数
通过 @Param
注解 标识 mapper
接口中的方法参数 ,此时,MyBatis
会自动 将这些参数放在一个 Map 集合 :
以 @Param
注解的 value
属性值为键 ,以参数为值 ;
以 param1, param2, ...
为键 ,以参数为值 ;
因此只需要通过 ${}
和 #{}
访问 Map
集合的键 就可以获取相对应的值 ,注意 ${}
需要手动加单引号 。
使用 @Param
注解的 value
或者 param
都行,推荐使用 @Param
注解的 value
1 2 3 4 5 6 7 8 9 10 package fr.gdai.mybatis.mapper;import fr.gdai.mybatis.pojo.User;import org.apache.ibatis.annotations.Param;public interface UserMapper { @Select("select * from t_user where username = #{username} and password = #{password}") User selectUserByAnnotation (@Param("username") String username, @Param("password") String password) ;}
1 2 3 4 5 <select id ="selectUserByAnnotation" resultType ="User" > select * from t_user where username = #{username} and password = #{password} </select >
1 2 3 4 5 6 7 @Test public void testSelectUserByAnnotation () { SqlSession sqlSession = SqlSessionUtils.getSqlSession(); UserMapper userMapper = sqlSession.getMapper(UserMapper.class); User user = userMapper.selectUserByAnnotation("tom" ,"123456" ); System.out.println(user.toString()); }
6 MyBatis 的增删改
6.1 添加
1 2 3 4 <insert id ="insertUser" > insert into t_user values(null,'admin','123456',23,'男','12345@qq.com') </insert >
6.2 删除
1 2 3 4 <delete id ="deleteUser" > delete from t_user where id = 6 </delete >
6.3 修改
1 2 3 4 <update id ="updateUser" > update t_user set username = '张三' where id = 5 </update >
7. 特殊SQL的执行
7.1 模糊查询
模糊查询的 SQL
语句形如:
1 select * from t_user where name like '%g%' ;
我们可以看到,在语句中我们需要传入的参数是 "g"
这个字符串,当我们使用
#{}
时,在执行SQL语句时会转化为 '%?%'
,此时模糊查询的内容就变成了 "?"
${}
时,我们应该使用这个占位符, ${}
所转化的字符是 g
或者直接使用 SQL
中的 concat()
函数,进行字符串拼接
或者直接使用 select * from t_user where name like "%"#{}"%"
1 2 @Select("select * from t_user where username like '%${username}%'") List<User> selectUserByNameLike (@Param("username") String name) ;
1 2 3 4 5 6 <select id ="selectUserByNameLike" resultType ="User" > select * from t_user where username like "%"#{username}"%" </select >
其中 select * from t_user where username like "%"#{username}"%"
是最常用的
7.2 批量删除
只能使用 ${}
,如果使用 #{}
,则解析后的 SQL
语句为
1 delete from t_user where id in ('1,2,3' );
这样是将 '1,2,3'
看做是一个整体,只有 id
为 '1,2,3'
的数据会被删除。
正确的语句应该是
1 2 3 delete from t_user where id in (1 ,2 ,3 );delete from t_user where id in ('1' ,'2' ,'3' );
示例:
1 2 @Delete("delete from t_user where id in (${ids})") int deleteMore (@Param("ids") String ids) ;
1 2 3 <delete id ="deleteMore" > delete from t_user where id in (${ids}) </delete >
1 2 3 4 5 6 7 8 @Test public void deleteMore () { SqlSession sqlSession = SqlSessionUtils.getSqlSession(); SQLMapper sqlMapper = sqlSession.getMapper(SQLMapper.class); int result = sqlMapper.deleteMore("1,2,3,8" ); System.out.println(result); }
7.3 动态设置表名
1 2 @Select("select * from ${tableName}") List<User> getUserByTable (@Param("tableName") String tableName) ;
1 2 3 4 <select id ="getUserByTable" resultType ="User" > select * from ${tableName} </select >
7.4 添加功能获取自增的主键
如果我们在数据库表的创建时使用了主键自增 (auto_increment
),那么我们在创建一个实体类对象并将其持久化到数据库 的过程中就不需要为其设置主键 (即 id=null
)。但是我们在实体类 中依然要获得数据库为对象自动分配的这个主键 ,我们就可以使用以下功能:
在 mapper.xml
中设置两个属性 (@Options)
:
useGeneratedKeys = "true | false"
:标识是否使用了主键的自动生成
keyProperty="_filed"
:将获取的自增的主键放在对象的某个属性 (_filed
) 中
1 2 3 @Select("insert into t_user values (null,#{username},#{password},#{age},#{sex},#{email})") @Options(useGeneratedKeys = true, keyProperty = "id") void insertUser (User user) ;
1 2 3 4 <insert id ="insertUser" useGeneratedKeys ="true" keyProperty ="id" > insert into t_user values (null,#{username},#{password},#{age},#{sex},#{email}) </insert >
1 2 3 4 5 6 7 8 9 10 11 12 13 @Test public void insertUser () { SqlSession sqlSession = SqlSessionUtils.getSqlSession(); SQLMapper mapper = sqlSession.getMapper(SQLMapper.class); User user = new User (null , "tom" , "123" , 23 , "男" , "123@321.com" ); mapper.insertUser(user); System.out.println(user); }
四、自定义映射 resultMap
我们之前在查询部分说过:
查询的标签 <select>
必须设置属性 resultType
或 resultMap
,用于设置实体类和数据库表的映射关系
resultType
:自动映射,用于属性名和表中字段名一致 的情况
resultMap (@Results(@Result() ...))
:自定义映射,用于一对多或多对一 或字段名和属性名不一致 的情况
如果当前字段名与属性名不一致或一对多的关系映射时,我们需要使用 resultMap
用来自定义映射关系 。
在此之前,我们要先准备实验环境:两个实体类(员工类 Emp
和部门类 Dept
)和与之相对应的表(t_emp
和 t_dept
)
Emp.java
1 2 3 4 5 6 7 8 9 10 11 package fr.gdai.mybatis.pojo;public class Emp { private Integer empId; private String empName; private Integer empAge; private String empGender; private String empEmail; private Dept dept; }
t_emp
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 create table t_emp( emp_id int auto_increment primary key, emp_name varchar (20 ) null , emp_age int null , emp_gender varchar (1 ) null , emp_email varchar (50 ) null , dept_id int null ) comment '员工表' ; insert into t_emp values (null , 'gdai' , 24 , '男' , 'gdai@gdai.com' , 3 );insert into t_emp values (null , 'tom' , 21 , '男' , 'tom@tom.com' , 1 );insert into t_emp values (null , 'enzo' , 22 , '男' ,'enzo@enzo.com' , 1 );insert into t_emp values (null , 'theo' , 22 , '男' , empEmail= 'theo@theo.com' , dept= null )insert into t_emp values (null , 'amina' , 22 , '女' ,'amina@amina.com' , 2 );insert into t_emp values (null , 'clement' , 23 , '男' ,'clement@clement.com' , 1 );
Dept.java
1 2 3 4 5 6 7 8 package fr.gdai.mybatis.pojo;public class Dept { private Integer deptId; private String deptName; }
t_dept
1 2 3 4 5 6 7 8 create table t_dept( dept_id int auto_increment primary key, dept_name varchar (20 ) null ) comment '部门表' ; insert into t_dept values (null , 'enseeiht' );insert into t_dept values (null , 'tbs' );insert into t_dept values (null , 'tgu' );
1 字段名与属性名不一致
我们先从一个例子开始讲起:我们先不做任何修改,按照之前的方法尝试获得结果
1 2 3 4 5 6 7 8 package fr.gdai.mybatis.mapper;import java.util.List;public interface EmpMapper { List<Emp> getAllEmp () ; }
1 2 3 4 <select id ="getAllEmp" resultType ="Emp" > select * from t_emp </select >
测试:
1 2 3 4 5 6 7 8 9 @Test public void testGetAllEmp () { SqlSession sqlSession = SqlSessionUtils.getSqlSession(); EmpMapper empMapper = sqlSession.getMapper(EmpMapper.class); List<Emp> empList = empMapper.getAllEmp(); for (Emp e:empList) { System.out.println(e); } }
此时我们进行测试的话,并不会报错,但是无法得到相对应的实体类:
1 2 3 4 5 6 [1105 17:31:00 870 DEBUG] [main] mapper.EmpMapper.getAllEmp - ==> Preparing: select * from t_emp [1105 17:31:00 885 DEBUG] [main] mapper.EmpMapper.getAllEmp - ==> Parameters: [1105 17:31:00 904 DEBUG] [main] mapper.EmpMapper.getAllEmp - <== Total: 6 null null ......
我们可以看到,在 Emp
类与表 t_emp
中,实体类的属性名与表的字段名 因为不同的命名规范 而不同(empId
与 emp_id
),所以我们需要自定义映射关系来讲他们一一联系起来 。这里我们一共三种方法:
1.1 为(SQL
)字段设置别名
我们在 SQL
中可以为最后的查询结果设置别名,这样就可以解决实体类的属性名与表的字段名不一致的问题:
1 2 3 4 <select id ="getAllEmp" resultType ="Emp" > select emp_id empId, emp_name empName, emp_age empAge, emp_gender empGender, emp_email empEmail from t_emp </select >
1 2 @Select("select emp_id empId, emp_name empName, emp_age empAge, emp_gender empGender, emp_email empEmail from t_emp") List<Emp> getAllEmp () ;
测试结果:
1 2 3 4 5 6 [1105 17:33:59 093 DEBUG] [main] mapper.EmpMapper.getAllEmp - ==> Preparing: select emp_id empId, emp_name empName, emp_age empAge, emp_gender empGender, emp_email empEmail from t_emp [1105 17:33:59 107 DEBUG] [main] mapper.EmpMapper.getAllEmp - ==> Parameters: [1105 17:33:59 125 DEBUG] [main] mapper.EmpMapper.getAllEmp - <== Total: 6 Emp{empId=1, empName='gdai', empAge=24, empGender='男', empEmail='gdai@gdai.com', dept=null} Emp{empId=2, empName='tom', empAge=22, empGender='男', empEmail='tom@tom.com', dept=null} ...
问题解决。
1.2 修改全局配置
在之前核心配置文件中介绍过 <settings>
标签 ,其中的 mapUnderscoreToCamelCase
选项可以设置是否开启驼峰命名自动映射 ,即从经典数据库字段名 a_column
映射到经典 Java 属性名 aColumn
。
注意 ⚠️ :数据库字段名与 Java 属性的驼峰命名必须符合规范,且按照规则对应。
mybatis-config.xml
1 2 3 <settings > <setting name ="mapUnderscoreToCamelCase" value ="true" /> </settings >
mapper.xml
1 2 3 4 5 <select id ="getAllEmp" resultType ="Emp" > select * from t_emp </select >
1 2 @Select("select * from t_emp") List<Emp> getAllEmp () ;
测试结果:
1 2 3 4 5 6 [1105 17:33:59 093 DEBUG] [main] mapper.EmpMapper.getAllEmp - ==> Preparing: select * from t_emp [1105 17:33:59 107 DEBUG] [main] mapper.EmpMapper.getAllEmp - ==> Parameters: [1105 17:33:59 125 DEBUG] [main] mapper.EmpMapper.getAllEmp - <== Total: 6 Emp{empId=1, empName='gdai', empAge=24, empGender='男', empEmail='gdai@gdai.com', dept=null} Emp{empId=2, empName='tom', empAge=22, empGender='男', empEmail='tom@tom.com', dept=null} ...
问题解决。
1.3 resultMap (@Results)
自定义映射关系
此时,我们就不能使用默认的自动映射 resultType
,而需要改用 resultMap
:
<resultMap> (@Results)
标签:设置自定义映射
属性:
id (id)
:表示自定义映射<resultMap>
的唯一标识 ,不能重复
type
:查询的数据要映射的实体类 的类型
(value{...})
:以注解的形式配置映射关系(@Result)
子标签 (@Reslut)
:
<id> (id=true)
:设置主键 的映射关系
<result>
:设置普通字段的映射关系
property
:设置映射关系中实体类中的属性名
column
:设置映射关系中表中的字段名
注意 ⚠️ :即使字段名和属性名一致的属性也要映射,也就是全部属性都要列出来 。
1 2 3 4 5 6 7 8 9 10 11 12 13 <resultMap id ="empResultMap" type ="Emp" > <id property ="empId" column ="emp_id" /> <result property ="empName" column ="emp_name" /> <result property ="empAge" column ="emp_age" /> <result property ="empGender" column ="emp_gender" /> <result property ="empEmail" column ="emp_email" /> </resultMap > <select id ="getAllEmp" resultMap ="empResultMap" > select * from t_emp </select >
1 2 3 4 5 6 7 8 9 @Select("select * from t_emp") @Results( id="empResultMap", value={ @Result (id = true, property="empId", column="emp_id"), @Result (property="empName", column="emp_name"), @Result (property="empAge", column="emp_age"), @Result (property="empGender", column="emp_gender"), @Result (property="empEmail", column="emp_email"), }) List<Emp> getAllEmp () ;
测试结果:
1 2 3 4 5 6 7 [1105 17:33:59 093 DEBUG] [main] mapper.EmpMapper.getAllEmp - ==> Preparing: select * from t_emp [1105 17:33:59 107 DEBUG] [main] mapper.EmpMapper.getAllEmp - ==> Parameters: [1105 17:33:59 125 DEBUG] [main] mapper.EmpMapper.getAllEmp - <== Total: 6 Emp{empId=1, empName='gdai', empAge=24, empGender='男', empEmail='gdai@gdai.com', dept=null} Emp{empId=2, empName='tom', empAge=22, empGender='男', empEmail='tom@tom.com', dept=null} ...
问题解决。
2 多对一关系映射
在处理多对一 关系时,需要在多的一端 创建一个一的实体对象 ;相同的,需要在一的一端 创建一个多的实体对象集合 。使用员工类 Emp
和部门类 Dept
来举例:
多个员工 对应着一个部门 ,所以在这段关系里,员工是多、部门为一。
我们需要在员工类 Emp
里添加属性:
在部门类 Dept
里添加属性:
但是这些信息并不存储在同一张表里,那么,我们如何描述这种多对一 关系映射呢?
一共有三种方法:
2.1 级联属性赋值
如果我们需要查询“所有的员工信息及其部门信息 ” ,那么就意味着我们不仅需要查找员工表,还要查找部门表。在 SQL
中,我们可以使用外连接来实现多表查询 。
1 2 List<Emp> getAllEmpAndDept () ;
我们仍然需要自定义映射 resultMap
:
1 2 3 4 5 6 7 8 9 10 11 <resultMap id ="empAndDeptResultMap_Many2One_Cascade" type ="Emp" > <id property ="empId" column ="emp_id" /> <result property ="empName" column ="emp_name" /> <result property ="empAge" column ="emp_age" /> <result property ="empGender" column ="emp_gender" /> <result property ="empEmail" column ="emp_email" /> <result property ="dept.deptId" column ="dept_id" /> <result property ="dept.deptName" column ="dept_name" /> </resultMap >
1 2 3 4 5 6 7 8 9 10 11 @Select("select * from t_emp left join t_dept on t_emp.dept_id = t_dept.dept_id") @Results( id="empAndDeptResultMap_Many2One_Cascade", value={ @Result (id = true, property="empId", column="emp_id"), @Result (property="empName", column="emp_name"), @Result (property="empAge", column="emp_age"), @Result (property="empGender", column="emp_gender"), @Result (property="empEmail", column="emp_email"), @Result (property="dept.deptId", column="dept_id"), @Result (property="dept.deptName", column="dept_name"), }) List<Emp> getAllEmpAndDept () ;
这里处理多对一关系时,
1 2 3 4 <select id ="getAllEmpAndDept" resultMap ="empAndDeptResultMap_Many2One_Cascade" > select * from t_emp left join t_dept on t_emp.dept_id = t_dept.dept_id </select >
测试:
1 2 3 4 5 6 7 8 9 @Test public void testGetAllEmpAndDept () { SqlSession sqlSession = SqlSessionUtils.getSqlSession(); EmpMapper empMapper = sqlSession.getMapper(EmpMapper.class); List<Emp> empList = empMapper.getAllEmpAndDept(); for (Emp e:empList) { System.out.println(e.toString()); } }
测试输出:
1 2 3 4 5 6 7 8 9 [1105 18:25:25 939 DEBUG] [main] mapper.EmpMapper.getAllEmpAndDept - ==> Preparing: select * from t_emp left join t_dept on t_emp.dept_id = t_dept.dept_id [1105 18:25:25 952 DEBUG] [main] mapper.EmpMapper.getAllEmpAndDept - ==> Parameters: [1105 18:25:25 972 DEBUG] [main] mapper.EmpMapper.getAllEmpAndDept - <== Total: 6 Emp{empId=1, empName='gdai', empAge=24, empGender='男', empEmail='gdai@gdai.com', dept=Dept{deptId=3, deptName='tgu'}} Emp{empId=2, empName='tom', empAge=22, empGender='男', empEmail='tom@tom.com', dept=Dept{deptId=1, deptName='enseeiht'}} ...
我们可以看到,部门表中的信息 dept
也存在与实体类中了。
2.2 <association> (@One)
标签
注意 ⚠️ :使用注解 @One
时,详见下一小节:分步查询
我们也可以使用 <association>
标签专门处理多对一 的映射关系:
<association>
:处理多对一的映射关系
property
:需要处理多对一映射关系的属性名
javaType
:对应 property
的实体类的类型 ,通过反射获得对应对象。
1 2 3 4 5 6 7 8 9 10 11 12 <resultMap id ="empAndDeptResultMap_Many2One_Association" type ="Emp" > <id property ="empId" column ="emp_id" /> <result property ="empName" column ="emp_name" /> <result property ="empAge" column ="emp_age" /> <result property ="empGender" column ="emp_gender" /> <result property ="empEmail" column ="emp_email" /> <association property ="dept" javaType ="Dept" > <id property ="deptId" column ="dept_id" /> <result property ="deptName" column ="dept_name" /> </association > </resultMap >
1 2 3 4 <select id ="getAllEmpAndDept" resultMap ="empAndDeptResultMap_Many2One_Association" > select * from t_emp left join t_dept on t_emp.dept_id = t_dept.dept_id </select >
测试输出:
1 2 3 4 5 6 7 8 9 [1105 18:25:25 939 DEBUG] [main] mapper.EmpMapper.getAllEmpAndDept - ==> Preparing: select * from t_emp left join t_dept on t_emp.dept_id = t_dept.dept_id [1105 18:25:25 952 DEBUG] [main] mapper.EmpMapper.getAllEmpAndDept - ==> Parameters: [1105 18:25:25 972 DEBUG] [main] mapper.EmpMapper.getAllEmpAndDept - <== Total: 6 Emp{empId=1, empName='gdai', empAge=24, empGender='男', empEmail='gdai@gdai.com', dept=Dept{deptId=3, deptName='tgu'}} Emp{empId=2, empName='tom', empAge=22, empGender='男', empEmail='tom@tom.com', dept=Dept{deptId=1, deptName='enseeiht'}} ...
2.3 分步查询
思想
以上两种方法都是基于一条(左/右)外连接 的 SQL
来实现多表查询,即
1 select * from t_emp left join t_dept on t_emp.dept_id = t_dept.dept_id
其中,外连接的条件是 t_emp.dept_id = t_dept.dept_id
。
那么我们可以将这条 SQL
分开,分成两个部分 ,从而实现相同的功能:
我们先在 t_emp
表中查询所有的员工信息;
然后将查询结果中的 t_emp.dept_id
字段作为查询条件,在 t_dept
表中查询部门信息。
1 select * from t_dept where dept_id= 员工表查询结果;
基于这个思路,我们可以通过 <association>
标签实现:
1 <association property ="" select ="" column ="" />
property
:需要处理多对一映射关系的属性名
select
:设置分布查询的 SQL
的唯一标识 (XxxMapper
接口的全限定类名.方法名
)
column
:设置分步查询的条件 ,也即两表查询时的“纽带”(相同的部分,主/外键)
操作
1 2 3 4 5 6 7 8 9 10 11 12 <resultMap id ="empAndDeptResultMap_Many2One_ByStep" type ="Emp" > <id property ="empId" column ="emp_id" /> <result property ="empName" column ="emp_name" /> <result property ="empAge" column ="emp_age" /> <result property ="empGender" column ="emp_gender" /> <result property ="empEmail" column ="emp_email" /> <association property ="dept" select ="fr.gdai.mybatis.mapper.DeptMapper.getDeptByDeptId" column ="dept_id" /> </resultMap >
1 2 3 4 5 <select id ="getAllEmpAndDept" resultMap ="empAndDeptResultMap_Many2One_StepByStep" > select * from t_emp where emp_id = #{empId} </select >
1 2 3 4 5 6 7 8 9 10 11 @Select("select * from t_emp where emp_id = #{empId}") @Results( id="empAndDeptResultMap_Many2One_StepByStep", value={ @Result (id = true, property="empId", column="emp_id"), @Result (property="empName", column="emp_name"), @Result (property="empAge", column="emp_age"), @Result (property="empGender", column="emp_gender"), @Result (property="empEmail", column="emp_email"), @Result (javaType = Dept.class, property="dept", column="dept_id", one=@One(select = "fr.gdai.mybatis.mapper.DeptMapper.getDeptByDeptId")), }) Emp getEmpAndDeptStepByStep (@Param("empId") int empId) ;
以下为第 2 步查询 的部分:
fr.gdai.mybatis.mapper.DeptMapper.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 package fr.gdai.mybatis.mapper;import fr.gdai.mybatis.pojo.Dept;import org.apache.ibatis.annotations.Param;public interface DeptMapper { @Select("select * from t_dept where dept_id = #{deptId}") @Results( id = "deptResultMap", value={ @Result(id = true, property="deptId", column="dept_id"), @Result(property="deptName", column="dept_name"), }) Dept getDeptByDeptId (@Param("deptId") int deptId) ; }
fr/gdai/mybatis/mapper/DeptMapper.xml
1 2 3 4 5 6 7 8 9 <resultMap id ="deptResultMap" type ="Dept" > <id property ="deptId" column ="dept_id" /> <result property ="deptName" column ="dept_name" /> </resultMap > <select id ="getDeptBtDeptId" resultMap ="deptResultMap" > select * from t_dept where dept_id = #{deptId} </select >
测试输出:
1 2 3 4 5 6 7 8 9 [1105 18:25:25 939 DEBUG] [main] mapper.EmpMapper.getAllEmpAndDept - ==> Preparing: select * from t_emp left join t_dept on t_emp.dept_id = t_dept.dept_id [1105 18:25:25 952 DEBUG] [main] mapper.EmpMapper.getAllEmpAndDept - ==> Parameters: [1105 18:25:25 972 DEBUG] [main] mapper.EmpMapper.getAllEmpAndDept - <== Total: 6 Emp{empId=1, empName='gdai', empAge=24, empGender='男', empEmail='gdai@gdai.com', dept=Dept{deptId=3, deptName='tgu'}} Emp{empId=2, empName='tom', empAge=22, empGender='男', empEmail='tom@tom.com', dept=Dept{deptId=1, deptName='enseeiht'}} ...
优点
(模块化、解耦)可以将多个 SQL
语句通过这种方式组合起来,从而实现复杂的功能;
延迟加载,可以实现按需加载,获取的数据是什么,就只会执行相应的 SQL
lazyLoadingEnabled
:延迟加载的全局开关。当开启时 ,所有关联对象都会延迟加载
aggressiveLazyLoading
:当开启时 ,任何方法的调用都会加载该对象的所有属性 。 否则,每个属性会按需加载
当开启全局的延迟加载 后,可通过 association
和 collection
中的 fetchType
属性设置当前的分步查询是否使用延迟加载,fetchType="lazy(默认,延迟加载)|eager(立即加载)"
3 一对多关系映射
在本章第 2 节中,我们提到:
在处理多对一 关系时,需要在多的一端 创建一个一的实体对象 ;相同的,需要在一的一端 创建一个多的实体对象集合 。
相应的,我们在“多的一端”部门类 Dept
里添加属性:
与多对一关系映射相似,一对多关系映射也有多种方法:
3.1 <collection> (@Many)
标签
注意 ⚠️ :使用注解 @Many
时,详见下一小节:分步查询
与 <association>
标签专门处理多对一的映射关系类似,<collection>
标签专门处理一对多的映射关系 。
<collection>
标签:用来处理一对多的映射关系
property
:需要处理多对一映射关系的实体类集合属性名
ofType
:对应 property
的集合中存储的数据的类型
1 2 Dept getDeptAndEmpsByDeptId (@Param("deptId") int deptId) ;
1 2 3 4 5 6 7 8 9 10 11 12 <resultMap id ="deptAndEmpsResultMap" type ="Dept" > <id property ="deptId" column ="dept_id" /> <result property ="deptName" column ="dept_name" /> <collection property ="emps" ofType ="Emp" > <id property ="empId" column ="emp_id" /> <result property ="empName" column ="emp_name" /> <result property ="empAge" column ="emp_age" /> <result property ="empGender" column ="emp_gender" /> <result property ="empEmail" column ="emp_email" /> </collection > </resultMap >
1 2 3 4 <select id ="getDeptAndEmpsByDeptId" resultMap ="deptAndEmpsResultMap" > select * from t_dept left join t_emp on t_dept.dept_id = t_emp.dept_id where t_dept.dept_id = #{deptId} </select >
测试:
1 2 3 4 5 6 @Test public void testGetDeptAndEmpsByDeptId () { SqlSession sqlSession = SqlSessionUtils.getSqlSession(); DeptMapper deptMapper = sqlSession.getMapper(DeptMapper.class); System.out.println(deptMapper.getDeptAndEmpsByDeptId(1 )); }
测试输出:
1 2 3 4 5 6 7 8 [1105 19:46:22 221 DEBUG] [main] mapper.DeptMapper.getDeptAndEmpsByDeptId - ==> Preparing: select * from t_dept left join t_emp on t_dept.dept_id = t_emp.dept_id where t_dept.dept_id = ? [1105 19:46:22 236 DEBUG] [main] mapper.DeptMapper.getDeptAndEmpsByDeptId - ==> Parameters: 1(Integer) [1105 19:46:22 259 DEBUG] [main] mapper.DeptMapper.getDeptAndEmpsByDeptId - <== Total: 4 Dept{deptId=1, deptName='enseeiht', emps= [Emp{empId=2, empName='tom', empAge=22, empGender='男', empEmail='tom@tom.com', dept=null}, Emp{empId=3, empName='enzo', empAge=21, empGender='男', empEmail='enzo@enzo.com', dept=null}, Emp{empId=4, empName='theo', empAge=22, empGender='男', empEmail='theo@theo.com', dept=null}, Emp{empId=6, empName='clement', empAge=21, empGender='男', empEmail='clement@clement.com', dept=null}]}
3.2 分步查询
思想
相同的,对于一对多的关系映射,我们依然可以将“外连接”的 SQL
拆分成多步来执行。
1 2 select * from t_dept left join t_emp on t_dept.dept_id = t_emp.dept_id
那么我们可以将这条 SQL
分开,分成两个部分 ,从而实现相同的功能:
我们先在 t_dept
表中查询所有的部门信息;
然后将查询结果中的 t_dept.dept_id
字段作为查询条件,在 t_emp
表中查询部门信息。
1 select * from t_emp where dept_id= 部门表查询结果;
与之前一致。基于这个思路,我们可以通过 <collection>
标签实现:
1 <association property ="" select ="" column ="" />
property
:需要处理多对一映射关系的属性名
select
:设置分布查询的 SQL
的唯一标识 (XxxMapper
接口的全限定类名.方法名
)
column
:设置分步查询的条件 ,也即两表查询时的“纽带”(相同的部分,主/外键)
操作
1 2 3 4 5 6 7 <resultMap id ="deptAndEmpsResultMapByStep" type ="Dept" > <id property ="deptId" column ="dept_id" /> <result property ="deptName" column ="dept_name" /> <collection property ="emps" select ="fr.gdai.mybatis.mapper.EmpMapper.getEmpsListByDeptId" column ="dept_id" /> </resultMap >
1 2 3 4 <select id ="getDeptAndEmpsByDeptId" resultMap ="deptAndEmpsResultMapByStep" > select * from t_dept where dept_id = #{deptId} </select >
1 2 3 4 5 6 7 8 9 10 11 @Select("select * from t_dept where dept_id = #{deptId}") @Results( id="deptAndEmpResultMap_One2Many_Step", value={ @Result(id = true, property="deptId", column="dept_id"), @Result(property="deptName", column="dept_name"), @Result(property = "emps", column = "dept_id", javaType = List.class, many = @Many(select = "fr.gdai.mybatis.mapper.EmpMapper.getEmpsListByDeptId") ) }) Dept getDeptAndEmpsByDeptIdStep (@Param("deptId") int deptId) ;
以下为第 2 步查询 的部分:
fr.gdai.mybatis.mapper.EmpMapper.java
1 2 3 4 5 6 7 8 9 10 11 12 package fr.gdai.mybatis.mapper;import fr.gdai.mybatis.pojo.Emp;import org.apache.ibatis.annotations.Param;import java.util.List;public interface EmpMapper { @Select("select * from t_emp where dept_id = #{DeptId}") @ResultMap("empResultMap") List<Emp> getEmpsListByDeptId (@Param("DeptId") int DeptId) ; }
fr/gdai/mybatis/mapper/EmpMapper.xml
1 2 3 4 5 6 7 8 9 10 11 12 <resultMap id ="empResultMap" type ="Emp" > <id property ="empId" column ="emp_id" /> <result property ="empName" column ="emp_name" /> <result property ="empAge" column ="emp_age" /> <result property ="empGender" column ="emp_gender" /> <result property ="empEmail" column ="emp_email" /> </resultMap > <select id ="getEmpsListByDeptId" resultMap ="empResultMap" > select * from t_emp where dept_id = #{DeptId} </select >
测试输出:
1 2 3 4 5 6 7 8 9 10 11 12 13 [1106 15:56:19 033 DEBUG] [main] mapper.DeptMapper.getDeptAndEmpsByDeptIdStep - ==> Preparing: select * from t_dept where dept_id = ? [1106 15:56:19 052 DEBUG] [main] mapper.DeptMapper.getDeptAndEmpsByDeptIdStep - ==> Parameters: 1(Integer) [1106 15:56:19 066 DEBUG] [main] mapper.EmpMapper.getEmpsListByDeptId - ====> Preparing: select * from t_emp where dept_id = ? [1106 15:56:19 066 DEBUG] [main] mapper.EmpMapper.getEmpsListByDeptId - ====> Parameters: 1(Integer) [1106 15:56:19 067 DEBUG] [main] mapper.EmpMapper.getEmpsListByDeptId - <==== Total: 4 [1106 15:56:19 068 DEBUG] [main] mapper.DeptMapper.getDeptAndEmpsByDeptIdStep - <== Total: 1 Dept{deptId=1, deptName='enseeiht', emps=[ Emp{empId=2, empName='tom', empAge=22, empGender='男', empEmail='tom@tom.com', dept=null}, Emp{empId=3, empName='enzo', empAge=21, empGender='男', empEmail='enzo@enzo.com', dept=null}, Emp{empId=4, empName='theo', empAge=22, empGender='男', empEmail='theo@theo.com', dept=null}, Emp{empId=6, empName='clement', empAge=21, empGender='男', empEmail='clement@clement.com', dept=null} ]}
优点
(模块化、解耦)可以将多个 SQL
语句通过这种方式组合起来,从而实现复杂的功能;
延迟加载,可以实现按需加载,获取的数据是什么,就只会执行相应的 SQL
lazyLoadingEnabled
:延迟加载的全局开关。当开启时 ,所有关联对象都会延迟加载
aggressiveLazyLoading
:当开启时 ,任何方法的调用都会加载该对象的所有属性 。 否则,每个属性会按需加载
当开启全局的延迟加载 后,可通过 association
和 collection
中的 fetchType
属性设置当前的分步查询是否使用延迟加载,fetchType="lazy(默认,延迟加载)|eager(立即加载)"
五、动态 SQL
Mybatis 框架的动态 SQL
技术是一种根据特定条件动态拼装 SQL
语句 的功能,它存在的意义是为了解决拼接 SQL
语句字符串时的痛点问题。
举一个简单的例子,当我们想在一个员工管理页面 中检索符合条件的员工 ,通常会有如下所示的下拉列表。如果我们选择了其中的值 ,相应地,SQL
的语句也应该为此动态地做出改变 。
1 <if>
标签
1.1 思路
1 2 3 4 5 <select id ="方法名" resultMap ="结果返回类型" > select * from table where 1=1 <if test ="param != null and param != ''" > and table_param = #{param} </if >
1.2 操作
fr.gdai.mybatis.mapper.DynamicSQLMapper.java
接口
1 2 3 4 5 6 7 8 9 package fr.gdai.mybatis.mapper;import fr.gdai.mybatis.pojo.Emp;import java.util.List;public interface DynamicSQLMapper { List<Emp> getEmpByConditions (Emp emp) ; }
fr/gdai/mybatis/mapper/DynamicSQLMapper.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <select id ="getEmpByConditionsIfLabel" resultMap ="empResultMap" > select * from t_emp where 1=1 <if test ="empName != null and empName != ''" > and emp_name = #{empName} </if > <if test ="empAge != null and empAge != ''" > and emp_age = #{empAge} </if > <if test ="empGender != null and empGender != ''" > and emp_gender = #{empGender} </if > <if test ="empEmail != null and empEmail != ''" > and emp_email = #{empEmail} </if > </select >
测试
1 2 3 4 5 6 7 8 @Test public void testGetEmpByConditionsIfLabel () { SqlSession sqlSession = SqlSessionUtils.getSqlSession(); DynamicSQLMapper mapper = sqlSession.getMapper(DynamicSQLMapper.class); List<Emp> result = mapper.getEmpByConditionsIfLabel( new Emp (null , "gdai" , 24 , "男" ,null , null )); System.out.println(result); }
测试输出
1 2 3 4 [1106 17:33:45 501 DEBUG] [main] mapper.DynamicSQLMapper.getEmpByConditionsIfLabel - ==> Preparing: select * from t_emp where 1=1 and emp_name = ? and emp_age = ? and emp_gender = ? [1106 17:33:45 520 DEBUG] [main] mapper.DynamicSQLMapper.getEmpByConditionsIfLabel - ==> Parameters: gdai(String), 24(Integer), 男(String) [1106 17:33:45 534 DEBUG] [main] mapper.DynamicSQLMapper.getEmpByConditionsIfLabel - <== Total: 1 [Emp{empId=1, empName='gdai', empAge=24, empGender='男', empEmail='gdai@gdai.com', dept=null}]
2 <where>
标签
2.1 思路
从上一节 <if>
标签中我们可以发现,在 SQL
语句中,where
是写死在 SQL
语句中的,无论是否有条件,where
子句都会执行。
1 select * from table where
此时我们思考,能不能使 where
标签也动态生成呢?我们引入 where
标签。
1 2 3 4 5 6 7 8 9 <select id ="方法名" resultMap ="结果返回类型" > select * from table <where > <if test ="param != null" > and table_字段 = #{param} </if > <if ... /> </where > </select >
<where>
和 <if>
一般结合使用 :
若 <where>
标签中的 <if>
条件都不满足 ,则 <where>
标签没有任何功能,即不会添加 where
关键字
若 <where>
标签中的 <if>
条件满足 ,则 <where>
标签会自动添加 where
关键字 ,并将条件前方多余的and/or
去掉
注意 ⚠️ :where
标签不能去掉条件后多余的 and/or
1 2 3 4 5 6 <where > <if test ="param != null" > table_字段 = #{param} and </if > </where >
2.2 操作
fr.gdai.mybatis.mapper.DynamicSQLMapper.java
接口
1 2 3 4 5 6 7 8 9 package fr.gdai.mybatis.mapper;import fr.gdai.mybatis.pojo.Emp;import java.util.List;public interface DynamicSQLMapper { List<Emp> getEmpByConditionsWhereLabel (Emp emp) ; }
fr/gdai/mybatis/mapper/DynamicSQLMapper.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <select id ="getEmpByConditionsWhereLabel" resultMap ="empResultMap" > select * from t_emp <where > <if test ="empName != null and empName != ''" > and emp_name = #{empName} </if > <if test ="empAge != null and empAge != ''" > and emp_age = #{empAge} </if > <if test ="empGender != null and empGender != ''" > and emp_gender = #{empGender} </if > <if test ="empEmail != null and empEmail != ''" > and emp_email = #{empEmail} </if > </where > </select >
测试
1 2 3 4 5 6 7 8 @Test public void testGetEmpByConditionsWhereLabel () { SqlSession sqlSession = SqlSessionUtils.getSqlSession(); DynamicSQLMapper mapper = sqlSession.getMapper(DynamicSQLMapper.class); List<Emp> result = mapper.getEmpByConditionsWhereLabel( new Emp (null , "gdai" , 24 , "男" ,null , null )); System.out.println(result); }
测试输出
1 2 3 4 [1106 18:07:08 215 DEBUG] [main] mapper.DynamicSQLMapper.getEmpByConditionsWhereLabel - ==> Preparing: select * from t_emp WHERE emp_name = ? and emp_age = ? and emp_gender = ? [1106 18:07:08 228 DEBUG] [main] mapper.DynamicSQLMapper.getEmpByConditionsWhereLabel - ==> Parameters: gdai(String), 24(Integer), 男(String) [1106 18:07:08 245 DEBUG] [main] mapper.DynamicSQLMapper.getEmpByConditionsWhereLabel - <== Total: 1 [Emp{empId=1, empName='gdai', empAge=24, empGender='男', empEmail='gdai@gdai.com', dept=null}]
3 <trim>
标签
3.1 思路
<trim>
标签是一个更通用的,用于添加或者去掉指定的字符 的标签。可以将其视为 <where>
标签更泛化的标签。
1 2 3 4 5 6 7 8 9 <select id ="方法名" resultMap ="结果返回类型" > select * from table <trim prefix ="" suffix ="" prefixOverrides ="" suffixOverrides ="" > <if test ="param != null" > and table_字段 = #{param} </if > <if ... /> </trim > </select >
属性说明:
prefix
(添加前缀):<trim>
标签体中是整个字符串拼串后的结果,是给拼串后的整个字符串加一个前缀 。
suffix
(添加后缀):suffix
给拼串后的整个字符串加一个后缀 。
prefixOverrides
(前缀覆盖): 在 <trim>
标签中的内容的前面去掉某些内容 (去掉前缀 )
suffixOverrides
(后缀覆盖): 在 <trim>
标签中的内容的后面去掉某些内容 (去掉后缀 )
3.2 操作
fr.gdai.mybatis.mapper.DynamicSQLMapper.java
接口
1 2 3 4 5 6 7 8 9 package fr.gdai.mybatis.mapper;import fr.gdai.mybatis.pojo.Emp;import java.util.List;public interface DynamicSQLMapper { List<Emp> getEmpByConditionsTrimLabel (Emp emp) ; }
fr/gdai/mybatis/mapper/DynamicSQLMapper.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 <select id ="getEmpByConditionsTrimLabel" resultMap ="empResultMap" > select * from t_emp <trim prefix ="where" suffix ="" prefixOverrides ="and|or" suffixOverrides ="and|or" > <if test ="empName != null and empName != ''" > and emp_name = #{empName} </if > <if test ="empAge != null and empAge != ''" > and emp_age = #{empAge} </if > <if test ="empGender != null and empGender != ''" > and emp_gender = #{empGender} </if > <if test ="empEmail != null and empEmail != ''" > and emp_email = #{empEmail} </if > </trim > </select >
测试
1 2 3 4 5 6 7 8 @Test public void testGetEmpByConditionsTrimLabel () { SqlSession sqlSession = SqlSessionUtils.getSqlSession(); DynamicSQLMapper mapper = sqlSession.getMapper(DynamicSQLMapper.class); List<Emp> result = mapper.getEmpByConditionsTrimLabel( new Emp (null , "gdai" , 24 , "男" ,null , null )); System.out.println(result); }
测试输出
1 2 3 4 [1106 18:32:40 739 DEBUG] [main] mapper.DynamicSQLMapper.getEmpByConditionsTrimLabel - ==> Preparing: select * from t_emp where emp_name = ? and emp_age = ? and emp_gender = ? [1106 18:32:40 760 DEBUG] [main] mapper.DynamicSQLMapper.getEmpByConditionsTrimLabel - ==> Parameters: gdai(String), 24(Integer), 男(String) [1106 18:32:40 776 DEBUG] [main] mapper.DynamicSQLMapper.getEmpByConditionsTrimLabel - <== Total: 1 [Emp{empId=1, empName='gdai', empAge=24, empGender='男', empEmail='gdai@gdai.com', dept=null}]
4 <choose>, <when>, <otherwise>
标签
4.1 思路
<choose>, <when>, <otherwise>
标签逻辑上 相当于 switch...cases...default
。
所以与 <if>
标签不同的是:
<choose>, <when>, <otherwise>
标签会按照 <when>
标签的顺序依次执行,只执行最先符合条件 的 <when>
标签中的内容(只执行一个 )
如果都不符合,则执行 <otherwise>
标签中的内容
<if>
标签中只要符合条件, <if>
标签中的内容就都执行(执行若干个 )
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 <select id ="方法名" resultMap ="结果返回类型" > select * from table <where > <choose > <when test ="param1 != null" > table_字段 = #{param1} </when > <when test ="param2 != null" > table_字段 = #{param2} </when > <when ... /> <otherwise ... /> </choose > </where > </select >
<when>
至少要有一个,<otherwise>
最多一个
4.2 操作
fr.gdai.mybatis.mapper.DynamicSQLMapper.java
接口
1 2 3 4 5 6 7 8 9 package fr.gdai.mybatis.mapper;import fr.gdai.mybatis.pojo.Emp;import java.util.List;public interface DynamicSQLMapper { List<Emp> getEmpByConditionsChooseLabel (Emp emp) ; }
fr/gdai/mybatis/mapper/DynamicSQLMapper.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 <select id ="getEmpByConditionsChooseLabel" resultMap ="empResultMap" > select * from t_emp <where > <choose > <when test ="empName != null and empName != ''" > emp_name = #{empName} </when > <when test ="empAge != null and empAge != ''" > emp_age = #{empAge} </when > <when test ="empGender != null and empGender != ''" > emp_gender = #{empGender} </when > <when test ="empEmail != null and empEmail != ''" > emp_email = #{empEmail} </when > <otherwise > 1=1 </otherwise > </choose > </where > </select >
测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 @Test public void testGetEmpByConditionsChooseLabel () { SqlSession sqlSession = SqlSessionUtils.getSqlSession(); DynamicSQLMapper mapper = sqlSession.getMapper(DynamicSQLMapper.class); List<Emp> result = mapper.getEmpByConditionsChooseLabel( new Emp (null , "gdai" , 24 , "男" ,null , null )); System.out.println(result); System.out.println("\n---------------------otherwise-------------------" ); List<Emp> result_otherwise = mapper.getEmpByConditionsChooseLabel( new Emp (null , "" , null , "" ,"" , null )); for (Emp e:result_otherwise) { System.out.println(e.toString()); } }
测试输出
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 [1106 19:55:46 778 DEBUG] [main] mapper.DynamicSQLMapper.getEmpByConditionsChooseLabel - ==> Preparing: select * from t_emp WHERE emp_name = ? [1106 19:55:46 791 DEBUG] [main] mapper.DynamicSQLMapper.getEmpByConditionsChooseLabel - ==> Parameters: gdai(String) [1106 19:55:46 808 DEBUG] [main] mapper.DynamicSQLMapper.getEmpByConditionsChooseLabel - <== Total: 1 [Emp{empId=1, empName='gdai', empAge=24, empGender='男', empEmail='gdai@gdai.com', dept=null}] ---------------------otherwise------------------- [1106 19:55:46 809 DEBUG] [main] mapper.DynamicSQLMapper.getEmpByConditionsChooseLabel - ==> Preparing: select * from t_emp WHERE 1=1 [1106 19:55:46 809 DEBUG] [main] mapper.DynamicSQLMapper.getEmpByConditionsChooseLabel - ==> Parameters: [1106 19:55:46 811 DEBUG] [main] mapper.DynamicSQLMapper.getEmpByConditionsChooseLabel - <== Total: 6 Emp{empId=1, empName='gdai', empAge=24, empGender='男', empEmail='gdai@gdai.com', dept=null} Emp{empId=2, empName='tom', empAge=22, empGender='男', empEmail='tom@tom.com', dept=null} Emp{empId=3, empName='enzo', empAge=21, empGender='男', empEmail='enzo@enzo.com', dept=null} Emp{empId=4, empName='theo', empAge=22, empGender='男', empEmail='theo@theo.com', dept=null} Emp{empId=5, empName='amina', empAge=23, empGender='女', empEmail='amina@amina.com', dept=null} Emp{empId=6, empName='clement', empAge=21, empGender='男', empEmail='clement@clement.com', dept=null}
5 <foreach>
标签
5.1 思路
批量搜索(或删除)
在 SQL
中,我们经常使用 where _column in(item1, item2, ...)
子句来对集合 [item1, item2, ...]
内的元素进行操作,如下所示。
1 2 3 4 delete from _table where _column in (item1, item2, ...);select * from _table where _column in (item1, item2, ...);
当 XxxMapper
接口中对应方法的传入参数是一个集合 时(List, Map
),可以使用 <foreach>
标签来遍历 这个集合 collection
,将每次遍历的元素 item
放入 in()
子句中。
1 2 3 4 5 6 7 8 9 10 <select id ="方法名" resultMap ="结果返回类型" > select * from table <where > <foreach collection ="集合类型参数params" item ="item" open ="table_param in(" separator ="," close =")" > #{item} </foreach > </where > </select >
批量添加
对于 SQL
中的批量添加语句,形式如下:
1 insert into _table values (item1), (item2), ..., (itemn);
当 XxxMapper
接口中对应方法的传入参数是一个集合 时(List, Map
),可以使用 <foreach>
标签来遍历 这个集合 collection
,将每次遍历的元素 item
放入 values
子句中。
1 2 3 4 5 6 7 <insert id ="方法名" > insert into _table values <foreach collection ="params" item ="param" separator ="," > (#{param._filed1}, #{param._filed2}, ..., #{param._filedn}) </foreach > </insert >
5.2 操作
批量搜索(或删除)
fr.gdai.mybatis.mapper.DynamicSQLMapper.java
接口
1 2 3 4 5 6 7 8 9 package fr.gdai.mybatis.mapper;import fr.gdai.mybatis.pojo.Emp;import java.util.List;public interface DynamicSQLMapper { List<Emp> selectManyEmps (@Param("empIds") List<Integer> empIds) ; }
fr/gdai/mybatis/mapper/DynamicSQLMapper.xml
1 2 3 4 5 6 7 8 9 10 <select id ="selectManyEmps" resultMap ="empResultMap" > select * from t_emp <where > <foreach collection ="empIds" item ="empId" open ="emp_id in(" separator ="," close =")" > #{empId} </foreach > </where > </select >
测试
1 2 3 4 5 6 7 8 9 10 11 12 13 @Test public void testSelectManyEmps () { SqlSession sqlSession = SqlSessionUtils.getSqlSession(); DynamicSQLMapper mapper = sqlSession.getMapper(DynamicSQLMapper.class); List<Integer> list = new ArrayList <>(); list.add(1 ); list.add(2 ); list.add(3 ); List<Emp> resluts = mapper.selectManyEmps(list); for (Emp e:resluts) { System.out.println(e.toString()); } }
测试输出
1 2 3 4 5 6 [1107 11:36:42 377 DEBUG] [main] mapper.DynamicSQLMapper.selectManyEmps - ==> Preparing: select * from t_emp WHERE emp_id in( ? , ? , ? ) [1107 11:36:42 391 DEBUG] [main] mapper.DynamicSQLMapper.selectManyEmps - ==> Parameters: 1(Integer), 2(Integer), 3(Integer) [1107 11:36:42 408 DEBUG] [main] mapper.DynamicSQLMapper.selectManyEmps - <== Total: 3 Emp{empId=1, empName='gdai', empAge=24, empGender='男', empEmail='gdai@gdai.com', dept=null} Emp{empId=2, empName='tom', empAge=22, empGender='男', empEmail='tom@tom.com', dept=null} Emp{empId=3, empName='enzo', empAge=21, empGender='男', empEmail='enzo@enzo.com', dept=null}
批量添加
fr.gdai.mybatis.mapper.DynamicSQLMapper.java
接口
1 2 3 4 5 6 7 8 9 package fr.gdai.mybatis.mapper;import fr.gdai.mybatis.pojo.Emp;import java.util.List;public interface DynamicSQLMapper { int insertManyEmps (@Param("emps") List<Emp> emps) ; }
fr/gdai/mybatis/mapper/DynamicSQLMapper.xml
1 2 3 4 5 6 7 <insert id ="insertManyEmps" > insert into t_emp values <foreach collection ="emps" item ="emp" separator ="," > (null, #{emp.empName}, #{emp.empAge}, #{emp.empGender}, #{emp.empEmail}, #{emp.dept.deptId}) </foreach > </insert >
测试
1 2 3 4 5 6 7 8 9 10 11 12 @Test public void testInsertManyEmps () { SqlSession sqlSession = SqlSessionUtils.getSqlSession(); DynamicSQLMapper dynamicSQLMapper = sqlSession.getMapper(DynamicSQLMapper.class); DeptMapper deptMapper = sqlSession.getMapper(DeptMapper.class); List<Emp> empList = new ArrayList <>(); empList.add(new Emp (null , "calioppe" , 22 , "女" , "calioppe@calioppe.com" ,deptMapper.getDeptBtDeptId(1 ))); empList.add(new Emp (null , "david" , 22 , "男" , "david@david.com" , deptMapper.getDeptBtDeptId(1 ))); empList.add(new Emp (null , "alex" , 23 , "男" , "alex@alex.com" ,deptMapper.getDeptBtDeptId(2 ))); int result = dynamicSQLMapper.insertManyEmps(empList); System.out.println(result); }
测试输出
1 2 3 4 5 6 7 8 9 10 [1107 11:38:42 300 DEBUG] [main] mapper.DeptMapper.getDeptBtDeptId - ==> Preparing: select * from t_dept where dept_id = ? [1107 11:38:42 314 DEBUG] [main] mapper.DeptMapper.getDeptBtDeptId - ==> Parameters: 1(Integer) [1107 11:38:42 330 DEBUG] [main] mapper.DeptMapper.getDeptBtDeptId - <== Total: 1 [1107 11:38:42 332 DEBUG] [main] mapper.DeptMapper.getDeptBtDeptId - ==> Preparing: select * from t_dept where dept_id = ? [1107 11:38:42 332 DEBUG] [main] mapper.DeptMapper.getDeptBtDeptId - ==> Parameters: 2(Integer) [1107 11:38:42 332 DEBUG] [main] mapper.DeptMapper.getDeptBtDeptId - <== Total: 1 [1107 11:38:42 340 DEBUG] [main] mapper.DynamicSQLMapper.insertManyEmps - ==> Preparing: insert into t_emp values (null, ?, ?, ?, ?, ?) , (null, ?, ?, ?, ?, ?) , (null, ?, ?, ?, ?, ?) [1107 11:38:42 341 DEBUG] [main] mapper.DynamicSQLMapper.insertManyEmps - ==> Parameters: calioppe(String), 22(Integer), 女(String), calioppe@calioppe.com(String), 1(Integer), david(String), 22(Integer), 男(String), david@david.com(String), 1(Integer), alex(String), 23(Integer), 男(String), alex@alex.com(String), 2(Integer) [1107 11:38:42 344 DEBUG] [main] mapper.DynamicSQLMapper.insertManyEmps - <== Updates: 3 3
6 <sql>
与 <include>
标签
<sql>
标签记录一段公共 SQL
片段 ,在使用的地方通过 <include>
标签进行引入
1 2 3 <sql id ="aliasDept" > dept_id as deptId, dept_name as deptName </sql >
1 2 3 4 <select id ="getAllDept" resultType ="Dept" > select <include refid ="aliasDept" /> from t_dept </select >
1 2 3 4 5 6 7 8 9 @Test public void testGetAllDept () { SqlSession sqlSession = SqlSessionUtils.getSqlSession(); DynamicSQLMapper dynamicSQLMapper = sqlSession.getMapper(DynamicSQLMapper.class); List<Dept> results = dynamicSQLMapper.getAllDept(); for (Dept d:results) { System.out.println(d.toString()); } }
1 2 3 4 5 6 [1107 12:57:37 445 DEBUG] [main] mapper.DynamicSQLMapper.getAllDept - ==> Preparing: select dept_id as deptId, dept_name as deptName from t_dept [1107 12:57:37 464 DEBUG] [main] mapper.DynamicSQLMapper.getAllDept - ==> Parameters: [1107 12:57:37 478 DEBUG] [main] mapper.DynamicSQLMapper.getAllDept - <== Total: 3 Dept{deptId=1, deptName='enseeiht', emps=null} Dept{deptId=2, deptName='tbs', emps=null} Dept{deptId=3, deptName='tgu', emps=null}
六、MyBatis 中的分页
举一个简单的例子,我们在将后台查询到的数据展示到前端页面时,如果记录过多,就需要一个如下所示的导航栏来将记录分页展示。
1 SQL
语句中的分页
点击跳转至 MySQL
: LIMIT
分页查询
2 分页插件
2.1 添加依赖
1 2 3 4 5 6 <dependency > <groupId > com.github.pagehelper</groupId > <artifactId > pagehelper</artifactId > <version > 5.2.0</version > </dependency >
2.2 配置分页插件
在 MyBatis 的核心设置 mybatis-config.xml
中设置分页插件。
1 2 3 4 <plugins > <plugin interceptor ="com.github.pagehelper.PageInterceptor" /> </plugins >
2.3 使用分页插件功能
获取简单的分页信息
在查询功能之前使用 PageHelper.startPage(int pageNum, int pageSize)
开启分页功能,其中
pageNum
:当前页的页码
pageSize
:每页显示的条数
我们可以使用一个 Page<Object> page
对象来接收分页的简单结果 (当前页的页码 pageNum
,每页显示的记录数 pageSize
,从第几行记录开始 startRow
,到第几行记录结束 endRow
,总记录数 total
,总页数 page
)即:
1 2 3 Page<Object> page = PageHelper.startPage(int pageNum, int pageSize); List<T> results = mapper.selectAll();
测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 @Test public void testSelectAllEmpsInPageHelper () { SqlSession sqlSession = SqlSessionUtils.getSqlSession(); EmpMapper empMapper = sqlSession.getMapper(EmpMapper.class); Page<Object> page = PageHelper.startPage(1 ,2 ); List<Emp> result = empMapper.getAllEmp(); for (Emp e:result) { System.out.println(e.toString()); } System.out.println( "每页显示" + page.getPageSize() + "条记录" + "\t" + "当前页数:" + page.getPageNum() + "\t" + "共" + page.getPages() + "页," + page.getTotal() + "条数据\n" ); }
1 2 3 Emp{empId=1, empName='gdai', empAge=24, empGender='男', empEmail='gdai@gdai.com', dept=null} Emp{empId=2, empName='tom', empAge=22, empGender='男', empEmail='tom@tom.com', dept=null} 每页显示2条记录 当前页数:1 共6页,12条数据
获取更多的分页信息
如果 Page<Object> page
对象获取的简单结果并不能满足我们的需要,我们可以通过 PageInfo<T> pageInfo
对象来获取更详细的信息:
1 2 3 4 5 PageHelper.startPage(int pageNum, int pageSize); List<T> results = mapper.selectAll(); PageInfo<T> page = new PageInfo <>(results, navigatePages);
其中:
results
:是***分页查询之后的数据(记录)***
navigatePages
:导航分页的页面个数 ,即在如下所示的导航栏中,页面 “1, 2, 3, 4, 5
” 的显示个数。通常为奇数 。
navigatePages = 5
:
navigatePages = 3
:
我们通过 PageInfo<T> pageInfo
对象来获取的详细信息如下:
int pageNum
:当前页的页码
int pageSize
:每页显示的记录数
int size
:当前页显示的真实记录数
int total
:总记录数
int pages
:总页数
int prePage
:上一页的页码
int nextPage
:下一页的页码
boolean isFirstPage
/ isLastPage
:是否为第一页 / 最后一页
boolean hasPreviousPage
/ hasNextPage
:是否存在上一页 / 下一页
int navigatePages
:导航分页的页码数
int[] navigatepageNums
:导航分页的页码,[1,2,3,4,5]
测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 @Test public void testSelectAllEmpsInPageHelperPageInfo () { SqlSession sqlSession = SqlSessionUtils.getSqlSession(); EmpMapper empMapper = sqlSession.getMapper(EmpMapper.class); PageHelper.startPage(5 ,2 ); List<Emp> result = empMapper.getAllEmp(); for (Emp e:result) { System.out.println(e.toString()); } System.out.println( "本页记录数:" + pageInfo.getSize() +"/" + pageInfo.getPageSize() + "\t" + "共" + pageInfo.getPages() + "页," + pageInfo.getTotal() + "条数据\n" + "当前页数:" + pageInfo.getPageNum() + "\t" + "是否有上一页:" + pageInfo.isHasPreviousPage() + "\t" + "是否有下一页:" + pageInfo.isHasNextPage() + "\t" + "导航分页的页码:" + Arrays.toString(pageInfo.getNavigatepageNums()) + "\t" ); }
1 2 3 4 Emp{empId=9, empName='alex', empAge=23, empGender='男', empEmail='alex@alex.com', dept=null} Emp{empId=14, empName='calioppe', empAge=22, empGender='女', empEmail='calioppe@calioppe.com', dept=null} 本页记录数:2/2 共6页,12条数据 当前页数:5 是否有上一页:true 是否有下一页:true 导航分页的页码:[2, 3, 4, 5, 6]
七、MyBatis 的缓存机制
官方文档:缓存
1 一级缓存
一级缓存 是 SqlSession
级别 的,通过同一个 SqlSession
查询的数据会被缓存,下次查询相同的数据 ,就会从缓存中直接获取,不会从数据库重新访问。
开启缓存
MyBatis
默认开启 一级缓存(本地缓存)。
缓存失效的情况
不同的 SqlSession
对应不同的一级缓存
同一个 SqlSession
但是查询条件不同
同一个 SqlSession
两次查询期间执行了任何一次增删改操作
同一个 SqlSession
两次查询期间手动清空了缓存
2 二级缓存
二级缓存 是 SqlSessionFactory
级别 ,通过同一个 SqlSessionFactory
创建的 SqlSession
查询的结果会被缓存;此后若再次执行相同的查询语句,结果就会从缓存中获取。
开启缓存
要启用全局的二级缓存,只需要检查核心配置文件 mybatis-config.xml
中,与缓存有关的设置 cacheEnabled="true"
,默认 为 true
。然后在你的 XxxMapper.xml
映射文件中添加一行:
这个简单语句的效果如下:
映射文件中的所有 select
语句 的结果将会被缓存 。
映射语句文件中的所有 insert
、update
和 delete
语句 会刷新缓存 。
缓存会使用最近最少使用算法(LRU, Least Recently Used
)算法来清除不需要的缓存。
缓存不会定时进行刷新 (也就是说,没有刷新间隔)。
缓存会保存列表或对象 (无论查询方法返回哪种)的 1024 个引用 。
缓存会被视为 读/写缓存
,这意味着获取到的对象并不是共享的,可以安全地被调用者修改,而不干扰其他调用者或线程所做的潜在修改。
这些属性可以通过 <cache>
元素的属性来修改。比如:
1 2 3 4 5 6 <cache eviction ="LRU" | "FIFO " | "SOFT " | "WEAK " flushInterval ="" size ="" readOnly ="true" | "false " />
下面我们对这些元素属性逐一介绍:
eviction
(清除策略):
LRU
– 最近最少使用(默认):移除最长时间不被使用的对象。
FIFO
– 先进先出:按对象进入缓存的顺序来移除它们。
SOFT
– 软引用:基于垃圾回收器状态和软引用规则移除对象。
WEAK
– 弱引用:更积极地基于垃圾收集器状态和弱引用规则移除对象。
flushInterval
(刷新间隔)属性可以被设置为任意的正整数,设置的值应该是一个以毫秒为单位 的合理时间量。 默认情况是不设置,也就是没有刷新间隔,缓存仅仅会在调用语句时刷新。
size
(引用数目)属性可以被设置为任意正整数,要注意欲缓存对象的大小和运行环境中可用的内存资源。默认值是 1024 。
readOnly
(只读)属性可以被设置为 true
或 false
。
只读的缓存会给所有调用者返回缓存对象的相同实例。 因此这些对象不能被修改。这就提供了可观的性能提升。
可读写的缓存会(通过序列化)返回缓存对象的拷贝。 速度上会慢一些,但是更安全,因此默认值是 false
。
3 一二级缓存查询的顺序
先查询二级缓存 ,因为二级缓存中可能会有其他程序已经查出来的数据,可以拿来直接使用;
如果二级缓存没有命中,再查询一级缓存 ;
如果一级缓存也没有命中,则查询数据库 。
当一级缓存的 SqlSession
关闭 之后,一级缓存中的数据会写入二级缓存 。
八、MyBatis 与 Spring 框架整合
首先,Spring 本质上就是一个管理对象的容器,而 MyBatis 可是看作是一个操作数据库的一系列对象。我们也可以将 MyBatis 的对象在 Spring 中注册 ,交由 Spring 来管理 ,使用时从 Spring 容器中获取 。
所以在 MyBatis 中,哪些对象应该交给 Spring 管理呢?我们现在看一组 MyBatis 的测试用例:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 @Test public void testMyBatis () throws IOException { InputStream is = Resources.getResourceAsStream("myBatis-config.xml" ); SqlSessionFactoryBuilder ssfb = new SqlSessionFactoryBuilder (); SqlSessionFactory ssf = ssfb.build(is); SqlSession sqlSession = ssf.openSession(true ); UserMapper userMapper = sqlSession.getMapper(UserMapper.class); int result = userMapper.insertUser(); System.out.println(result); }
其中我们发现,SqlSessionFactory
对象通过 myBatis-config.xml
配置文件创建,从而创建 SqlSession
对象,进而通过 Mapper
接口动态代理生成的实现类 。
我们分析一下之前学习的 MyBatis 核心配置 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 <configuration > <properties resource ="properties文件名" /> <settings > </settings > <typeAliases > <package name ="" /> </typeAliases > <environments default ="development" > <environment id ="development" > <transactionManager type ="JDBC" /> <dataSource type ="POOLED" > <property name ="driver" value ="${jdbc.driver}" /> <property name ="url" value ="${jdbc.url}" /> <property name ="username" value ="${jdbc.name}" /> <property name ="password" value ="${jdbc.password}" /> </dataSource > </environment > </environments > <mappers > <package name ="需要引入的映射文件所在包的名字,如<fr.gdai.mybatis.mapper>" /> </mappers > </configuration >
所以在 Spring 整合 MyBatis 中,我们需要:
数据源 DataSource
对象
依托数据源创建的 SqlSessionFactory
对象 (Spring 中提供了 SqlSessionFactoryBean
)
1. 操作流程
1.1 添加依赖包
首先,我们在 maven
的 pom.xml
文件中添加依赖(主要):
Spring
的依赖 spring-context
MySQL
的驱动依赖 mysql-connector-java
MyBatis
的依赖 mybatis
Spring
整合 MyBatis
的工具类 mybatis-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 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 <dependencies > <dependency > <groupId > junit</groupId > <artifactId > junit</artifactId > <version > 4.12</version > <scope > test</scope > </dependency > <dependency > <groupId > org.springframework</groupId > <artifactId > spring-test</artifactId > <version > 4.3.5.RELEASE</version > </dependency > <dependency > <groupId > mysql</groupId > <artifactId > mysql-connector-java</artifactId > <version > 8.0.29</version > </dependency > <dependency > <groupId > org.mybatis</groupId > <artifactId > mybatis</artifactId > <version > 3.5.7</version > </dependency > <dependency > <groupId > org.mybatis</groupId > <artifactId > mybatis-spring</artifactId > <version > 1.3.1</version > </dependency > <dependency > <groupId > log4j</groupId > <artifactId > log4j</artifactId > <version > 1.2.17</version > </dependency > <dependency > <groupId > org.springframework</groupId > <artifactId > spring-context</artifactId > <version > 4.3.5.RELEASE</version > </dependency > <dependency > <groupId > org.springframework</groupId > <artifactId > spring-jdbc</artifactId > <version > 4.3.5.RELEASE</version > </dependency > <dependency > <groupId > com.mchange</groupId > <artifactId > c3p0</artifactId > <version > 0.9.5.2</version > </dependency > </dependencies >
1.2 准备数据源 DataSource
这里我们使用 c3p0
连接池获取数据源。
jdbc.properties
1 2 3 4 jdbc.driver =com.mysql.jdbc.Driver jdbc.url =jdbc:mysql://localhost:3306/test jdbc.username =root jdbc.password =root
注解形式
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 @PropertySource("classpath:jdbc.properties") public class DataSourceConfig { @Value("${jdbc.driver}") private String driver; @Value("${jdbc.url}") private String url; @Value("${jdbc.name}") private String userName; @Value("${jdbc.password}") private String password; @Bean public DataSource getDataSource () throws PropertyVetoException { ComboPooledDataSource dataSource = new ComboPooledDataSource (); dataSource.setDriverClass(driver); dataSource.setJdbcUrl(url); dataSource.setUser(userName); dataSource.setPassword(password); return dataSource; } }
配置文件形式
1 2 3 4 5 6 7 <context:property-placeholder location ="classpath:c3p0.properties" /> <bean name ="dataSource" class ="com.mchange.v2.c3p0.ComboPooledDataSource" > <property name ="driverClass" value ="${jdbc.driver}" /> <property name ="jdbcUrl" value ="${jdbc.url}" /> <property name ="user" value ="${jdbc.username}" /> <property name ="password" value ="${jdbc.password}" /> </bean >
1.3 获取 SqlSessionFactory
对象
注解形式
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public class MybatisConfig { @Bean public SqlSessionFactoryBean sqlSessionFactoryBean (DataSource dataSource) { SqlSessionFactoryBean sqlSessionFactoryBean = new SqlSessionFactoryBean (); sqlSessionFactoryBean.setTypeAliasesPackage("fr.gdai.mybatis.pojo" ); sqlSessionFactoryBean.setDataSource(dataSource); return sqlSessionFactoryBean; } }
配置文件形式
1 2 3 4 5 6 <bean name ="sqlSessionFactory" class ="org.mybatis.spring.SqlSessionFactoryBean" > <property name ="dataSource" ref ="dataSource" /> <property name ="typeAliasesPackage" value ="fr.gdai.mybatis.pojo" /> </bean >
1.4 配置 Mapper
映射接口
此时我们发现,我们并没有设置 Mapper
映射,即
1 2 3 4 <mappers > <package name ="需要引入的映射文件所在包的名字,如<fr.gdai.mybatis.mapper>" /> </mappers >
当以包为单位 ,自动扫描包下所有的映射文件时需要保证 :
mapper
接口和 mapper
映射文件必须在相同的包下
mapper
接口要和 mapper
映射文件的名字一致
在 Spring 整合 MyBatis 框架中,我们需要通过 MapperScannerConfigurer
来设置。当 Spring 容器启动时,如果容器内存在 MapperScannerConfigurer
对象,则会自动开启包扫描 ,生成 Mapper
代理对象 ,从而可以直接从容器内获得 Mapper
代理对象 。
注解形式
1 2 3 4 5 6 7 8 9 10 11 12 public class MybatisConfig { @Bean public MapperScannerConfigurer mapperScannerConfigurer () { MapperScannerConfigurer mapperScannerConfigurer = new MapperScannerConfigurer (); mapperScannerConfigurer.setBasePackage("fr.gdai.mybatis.mapper" ); return mapperScannerConfigurer; } }
或者直接在 XxxConfig
设置类前设置***注解 @MapperScanner()
***
1 2 @MapperScan("fr.gdai.mybatis.mapper") public class MybatisConfig {...}
配置文件形式
1 2 3 4 5 <bean name ="MapperScannerConfigurer" class ="org.mybatis.spring.mapper.MapperScannerConfigurer" > <property name ="basePackage" value ="fr.gdai.mybatis.mapper" /> </bean >
1.5 测试
1 2 3 4 5 6 7 8 9 10 11 12 13 14 public class EmpMapperTest { @Test public void getAllEmp () { ApplicationContext ctx = new AnnotationConfigApplicationContext (SpringConfig.class); EmpMapper empMapper = ctx.getBean(EmpMapper.class); List<Emp> empList = empMapper.getAllEmp(); for (Emp e:empList) { System.out.println(e.toString()); } } }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 [1108 19:47:32 231 DEBUG] [main] v2.resourcepool.BasicResourcePool - trace com.mchange.v2.resourcepool.BasicResourcePool@41f69e84 [managed: 3, unused: 2, excluded: 0] (e.g. com.mchange.v2.c3p0.impl.NewPooledConnection@46c570bb) [1108 19:47:32 232 DEBUG] [main] spring.transaction.SpringManagedTransaction - JDBC Connection [com.mchange.v2.c3p0.impl.NewProxyConnection@5167f57d [wrapping: com.mysql.cj.jdbc.ConnectionImpl@64b0598]] will not be managed by Spring [1108 19:47:32 234 DEBUG] [main] mapper.EmpMapper.getAllEmp - ==> Preparing: select * from t_emp [1108 19:47:32 247 DEBUG] [main] mapper.EmpMapper.getAllEmp - ==> Parameters: [1108 19:47:32 268 DEBUG] [main] mapper.EmpMapper.getAllEmp - <== Total: 12 [1108 19:47:32 270 DEBUG] [main] mybatis.spring.SqlSessionUtils - Closing non transactional SqlSession [org.apache.ibatis.session.defaults.DefaultSqlSession@29d80d2b] [1108 19:47:32 270 DEBUG] [main] jdbc.datasource.DataSourceUtils - Returning JDBC Connection to DataSource [1108 19:47:32 270 DEBUG] [main] v2.async.ThreadPoolAsynchronousRunner - com.mchange.v2.async.ThreadPoolAsynchronousRunner@7068e664: Adding task to queue -- com.mchange.v2.resourcepool.BasicResourcePool$1RefurbishCheckinResourceTask@3336e6b6 [1108 19:47:32 270 DEBUG] [main] v2.resourcepool.BasicResourcePool - trace com.mchange.v2.resourcepool.BasicResourcePool@41f69e84 [managed: 3, unused: 2, excluded: 0] (e.g. com.mchange.v2.c3p0.impl.NewPooledConnection@46c570bb) Emp{empId=1, empName='gdai', empAge=24, empGender='男', empEmail='gdai@gdai.com', dept=null} Emp{empId=2, empName='tom', empAge=22, empGender='男', empEmail='tom@tom.com', dept=null} Emp{empId=3, empName='enzo', empAge=21, empGender='男', empEmail='enzo@enzo.com', dept=null} Emp{empId=4, empName='theo', empAge=22, empGender='男', empEmail='theo@theo.com', dept=null} Emp{empId=5, empName='amina', empAge=23, empGender='女', empEmail='amina@amina.com', dept=null} Emp{empId=6, empName='clement', empAge=21, empGender='男', empEmail='clement@clement.com', dept=null} Emp{empId=7, empName='calioppe', empAge=22, empGender='女', empEmail='calioppe@calioppe.com', dept=null} Emp{empId=8, empName='david', empAge=22, empGender='男', empEmail='david@david.com', dept=null} Emp{empId=9, empName='alex', empAge=23, empGender='男', empEmail='alex@alex.com', dept=null} Emp{empId=14, empName='calioppe', empAge=22, empGender='女', empEmail='calioppe@calioppe.com', dept=null} Emp{empId=15, empName='david', empAge=22, empGender='男', empEmail='david@david.com', dept=null} Emp{empId=16, empName='alex', empAge=23, empGender='男', empEmail='alex@alex.com', dept=null}
至此,MyBatis 的对象已经完全的交由 Spring 容器来管理了。