# JDBC
需要手动引入 Mysql 的 jar 包
1 | public class JDBCDemo { |
- 为什么要有 ORM 框架
- 驱动 uri、数据库地址、账号密码,硬编码,不灵活
- 重复的建立连接
- 处理结果集麻烦
# 自定义
# 创建两个工程
- IPersistence、IPersistence_Test
# IPersistence_Test 使用端
# IPersistence 自定义框架
# 根据配置文件的路径,将配置文件加载成字节输入流,存储在内存中
1 | Resources.getResourceAsStream(String path) |
- 获得 sqlSession 对象
sqlSession 通过 sqlSessionFatory.open 获得
sqlSessionFatory 通过 sqlSessionFatoryBuilder.build (configuration) 获得
build 需要获取数据库信息
- 创建 SqlSessionFactoryBuilder
- 通过 SqlSessionFatoryBuilder.build () 获得 SqlSessionFatory
- 通过 DefaultSqlSessionFactory.open () 获得 SqlSession
- 创建 DefaultSqlSession 实现基础方法 selectAll,selectList
- 执行 JDBC 逻辑
创建 Executor、Executor 实现类,执行 CURD
- 处理返回结果
通过反射或内省 + SQLID 上的 resultType 全路径,处理返参
- 问题 1:数据库类型与实体类型不一致
1
Exception in thread "main" java.lang.IllegalArgumentException: argument type mismatch at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) at java.lang.reflect.Method.invoke(Method.java:498)
- 问题 2:数据库版本与驱动版本不一致
无法获取数据库连接,报错信息和获取连接方法有关
使用 C3P0 连接池是报错:
1 | java.sql.SQLException: Connections could not be acquired from the underlying database! |
使用 DriverManager 直接连接时:
1 | Client does not support authentication protocol requested by server; consider upgrading MySQL client |
- 持久层实现
通过 mapper 接口,数据库的交互
SqlSession 中创建一个 getMapper 方法,获取 mapper 的代理类,执行被代理类的方法
# Mybatis
# 概念
基于 ORM 的 半自动
轻量级持久层框架。
# 缓存
底层数据结构: 就是一个 HashMap。
先去缓存中查,然后到数据库中,如果缓存中有,就直接返回,不再去数据库查询。
# 一级缓存 - SqlSession 级别
是否启用: 默认开启
cacheKey: org.apache.ibatis.executor.BaseExecutor#createCacheKey
增删改操作时,会刷新缓存(全部缓存)
# 二级缓存 - NameSpace 级别
是否启用: 默认关闭,需要手动开启
[I] 二级缓存是在 SqlSession 事务提交时写入的
[!] 二级缓存在分布式的情况下,可能有问题。
1 |
|
结论: 节省了数据库的交互
Q:
1 |
|
# 插件
- [I] 需要在 SqlMapConfig.xml 中启用
1 | <plugins> |
# 分页插件
拦截器实现
[*]
com.github.pagehelper.PageHelper
[*] 入口:
com.github.pagehelper.SqlUtil#_processPage
[*] 增加 COUNTSQL:
com.github.pagehelper.MSUtils#processCountMappedStatement(MappedStatement ms, SqlSource sqlSource, Object[] args)
countSql 返回结果大于 0 时,执行分页,将总数设置到 page 对象中
替换参数
com.github.pagehelper.MSUtils#processPageMappedStatement(MappedStatement ms, SqlSource sqlSource, Page page, Object[] args)
创建新的 mapperStatement,执行分页 SQL
设置分页参数:
com.github.pagehelper.MSUtils#setPageParameter
# 通用 Mapper
# 架构原理
# 架构设计
# 接口
- 通过 sqlSession.method (statementId) 或者 Mapper 代理类调用方法,执行主句的增删改查。
- 调用接口修改配置信息等。
# 数据处理
- 请求参数处理 (@Param):ParameterHandler
- SQL 解析 (处理占位符、Mapper 标签):SqlSource
- SQL 执行 (JDBC):Executor
- 返回结果处理 (类型转换等):ResultSetHandler
# 框架支撑
- 事务管理
- 连接池管理
- 缓存机制
# 主要构件
SqlSession:session 表示与数据库的连接
Executor:执行器
StatementHandler:
ParameterHandler:
BoundSql:
ResultSetHander:
TypeHandler:数据库类型与 JavaBean 类型的转换
MappedStatement:
SqlSource:
# 总体流程
- SqlSessionFactoryBuilder 获取 SqlSessionFactory
- SqlSessionFactory.openSession 获取 SqlSession 对象
- 通过 getMapper 获取 Mapper 代理对象
- 执行代理 Mapper 的方法
- => Executor Mybatis 的执行器
- => StatementHandler 与 JDBC Statement 的交互
- => ParameterHandler 处理方法中携带的参数,拼接到 Sql 中
- => 执行 JDBC 流程(加载驱动、建立连接、定义 Sql、获取预处理对象、处理参数、执行、处理返回结果)
- => 处理 Java 类型和数据库类型映射
# 源码分析
# getMapper
扫描 @Mapper 注解、从 sqlMapConfigXml 中读取 Mapper 包名,或者 Mapper 接口,将其存到 MapperRegistry.knownMappers 中
1 | Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>(); |
value 值存储的是一个工厂类,有个 Class<T>
的变量,和 newInstance(SqlSession sqlSession)
方法,用于给 Mapper 创建代理对象
- [*]
Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[]{mapperInterface}, mapperProxy);
1 | // JDK动态代理 生成代理对象 |
# 二级缓存
[*]
org.apache.ibatis.executor.CachingExecutor#flushCacheIfRequired
[*]
org.apache.ibatis.builder.MapperBuilderAssistant#addMappedStatement(java.lang.String, org.apache.ibatis.mapping.SqlSource, org.apache.ibatis.mapping.StatementType, org.apache.ibatis.mapping.SqlCommandType, java.lang.Integer, java.lang.Integer, java.lang.String, java.lang.Class<?>, java.lang.String, java.lang.Class<?>, org.apache.ibatis.mapping.ResultSetType, boolean, boolean, boolean, org.apache.ibatis.executor.keygen.KeyGenerator, java.lang.String, java.lang.String, java.lang.String, org.apache.ibatis.scripting.LanguageDriver, java.lang.String)
[?] 二级缓存需要再事务提交后或者关闭后生效
org.apache.ibatis.executor.CachingExecutor#query(org.apache.ibatis.mapping.MappedStatement, java.lang.Object, org.apache.ibatis.session.RowBounds, org.apache.ibatis.session.ResultHandler, org.apache.ibatis.cache.CacheKey, org.apache.ibatis.mapping.BoundSql)
=> 使用 CachingExecutor.query()
=> 清空缓存
=>
1 | // 从二级缓存中,获取结果 |
- [?] 二级缓存为什么使用的是
CachingExecutor
sqlSessionFactory.openSession () 时会 new Executororg.apache.ibatis.session.defaults.DefaultSqlSessionFactory#openSessionFromDataSource
org.apache.ibatis.session.Configuration#newExecutor(org.apache.ibatis.transaction.Transaction, org.apache.ibatis.session.ExecutorType)