mybatis延迟加载是为什么(mybatis延迟加载的原理)

mybatis

v20190523

目录

一、框架概述

二、Mybatis概述

三、Mybatis开发

四、mybatis-config.xml

五、Mybatis源码专题

六、关联查询/延迟加载
mybatis延迟加载是为什么(mybatis延迟加载的原理)

七、动态SQL

八、缓存

九、Mybatis相关工具和插件

十、扩展知识

十一、参考

一、框架概述1 什么是框架?

一个框架是一个可复用的设计构件

整体设计、依赖关系、责任分配、流程控制

上下文 Context

2 为什么要使用框架?

软件系统十分复杂

现代系统,不仅是模块很多,系统也很多

例如互联网、电商、分布式等项目

帮助完成基础工作

开发人员可以集中精力在业务逻辑设计

消除业务无关的重复代码

解决细节问题

成熟稳健

事务处理、安全性、数据流控制等

持续升级

很多人使用和维护

不需要自己升级代码

3 软件分层分层

表现层

servlet

spring mvc

struts2

业务层

java bean

spring

持久层

jdbc

mybatis

hibernate

spring data jpa

二、Mybatis概述1 官网

http://www.mybatis.org/mybatis-3/zh/index.html

2 mybatis是什么?

持久层框架

java本身使用jdbc驱动做持久层开发

JDBCJava数据库连接,Java Database Connectivity,简称JDBC

半自动化ORM框架

ORM 对象关系映射,Object Relational Mapping,简称ORM,或O/RM,或O/R mapping

封装通用方法

封装了 注册驱动、获取连接、创建statement、设置参数、对结果处理等

xml/注解方式处理SQL

3 mybatis由来

iBatis

mybatis 前身

mybaits

官方简介https://blog.mybatis.org/p/about.html

MyBatis项目继承自iBATIS 3.0,其维护团队也包含iBATIS的初创成员。

2010年5月19日项目创建。当时Apache iBATIS 3.0发布,其开发团队宣布会在新的名字、新的站点中继续开发。

2013年11月10日,项目迁移到了GitHub。

4 JDBC使用步骤

JDBC代码示例

1 加载驱动

2 获取数据库连接

3 sql预处理

4 执行sql

5 获取执行结果

5 简单使用JDBC存在的问题

数据库连接操作存在硬编码

Mybatis全局配置文件,支持配置连接信息

statement操作存在硬编码

Mybatis通过Mapper文件,提供sql、参数映射

频繁开启数据库连接会降低数据库性能

Mybatis全局配置文件,支持配置连接池

6 mybatis架构原理

XML配置文件解析

SqlSessionFactory

SqlSession

Executor

MappedStatement

架构图

三、Mybatis开发1 开发示例

开发示例

POJO类(DTO、VO、PO等)

Mapper(dao)

Mapper 映射文件

全局配置文件

3 mybatis使用常见问题

#{}相当于jdbc里的占位符 ?

PrepareStatement

可以做SQL预编译

进行输入映射时,会对参数进行类型解析(例如 string 类型,解析后SQL语句会加上 ”)

如果进行简单类型(String、Date、8种基本类型的包装类)的输入映射时,#{}中参数名称可以任意

${}相当于jdbc里的连接符

Statement

每一次都是一个新的SQL语句,就是字符串拼SQL

存在SQL注入问题,使用 OR 关键字(OR 1=1),将查询条件忽略

如果进行简单类型(String、Date、8种基本类型的包装类)的输入映射时,#{}中参数名称必须用value

OGNL

对象图导航语言 Object-Graph Navigation Language

多个入参时,使用对象

返回值是单一/多个对象,resultType都只描述单一对象的类型

mapper xml 加载注意事项

当遇到 resource not found 或者 parsing error 时,注意检查mapper是否有内容写错误(参数、返回类型等)

4 mybatis开发dao层的三种方式

原始开发实现 dao接口及实现

SqlSessionFactoryBuilder

SqlSessionFactory

SqlSession

方法级别

全局范围(应用级别)

方法级别

代码示例

该种方式常见于ibatis迁移过来的项目

实现层注入 SqlSessionFactory 创建SqlSession

作用域

Mapper 代理开发实现(基于JDK的动态代理)

后续讲解

只需要 Mapper 接口(dao 接口)和 Mapper 约束文件,不需要实现类

规范

与 spring 整合后,需要在 ioc 容器中管理一个 SqlSessionFactory 对象

接口类的路径与 XML 文件中的 namespace 相同

接口方法名与 XML 文件中每个 statement 的 id 相同

