19-MyBatis(1)
Mybatis 概述#
Mybatis 是 Apache 的一个开源项目 iBatis,iBatis 3.x 正式更名为 MyBatis。
iBatis 一词来源于 "internet" 和 "abatis" 的组合,是一个基于 Java 的持久层框架。iBatis 提供的持久层框架包括 SQL Maps 和 Data Access Object(DAO)。
- MyBatis 是支持定制化 SQL、存储过程以及高级映射的优秀的持久层框架
- MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集
- MyBatis 可以使用简单的 XML 或注解用于配置和原始映射,将接口和 Java 的 POJO(Plain Old Java Objects,普通的 Java 对象) 映射成数据库中的记录
- Mybatis 是一个半自动的 ORM(Object Relation Mapping) 框架
现有持久化技术的对比#
- JDBC
- SQL 夹在 Java 代码块里,耦合度高导致硬编码内伤
- 维护不易且实际开发需求中 SQL 有变化,频繁修改的情况多见
- Hibernate 和 JPA
- 长难复杂 SQL,对于 Hibernate 而言处理也不容易
- 内部自动生产的 SQL,不容易做特殊优化
- 基于全映射的全自动框架,大量字段的 POJO 进行部分映射时比较困难,导致 DB 性能下降
- MyBatis
- 对开发人员而言,核心 SQL 还是需要自己优化
- SQL 和 Java 编码分开,功能边界清晰,一个专注业务,一个专注数据
HelloWorld#
- 导入 jar
- 配置 log4j.xml // 定义日志输出格式
<?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>
- 创建 jdbc.properties
jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql:///test jdbc.username=root jdbc.password=root
- 创建 JavaBean 以及 DB 对应表
- 创建 Mybatis 的核心(全局) 配置文件 [mybatis-config.xml] // 如何连接 DB
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <!-- 引入资源文件 --> <properties resource="jdbc.properties"></properties> <settings> <!-- 下划线 → 驼峰 --> <setting name="mapUnderscoreToCamelCase" value="true"/> </settings> <!-- 设置连接 DB 的环境(可设置多个) --> <environments default="MySQL"> <!-- 设置某个具体的 DB 环境 --> <environment id="MySQL"> <transactionManager type="JDBC" /> <dataSource type="POOLED"> <property name="driver" value="${jdbc.driver}" /> <property name="url" value="${jdbc.url}" /> <property name="username" value="${jdbc.username}" /> <property name="password" value="${jdbc.password}" /> </dataSource> </environment> </environments> <!-- 引入映射文件 // 注册功能 --> <mappers> <mapper resource="UserMapper.xml" /> </mappers> </configuration>
- 创建 Mapper 接口,在 mapper.xml 中实现 2 个绑定
public interface UserMapper { User getUserByUid(String uid); }
- 创建映射文件 [XxxMapper.xml] // 如何操作 DB
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <!-- 1. [接口全限定名] 和 [映射文件根标签的命名空间] 一致(绑定) --> <mapper namespace="cn.edu.nuist.mapper.UserMapper"> <!-- 2. [方法名] 和 [SQL 语句标签的 id 属性值] 一致(绑定) --> <select id="getUserByUid" resultType="cn.edu.nuist.bean.User"> <!-- 如果没有配置 mapUnderscoreToCamelCase,就给 user_name 起个别名 --> SELECT uid, user_name, password, address, phone FROM user WHERE uid = #{id} </select> <!-- select * from user where uid = #{id} (PStat) ==> select * from user where uid = ? select * from user where uid = ${value} (Stat) ==> select * from user where uid = 1 --> </mapper>
- 获取 Mybatis 操作数据库的会话对象(SqlSession) 并进行测试
@Test public void test() throws IOException { InputStream is = Resources.getResourceAsStream("mybatis-config.xml"); SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is); SqlSession sqlSession = factory.openSession(); // getMapper(): 会通过动态代理生成 UserMapper 的代理实现类 UserMapper mapper = sqlSession.getMapper(UserMapper.class); // com.sun.proxy.$Proxy4 System.out.println(mapper.getClass().getName()); User user = mapper.getUserByUid("1"); System.out.println(user); } ====================== 测试结果 ====================== DEBUG 08-16 15:23:32,125 ==> Preparing: select uid, user_name, password , address, phone from user where uid = ? (BaseJdbcLogger.java:145) DEBUG 08-16 15:23:32,165 ==> Parameters: 1(String) (BaseJdbcLogger.java:145) DEBUG 08-16 15:23:32,481 <== Total: 1 (BaseJdbcLogger.java:145) User [uid=1, userName=刘心悠, password=xiaohai, address=HongKong, phone=123456789]
Tip:没有网络时 XML 的提示功能
全局配置文件#
The MyBatis configuration contains settings and properties that have a dramatic effect on how MyBatis behaves。
environments 环境配置#
- MyBatis 可以配置多种环境,比如开发、测试和生产环境需要有不同的配置
- 每种环境使用一个
<environment>
标签进行配置并指定唯一标识符- 可以通过
<environments>
标签中的 default 属性指定一个环境的标识符来快速的切换环境 <environment>
指定具体环境,其中 id 属性指定当前环境的唯一标识,<transactionManager>
和<dataSource>
子标签都必须有
- 可以通过
<transactionManager>
标签设置事务;type 属性取值常见的有如下几种- JDBC:使用原生 JDBC 的提交和回滚设置
- MANAGED:让容器来管理事务的整个生命周期
- POOLED:使用数据库连接池
- UNPOOLED:不使用数据库连接池
- 自定义:实现
TransactionFactory<I>
,type=全类名/别名
- 实际开发中我们使用 Spring 管理数据源,并进行事务控制的配置来覆盖上述配置
<environments default="oracle">
<environment id="mysql">
<transactionManager type="JDBC" />
<dataSource type="POOLED">
<property name="driver" value="${jdbc.driver}" />
<property name="url" value="${jdbc.url}" />
<property name="username" value="${jdbc.username}" />
<property name="password" value="${jdbc.password}" />
</dataSource>
</environment>
<environment id="oracle">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="${orcl.driver}" />
<property name="url" value="${orcl.url}" />
<property name="username" value="${orcl.username}" />
<property name="password" value="${orcl.password}" />
</dataSource>
</environment>
</environments>
properties 属性#
- 可外部配置且可动态替换的,既可以在典型的 Java 属性文件中配置,亦可通过 properties 元素的子元素来配置
<properties> <property name="driver" value="com.mysql.jdbc.Driver" /> <property name="url" value="jdbc:mysql:///test" /> <property name="username" value="root" /> <property name="password" value="1234" /> </properties>
- properties 的作用并不单单是这样,你可以创建一个资源文件,名为 jdbc.properties 的文件,将 4 个连接字符串的数据在资源文件中通过键值对(key=value) 的方式放置,不要任何符号,一条占一行。最终在
<environment>
元素的<dataSource>
元素动态设置连接 DB 的参数(见 ## 4.1 code)>>> [jdbc.properties] <<< jdbc.driver=com.mysql.jdbc.Driver jdbc.url=jdbc:mysql:///test jdbc.username=root jdbc.password=1234 >>> [mybatis-config.xml] <<< <!-- <properties> 设置或引入资源文件 resource: 在类路径下访问资源文件 url: 在网络路径或磁盘路径下访问资源文件 --> <properties resource="jdbc.properties"></properties>
settings 设置#
MyBatis 中极为重要的调整设置,它们会改变 MyBatis 的运行时行为。包含如下的 setting 设置:
<settings>
<setting name="cacheEnabled" value="true"/>
<setting name="lazyLoadingEnabled" value="true"/>
<setting name="multipleResultSetsEnabled" value="true"/>
<setting name="useColumnLabel" value="true"/>
<setting name="useGeneratedKeys" value="false"/>
<setting name="autoMappingBehavior" value="PARTIAL"/>
<setting name="autoMappingUnknownColumnBehavior" value="WARNING"/>
<setting name="defaultExecutorType" value="SIMPLE"/>
<setting name="defaultStatementTimeout" value="25"/>
<setting name="defaultFetchSize" value="100"/>
<setting name="safeRowBoundsEnabled" value="false"/>
<setting name="mapUnderscoreToCamelCase" value="false"/>
<setting name="localCacheScope" value="SESSION"/>
<setting name="jdbcTypeForNull" value="OTHER"/>
<setting name="lazyLoadTriggerMethods" value="equals,clone,hashCode,toString"/>
</settings>
mapUnderscoreToCamelCase:将下划线映射为驼峰
typeAliases 别名处理#
- 类型别名是为 Java 类型设置一个短的名字,可以方便我们引用某个类
<typeAliases> <!-- 若不设置 alias 属性,则默认的别名就是简单类名 (不区分大小写) --> <typeAlias type="cn.edu.nuist.beans.Employee" alias="emp"/> </typeAliases>
- 类很多的情况下,可以批量设置别名。这个包下的每一个类创建一个默认的别名,就是简单类名小写
<typeAliases> <package name="cn.edu.nuist.beans"/> </typeAliases>
- MyBatis已经取好的别名
mappers 映射器#
- 用来在 Mybatis 初始化的时候,告诉 Mybatis 需要引入哪些 Mapper 映射文件
- 在核心配置文件的
<mapper>
中逐个注册 SQL 映射文件<mappers> <!-- resource:引入类路径下的文件 --> <mapper resource="EmpMapper.xml" /> <mapper resource="DeptMapper.xml" /> </mappers>
- 有 SQL 映射文件,要求
Mapper<I>
与 SQL 映射文件同名同位置;没有 SQL 映射文件,使用注解在接口的方法上写 SQL 语句 - 可使用
<package>
实现批量注册,这种方式要求 SQL 映射文件名必须和接口名相同并且在同一目录下
映射文件#
MyBatis 的真正强大在于它的映射语句,也是它的魔力所在。由于它的异常强大,映射器的 XML 文件就显得相对简单。如果拿它跟具有相同功能的 JDBC 代码进行对比,你会立即发现省掉了将近 95% 的代码。MyBatis 就是针对 SQL 构建的,并且比普通的方法做的更好。
实现 CRUD#
select
- EmpMapper.java
Emp getEmpByEid(String eid); List<Emp> getAllEmps();
- EmpMapper.xml
<select id="getEmpByEid" resultType="Emp"> SELECT * FROM emp WHERE eid = #{eid } </select> <!-- 这里的 resultType 依旧是表记录所映射的 Java 类型 Mybatis 底层会自动将获取到的 JavaBean 们放入方法 返回值所声明的容器类型返回。重要的是映射关系! --> <select id="getAllEmps" resultType="Emp"> SELECT * FROM emp </select>
insert
- EmpMapper.java
void addEmp(Emp emp);
- EmpMapper.xml
<insert id="addEmp"> INSERT INTO emp VALUES(null, #{ename}, #{age}, #{sex}) </insert>
update
- EmpMapper.java
// 受影响的记录行数 Integer updateEmp(Emp emp);
- EmpMapper.xml
<update id="updateEmp" parameterType="Emp"> UPDATE emp SET ename=#{ename}, age=#{age}, sex=#{sex} WHERE eid=#{eid} </update>
delete
- EmpMapper.java
// 是否执行成功 Boolean deleteEmp(String eid);
- EmpMapper.xml
<delete id="deleteEmp"> DELETE FROM emp WHERE eid=#{eid} </delete>
测试
@Test
public void test() throws IOException {
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
/* mybatis-config.xml: <transactionManager type="JDBC" /> ↓
使用 JDBC 原生的事务管理方式,即 commit 和 rollback 都需要手动处理
SqlSession session = factory.openSession([false]); // 手动处理事务 */
SqlSession session = factory.openSession(true); // 自动处理事务
EmpMapper mapper = session.getMapper(EmpMapper.class);
/* 根据 eid 获取员工信息
Emp emp = mapper.getEmpByEid("1");
System.out.println(emp); */
/* 获取所有的员工信息
List<Emp> emps = mapper.getAllEmps();
System.out.println(emps); */
/* 添加员工信息
Emp emp = new Emp("外援", 30, "741");
mapper.addEmp(emp); */
// 如果没提交,数据库中不会增添记录,但是主键会自增
// session.commit(); // 提交事务
/* 删除员工
boolean success = mapper.deleteEmp("7");
System.out.println(success); */
}
select 查询的几种情况#
- 查询单行数据,返回单个对象
<!-- Emp getEmpByEid(String eid); --> <select id="getEmpByEid" resultType="Emp"> SELECT * FROM emp WHERE eid = ${eid } </select>
- 查询多行数据,返回对象组成的集合
<!-- List<Emp> getAllEmps(); 这里 resultType 依旧设置为记录所映射的 Java 类型! Mybatis 底层会自动将获取到的 Java 对象们放入方法 返回值所声明的容器类型返回。 --> <select id="getAllEmps" resultType="Emp"> SELECT * FROM emp </select>
- 查询单行数据,返回 Map 集合
<!-- // Map {attr1:value1, attr2: value2 ...} Map<String, Object> getEmpMapByEid(String eid); --> <select id="getEmpMapByEid" resultType="java.util.HashMap"> SELECT eid, ename, age, sex FROM emp WHERE eid=#{eid} </select>
- 查询多行数据,返回 Map 集合
<!-- @MapKey("eid") // 指定使用对象的哪个属性来充当 map 的 key Map<String, Object> getAllEmpMap(); --> <select id="getAllEmpMap" resultType="Emp"> SELECT eid, ename, age, sex FROM emp </select>
主键生成方式、获取主键值#
取 Statement.RETURN_GENERATED_KEYS
值且使用的是 INSERT
语句时,可以取出新插入数据行中自动增长的列的值。
PreparedStatement prepareStatement(String sql, int autoGeneratedKeys) throws SQLException;
autoGeneratedKeys 可以取值:
> Statement.RETURN_GENERATED_KEYS(= 1)
> Statement.NO_GENERATED_KEYS(= 2)
===============================================
PreparedStatement ps = conn.preparedStatement(sql, Statement.RETURN_GENERATED_KEYS);
ResultSet rsKey = ps.getGeneratedKeys();
rsKey.next();
newId = rsKey.getInt(1);
- 若数据库支持自动生成主键的字段(比如 MySQL 和 SQL Server),则可以设置
useGeneratedKeys="true"
,然后再用keyProperty
来设置自动生成的主键赋值给形参 bean 的哪个属性<insert id="insertEmp" databaseId="mysql" useGeneratedKeys="true" keyProperty="eid"> INSERT INTO emp VALUES(null, '${ename}', ${age}, '${sex}') </insert>
- 对于不支持自增型主键的数据库(例如 Oracle),则可以使用 selectKey 子元素,它将会首先运行,id 会被设置,然后插入语句会被调用
<insert id="insertEmp" databaseId="oracle"> <selectKey order="BEFORE" keyProperty="eid" resultType="integer"> SELECT emp_seq.nextval from dual </selectKey> INSERT INTO emp (eid, ename, age, sex) values(#{eid}, #{ename}, #{sex}, #{age}) </insert>
参数传递#
参数传递的方式
- 单个普通类型参数:可以接受基本类型,包装类型,字符串类型等。这种情况MyBatis可直接使用这个参数,不需要经过任何处理
- 多个参数:任意多个参数,都会被 MyBatis 重新包装成一个 Map 传入。Map 的 key 是 param1,param2,或者0,1,…,value 就是参数的值
- 命名参数:为参数使用 @Param 起一个名字,MyBatis 就会将这些参数封装进 map 中,key 就是我们自己指定的名字
- POJO:当这些参数属于我们业务 POJO 时,我们直接传递 POJO
- Map:也可以封装多个参数为 map,直接传递
- Collection/Array:会被 MyBatis 封装成一个 map 传入,Collection 对应的 key 是 "collection",Array 对应的 key 是 array,如果是 List 集合,key 还可以是 list
参数获取的 2 种方式
有 2 种,举例说明(建议使用 #{...}
;但特殊情况下,比如模糊查询和批量删改,需要使用 ${...}
)
#{...} // PreparedStatement, 可以使用通配符,底层填充参数的时候会自动加上单引号
INSERT INTO emp VALUES(null, #{ename}, #{age}, #{sex})
Preparing: INSERT INTO emp VALUES(null, ?, ?, ?)
Parameters: 警察(String), 35(Integer), 110(String)
Updates: 1
========================================================================
${...} // Statement, 必须使用字符串拼接的方式操作 SQL,一定要注意单引号问题
INSERT INTO emp VALUES(null, '${ename}', ${age}, '${sex}')
>>> Preparing: INSERT INTO emp VALUES(null, 黑帮, 30, 444)
·············↑ MySQLSyntaxErrorException ↑·············
INSERT INTO emp VALUES(null, '${ename}', ${age}, '${sex}')
>>> INSERT INTO emp VALUES(null, '黑帮', 30, '444') ← 手动加引号
Parameters:
不同的参数类型,${...} 和 #{...} 的不同取值方式:
- 当传输参数为单个String 或 基本数据类型和其包装类
#{...}
可以以任意的名字获取参数值(只是一个占位符)${...}
只能以${value}
或${_parameter}
获取- 代码演示
<!-- Emp getEmpByEid(String eid); --> <select id="getEmpByEid" resultType="Emp"> <!-- #{...} 随便写,只作占位符之用,但尽量写方法形参名 --> SELECT * FROM emp WHERE eid=#{lalalalalaLisa} <!-- SELECT * FROM emp WHERE eid=${eid} >>> There is no getter for property named 'eid' in 'class java.lang.String' 当使用 ${...} 来取值的时候,默认会把传过来的参数当作一个实体类对象。然后把 ${...} 中的内容当作该实体类对象的一个属性,并通过 getXxx 方法取得这个属性所对应的值 >>> 所以!${...} 只能以固定的格式:${value} 或 ${_parameter} 获取参数 --> <!-- SELECT * FROM emp WHERE eid=${value} SELECT * FROM emp WHERE eid=${_parameter} --> </select>
- 当传输参数为 JavaBean
#{...}
和${...}
都可以通过属性名直接获取属性值- 要注意
${...}
的单引号问题
- 当传输多个参数时,Mybatis 会默认将这些参数放在 Map 集合中,2 种方式实现
- key 为 0, 1, 2, ... , N-1; value 为参数值 // 参数索引
- key 为 param1, param2, ..., paramN; value 为参数值 // 参数个数
- 代码演示
<!-- Emp getEmpByEidAndEname(String eid, String ename); --> <select id="getEmpByEidAndEname" resultType="Emp"> <!-- SELECT * FROM emp WHERE eid=#{eid} AND ename=#{ename}· BindingException: Parameter 'eid' not found. Available parameters are [0, 1, param1, param2] --> <!-- #{...}: #{0}, #{1}; #{param1}, #{param2} ${...}: '${param1}', '${param2}' // 不能用参数索引方式 SELECT * FROM emp WHERE eid='${0}' AND ename='${1}' → SELECT * FROM emp WHERE eid='0' AND ename='1' --> <!-- SELECT * FROM emp WHERE eid=#{0} AND ename=#{1} --> SELECT * FROM emp WHERE eid='${param1}' AND ename='${param2}' </select>
- 当传输 Map 参数时,
#{...}
和${...}
都可以通过 key 的名字直接获取值,但是要注意${...}
的单引号问题<!-- Emp getEmpByMap(Map<String, Object> paramMap); --> <select id="getEmpByMap" resultType="emp"> SELECT * FROM emp WHERE eid='${eid}' AND ename='${ename}' </select> ========== Map<String, Object> paramMap = new HashMap<>(); paramMap.put("eid", 1); paramMap.put("ename", "Finch"); Emp emp = mapper.getEmpByMap(paramMap);
- 【命名参数】通过 @Param("key") 为 paramMap 指定 key 的名字
- 无需手动创建封装参数的Map,既不需要 012 也不需要 param123
- 代码演示
<!-- Emp getEmpByEidAndEnameByParam( @Param("eid")String eid, @Param("ename")String ename ); --> <select id="getEmpByEidAndEnameByParam" resultType="emp"> SELECT * FROM emp WHERE eid='${eid}' AND ename='${ename}' </select>
- 当传输参数为 List 或数组,Mybatis 会将 List 或数组放在 map 中 List 以 list 为键,数组以 array 为键
源码分析
@Test
public void test5() throws IOException {
InputStream is = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is);
SqlSession session = factory.openSession(true);
ParamMapper mapper = session.getMapper(ParamMapper.class);
// (@Param("eid")String eid, @Param("ename")String ename)
Emp emp = mapper.getEmpByEidAndEnameByParam("1", "Finch");
System.out.println(emp);
}
resultType 自动映射#
- MyBatis 中在查询进行 select 映射的时候,返回类型可以用 resultType,也可以用 resultMap。resultType 是直接表示返回类型的,而 resultMap 则是对外部 ResultMap 的引用,但是 resultType 跟 resultMap 不能同时存在。
- 在 MyBatis 进行查询映射时,其实查询出来的每一个属性都是放在一个对应的 Map 里面的,其中键是属性名,值则是其对应的值
- 当提供的返回类型属性是 resultType 时,MyBatis 会将 Map 里面的键值对取出赋给 resultType 所指定的对象对应的属性。所以其实 MyBatis 的每一个查询映射的返回类型都是 ResultMap,只是当提供的返回类型属性是 resultType 的时候,MyBatis 对自动的给把对应的值赋给 resultType 所指定对象的属性。
- 当提供的返回类型是 resultMap 时,因为 Map 不能很好表示领域模型,就需要自己再进一步的把它转化为对应的对象,这常常在复杂查询中很有作用。
- autoMappingBehavior
- 默认是 PARTIAL,开启自动映射的功能。唯一的要求是结果集列名和 JavaBean 属性名一致
- 如果设置为 null 则会取消自动映射
- 数据库字段命名规范,POJO 属性符合驼峰命名法,如
A_COLUMN ↔ aColumn
,我们可以开启自动驼峰命名规则映射功能:mapUnderscoreToCamelCase = true
resultMap & 延迟加载#
自定义 resultMap,描述如何将结果集映射到 Java 对象。实现高级结果集映射
基本使用
- id & result
- association(一个复杂的类型关联,处理多对一的映射关系)
- POJO 中的属性可能会是一个对象,我们可以使用联合查询:① 以级联属性的方式封装对象;② 使用 association 标签定义对象的封装规则
- 代码演示
<!-- ① 级联属性赋值 --> <resultMap type="Emp" id="empMap"> <id column="eid" property="eid"/> <result column="ename" property="ename"/> <result column="age" property="age"/> <result column="sex" property="sex"/> <result column="did" property="dept.did"/> <result column="dname" property="dept.dname"/> </resultMap> <!-- ② association 标签 --> <resultMap type="Emp" id="empMap"> <id column="eid" property="eid"/> <result column="ename" property="ename"/> <result column="age" property="age"/> <result column="sex" property="sex"/> <association property="dept" javaType="Dept"> <id column="did" property="did"/> <result column="dname" property="dname"/> </association> </resultMap> <!-- List<Emp> getAllEmp(); --> <select id="getAllEmp" resultType="Emp" resultMap="empMap"> SELECT e.*, d.dname FROM emp e LEFT JOIN dept d ON e.did = d.did </select>
- collection : 复杂类型的集合
- 处理一对多和多对多的映射关系
- ofType 属性:指集合中的类型(和 javaType 区分开)
- 代码演示
<select id="getDeptIncludeEmpsByDid" resultMap="deptMap"> SELECT e.*, d.dname FROM emp e RIGHT JOIN dept d ON e.did = d.did WHERE d.did = e.did AND e.did = #{eid} </select> <resultMap type="Dept" id="deptMap"> <id column="did" property="did"/> <result column="dname" property="dname"/> <collection property="emps" ofType="Emp"> <id column="eid" property="eid"/> <result column="ename" property="ename"/> <result column="age" property="age"/> <result column="sex" property="sex"/> </collection> </resultMap>
分步查询
实际的开发中,对于每个实体类都应该有具体的增删改查方法,也就是 DAO 层, 因此对于查询员工信息并且将对应的部门信息也查询出来的需求,就可以通过分步的方式完成查询。
举例:① 先通过员工的 id 查询员工信息;② 再通过查询出来的员工信息中的外键(部门 id) 查询对应的部门信息。
<!-- ① Emp getEmpStep(String eid); -->
<select id="getEmpStep" resultMap="empMapStep">
SELECT * FROM emp WHERE eid = #{eid}
</select>
<!-- ② 分步查询,再根据员工的 did 查询员工所在部门 -->
<resultMap type="Emp" id="empMapStep">
<id column="eid" property="eid"/>
<result column="ename" property="ename"/>
<result column="age" property="age"/>
<result column="sex" property="sex"/>
<!--
select 值是分步 SQL 的 id (namespace.id)
column 是分步查询的字段名(必须是上一步从 DB 中查出来的)
property 值所对应的属性保存分步查询的结果
-->
<association column="did" property="dept"
select="cn.edu.nuist.mapper.DeptMapper.getDeptByDid" />
</resultMap>
对于查询部门信息并且将对应的所有的员工信息也查询出来的需求,也可以通过分步的方式完成查询。
举例:① 先通过部门的 id 查询部门信息;② 再通过部门 id 作为员工的外键查询对应的部门信息。
<select id="getOnlyDeptInfoByDid" resultMap="deptMapStep">
SELECT did, dname FROM dept WHERE did = #{did}
</select>
<resultMap type="Dept" id="deptMapStep">
<id column="did" property="did"/>
<result column="dname" property="dname"/>
<collection column="did" property="emps"
select="cn.edu.nuist.mapper.EmpDeptMapper.getEmpListByDid">
</collection>
</resultMap>
延迟加载
在分步查询的基础上,可以使用延迟加载来提升查询的效率,只需要在全局的 Settings 中进行如下的配置(延迟加载只对分步查询有效果)。
完成 mybatis-config.xml 的相关配置:
<settings>
<!-- 开启延迟加载 -->
<setting name="lazyLoadingEnabled" value="true"/>
<!-- 设置加载的数据是按需还是全部 -->
<setting name="aggressiveLazyLoading" value="false"/>
</settings>
- association 测试代码
public void test() throws IOException { InputStream is = Resources.getResourceAsStream("mybatis-config.xml"); SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is); SqlSession session = factory.openSession(true); EmpDeptMapper mapper = session.getMapper(EmpDeptMapper.class); // 查询员工! Emp emp = mapper.getEmpStep("4"); System.out.println(emp.getEname()); System.out.println("==============="); System.out.println(emp.getDept()); }
- 打印结果1:延迟加载开启前
==> Preparing: SELECT * FROM emp WHERE eid = ? ==> Parameters: 4(String) (BaseJdbcLogger.java:145) ====> Preparing: SELECT did, dname FROM dept WHERE did=? ====> Parameters: 4(Integer) (BaseJdbcLogger.java:145) <==== Total: 1 (BaseJdbcLogger.java:145) <== Total: 1 (BaseJdbcLogger.java:145) shaw =============== Dept [did=4, dname=财务部]
- 打印结果2:延迟加载开启后
==> Preparing: SELECT * FROM emp WHERE eid = ? ==> Parameters: 4(String) (BaseJdbcLogger.java:145) <== Total: 1 (BaseJdbcLogger.java:145) shaw =============== ==> Preparing: SELECT did, dname FROM dept WHERE did=? ==> Parameters: 4(Integer) (BaseJdbcLogger.java:145) <== Total: 1 (BaseJdbcLogger.java:145) Dept [did=4, dname=财务部]
- collection 测试代码
@Test public void test2() throws IOException { InputStream is = Resources.getResourceAsStream("mybatis-config.xml"); SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(is); SqlSession session = factory.openSession(true); EmpDeptMapper mapper = session.getMapper(EmpDeptMapper.class); Dept dept = mapper.getOnlyDeptInfoByDid("1"); System.out.println(dept.getDname()); System.out.println(dept.getEmps()); }
- fetchType="lazy|eager" 可单独指定某一个分步查询是否延迟加载
<!-- Dept getOnlyDeptInfoByDid(String did); --> <select id="getOnlyDeptInfoByDid" resultMap="deptMapStep"> <!-- 如果分步查询时,需要传递给调用的查询中有多个参数,则需要将多个参 数封装成 Map 来进行传递,语法如下: {k1=v1, k2=v2....} 在所调用的查询 SQL,取值时就要参考 Map 的取值方式,需要严格的按 照封装 Map 时所用的 key 来取值 ························································ 即如下 #{} 的内容必须与 {did=did} 的 key 相同 --> SELECT did, dname FROM dept WHERE did = #{did} </select> <!-- List<Emp> getEmpListByDid(String did); --> <select id="getEmpListByDid" resultType="Emp"> SELECT * FROM emp WHERE did = #{did} </select> <resultMap type="Dept" id="deptMapStep"> <id column="did" property="did"></id> <result column="dname" property="dname"/> <!-- fetchType="lazy|eager" 在 <association> 和 <collection> 标签中都可以设置 fetchType 可单独指定某一个分步查询是否延迟加载。 fetchType 可以灵活的设置查询是否需要使用延迟加载,而不需要因为 某个查询不想使用延迟加载将全局的延迟加载设置关闭。 --> <collection property="emps" column="{did=did}" fetchType="lazy" select="cn.edu.nuist.mapper.EmpDeptMapper.getEmpListByDid" /> </resultMap>
- 打印结果
Preparing: SELECT did, dname FROM dept WHERE did = ? Parameters: 1(String) Preparing: SELECT * FROM emp WHERE did = ? Parameters: 1(Integer) Total: 2 Total: 1 公关部 [Emp [eid=1, ename=Finch, age=40, sex=男, dept=null] , Emp [eid=11, ename=Samaritan, age=999, sex=999, dept=null]] =================↓ lazy ↓=================↑ eager ↑================= ==> Preparing: SELECT did, dname FROM dept WHERE did = ? ==> Parameters: 1(String) <== Total: 1 公关部 ==> Preparing: SELECT * FROM emp WHERE did = ? ==> Parameters: 1(Integer) <== Total: 2 [Emp [eid=1, ename=Finch, age=40, sex=男, dept=null] , Emp [eid=11, ename=Samaritan, age=999, sex=999, dept=null]]
ofType 和 javaType#
摘自:https://blog.csdn.net/u013216156/article/details/78642920/
javaType 和 ofType 都是用来指定对象类型的,但是 javaType 是用来指定 POJO 中属性的类型,而 ofType 指定的是映射到 List 集合属性中 POJO 的类型。
- POJO
public class User { private int id; private String username; private String mobile; private List<Post> posts; // ... }
- UserMapper.xml
<resultMap type="User" id="resultUserMap"> <result property="id" javaType="int" column="user_id" /> <result property="username" javaType="string" column="username" /> <result property="mobile" column="mobile" /> <!-- javatype 指定的是 User 对象的属性的类型(例如 id,posts) ofType 指定的是映射到 List 集合属性中 POJO 的类型(本例指的是 post 类型) --> <collection property="posts" ofType="com.spenglu.Post" javaType="java.util.ArrayList" column="userid"> <id property="id" column="post_id" javaType="int" jdbcType="INTEGER"/> <result property="title" column="title" javaType="string" jdbcType="VARCHAR"/> <result property="content" column="content" javaType="string" jdbcType="VARCHAR"/> </collection> </resultMap>
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· Manus的开源复刻OpenManus初探
· AI 智能体引爆开源社区「GitHub 热点速览」
· 三行代码完成国际化适配,妙~啊~
· .NET Core 中如何实现缓存的预热?