【文章内容输出来源:拉勾教育Java高薪训练营】
--- 所有脑图均本人制作,未经允许请勿滥用 ---
〇、 脑图总览
图片可能存在更新延迟,最新脑图会在“传送门”中实时更新。
一、 从持久层开始
:) Hello Mybatis
1.1 持久层定义
系统逻辑层面上,专注于实现数据持久化的一个相对独立的领域(Domain),把数据保存到可掉电式存储设备中。持久层是负责向(或者从)一个或者多个数据存储器中存储(或者获取)数据的一组类和组件
1.2 早期JDBC
JDBC虽然是最接近数据库的访问方式,但也是最“僵硬”的,随着项目庞大起来,响应的维护量巨大。
它的问题非常明显:
数据库连接创建、释放频繁造成系统资源浪费,从而影响系统性能
Sql语句在代码中硬编码,造成代码不易维护
使用preparedStatement向占有位符号传参数存在硬编码,因为sql语句的where条件不一定,可能多也可能少,修改sql还要修改代码,系统不易维护
对结果集解析存在硬编码(查询列名),sql变化导致解析代码变化,系统不易维护,如果能将数据 库记录封装成pojo对象解析比较方便
public static void main() {
Connection connection = null; // 准备连接实体
PreparedStatement preparedStatement = null; // 准备statement
ResultSet resultSet = null; // 准备结果集
try {
/* 1. 加载数据库驱动 (适用Mysql5+,8+请使用新驱动com.mysql.cj.jdbc.Driver)*/
Class.forName("com.mysql.jdbc.Driver");
/* 2. 通过驱动管理类获取数据库链接 */
connection = DriverManager
.getConnection("jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8", "root", "root");
/* 3. 定义sql语句 ? 占位符 */
String sql = "SELECT * FROM user WHERE user_name = ?";
/* 4. 获取预处理statement */
preparedStatement = connection.prepareStatement(sql);
/* 5. 设置参数,第一个参数为sql语句中参数的序号(从1开始),第二个参数为设置的参数值 */
preparedStatement.setString(1, "Archie");
/* 6. 向数据库发出sql查询,并接收结果集 */
resultSet = preparedStatement.executeQuery();
/* 7. 遍历查询结果集 */
while (resultSet.next()) {
// Do something ...
}
} catch (Exception e) {
e.printStackTrace();
} finally {
/* 8. 释放资源 */
try {
if (resultSet != null) {
resultSet.close();
}
if (preparedStatement != null) {
preparedStatement.close();
}
if (connection != null) {
connection.close();
}
} catch (SQLException e) {
e.printStackTrace();
}
}
}
于是一个个持久层框架——Hibernate
、MyBatis
、Spring Data
……横空出世
1.3 自定义持久层框架(借鉴Mybatis)
1.3.1 我们对于JDBC的问题,可以有以下解决思路:
使用数据库连接池 初始化 资源;
将“僵硬的”sql语句抽取到xml配置文件中;
使用反射、内省等底层技术,自动将实体与表进行属性与字段的自动映射
1.3.2 框架设计
有心者可以使用这份代码认真研读,这对后期“手撕”Mybatis源码很有帮助!
二、 Mybatis由浅入深 (代码附小节末)
2.1 Mybatis基本概念
2.1.1 ORM(Object/Relation Mapping)--对象/关系数据库映射:
编程人员只需利用面向对象的编程语言,把对持久化对象的CRUD操作,转化为对数据库底层的操作。
2.1.2 Mybatis简介与历史:
是基于ORM的
半自动/轻量级
/持久层框架;支持定制化SQL、高级映射;
可以使用简单的
XML/注解
来 配置/映射 接口、POJO.
原是apache的一个开源项目iBatis, 2010年6月这个项目由apache software foundation 迁移到了google code,随着开发团队转投Google Code旗下,ibatis3.x正式更名为Mybatis ,代码于2013年11月迁移到Github。iBATIS一词来源于“internet”和“abatis”的组合,是一个基于Java的持久层框架。 iBATIS提供的持久层框架包括SQL Maps和Data Access Objects(DAO)
2.1.3 Mybatis优势:
核心SQL可以由程序员自己定制以及优化,且SQL和java编码分离,使得功能边界清晰———前者专注数据、后者专注业务逻辑
2.2 踏出第一步
首先要明确,无论是何种技术,我们最好的学习文档就是 官方网址
-->
-----鉴于大部分人在工作中经常使用Mybatis,基本的使用在此简要带过,详细步骤可以参考各大教程博客------
2.2.1 开发顺序
确认核心文件
SqlMapConfig.xml
pom.xml中添加mybatis依赖
确认数据库的目标table
确认与之对应的POJO类(属性和表对应)
编写“桥梁”——
XXMapper.xml
自测
Mybatis的代理开发方式:程序员只编写Mapper接口(相当于Dao接口),由Mybatis识别这些接口并创建动态代理对象。
2.2.2 编写Mapper接口的基本规范:
Mapper.xml文件中的
namespace
与mapper接口的全限定名
相同Mapper接口方法名
和Mapper.xml中定义的每个statement的id
相同Mapper接口方法的
输入参数类型
和mapper.xml中定义的每个sql的parameterType
的类型相同Mapper接口方法的
输出参数类型
和mapper.xml中定义的每个sql的resultType
的类型相同
2.3 核心配置文件sqlMapConfig.xml
2.3.1 基本配置
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<environments default="development">
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.cj.jdbc.Driver"/>
<property name="url"
value="jdbc:mysql://localhost:3306/springdb?useSSL=false&serverTimezone=UTC&allowPublicKeyRetrieval=true"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="UserMapper.xml"/>
</mappers>
</configuration>
2.3.2 mybatis支持的三种数据源(DataSource)
UNPOOLED:每次被请求时打开和关闭连接
POOLED:这种数据源的实现利用“池”的概念将 JDBC 连接对象组织起来
JDNI:为了能在如 EJB 或应用服务器这类容器中使用而实现的数据源
2.3.3 mybatis自生产常用类型别名(TypeAlias)
| 别名 | 数据类型 |
| ------- | -------- |
| string | String |
| long | Long |
| int | Integer |
| _int | int |
| double | Double |
| boolean | Boolean |
2.3.4 动态SQL
| 标签元素 | 作用 | 备注 |
| ------------------------------------------------------------ | -------------------------------------- | ---------- |
| if | 对输入参数进行判定,实现按条件拼接语句 | 单条件分支 |
| choose / when / otherwise | 相当于JAVA中的 if...else...
| 多条件分支 |
| trim | 辅助(前置/后置)拼接 | 处理 SQL 拼接问题 |
| where | 内嵌sql职能处理至'where'后拼接 | 1.若输出是以and开头,会把首个'and'忽略掉
2.内部条件可以全空 |
| set | 和where类似 | 内部条件不可
全空 |
| foreach | 在sql中迭代一个集合 | collection情况:
1. List -> "list"
2. Array -> "array"
3. Map -> 对应key |
| script | 注解中使用动态sql | |
| bind | 在OGNL表达式外创建一个变量并绑定到当前上下文 | |
2.4 复杂映射开发(基于配置)
2.4.1 一对一查询
业务描述:每一个订单包含唯一一个用户信息
分析语句:
SELECT * FROM orders o, user u WHERE o.uid = u.id;
2.4.1.1 orders表 + user表
2.4.1.2 Order实体 + User实体
public class User {
private Integer id;
private String userName;
private Integer age;
// ...省略 get/set/toString
}
public class Order {
private Integer id;
private Date orderTime;
private Double total;
// 表明该订单属于哪个用户
private User user;
// ......省略 get/set/toString
}
2.4.1.3 OrderMapper.xml + OrderMapper.java
<?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="orderMapper">
<resultMap id="orderMapper" type="com.archie.pojo.Order">
<result column="id" property="id"/>
<result column="ordertime" property="orderTime"/>
<result column="total" property="total"/>
<association property="user" javaType="com.archie.pojo.User">
<result column="uid" property="id"/>
<result column="username" property="userName"/>
<result column="age" property="age"/>
</association>
</resultMap>
<select id="listAll" resultMap="orderMapper">
SELECT * FROM orders o, user u WHERE o.uid = u.id;
</select>
</mapper>
public interface OrderMapper {
List<Order> listAll();
}
2.4.1.4 自测 & 结果
@Test
public void testDemo() {
SqlSession sqlSession = getFactory().openSession();
List<Order> orderList = sqlSession.selectList("orderMapper.listAll");
Assert.assertNotNull(orderList);
for (Order order : orderList) {
System.out.println(order);
}
sqlSession.close();
}
2.4.2 一对多查询
业务描述:一个用户有多个订单,一个订单只从属于一个用户
分析语句:
SELECT *,o.id oid FROM user u LEFT JOIN orders o ON u.id=o.uid;
2.4.2.1 修改User实体
public class User {
private Integer id;
private String userName;
private Integer age;
//【新增】表示用户关联的订单
private List<Order> orderList;
// ......省略 get/set/toString
}
2.4.2.2 UserMapper.xml + UserMapper.java
<?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="userMapper">
<resultMap id="userMapper" type="com.archie.pojo.User">
<result property="id" column="id"/>
<result property="userName" column="username"/>
<result property="age" column="age"/>
<!-- 使用collection接收List<Order> -->
<collection property="orderList" ofType="com.archie.pojo.Order">
<result column="oid" property="id"/>
<result column="ordertime" property="orderTime"/>
<result column="total" property="total"/>
</collection>
</resultMap>
<select id="listAll" resultMap="userMapper">
SELECT *,o.id oid FROM user u LEFT JOIN orders o ON u.id=o.uid;
</select>
</mapper>
public interface UserMapper {
List<User> listAll();
}
2.4.2.3 自测 & 结果
@Test
public void test003() {
SqlSession sqlSession = getFactory().openSession();
List<User> userList = sqlSession.selectList("userMapper.listAllWithOrders");
Assert.assertNotNull(userList);
for (User user : userList) {
System.out.println(user);
}
sqlSession.close();
}
2.4.3 多对多查询
业务描述:一个用户有多个角色,一个角色被多个用户使用
语句分析:`SELECT * FROM user u LEFT JOIN sys_user_role ur ON u.id = ur.userid
LEFT JOIN sys_role r ON r.id = ur.roleid`
2.4.3.2 sys_role表 + sys_user_role表
2.4.3.2 创建Role实体 + 修改User实体
public class Role {
private Integer id;
private String roleName;
private String roleDesc;
// ...省略set/get/toString
}
public class User {
private Integer id;
private String userName;
private Integer age;
//表示用户关联的订单
private List<Order> orderList;
//【新增】表示用户关联的角色
private List<Role> roleList = new ArrayList<>();
// ...省略set/get/toString
}
2.4.3.3 在UserMapper接口中添加方法
public interface UserMapper {
List<User> listAll(); // 获取所有用户(自身)
List<User> listAllWithOrders(); // 获取所有用户和对应的订单信息(一对多)
List<User> listAllWithRole(); // 获取所有用户和他们的角色(多对多)
}
2.4.3.4 修改UserMapper.xml中的配置(多加一个resultMap)
<?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="userMapper">
<resultMap id="userMapper01" type="com.archie.pojo.User">
<result property="id" column="id"/>
<result property="userName" column="username"/>
<result property="age" column="age"/>
<!-- 使用collection接收List<Order> -->
<collection property="orderList" ofType="com.archie.pojo.Order">
<result column="oid" property="id"/>
<result column="ordertime" property="orderTime"/>
<result column="total" property="total"/>
</collection>
</resultMap>
<resultMap id="userMapper02" type="com.archie.pojo.User">
<result property="id" column="id"/>
<result property="userName" column="username"/>
<result property="age" column="age"/>
<collection property="roleList" ofType="com.archie.pojo.Role">
<result property="id" column="roleid"/>
<result property="roleName" column="rolename"/>
<result property="roleDesc" column="roleDesc"/>
</collection>
</resultMap>
<!-- 自身查询 -->
<select id="listAll" resultType="com.archie.pojo.User">
SELECT * FROM user;
</select>
<!-- 获取所有用户和对应的订单信息(一对多) -->
<select id="listAllWithOrders" resultMap="userMapper01">
SELECT *,o.id oid FROM user u LEFT JOIN orders o ON u.id=o.uid;
</select>
<!-- 获取所有用户和他们的角色(多对多) -->
<select id="listAllWithRole" resultMap="userMapper02">
SELECT * FROM user u LEFT JOIN sys_user_role ur ON u.id = ur.userid LEFT JOIN sys_role r ON r.id = ur.roleid
</select>
</mapper>
2.4.3.5 自测 & 结果
@Test
public void test004() {
SqlSession sqlSession = getFactory().openSession();
List<User> userList = sqlSession.selectList("userMapper.listAllWithRole");
Assert.assertNotNull(userList);
for (User user : userList) {
System.out.println(user);
}
sqlSession.close();
}
2.5 复杂映射开发(基于注解)
暂略......
2.6 Mybatis一级缓存
Mybatis的一级缓存默认打开,底层的实现是HashMap,生命周期在每一条SqlSession
中
2.6.1 测试 & 结果
@Test
public void test005() {
SqlSession sqlSession = getFactory().openSession();
List<Object> userList01 = sqlSession.selectList("userMapper.listAll");
System.out.println(userList01.get(0));
System.out.println("---------------------------------------------------");
List<Object> userList02 = sqlSession.selectList("userMapper.listAll");
System.out.println(userList02.get(0));
Assert.assertEquals(userList01, userList02); // 断言:两个对象地址是否一样
}
再试试如果在两次相同操作过程中,执行了其他RUD操作,是否会导致缓存被刷新
@Test
public void test005() {
SqlSession sqlSession = getFactory().openSession();
List<Object> userList01 = sqlSession.selectList("userMapper.listAll");
System.out.println(userList01.get(0));
System.out.println("---------------------------------------------------");
User user = new User();
user.setId(1);
user.setUserName("Archie(2.0)");
sqlSession.update("userMapper.updateById", user);
sqlSession.commit();
System.out.println("---------------------------------------------------");
List<Object> userList02 = sqlSession.selectList("userMapper.listAll");
System.out.println(userList02.get(0));
Assert.assertEquals(userList01, userList02); // 断言:两个对象地址是否一样
sqlSession.close();
}
2.7 Mybatis二级缓存
Mybatis的二级缓存是跨SqlSession的,它基于mapper文件中的namespace
——
所以:若两个mapper的namespace相同,就算mapper不同,也共享同一个二级缓存。
2.7.1 打开二级缓存
和一级缓存默认开启不一样,二级缓存需要我们手动开启
<settings>
<setting name="cacheEnable" value="true"/>
</settings>
还得再UserMapper.xml中开启缓存
<mapper namespace="userMapper">
<cache/>
...
<mapper/>
最后,因为二级缓存的存储介质多种多样(内存/硬盘...),所以我们要将对应POJO类实现Serializable接口
,从而实现序列/反序列化。
public class User implements Serializable
2.7.2 测试 & 结果
A. 二级缓存和sqlSession无关
@Test
public void test006() {
SqlSession sqlSession1 = sqlSessionFactory.openSession();
UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class);
SqlSession sqlSession2 = sqlSessionFactory.openSession();
UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);
List<User> userList1 = mapper1.listAll();
System.out.println(userList1.get(0));
sqlSession1.close(); // 关闭 1号SqlSession ,清空一级缓存
System.out.println("---------------------------------------------------");
List<User> userList2 = mapper2.listAll();
System.out.println(userList2.get(0));
sqlSession2.close();
Assert.assertNotEquals(userList1, userList2); // 断言:两个对象地址是否不一样
}
B. 执行commit()操作,二级缓存数据清空
@Test
public void test007() {
SqlSession sqlSession1 = sqlSessionFactory.openSession();
SqlSession sqlSession2 = sqlSessionFactory.openSession();
SqlSession sqlSession3 = sqlSessionFactory.openSession();
UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class);
UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);
UserMapper mapper3 = sqlSession3.getMapper(UserMapper.class);
List<User> userLis1 = mapper1.listAll();
sqlSession1.close(); // 清空一级缓存
System.out.println("-----------------执行commit操作-------------------");
User user = new User();
user.setId(1);
user.setUserName("Archie(3.0)");
mapper2.updateById(user);
sqlSession2.commit();
System.out.println("------------------------------------");
List<User> userList2 = mapper3.listAll();
Assert.assertNotEquals(userLis1, userList2); // 断言:两个对象地址是否不一样
}
2.7.3 二级缓存整合Redis
由于mybatis自带的二级缓存是单服务器工作
,无法在分布式系统上工作,因此需要借助分布式缓存:【Redis/memcached/ehcache】
2.7.3.1 准备
- 准备pom依赖
<dependency>
<groupId>org.mybatis.caches</groupId>
<artifactId>mybatis-redis</artifactId>
<version>1.0.0-beta2</version>
</dependency>
- sqlMapConfig.xml指定分布式缓存类型 + 对应接口是否启用
<!-- 整合Redis加入分布式缓存 -->
<cache type="org.mybatis.caches.redis.RedisCache"/>
<!-- useCache="true" -->
<select id="listAll" resultType="com.archie.pojo.User" useCache="true">
SELECT * FROM user;
</select>
- 加入redis.properties文件(若无指定修改,可不添加该文件,底层有默认值)
redis.host=localhost
redis.port=6379
redis.connectionTimeout=5000
redis.password=
redis.database=0
- 测试前 开启Redis服务
Redis快速运行脚本可以自己写:
redis-server redis.windows.conf
2.7.3.1 测试
@Test
public void test008() {
SqlSession sqlSession1 = sqlSessionFactory.openSession();
UserMapper mapper1 = sqlSession1.getMapper(UserMapper.class);
SqlSession sqlSession2 = sqlSessionFactory.openSession();
UserMapper mapper2 = sqlSession2.getMapper(UserMapper.class);
List<User> userList1 = mapper1.listAll();
System.out.println(userList1.get(0));
sqlSession1.close(); // 清空一级缓存
System.out.println("---------------------------------------------------");
List<User> userList2 = mapper2.listAll();
System.out.println(userList2.get(0));
sqlSession2.close();
Assert.assertNotEquals(userList1, userList2); // 断言:两个对象地址是否不一样
}
2.8 MyBatis插件(plugin)
暂略...
三、Mybatis源码“斩杀”
攻坚战开始了!O.O
P.S. 之前的自定义持久层框架一定要熟烂于心!
3.1 流程总览
暂略...
3.2 传统方式源码剖析
// 1.使用ClassLoader对文件进行 “流化” !(但还未具体解析)
InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
// 2. 使用XPathParse 解析出 XMLConfigBuilder 对象,调用parse()方法
// -- 2.1. parse()方法内部 利用 XNode 解析出 每个标签并将它们封装在 [JavaBean]Configuration中
// -- 2.2. 构建者模式创建 DefaultSqlSessionFactory 对象
sqlSessionFactory = new SqlSessionFactoryBuilder().build(resourceAsStream);
// 3. 生成DefaultSqlsession实例对象了,并将 Executor 对象封装在其中
// -- 3.1. 其中默认设置了 事务提交方式为:[非自动提交]
sqlSession = sqlSessionFactory.openSession();
// 4. 根据statementid来从Configuration中map集合中获取到了指定的MappedStatement对象
// -- 4.1. 执行 executor.query() 从而 将查询任务委派到了executor执行器
// --- 4.1.2. 顶层query() -> 设置分页值、设置CacheKey
// --- 4.1.2. 判断 执行器是否被关闭、是否需要刷新缓存, 若否 -> 调用queryFromDatabase()方法
// --- 4.1.3. 核心调用SimpleExecutor.doQuery()
// ---- 4.1.3.1. 创建StatementHandler对象
// ---- 4.1.3.2. 构建者模式生成ParameterHandler
// ---- 4.1.3.3. 获取TypeHandler
// ---- 4.1.3.4. 调用ResultSetHandler.handleResultSets() 处理statement
// ***** 底层JDBC操作 *****
// 5. 返回List<E> 的结果
List<User> userList = sqlSession.selectList("com.archie.dao.UserMapper.listAll");
// 6. 释放资源
sqlSession.close();
3.3 Mapper代理方式源码剖析
获取sqlSession前的步骤和 传统方式一致;
// ......
SqlSession sqlSession = sqlSessionFactory.openSession();
// 4. 通过mapperRegistry 调用 getMapper()方法
// -- 4.1. 从mapperRegistry中的 HashMap 拿 MapperProxyFactory
// -- 4.2. 通过动态代理工厂 生成实例
// --- 4.2.1. 实例的生成过程中实现了对每一个SqlSession 的动态代理
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
// 5. 获取到 代理对象后,就可以直接调用被代理接口的方法了
// -- 5.1. 代理对象,调用方法时都是执行MapperPorxy中的invoke()方法
// ---- 5.1.1. invoke()中,若非Object的方法,则获取MapperMethod对象,【重点】它来最终调用execute()方法
// ---- 5.1.2. execute()中,判断方法类型-->CRUD, 最终交由ResultHandler处理,返回Object型的result值
List<User> userList01 = mapper.listAll();
3.4 二级缓存源码剖析
查询请求优先级:【 二级缓存 > 一级缓存 > 数据库 】
二级缓存和具体namespace绑定,一个Mapper中有一个Cache,相同Mapper的MapperStatement共用一个Cache
3.4.1 思考A - ‘为什么说一个Mapper中的Cache被共用?’
在3.2小节中,我们分析步骤2时,sqlMapConfig.xml 的具体解析是由XMLConfigBuilder调用parse()解析的,它中途需要解析到<cache/>
标签,这个标签也是我们打开二级缓存的标志。所以我们得分析从这里开始。
3.4.2 思考B - ‘为什么优先级 :二级缓存 > 一级缓存?’
P.S. 二级缓存开启时,走的是
CachingExecutor实现类
3.4.3 思考C - ‘为何只有SqlSession提交或者关闭后,二级缓存才会生效?’
暂略...
3.5 延迟加载源码剖析
暂略...
3.5.1 使用场景
当需要用到数据的时候才进行多表关联查询。
优点:大大提高数据库性能;
缺点:因为查询工作也要消耗时间,所以可能造成用户等待时间变长,造成用户体验下降
建议使用场景:
- 一对一/多对一: 立即加载
- 一对多/多对多: 延迟加载
懒加载是基于 嵌套查询的!
3.5.2 实现
3.5.2.1 UserMapper.xml + UserMapper.java
指明当前 collection标签 需要定位的内嵌sql
select="com.archie.dao.OrderMapper.listByUid"
传给内嵌sql的参数
column="id"
实现局部懒加载
fetchType="lazy"
<resultMap id="userMapper01" type="com.archie.pojo.User">
<id property="id" column="id"/>
<result property="userName" column="username"/>
<result property="age" column="age"/>
<collection property="orderList" ofType="com.archie.pojo.Order"
select="com.archie.dao.OrderMapper.listByUid" column="id"
fetchType="lazy">
<result column="uid" property="id"/>
<result column="ordertime" property="orderTime"/>
<result column="total" property="total"/>
</collection>
</resultMap>
<select id="listById" resultMap="userMapper01" parameterType="com.archie.pojo.User">
SELECT * FROM user WHERE id = #{id};
</select>
User listById(User user);
3.5.2.2 OrderMapper.xml + OrderMapper.java
<resultMap id="orderMapper" type="com.archie.pojo.Order">
<id column="id" property="id"/>
<result column="ordertime" property="orderTime"/>
<result column="total" property="total"/>
<association property="user" javaType="com.archie.pojo.User">
<result column="uid" property="id"/>
<result column="username" property="userName"/>
<result column="age" property="age"/>
</association>
</resultMap>
<select id="listByUid" resultType="com.archie.pojo.Order">
SELECT * FROM orders WHERE uid = #{uid};
</select>
List<Order> listByUid(Integer uid);
3.5.2.3 自测 & 结果
@Test
public void test009() {
SqlSession sqlSession = sqlSessionFactory.openSession();
User user = new User();
user.setId(1);
User resultUser = sqlSession.selectOne("com.archie.dao.UserMapper.listById", user);
System.out.println(resultUser.getUserName());
System.out.println("****** 还未使用到订单信息 *******");
System.out.println("********** 现在想要获取对应用户的订单信息了 ************");
System.out.println(resultUser.getOrderList());
}
3.5.2.4 全局配置懒加载
在sqlMapConfig.xml中加入<setting name="lazyLoadingEnabled" value="true">
即可实现全局懒加载
懒加载的优先级: 局部 高于 全局
因此——当全局开启时,若有部分接口不想懒加载,可以直接 fetchType="eager"
实现局部立即加载
3.5.3 源码剖析
暂略...
四、 设计模式
4.1 Builder构建者模式
4.1.1 通俗定义
使用多个简单对象 --构建--> 复杂对象 的过程。
4.1.2 简单实现
Demo【构建一个Computer复杂对象】
简单对象:键盘、鼠标、显示器、主机……
复杂对象:Computer
顺序:创建构造类 --> 依次创建部件 --> 将部件组装成target对象
【step-A】 定义目标对象Computer
public class Computer {
/* 显示器部件 */
private String disPlayer;
/* 主机部件 */
private String mainEngine;
/* 鼠标部件 */
private String mouse;
/* 键盘部件 */
private String keyBoard;
// ...省略 set、get、toString
}
【step-B】 定义构建者ComputerBuilder
public class ComputerBuilder {
/* 定义目标对象 */
private Computer target = new Computer();
/**
* 安装显示器部件给目标对象
* @param disPlayer
*/
public void installDisPlayer(String disPlayer) {
target.setDisPlayer(disPlayer);
}
/**
* 安装主机部件给目标对象
* @param mainEngine
*/
public void installMainEngine(String mainEngine) {
target.setMainEngine(mainEngine);
}
/**
* 安装鼠标部件给目标对象
* @param mouse
*/
public void installMouse(String mouse) {
target.setMouse(mouse);
}
/**
* 安装键盘部件给目标对象
* @param keyBoard
*/
public void installKeyBoard(String keyBoard) {
target.setKeyBoard(keyBoard);
}
/**
* 实现复杂对象构建
* @return
*/
public Computer build() {
return target;
}
}
测试
public class realizeBuildingComputer {
public static void main(String[] args) {
ComputerBuilder builder = new ComputerBuilder();
builder.installDisPlayer("A-1001-显示器");
builder.installMainEngine("B-2344-主机");
builder.installMouse("C-6617-鼠标");
builder.installKeyBoard("D-4990-键盘");
/* 安装部件完毕,生产电脑 */
Computer computer = builder.build();
/* 测试打印 */
System.out.println("computer = \n" + computer);
}
}
4.1.3 mybatis中的体现
Mybatis的初始化流程非常复杂,在创建SqlSessionFactory的过程中,利用了大量的Builder进行分层构建;
核心javaBean-'Configuration' 也是使用了XmlConfigBuilder来构建的;
4.2 工厂模式
(目前只需掌握 /简单工厂模式/ 即可)
4.2.1 通俗定义
简单工厂模式(Simple Factory Pattern):又称为静态工厂方法(Static Factory Method)模式,它属于创建型模式。
专门有一个类 ——> 根据参数的不同返回不同类的实例(这些实例往往是同一个父类)
4.2.2 简单实现
Demo 【一个厂商生产不同品牌的电脑】
【Step-A】创建产品抽象类
public abstract class Computer {
/* 启动电脑方式 :由具体产品实现 */
public abstract void start();
}
【Step-B】创建具体产品
创建不同品牌的电脑,都继承抽象父类->Computer
public class HPComputer extends Computer{
@Override
public void start() {
System.out.println("惠普电脑启动");
}
}
public class LenovoComputer extends Computer {
@Override
public void start() {
System.out.println("联想电脑开机");
}
}
【Step-C】创建工厂类
提供静态方法createComputer用来生产电脑(用户只需要传输想要的品牌,响应对象即刻实例化)
public class ComputerFactory {
public static Computer createComputer(String brand) {
Computer computer = null;
switch (brand) {
case "lenovo":
computer = new LenovoComputer();
case "hp":
computer = new HPComputer();
}
return computer;
}
}
测试
public static void main(String[] args) {
ComputerFactory.createComputer("hp").start();
System.out.println("-----------");
ComputerFactory.createComputer("lenovo").start();
}
4.2.3 Mybatis中体现
SqlSessionFactory中的openSession有很多重载,支持各类参数类别的输入,从而构建核心SqlSeesion对象
这个工厂的核心底层方法是openSessionFromDataSource
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
final Environment environment = configuration.getEnvironment();
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
// 根据参数创建制定类型的Executor
final Executor executor = configuration.newExecutor(tx, execType);
// 执行器注入构建方法,返回SqlSession对象
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
closeTransaction(tx); // may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
4.3 代理模式
(目前掌握动态代理即可)
4.3.1 通俗定义
给某一个对象提供一个代理,并由代理对象控制对原对象的引用
4.3.2 简单实现
Demo【对Person实体进行代理】
【Step-A】‘人’接口和默认实现类
public interface Person {
void doSomething();
}
public class Justin implements Person{
@Override
public void doSomething() {
System.out.println("Justin is doing something...");
}
}
【Step-B】具体的代理实现类
public class JdkDynamicProxyImpl implements InvocationHandler {
// 声明被代理的对象
private Person person;
// 构造函数
public JdkDynamicProxyImpl(Person person) {
this.person = person;
}
/**
* 获取代理对象
* @return
*/
public Object getTarget() {
Object proxyInstance = Proxy.newProxyInstance(person.getClass().getClassLoader(), person.getClass().getInterfaces(), this);
return proxyInstance;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
System.out.println("...对原方法进行前置增强");
Object invoke = method.invoke(person, args); // 原方法的执行
System.out.println("...对原方法进行后置增强");
return invoke;
}
}
测试
public class ProxyTest {
public static void main(String[] args) {
System.out.println("【直接调用某人类的方法】");
Justin justin = new Justin();
justin.doSomething();
System.out.println("-----------------");
System.out.println("【使用代理类调用同一个人类的行为】");
Person proxyJustn = (Person) new JdkDynamicProxyImpl(new Justin()).getTarget();
proxyJustn.doSomething();
}
}
4.3.3 Mybatis中体现
代理模式是MyBatis的核心模式
由于这个模式,我们只需要编写XXXMapper.java
接口,而不需要实现;由MyBatis完成具体SQL的执行。