接口方法入参类型与 XML 文件中每个 statement 的 parameterType 相同

接口方法返回值类型与 XML 文件中每个 statement 的 resultType 相同

基于JDK

基于CGLIB

针对有接口的类,进行动态代理

针对子类继承父类的方式,进行动态代理

目标类是父类,代理类是子类

代码示例

动态代理(代理分为静态代理、动态代理)

XML方式

注解方式

5 POJO输入映射和输出映射5.1 parameterType 输入映射

输入参数类型

动态SQL会讲解

#{}通过反射获取数据

${}通过OGNL表达式获取数据

StaticSqlSource (RawSqlSource)

DynamicSqlSource

简单类型

POJO类型

Map类型

List类型

5.2 resultType/resultMap 结果映射

resultType

查询结果列名和属性名一致

不一致的会得不到结果

resultMap

关联查询 一对多查询可以进行对象嵌套

延时加载(懒加载)

四、mybatis-config.xml

properties(属性)

引入外部的 Java 配置文件(properties)

直接设置子标签

优先级 环境变量 > 配置文件 > 子标签

建议加前缀,避免受到环境影响

settings(全局配置参数)

typeAliases(类型别名)

用于POJO类

简化映射文件中的 parameterType resultType

typeHandlers(类型处理器)

转换:Java 类型 -> JDBC 类型(mybatis) -> 数据库类型(数据库驱动)

objectFactory(对象工厂)

plugins(插件)

增强 Mybatis 执行 SQL 语句过程中的功能

例如:PageHelper分页插件

environments(环境集合属性对象)

transactionManager(事务管理)

dataSource(数据源)

一般不使用这个,例如连接池

environment(环境子属性对象)

mappers(映射器)

批量加载 package,批量加载同一包下的 Mapper XML 文件,该包下接口和 XML 要配对才起效

五、Mybatis源码专题1 设计模式

Java 常见三类23种设计模式

创建型 5

工厂模式 同属性的同一对象

构建者模式 不同属性的同一对象

结构型 7

行为性 11

2 初始化过程解析2.1 核心流程

全局 XML 配置文件 -> Configuration 配置对象 -> SqlSessionFactory对象

// 1. 顶层 创建SqlSessionFactorysqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);// 1.1 new SqlSessionFactoryBuilder().build()底层 // 1.1.1 创建XMLConfigBuilderXMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);// 1.1.2 XMLConfigBuilder进行解析,返回ConfigurationConfiguration config = parser.parse();// 1.1.3 根据配置构造,返回DefaultSqlSessionFactorybuild(config);public SqlSessionFactory build(Configuration config) { return new DefaultSqlSessionFactory(config);}2.2 XMLxxxBuilder XML文件解析构造器

父类BaseBuilder(Configuration、TypeAliasRegistry、TypeHandlerRegistry)

XMLConfigBuilder用来解析 MyBatis 的全局配置文件mybatis-config.xml

XMLMapperBuilder用来解析 MyBatis 中的映射文件xxxMapper.xml

XMLStatementBuilder用来解析映射文件中的 statement 语句<select>xxx</select>

MapperBuilderAssistant用来辅助解析映射文件并生成MappedStatement对象

XML 解析使用了 XPath(javax.xml.xpath.XPath),将 XML 解析为七种类型节点(XNode)

