记录一下 J2EE 的 SSM 学习
MyBatis
configuration
mappers
MyBatis cache
简介
MyBatis 是持久层的一个 ORM 框架,移去了 JDBC 的繁琐操作以及 SQL
对代码的强绑定,采用 xml 的 SQL mapper 文件解决了这个问题,并能够进行从
SQL 结果到 实体类的转换
简单使用
1 2 3 4 5 6 7 use mybatis; create table users( uid int primary key auto_increment, uname varchar (20 ) not null , uage int not null ); insert into users(uid,uname,uage) values (null ,'张三' ,20 ),(null ,'李四' ,18 );
这个例子是基于 Maven
构建的,首先是配置依赖,这里是老版本了,实际上可以在 这里 搜,然后直接 copy
dependency
pom.xml
pom.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 <?xml version="1.0" encoding="UTF-8" ?> <project xmlns ="http://maven.apache.org/POM/4.0.0" xmlns:xsi ="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation ="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd" > <modelVersion > 4.0.0</modelVersion > <groupId > com.itheima</groupId > <artifactId > mybatistest</artifactId > <version > 1.0-SNAPSHOT</version > <dependencies > <dependency > <groupId > org.mybatis</groupId > <artifactId > mybatis</artifactId > <version > 3.5.2</version > </dependency > <dependency > <groupId > mysql</groupId > <artifactId > mysql-connector-java</artifactId > <version > 8.0.11</version > </dependency > <dependency > <groupId > junit</groupId > <artifactId > junit</artifactId > <version > 4.12</version > <scope > compile</scope > </dependency > </dependencies > <build > <resources > <resource > <directory > src/main/java</directory > <includes > <include > **/*.properties</include > <include > **/*.xml</include > </includes > <filtering > true</filtering > </resource > </resources > </build > </project >
这个 build part 实际上按这个目录写代码上不需要配置的
配置 MyBatis
db.properties
db.properties 1 2 3 4 mysql.driver=com.mysql.cj.jdbc.Driver mysql.url=jdbc:mysql://localhost:3306/mybatis?serverTimezone=UTC&characterEncoding=utf8&useUnicode=true&useSSL=false mysql.username=root mysql.password=mysql
应该不需要过多解释,数据库连接也就是这些东西
指定驱动
指定 JDBC URL,也就是 host, port 以及 database(可选)
用户名 + 密码
这个属性名(整个等号前面)实际上是任意的,在这个版本里还没有自动注入执行,只是为了语义
mybatis-config.xml
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 <?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 ="db.properties" /> <environments default ="development" > <environment id ="development" > <transactionManager type ="JDBC" /> <dataSource type ="POOLED" > <property name ="driver" value ="${mysql.driver}" /> <property name ="url" value ="${mysql.url}" /> <property name ="username" value ="${mysql.username}" /> <property name ="password" value ="${mysql.password}" /> </dataSource > </environment > </environments > <mappers > <mapper resource ="mapper/UserMapper.xml" /> </mappers > </configuration >
不在代码里配置,就得在 xml 里配置
<properties>
导入了我们的配置(记录的属性),随后的 <environments>
包含所有可能的环境,即不同的数据库配置,以及事务管理器配置,<dataSource>
中是主要的配置部分,将我们在 db.properties
中的属性加载过去
<mappers> 中配置了 mapper 部分,也即 SQL
到接口的映射配置,有几个 mapper 就有几条 <mapper>
environment 一个是通过 <environments> 的
default 切换,一个是代码里构建
SqlSessionFactory 的时候指定
UserMapper.xml
UserMapper.xml 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <?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" > <mapper namespace ="com.itheima.pojo.User" > <select id ="findById" parameterType ="int" resultType ="com.itheima.pojo.User" > select * from users where uid = #{id} </select > </mapper >
其中 <mapper> 中包含具体的映射关系(其实在
mybatis-config.xml
中我们就可以写这些,但是这样太耦合了,不推荐),这一元素必须要包含
namespace,这是为了解决 id
的命名空间问题,同一个 mapper 不能有相同的 id
id 是这一查询的接口名称(唯一标识)
parameterType 是传入参数类型
resultType 是传回结果类型,通常是一个
实体类,例如我们这里的 User
<select> 等标签内是一句动态解析的
SQL,不仅是参数可以动态,语句事实上也可以是动态的
关于这个参数传递,实际上对于这里 #{}
内部是任意的,这里是一个特殊情况,只有一个原子类型参数传递
对于一般情况,若传入类型直接为 实体类,那么 #{}
中的名称会直接解析为属性名,按照对象对应属性填充;若传入多个参数,且接口函数上无
@Param 注解标注,那么只能使用
#{0},#{1}
这样的顺序编号方式,按序读取参数;若传入接口函数上有注解标注或采用了 JVM
参数 -parameters
参数,则直接按函数接口上的命名对应填充
对于一个 mapper,在使用时我们更多讲它理解为一个函数,输入参数,生成 SQL
查询,返回结果,这也与他的接口设计模式相符合
User.sql
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 package com.itheima.pojo;public class User { private int uid; private String uname; private int uage; public int getUid () { return uid; } public void setUid (int uid) { this .uid = uid; } public String getUname () { return uname; } public void setUname (String uname) { this .uname = uname; } public int getUage () { return uage; } public void setUage (int uage) { this .uage = uage; } }
这是实体类的定义,ORM 通过这些 getter,
setter
函数来进行提取与修改,而避免直接对类成员的访问,这符合 OOP
的思想,且有更多参数的可操作性,例如对返回格式的调整以及参数验证
调用 MyBatis
UserTest.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 package Test;import com.itheima.pojo.User;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 org.junit.Test;import java.io.IOException;import java.io.Reader;public class UserTest { @Test public void userFindByIdTest () { String resources = "mybatis-config.xml" ; Reader reader = null ; try { reader = Resources.getResourceAsReader(resources); } catch (IOException e) { e.printStackTrace(); } SqlSessionFactory sqlMapper = new SqlSessionFactoryBuilder ().build(reader); SqlSession session = sqlMapper.openSession(); User user = session.selectOne("findById" , 2 ); System.out.println(user.getUname()); session.close(); } }
我们主要看这个类方法里面的代码,首先我们通过 Resources
类的 getResourceAsReader
方法,传入本地的配置文件路径来获取一个文件流的
Reader,并通过 SqlSessionFactoryBuilder 的
build 方法构建一个 SqlSessionFactory
SqlSessionFactory 这个类是大多数 SQL 操作的开始
我们在这里首先通过 openSession 方法打开一个数据库
session(连接),再通过 session 上的 selectOne
方法通过 statemenet参数(id) 选出我们配置的 mapper
的一个接口,在这里,2
是传入参数,这只是单参数,单对象输出(One,对应的有
List)的传递方法,该方法调用 mapper 接口,返回了我们指定的类型
需要注意的一点是,有时候我们运行导入的项目会遇到找不到
mybatis-config.xml 的问题,这是因为 Java 编译很大程度靠
IDE(编译参数)的调节,有的项目没有在 pom.xml 中配置
resources
目录,会导致这一目录在编译后不被复制到编译目录中,导致这个问题发生
因此要么配置 resources,要么在 src/main/resources
目录上右键,选择 Mark Director As >
Resources Root
核心配置
mapper 语法
除去一大堆关于 mybatis-config.xml 的克苏鲁选项以及
SqlSessionFactoryBuilder,SqlSessionFactory,等大量
api 之外,重要的其实只有 mapper 中的一些配置
1 2 3 4 5 6 7 8 9 10 11 12 13 14 <?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" > <mapper namespace ="com.itheima.mapper.StudentMapper" > <resultMap type ="com.itheima.pojo.Student" id ="studentMap" > <id property ="id" column ="sid" /> <result property ="name" column ="sname" /> <result property ="age" column ="sage" /> </resultMap > <select id ="findAllStudent" resultMap ="studentMap" > select * from t_student </select > </mapper >
这个配置文件中,使用了一个
resultMap,这通常用于处理当数据库字段和实体类属性名称不一致的问题,我们通过这个
map 映射字段,在一个查询中,我们指定 resultMap 参数为我们
resultMap 的 id
<result> 标签指定返回结果中的字段映射
<id> 标签指定数据表的主键
常用的查询类型有
<select>
<insert>
<update>
<delete>
<sql>
1 2 3 4 <insert id ="addUser" parameterType ="com.itheima.pojo.User" keyProperty ="uid" useGeneratedKeys ="true" > insert into users(uid,uname,uage) values(#{uid},#{uname},#{uage}) </insert >
这个 insert 中有几个特殊的字段,分别是 keyProperty 和
useGeneratedKeys,这是用于获取指定自动增长字段的值并写回传入对象的
uid 属性中
这常在我们做注册操作时使用,在注册时,用户 id
往往自动生成,但是我们可能需要获取这些 id
进行一些后续步骤,而避免二次查询
SqlSessionFactory 封装
在一个项目中,这部分往往可以封装并复用
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 package com.itheima.utils;import java.io.Reader;import org.apache.ibatis.io.Resources;import org.apache.ibatis.session.SqlSession;import org.apache.ibatis.session.SqlSessionFactory;import org.apache.ibatis.session.SqlSessionFactoryBuilder;public class MyBatisUtils { private static SqlSessionFactory sqlSessionFactory = null ; static { try { Reader reader = Resources.getResourceAsReader("mybatis-config.xml" ); sqlSessionFactory = new SqlSessionFactoryBuilder ().build(reader); } catch (Exception e) { e.printStackTrace(); } } public static SqlSession getSession () { return sqlSessionFactory.openSession(); } }
动态 SQL
一切都是为了不手动拼接字符串
有如下的表
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 use mybatis; create table users( uid int primary key auto_increment, uname varchar (20 ) not null , uage int not null ); insert into users(uid,uname,uage) values (null ,'张三' ,20 ),(null ,'李四' ,18 );CREATE TABLE t_student( sid INT PRIMARY KEY AUTO_INCREMENT, sname VARCHAR (50 ), sage INT ); INSERT INTO t_student(sname,sage) VALUES ('Lucy' ,25 );INSERT INTO t_student(sname,sage) VALUES ('Lili' ,20 );INSERT INTO t_student(sname,sage) VALUES ('Jim' ,20 );# 创建一个名称为t_customer的表 CREATE TABLE t_customer ( id int (32 ) PRIMARY KEY AUTO_INCREMENT, username varchar (50 ), jobs varchar (50 ), phone varchar (16 ) ); # 插入3 条数据 INSERT INTO t_customer VALUES ('1' , 'joy' , 'teacher' , '13733333333' );INSERT INTO t_customer VALUES ('2' , 'jack' , 'teacher' , '13522222222' );INSERT INTO t_customer VALUES ('3' , 'tom' , 'worker' , '15111111111' );
<if>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <select id ="findCustomerByNameAndJobs" parameterType ="com.itheima.pojo.Customer" resultType ="com.itheima.pojo.Customer" > select * from t_customer <trim prefix ="where" prefixOverrides ="and" > <if test ="username !=null and username !=''" > and username like concat('%',#{username}, '%') </if > <if test ="jobs !=null and jobs !=''" > and jobs= #{jobs} </if > </trim > </select >
这里 <if> 标签上的 test
属性表示当何时需要填充标签中的语句,在这个例子中,当 test
满足时才添加查询条件,也因此,if 常用于可变 where
查询条件的情况,即通过不同的字段组合进行查询。通过
<if> 标签,我们可以实现一个 mapper 查询实现动态的 SQL
生成
<trim>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 <update id ="updateCustomerByTrim" parameterType ="com.itheima.pojo.Customer" > update t_customer <trim prefix ="set" suffixOverrides ="," > <if test ="username !=null and username !=''" > username=#{username}, </if > <if test ="jobs !=null and jobs !=''" > jobs=#{jobs}, </if > <if test ="phone !=null and phone !=''" > phone=#{phone}, </if > </trim > where id=#{id} </update >
我们结合目前为止给出的两个例子,<trim> 标签上的
prefix
表示若内容存在,则以这一标签作为前缀,prefixOverrides 或
suffixOverrides 是需要被 trim
掉的多余前缀或后缀,例如这个例子的末尾多余逗号
<choose>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 <select id ="findCustomerByNameOrJobs" parameterType ="com.itheima.pojo.Customer" resultType ="com.itheima.pojo.Customer" > select * from t_customer where 1=1 <choose > <when test ="username !=null and username !=''" > and username like concat('%',#{username}, '%') </when > <when test ="jobs !=null and jobs !=''" > and jobs= #{jobs} </when > <otherwise > and phone is not null </otherwise > </choose > </select >
和 if-else 类似,选择其一满足 <when> 上
test 属性的
<foreach>
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 <select id ="findByArray" parameterType ="java.util.Arrays" resultType ="com.itheima.pojo.Customer" > select * from t_customer where id in <foreach item ="id" index ="index" collection ="array" open ="(" separator ="," close =")" > #{id} </foreach > </select > <select id ="findByList" parameterType ="java.util.Arrays" resultType ="com.itheima.pojo.Customer" > select * from t_customer where id in <foreach item ="id" index ="index" collection ="list" open ="(" separator ="," close =")" > #{id} </foreach > </select > <select id ="findByMap" parameterType ="java.util.Map" resultType ="com.itheima.pojo.Customer" > select * from t_customer where jobs=#{jobs} and id in <foreach item ="roleMap" index ="index" collection ="id" open ="(" separator ="," close =")" > #{roleMap} </foreach > </select >
结合这里的三个 mapper 例子以及对应 service 部分的代码
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 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 package Test;import com.itheima.pojo.Customer;import com.itheima.pojo.Student;import com.itheima.utils.MyBatisUtils;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 org.junit.After;import org.junit.Before;import org.junit.Test;import java.io.IOException;import java.io.Reader;import java.util.ArrayList;import java.util.HashMap;import java.util.List;import java.util.Map;public class MyBatisTest { private SqlSessionFactory sqlSessionFactory; private SqlSession sqlSession; @Before public void init () { String resources = "mybatis-config.xml" ; Reader reader = null ; try { reader = Resources.getResourceAsReader(resources); SqlSessionFactory sqlMapper = new SqlSessionFactoryBuilder ().build(reader); sqlSession = sqlMapper.openSession(); } catch (IOException e) { e.printStackTrace(); } } @Test public void findByArrayTest () { SqlSession session = MyBatisUtils.getSession(); Integer[] roleIds = {2 , 3 }; List<Customer> customers = session.selectList("com.itheima.mapper" + ".CustomerMapper.findByArray" , roleIds); for (Customer customer : customers) { System.out.println(customer); } session.close(); } @Test public void findByListTest () { SqlSession session = MyBatisUtils.getSession(); List<Integer> ids = new ArrayList <Integer>(); ids.add(1 ); ids.add(2 ); List<Customer> customers = session.selectList("com.itheima.mapper" + ".CustomerMapper.findByList" , ids); for (Customer customer : customers) { System.out.println(customer); } session.close(); } @Test public void findByMapTest () { SqlSession session = MyBatisUtils.getSession(); List<Integer> ids = new ArrayList <Integer>(); ids.add(1 ); ids.add(2 ); ids.add(3 ); Map<String, Object> conditionMap = new HashMap <String, Object>(); conditionMap.put("id" , ids); conditionMap.put("jobs" , "teacher" ); List<Customer> customers = session.selectList("com.itheima.mapper" + ".CustomerMapper.findByMap" , conditionMap); for (Customer customer : customers) { System.out.println(customer); } session.close(); } @After public void destory () { sqlSession.commit(); sqlSession.close(); } }
这里的
foreach,针对的是传入的一系列相同类型参数,第三个例子实际并不好,虽然传入
Map 类型参数,但是 foreach 最终是针对 Map
上一个 key 的 value,而这个 value 事实上是个 List
item
属性是每一个迭代对象的变量名(当我们需要自定义内容时会使用到,而默认就是直接
value + separator 填充)
index
属性是迭代的索引,对于列表/数组来说就是下标,
open/close 分别对应整体的前缀和后缀
值得注意的是 MyBatis 默认会将非 Map 参数封装为
Map,array 和 list 是这个
Map 对象的键名,这是固定的。
关联映射
一对一查询
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 <mapper namespace ="com.itheima.mapper.PersonMapper" > <select id ="findPersonById" parameterType ="Integer" resultMap ="IdCardWithPersonResult" > SELECT * from tb_person where id = #{id} </select > <resultMap type ="Person" id ="IdCardWithPersonResult" > <id property ="id" column ="id" /> <result property ="name" column ="name" /> <result property ="age" column ="age" /> <result property ="sex" column ="sex" /> <association property ="card" column ="card_id" javaType ="IdCard" select ="com.itheima.mapper.IdCardMapper.findCodeById" /> </resultMap > <select id ="findPersonById2" parameterType ="Integer" resultMap ="IdCardWithPersonResult2" > SELECT p.*, idcard.code from tb_person p, tb_idcard idcard where p.card_id = idcard.id and p.id = #{id} </select > <resultMap type ="Person" id ="IdCardWithPersonResult2" > <id property ="id" column ="id" /> <result property ="name" column ="name" /> <result property ="age" column ="age" /> <result property ="sex" column ="sex" /> <association property ="card" javaType ="IdCard" > <id property ="id" column ="card_id" /> <result property ="code" column ="code" /> </association > </resultMap > </mapper >
在第一个 mapper 中,我们在 <resultMap> 中添加了
<association> 标签,进行二次查询,首先我们获取到
card_id,随后我们使用 card_id 指定了一条
select 语句,即
select="com.itheima.mapper.IdCardMapper.findCodeById"。这一方法的缺点是需要二次查询,好处是可以进行
lazy load 的配置,也就是只有当我们从 Person
上调用 Person.getCard() 才会执行这条查询。
在第二个 mapper 中,我们在 <association>
元素里添加了和上面一样的
<id>,<result>
标签,这是因为我们通过等值连接进行了查询,直接将结果映射到
IdCard 对象上
值得注意的是这里的 javaType 或 type
都只写了类名,而非完整的带包名版本,这是因为配置了 MyBatis 的
<typeAliases>
1 2 3 4 5 6 7 8 9 10 11 12 13 <typeAliases > <package name ="com.itheima.pojo" /> </typeAliases > <settings > <setting name ="lazyLoadingEnabled" value ="true" /> <setting name ="aggressiveLazyLoading" value ="false" /> <setting name ="cacheEnabled" value ="true" /> </settings >
一对多查询
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 <?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" > <mapper namespace ="com.itheima.mapper.UsersMapper" > <resultMap type ="Users" id ="UserWithOrdersResult" > <id property ="id" column ="id" /> <result property ="username" column ="username" /> <result property ="address" column ="address" /> <collection property ="ordersList" ofType ="Orders" > <id property ="id" column ="orders_id" /> <result property ="number" column ="number" /> </collection > </resultMap > <select id ="findUserWithOrders" parameterType ="Integer" resultMap ="UserWithOrdersResult" > SELECT u.*,o.id as orders_id,o.number from tb_user u,tb_orders o WHERE u.id=o.user_id and u.id=#{id} </select > </mapper >
<collection> 标签内部指定了
<id> 和 <result>
元素,将一次的查询结果映射并封装成 ofType 存到
property (ordersList) 上,同样可以采用
select 属性指定查询语句来进行嵌套查询而不是连接
多对多查询
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 <?xml version="1.0" encoding="UTF8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd" > <mapper namespace ="com.itheima.mapper.OrdersMapper" > <select id ="findOrdersWithPorduct" parameterType ="Integer" resultMap ="OrdersWithProductResult" > select * from tb_orders WHERE id=#{id} </select > <resultMap type ="Orders" id ="OrdersWithProductResult" > <id property ="id" column ="id" /> <result property ="number" column ="number" /> <collection property ="productList" column ="id" ofType ="Product" select ="com.itheima.mapper.ProductMapper.findProductById" > </collection > </resultMap > <select id ="findOrdersWithPorduct2" parameterType ="Integer" resultMap ="OrdersWithPorductResult2" > select o.*,p.id as pid,p.name,p.price from tb_orders o,tb_product p,tb_ordersitem oi WHERE oi.orders_id=o.id and oi.product_id=p.id and o.id=#{id} </select > <resultMap type ="Orders" id ="OrdersWithPorductResult2" > <id property ="id" column ="id" /> <result property ="number" column ="number" /> <collection property ="productList" ofType ="Product" > <id property ="id" column ="pid" /> <result property ="name" column ="name" /> <result property ="price" column ="price" /> </collection > </resultMap > </mapper >
这里的两个 mapper 通过 <collection>
标签实现对于多个查询结果的封装和映射。
第一个 mapper 中,<collection> 标签上的
select 属性指定了查询语句,ofType
指定每一个查询结果的类型,最后这些结果会被封装成一个 List 并 set 到
property 上,这个方式属于二次(嵌套)查询
第二个 mapper 中,<collection> 标签内部指定了
<id> 和 <result>
元素,将一次的查询结果映射并封装成 ofType
一级缓存
对于同一 SqlSession 在无 update,delete,insert
操作期间,任何的查询都将被缓存(默认开启)
二级缓存
对于同一 Mapper 的相同 SQL 语句,对于 不同的 SqlSession
也将被缓存(手动开启),在 Mapper 的 namespace 下,用
<cache>...</cache> 标签包裹即可开启二级缓存