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)