private XMLConfigBuilder(XPathParser parser, String environment, Properties props) { // 1 初始化Configuration super(new Configuration()); … this.configuration.setVariables(props); this.parsed = false; this.environment = environment; this.parser = parser;}// 1.1 new Configuration()// 1.1.1 注册常用别名typeAliasRegistry.registerAlias(“JDBC”, JdbcTransactionFactory.class);…2.3 parser.parse() 解析成 Configuration 流程// 1 只解析一次,从configuration根节点开始if (parsed) { …}parsed = true;parseConfiguration(parser.evalNode(“/configuration”));return configuration;// 1.1 parseConfiguration() 流程 XML -> Configuration// 按顺序解析XML标签// 解析propertiespropertiesElement(root.evalNode(“properties”));…// 解析MappersmapperElement(root.evalNode(“mappers”));2.4 mapperElement() Mapper 解析流程

XMLMapperBuilder

// 1 全局配置文件获取到resource资源路径,加载xxxMapper.xml映射文件String resource = child.getStringAttribute(“resource”);…InputStream inputStream = Resources.getResourceAsStream(resource);XMLMapperBuilder mapperParser = new XMLMapperBuilder(…);// 2 执行Mapper解析mapperParser.parse();// 2.1 执行Mapper解析流程…configurationElement(parser.evalNode(“/mapper”));…parsePendingResultMaps();…parsePendingStatements();// 2.1.1 configurationElement()…parameterMapElement(context.evalNodes(“/mapper/parameterMap”));resultMapElements(context.evalNodes(“/mapper/resultMap”));sqlElement(context.evalNodes(“/mapper/sql”));buildStatementFromContext(context.evalNodes(“select|insert|update|delete”));// 2.1.1.1 buildStatementFromContext() // 使用 XMLStatementBuilder 解析 select|insert|update|delete 标签为 statementstatementParser.parseStatementNode();// 2.1.1.1.1 statementParser.parseStatementNode() 解析核心部分…// 2.1.1.1.2 转换为真正的SQL语言SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);…// 2.1.1.1.3 使用 MapperBuilderAssistant 转换为 statementbuilderAssistant.addMappedStatement(…);3 SQL执行流程3.1 核心流程

openSession ->

SqlSession sqlSession = sqlSessionFactory.openSession();User user = sqlSession.selectOne(“findUserById”, 1);3.2 openSessionFromDataSource() 从数据源打开session

执行器类型 ExecutorType

SIMPLE

REUSE

BATCH

设置事务级别 TransactionIsolationLevel

NONE

READ_COMMITTED

READ_UNCOMMITTED

REPEATABLE_READ

SERIALIZABLE

是否自动提交 autoCommit

// 创建 DefaultSqlSession 流程…final Environment environment = configuration.getEnvironment();…tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);final Executor executor = configuration.newExecutor(tx, execType);return new DefaultSqlSession(configuration, executor, autoCommit);…3.3 查询执行流程解析 sqlSession.selectList()

参数1 statement

“com.aizain.jhome.user.findUserById”

参数2 唯一入参

public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) { // 根据 statement(id)获取 MappedStatement MappedStatement ms = configuration.getMappedStatement(statement); // 委托者模式,委托给 executor 去执行 // 默认使用 CachingExecutor // CachingExecutor 主要用于处理二级缓存 // 如果无二级缓存 CachingExecutor 默认委托给 SimpleExecutor/BaseExecutor 真正执行 return executor.query( ms, // 特殊处理入参为集合类型的 涉及到动态SQL wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER );…}3.4 executor.query() 执行细节

CachingExecutor sqlSource绑定parameter -> CachingExecutor 查二级缓存 -> BaseExecutor 查一级缓存 -> BaseExecutor 查数据库 -> SimpleExecutor 真正执行查询

// 1 executor.query()// 绑定入参 通过sqlSource,之前初始化流程时,已装载过sqlSourceBoundSql boundSql = ms.getBoundSql(parameterObject);CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);// query 会先走二级缓存,之后走一级缓存,最后走数据库查询 BaseExecutor.queryFromDatabase()query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);// 1.1 BaseExecutor.queryFromDatabase() 部分// queryFromDatabase会调用SimpleExecutor.doQuery()真正执行查询,然后保存一级缓存…// 为了防止缓存穿透 后续讲解localCache.putObject(key, EXECUTION_PLACEHOLDER);…// SimpleExecutor.doQuery()list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql);…localCache.removeObject(key);…localCache.putObject(key, list);3.5 SimpleExecutor.doQuery() 真正执行查询

真正开始与 jdbc 打交道

根据 Statement 类型路由到不同 statement 处理

当想要访问数据库存储过程时使用

CallableStatement 接口也可以接受运行时输入参数

当计划要多次使用SQL语句时使用

PreparedStatement 接口在运行时接受输入参数

用于对数据库进行通用访问

在运行时使用静态SQL语句时很有用

Statement 接口不能接受参数

本质上是 jdbc statement 类型

STATEMENT

PREPARED

CALLABLE

// 1 SimpleExecutor.doQuery()Configuration configuration = ms.getConfiguration();// 获取不同类型的Statement处理器StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);stmt = prepareStatement(handler, ms.getStatementLog());handler.query(stmt, resultHandler);…closeStatement(stmt);// 1.1 prepareStatement()Connection connection = getConnection(statementLog);stmt = handler.prepare(connection, transaction.getTimeout());// 内部执行了 DefaultParameterHandler.setParameters()// 底层根据参数类型,调用不同的 prepareStatement.setXXX()handler.parameterize(stmt);// 1.1.1 handler.prepare()…// 内部执行了 connection.prepareStatement() 生成 prepareStatementstatement = instantiateStatement(connection);setStatementTimeout(statement, transactionTimeout);setFetchSize(statement);…4 Mapper代理解析// 解析xml时,注册mapper接口 XMLConfigBuilder.mapperElement()// 注册到 MapperRegistry对象中configuration.addMappers(mapperPackage);…configuration.addMapper(mapperInterface);// MapperRegistry对象// 用于存储注册的 接口类:代理工厂Map<Class<?>, MapperProxyFactory<?>> knownMappers// MapperProxyFactory 中使用 jdk 生成动态代理protected T newInstance(MapperProxy<T> mapperProxy) { // jdk 方法 java.lang.reflect.Proxy return (T) Proxy.newProxyInstance( mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy );}// addMapper() 解析if (hasMapper(type)) { throw new BindingException(“Type ” type ” is already known to the MapperRegistry.”);}knownMappers.put(type, new MapperProxyFactory<>(type));// It’s important that the type is added before the parser is run// otherwise the binding may automatically be attempted by the// mapper parser. If the type is already known, it won’t try.// MapperAnnotationBuilder 用于处理Mapper的注解MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);parser.parse();六、关联查询/延迟加载1 关联查询

关联查询 一对多查询可以进行对象嵌套嵌套结果

对象嵌套需要使用association/collection子标签

对象嵌套示例

对象嵌套可以将一对多的单维数据转换为二维数据,见下方例子

1.1 对象嵌套前列列2a1a2a3b1b21.2 对象嵌套后列列2a[ 1 2 3 ]b[ 1 2 ]2 延迟加载

懒加载,推迟关联对象的select查询,可以减小数据库压力

在resultMap中依赖association/collection子标签实现

延迟加载也被称为嵌套查询

分类 三类

只有用到了关联对象,才执行select

用到了对象的任何属性,就执行select

主/关联对象select同时执行

直接加载

侵入式延迟

深度延迟

为了防止逆向工程覆盖特殊业务字段或处理,可以使用继承关系避免该问题(父类/子类)

对修改关闭,对扩展开放

2.1 延迟加载 映射文件配置

select 指定延迟执行的语句

column 以哪一列为基准进行延迟查询

property 放入当前java对象的那个属性

javaType 延迟查询结果的java类型

<association column=”user_id” property=”user” javaType=”User” select=”xxx.findUserById”/>2.2 延迟加载 全局文件配置

lazyLoadingEnabled

是否启用延迟加载

默认 false

false 所有延迟加载配置都不会生效

aggressiveLazyLoading

任何方法的调用都会触发延迟加载

true 侵入式延迟模式 false 深度延迟

默认 true

lazyLoadTriggerMethods

指定哪些方法触发延迟加载

默认 equals,clone,hashCode,toString

<settings> <setting name=”lazyLoadingEnabled” value=”false”/> <setting name=”aggressiveLazyLoading” value=”true”/></settings>for (OrderWithUser orderWithUser : orderWithUsers) { // debug可能看不出来区别,需要日志观察 // 开启侵入式延迟加载时 aggressiveLazyLoading true, 这步之前就会查询sql log.debug(“Order get id {}”, orderWithUser.getId()); // 开启深度延迟加载时 aggressiveLazyLoading false, 这步之前才会查询sql log.debug(“Order get user {}”, orderWithUser.getUser());}2.3 延迟加载 N 1问题

主表查询一次,关联表查询 N 次

七、动态SQL

目的:处理SQL字符串动态拼接

源码 BoundSql 处理该动态SQL参数绑定

if 根据入参动态拼接SQL

sql 去除重复部分的语句

foreach 遍历拼接集合SQL

1 foreach collection属性 处理源码

DefaultSqlSessionFactory.wrapCollection()

if (object instanceof Collection) { StrictMap<Object> map = new StrictMap<>(); map.put(“collection”, object); if (object instanceof List) { map.put(“list”, object); } return map;} else if (object != null && object.getClass().isArray()) { StrictMap<Object> map = new StrictMap<>(); map.put(“array”, object); return map;}return object;八、缓存

一级缓存

默认开启

执行增删改会使缓存失效

SqlSession 内共享

SqlSession 使用 HashMap 存储一级缓存

取缓存源码位置 BaseExecutor.query()

存缓存源码位置 BaseExecutor.queryFromDatabase()

二级缓存

默认不开启

缓存对象需要实现序列化

同一 Mapper(namespace) 内共享

源码位置 CacheExecutor.query()

1 二级缓存

需要开启两处

缓存对象需要实现 Serializable

1.1 全局配置<setting name=”cacheEnabled” value=”true”/>1.2 Mapper配置

指定 mapper 文件命名空间下的二级缓存

<cache/><select useCache=”true” >xxx</select>1.3 整合ehcache1.3.1 简介

开源的 Java 分布式缓存

主要面向通用缓存

具有内存和磁盘存储、缓存加载器、缓存扩展、缓存异常处理程序

支持 REST、SOAP 等

分布式缓存常用 Redis

Mybatis 的定位是做持久层框架,缓存数据管理不是 Mybatis 特长

1.3.2 特点

快速、简单

多种缓存策略

支持磁盘和内存存储

缓存数据会在虚拟机重启的过程中写入磁盘

可通过 RMI 可插入 API 等方式进行分布式缓存

具有缓存和缓存管理器的侦听接口

支持多缓存管理实例,以及一个实例的多个缓存区域

提供 Hibernate 的缓存实现

1.3.3 分布式缓存

Mybatis 自身无法支持分布式缓存,需要整合其他框架

分布式缓存场景:集群部署方式

可以解决缓存不一致问题

1.3.4 整合思路

Mybatis Cache 接口

默认实现 PerpetualCache

实现该接口,就可以实现二级缓存

引入 jar 包

ehcache

mybatis-ehcache

指定 type 类型

<cache type=”org.mybatis.caches.ehcache.EhcacheCache”/>九、Mybatis相关工具和插件1 逆向工程

自动生成文件

po 类

mapper 接口

mapper 映射文件

相关扩展框架

mybatis-plus

注意事项

mapper.xml文件的内容不是被覆盖而是进行内容追加

po类及mapper.java文件的内容是直接覆盖

2 PafeHelper分页插件

3 注解开发

代码示例

不需要使用 xml 映射文件

增删改查 静态SQL

@Select

@Insert

@Delete

@Update

@SelectKey

增删改查 动态SQL

@SelectProvider

@InsertProvider

@DeleteProvider

@UpdateProvider

@Results 注解

代替的是标签<resultMap>

该注解中可以使用单个@Result 注解,也可以使用@Result 集合

@Results({@Result(),@Result()})或@Results(@Result())

@Resutl 注解

代替了<id>标签和<result>标签

@Result 中 属性介绍:

column 数据库的列名

Property 需要装配的属性名

one 需要使用的@One 注解(@Result(one=@One)()))

many 需要使用的@Many 注解(@Result(many=@many)()))

