mybatis
v20190523
目录
一、框架概述
二、Mybatis概述
三、Mybatis开发
四、mybatis-config.xml
五、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