JDBC操作数据库步骤
注册驱动
创建连接
创建statement
执行SQL
关闭连接
几种较为原始的jdbc封装工具
apache DbUtils
springJdbc => jdbcTemplate
————
简陋,功能少,还不足以称为框架。
解决了:数据源、方法封装、结果集映射;
没解决:SQL硬编码,映射不灵活,不支持缓存
ORM框架
object <=> Relation 对象和关系型数据库的映射,操作对象来实现操作数据库。
hibernate
优点:
自动生成SQL
2级缓存
缺点:
1. 不能操作部分字段
2. 无法自定义SQL,SQL优化困难
3. 不支持动态SQL
mybatis(前身ibatis)
SQL和代码分离
动态SQL
重复SQL提取
2级缓存
参考
业务简单:可以选择hibernate或jpa
SQL灵活或复杂:mybatis
性能要求高:jdbc
mybatis详解
4个核心对象:
SqlSessionFactoryBuilder => 方法局部(一旦创建了 SqlSessionFactory,就不再需要它了)
SqlSessionFactory => 单例,应用级别
SqlSession => 每一次request
Mapper => 方法级别
java类型和jdbc类型的映射:TypeHandlerRegistry
自定义TypeHandler:继承BaseTypeHandler抽象类
使用:
insert/update:javaType => jdbcType
select:jdbcType => javaType
eg:JsonTypeHandler原理
主要就是BaseTypeHandler里的两个方法set/get实现javaType和jdbcType的转换。
ObjectFactory
把数据库记录映射成java对象,需要创建java对象,使用ObjectFactory的create方法(反射),默认是DefaultObjectFactory
8个核心标签
调用存储过程
动态SQL标签
官网:https://mybatis.org/mybatis-3/zh/dynamic-sql.html
where
set
choose标签
choose标签是按顺序判断其内部when标签中的test条件出否成立,如果有一个成立,则 choose 结束。当 choose 中所有 when 的条件都不满足时,则执行 otherwise 中的sql
SELECT
FROM md_control_table_column u
AND u.is_update = #{isUpdate, jdbcType=INTEGER}
AND u.is_delete = #{isDelete, jdbcType=INTEGER}
AND table_column_id = #{tableColumnId}
强大的trim
bind标签
${}和#{}区别
Statement和PrepareStatement区别
- Statemet
单次执行开销比PrepareStatement小,但是每条SQL都是从0开始编译和执行;
select * from user where name = '张三'
select * from user where name = '李四'
- PrepareStatement
预编译的statement,先把SQL发给数据库预处理,也有人叫它jdbc存储过程;一次预编译,后续都会复用;支持batch操作
批量插入
mybatis sql的执行,最终依赖的是Executor执行器(默认SIMPLE)
- 方式1:java代码for循环insert
- 方式2:insert into 表名 (id, name, address) values (1, '张三', '北京'), (2, '李四', '上海')...
注:mysqlmax_allowed_packet默认4M,超长报错。- 方式3:借助sqlSession的ExecutorType实现(底层封装jdbc prepareStatement的addBatch)
原生jdbc addBatch:
注:
线性增加批量插入的数据条数,执行时间并非呈线性增长,而是3倍的比例增长。在此做一个大胆的推理,数量如果很大,时间将以指数级增长。所以数据量很大时,应该分多批插入。
嵌套查询
- 一次性查询
-
懒加载查询(默认javassist代理的方式实现)
懒加载查询有N+1的问题:
"1"是说1次主查询,"N"是说N次子查询(主查询查到N条记录)。可以配置成懒加载,即用到某个方法时才调用。
物理翻页和逻辑翻页
- 物理翻页
使用数据库语法。mysql-limit,oracle-rowNum,sql server-top- 逻辑翻页
一次性查询所有数据到内存,在内存中根据RowBounds对象的属性完成分页。
RowBounds使用:只需要在mapper方法形参中加入即可,mapper.xml文件无需改动。
mybatis流程图
mybatis架构分层
对外暴露使用:接口层(前台)
处理数据库操作:核心层(大厨)
支持工作:基础层(采购,保洁)
mybatis缓存
一级缓存
默认打开。
会话(sqlSession)级别的缓存,缓存对象位置:BaseExecutor的localCache中。
一级缓存是会话隔离的,不会有脏数据。
sqlSession未close前,相同的查询(statement和paramter一样)会触发一级缓存。
mybatis和spring整合后,如果有事务,则sqlSession在事务提交后关闭;如果没有事务,则每一次CRUD后都会立即关闭。上图所示:第二次查询会触发一级缓存;如果去除事务注解,则不会触发一级缓存,即查询两次。
注1:spring事务的代理无需通过接口
注2:调试发现目前发票项目是SimpleExecutor,而不是ReuseExecutor
二级缓存(比较鸡肋)
默认不开启。
先走二级缓存,再走一级缓存。
mapper级别缓存,在namespace范围内可以跨会话使用,生命周期同ProxyMapper。
通过装饰者模式,CachingExecutor持有Executor的引用,并使用TransactionalCacheManager tcm对象在namespace范围内存储数据。
抽象类BaseExecutor和子类SimpleExecutor、ReuseExecutor、BatchExecutor是模板设计模式。doQuery、doUpdate就是抽象方法。
SimpleExecutor、ReuseExecutor、BatchExecutor区别:
开启步骤步骤:
全局配置(默认开启) + mapper配置
注:如果
由于insert/update/delete标签flushCache默认是true,所以增删改操作会清空本地和二级缓存。
二级缓存的key由多方面组成:
mappedStatementId => com.xxx.BlobMapper.selectById;
分页条件;
SQL;
参数信息;
注:一级缓存和二级缓存的数据结构一样。
源码分析(300多个类)
分析流程:SqlSessionFactoryBuilder -> SqlSessionFactory -> SqlSession
sqlSession.getMapper() => configuration.getMapper() => mapperProxyFactory.newInstance() => mapperProxy => Proxy.newProxyInstance()
调用mapper接口的方法(eg: getById)时,就是执行mapperProxy对象的invoke()方法
SqlSessionFactoryBuilder.build(Reader reader, String environment, Properties properties)
SqlSessionFactoryBuilder加载配置文件,通过XmlConfigBuilder解析配置文件,创建Configuration对象,并把properties、settings、typeAlias、typeHandler、plugins、objectFactory、mappedStatement(增删改查标签)等射入。
factory.openSession()
创建一个Executor射入SqlSession;
Executor的创建依赖Environment和TransactionFactory;
根据是否开启二级缓存,是否有插件,对Executor进行装饰;
sqlSession.getMapper(BlobMapper.class)
获得 Mapper 对象的过程,实质上是获取了一个 MapperProxy 的代理对象
mapper对象所有的方法调用,实际都是InvocationHandler的invoke()方法调用
早期,可以通过session直接根据statementId执行SQL,但是硬编码,且编译期不报错,难以维护。所以后来演变出session.getMapper(BlobMapper.class)的方式。
Executor执行器根据configuration中找到的statement,组装好的parameter,rowBounds和resultHandler进行查询。
mybatis代理和jdk动态代理区别
插件
不改变源代码,通过拦截的方式,改变四大核心对象的行为,比如处理参数,处理 SQL,处理结果。
思考:
不改变源代码,怎么修改原对象的行为,比如,前边改一改,后边改一改。 => 代理
多个插件形成链路,层层拦截,存入InterceptorChain(里边是个List) => 责任链模式
代理对象可以不断被代理。
可以被代理的"四大天王"
- Executor => 最顶层的对象。openSession()时创建
- StatementHandler =>executor.doQuery()时创建,创建它的过程中会顺便创建下边两个Handler
- ParameterHandler
- ResultSetHandler
注1:mybatis中并不是所有对象都能被拦截的
注2:只有Executor是openSession()时创建,其他三个都是执行SQL时创建。
开发插件步骤
- 自定义拦截器实现Interceptor接口
- 类上添加注解@Intercepts并指定拦截对象、方法、形参
- 重写intercept(方法增强)、plugin(生成代理对象)、setProperties(注入配置文件中的插件属性)方法。
- 全局配置文件中注册插件(注意顺序)
分析GitHub的PageHelper
原理
存入分页参数:
PageHelper.startPage(pn, 10); => 将分页参数存入全局变量ThreadLocal对象中
取出分页参数:
Listemps = employeeService.getAll();
在查询时,如果存在分页,则进行分页。
与spring整合
为啥要和spring整合
事务
源码分析
创建SqlSessionFactory
mybatis-spring包中有个核心类:SqlSessionFactoryBean,用于创建mybatis的SqlSessionFactory,该类实现了InitializingBean接口,在SqlSessionFactoryBean的属性设置完成后会调用afterPropertiesSet()方法,在该方法内部完成全局/mapper配置文件解析,创建Configuration对象。
创建SqlSession
mybatis默认的DefaultSqlSession线程不安全,交由mybatis-spring中的SqlSessionTemplate(单例)。
SqlSessionTemplate中封装了DefaultSqlSession里边的方法,且有一个SqlSessionProxy代理对象(jdk动态代理),在jdbc方法调用时,实际就是调用代理对象的invoke(),而每次事务都创建一个新的DefaultSqlSession对象,由ThreadLocal保证线程安全。
mybatis原生包中有线程安全的SqlSessionManager,它同时实现了 SqlSessionFactory、SqlSession 接口,通过ThreadLocal 容器维护 SqlSession
如何拿到SqlSessionTemplate
继承SqlSessionDaoSupport,其成员变量就是SqlSessionTemplate。Hibernate也是如此,继承HibernateDaoSupport
mapper扫描注册
server层@Autowired一下UserMapper,就可以直接调用接口方法了,原理就是@MapperScan扫描@Repository注解,执行doScan()方法过程中,把mapper接口的原始类型替换成了MapperFactoryBean,而实现了FactoryBean接口的类会执行getObject()方法,内部拿到SqlSessionTemplate并调用getMapper()方法,得到了代理对象MapperProxy。
注:SqlSessionTemplate就是SqlSession
手写Mybatis
原理图
注:Configuration中持有MapperRegistry,MapperRegistry中有MapperProxyFactory的map集合,MapperProxyFactory生成MapperProxy。
总结
- 逻辑分页RowBounds
- Executor继承体系
- 缓存:
默认开启一级缓存。BaseExecutor的成员变量localCache
默认关闭二级缓存。CachingExecutor的成员变量tcm:TransactionCacheManager。增删改会清空二级缓存。 - openSession方法:
先创建Executor,然后把Executor注入Session形参。
Executor的创建依赖TransactionFactory和Configuration,内部层层装饰:缓存,插件 - 事务:
mybatis有两种事务配置:JDBC和managed(交给JBOSS,WegLogic等容器)
和spring整合后,spring会覆盖mybatis的事务。 - 流程
a. 创建session(内含Executor)
b. session开启事务,执行SQL(实际Executor执行)
c. session.getMapper(UserMapper.class)得到mapperProxy,调用其findByXx()方法,实际调用代理对象的invoke(),内部区分增删改查,拼装SQL并映射结果集。 -
xml builder
- 插件
层层代理,针对参数、SQL、结果集做拦截处理。InterceptorChain(里边是个List) 。
4大核心对象:Executor,StatementHandler ,ParameterHandler,ResultSetHandler。
只有Executor是openSession()时创建,其他三个都是执行SQL时创建 - spring-mybatis
核心配置SqlSessionFactoryBean实现InitializingBean接口,在属性设置完之后会执行afterPropertiesSet()方法,该方法内部会构建SqlSessionFactory(内部解析配置) - 创建sqlSession
mybatis默认的DefaultSqlSession线程不安全,交由mybatis-spring中的SqlSessionTemplate(单例)。
SqlSessionTemplate中封装了DefaultSqlSession里边的方法,且有一个SqlSessionProxy代理对象(jdk动态代理),在jdbc方法调用时,实际就是调用代理对象的invoke(),而每次事务都创建一个新的DefaultSqlSession对象,由ThreadLocal保证线程安全。
mybatis原生包中有线程安全的SqlSessionManager,它同时实现了 SqlSessionFactory、SqlSession 接口,通过ThreadLocal 容器维护 SqlSession