@One 注解(一对一)

@Result(column=” “,property=””,one=@One(select=””))

代替了<assocation>标签,是多表查询的关键,在注解中用来指定子查询返回单一对象。

@One 注解属性介绍:

select 指定用来多表查询的 sqlmapper

fetchType 会覆盖全局的配置参数 lazyLoadingEnabled。。

使用格式:

@Many 注解(多对一)

代替了标签,是是多表查询的关键,在注解中用来指定子查询返回对象集合。

注意:聚集元素用来处理“一对多”的关系。需要指定映射的 Java 实体类的属性,属性的 javaType(一般为 ArrayList)但是注解中可以不定义; 使用格式:

@Result(property=””,column=””,many=@Many(select=””))

十、扩展知识JDBC

参考https://zh.wikipedia.org/wiki/Java数据库连接

JDBC Java数据库连接,Java Database Connectivity,简称JDBC

JDBC API主要位于JDK中的java.sql包中(之后扩展的内容位于javax.sql包中)

DriverManager:负责加载各种不同驱动程序(Driver),并根据不同的请求,向调用者返回相应的数据库连接(Connection)。

Driver:驱动程序,会将自身加载到DriverManager中去,并处理相应的请求并返回相应的数据库连接(Connection)。

Connection:数据库连接,负责进行与数据库间的通讯,SQL执行以及事务处理都是在某个特定Connection环境中进行的。可以产生用以执行SQL的Statement。

Statement:用以执行SQL查询和更新(针对静态SQL语句和单次执行)。

PreparedStatement:用以执行包含动态参数的SQL查询和更新(在服务器端编译,允许重复执行以提高效率)。

CallableStatement:用以调用数据库中的存储过程。

SQLException:代表在数据库连接的创建和关闭和SQL语句的执行过程中发生了例外情况(即错误)。

十一、参考

https://www.kaikeba.com/vipcourse/java

https://zh.wikipedia.org/wiki/Wikipedia:首页

http://www.mybatis.org/mybatis-3/zh/index.html

https://blog.mybatis.org/

https://book.douban.com/subject/26858114/

https://github.com/guangyuzhihun/JHome

发表评论

登录后才能评论