自定义持久层框架
JDBC操作问题分析
- 数据库配置信息存在硬编码问题、频繁创建释放数据库连接
- SQL语句、设置参数、获取结果集均存在硬编码问题
- 手动封装返回结果集,较为频繁
解决思路
- 连接池解决连接问题
- 配置文件解决硬编码问题
- 反射和内省解决结果集问题
框架实现
-
使用端(引入自定义持久层框架的jar包)
-
配置信息:数据库配置信息、SQL配置信息:SQL语句、参数类型、返回值类型
-
sqlMapConfig.xml:存放数据库配置信息,存放mapper.xml的全路径
-
mapper.xml:存放SQL配置信息
-
自定义持久层框架本身(本质对就是队JDBC代码进行封装)
-
加载配置文件:根据配置文件路径加载配置文件成字节输入流,存储在内存中。创建Resources类 方法:InputStream getResourceAsStream(String path)
-
创建两个javaBean(容器对象):存放对配置文件解析出来的内容。Configuration:核心配置类:存放sqlMapConfig.xml解析出来的内容。MappedStatement:映射配置类:存放mapper.xml解析出来的内容
-
解析配置文件:dom4j。创建类:SqlSessionFactoryBuilder 方法:build(InputStream in)。第一:使用dom4j解析配置文件,将解析出来的内容封装导容器对象中。第二:创建SqlSessionFactory对象:生产SqlSessionFactory:会话对象
-
创建SqlSessionFactory接口及实现类DefaultqlSessionFactory。第一:openSession(),生产SqlSession
-
创建SqlSession接口及实现类DefaultSession,定义对数据库的crud操作:selectList()、selectOne()、update()、delete()
-
创建Executeor接口及实现类SimpleExecutor实现类:query(Configuration,MappedStatement,Object… params):执行JDBC代码
框架优化
- 问题1 dao的实现类中存在重复代码,操作过程模板重复(创建sqlSession、调用sqlSession方法和关闭sqlSession)
- dao的实现类中存在硬编码,调用sqlSession的方法时,参数statement的id硬编码
解决办法:使用代理模式来创建接口的代理对象
基本概念
对象/关系数据库映射(ORM Object Relation Mapping)
ORM全称Object/Relation Mapping:表示对象-关系映射的缩写
ORM完成面向对象的编程语言到关系数据库的映射。当ORM框架完成映射后,程序员既可以利用面向对象程序设计语言的简单易用性,又可以利用关系数据库的技术优势。
ORM把关系数据库包装成面向对象的模型。
ORM框架是面向对象设计语言与关系数据库发展不同步时的中间解决方案。
采用ORM框架后,应用程序不再直接访问底层数据库,而是以面向对象的形式来操作持久化对象,而ORM框架则将这些面向对象的操作转换成底层SQL操作。
ORM框架实现的效果:把对持久化对象的保存、修改、删除 等操作,转换为对数据库的操作。
简介
- 基于ORM的半自动化轻量级持久层框架
- 支持定制化SQL、存储过程和高级映射
- 避免了几乎所有的JDBC代码和手动设置参数及获取结果集
- 可以使用XML或注解来配置和映射原生类型、接口和Java的POJO(Plan Old Java Objects)为数据库中的记录
历史
-
Apache 开源项目iBatis
-
2020年6月 由Apache Software Foundation迁移至Google Code
-
团队转投Google iBatis3.x改名为 Mybatis,2013 11迁移GitHub
-
iBatis 来源于Internet 和abatis的组合,基于Java的持久层框架
优势
- 半自动化的持久层框架、SQL和Java编码分开,功能边界清晰,一个专注业务,一个专注数据
基本应用
快速入门
-
官网地址:http://www.mybatis.org/mybatis-3/
-
开发步骤
-
1:添加Mybatis的坐标
-
2:创建数据库表
-
3:编写实体类
-
4:编写映射文件XXXMapper.xml
- 动态sql语句
- if
- foreach
- collection:代表要遍历的集合元素,不要写#{}
- open:语句开始的部分
- close:结束部分
- item:遍历集合的每个元素,生成的变量名
- separator:分隔符
- sql:抽取重复的sql,使用时用include引用即可
- 动态sql语句
-
5:编写核心文件SqlMapConfig.xml
- configuration 配置
-
properties 属性 该标签可以加载额外配置的properties文件,如jdbc配置信息
-
settings 设置
-
typeAliases 类型别名
- 为Java对象设置一个短的名字
- 常用的类型别名如:string、long、int…
-
typeHandlers 类型处理器
-
objectFactory 对象工厂
-
plugins 插件
-
environments 环境
- environment 环境变量
- transactionManager 事务管理器
- JDBC:使用JDBC的提交和回滚设置,依赖从数据源得到的连接来管理事务作用域
- MANAGED:几乎没做什么,默认情况关闭连接
- dataSource 数据源
- UNPOOLED:数据源的实现知识每次请求时打开和关闭连接
- POOLED:数据源的实现利用“池”的概念将JDBC连接对象组织起来
- JNDI:为了能在如EJB或应用服务器这类容器中使用,容器可以集中或在外部配置数据源,然后放置一个JNDI上下文的引用
- transactionManager 事务管理器
- environment 环境变量
-
databaseProvider 数据库厂商标识
-
mappers 映射器
- 使用相对于类路径的资源引用
- 使用完全限定资源定位符(URL)
- 使用映射器接口实现类的完全限定类名
- 将包内的映射器接口实现全部注册为映射器
-
- configuration 配置
-
6:编写测试类
-
-
注意:增删改需要提交事务
Dao层实现
-
传统开发方式:编写Dao接口,编写实现类
-
代理开发方式:Mapper接口开发只需编写mapper接口,由Mybatis框架根据接口定义创建接口的动态代理对象。
- 1:Mapper.xml文件中的namespace与mapper接口的全限定名相同
- 2:Mapper接口方法名和Mapper.xml中定义的每个statement的id相同
- Mapper接口方法的输入参数类型和mapper.xml中定义的每个sql的parameterType的类型相同
- Mapper接口方法的输出参数类型和mapper.xml中定义的每个sql的resultType的类型相同
注解开发
常用注解
-
@Insert:新增
-
@Update:更新
-
@Delete:删除
-
@Select:查询
-
@Result:结果集封装
- 代替id和result标签
- column:数据库的列名
- property:需要装配的属性名
- one:需要使用@One注解(@Result(one=@One)())
- many:需要使用@Many注解(@Result(many=@Many)())
- 代替id和result标签
-
@Results:与@Result一起使用,封装多个结果集
- 代替标签该注解可以使用单个@Result注解,也可以使用@Result结合,使用格式@Results({@Result(),@Result()})
-
@One:实现一对一结果集封装
-
@Many:实现一对多结果集封装
缓存
一级缓存:SQLSession级别缓存,在对象中有一个数据结构(HashMap)用于存储缓存数据,不同的SQLSession肩的缓存数据互相不影响
- 执行插入、更新和删除会清空缓存
举个例子
- 在一个sqlSession中,对User表根据id进行两次查询,查看他们发出sql语句的情况
@Test
public void test1(){
//根据 sqlSessionFactory 产生 session
SqlSession sqlSession = sessionFactory.openSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
//第一次查询,发出sql语句,并将查询出来的结果放进缓存中
User u1 = userMapper.selectUserByUserId(1);
System.out.println(u1);
//第二次查询,由于是同一个sqlSession,会在缓存中查询结果
//如果有,则直接从缓存中取出来,不和数据库进行交互
User u2 = userMapper.selectUserByUserId(1);
System.out.println(u2);
sqlSession.close();
}
查看控制台打印情况:
2. 同样是对user表进行两次查询,只不过两次查询之间进行了一次update操作
@Test
public void test2(){
//根据 sqlSessionFactory 产生 session
SqlSession sqlSession = sessionFactory.openSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
//第一次查询,发出sql语句,并将查询的结果放入缓存中
User u1 = userMapper.selectUserByUserId( 1 );
System.out.println(u1);
//第二步进行了一次更新操作,sqlSession.commit()
u1.setSex("女");
userMapper.updateUserByUserId(u1);
sqlSession.commit();
//第二次查询,由于是同一个sqlSession.commit(),会清空缓存信息
//则此次查询也会发出sql语句
User u2 = userMapper.selectUserByUserId(1);
System.out.println(u2);
sqlSession.close();
}
查看控制台打印情况:
总结
1、第一次发起查询用户id为1的用户信息,先去找缓存中是否有id为1的用户信息,如果没有,从数据库查询用户信息。得到用户信息,将用户信息存储到一级缓存中。
2、 如果中间sqlSession去执行commit操作(执行插入、更新、删除),则会清空SqlSession中的 一级缓存,这样做的目的为了让缓存中存储的是最新的信息,避免脏读。
3、 第二次发起查询用户id为1的用户信息,先去找缓存中是否有id为1的用户信息,缓存中有,直接从缓存中获取用户信息
二级缓存:mapper级别缓存,多个SQLSession操作同一个Mapper的sql语句,多个SQLSession可以共用二级缓存,二级缓存是跨SQLSession的
- 在sqlMapConfig.xml中加入
- 在XXXMapper.xml中加入 开启缓存
- userCache:设置是否禁用二级缓存,默认是true
- flushCache:刷新缓存,commit后刷新缓存,避免数据库脏读
插件
插件即拦截器,用来增强核心功能,借助于动态代理实现
- 执行器Executor(update、query、commit、rollback等方法)
- SQL语法构建起StatementHandler(prepare、parameterize、batch、update、query等方法)
- 参数处理器(ParameterHandler) (getParameterObject、setParameters方法)
- 结果集处理器ResultSetHandler(handleResultSets、handleOutputParameters等方法)
原理
- 1:每个创建出来的对象不失直接返回的,而是interceptorChain.pluginAll(parameterHandler)
- 2:获取到所有的Interceptor(拦截器)插件实现的接口:调用interceptor.plugin(target) ;返回target包装后的对象
- 3:插件机制:使用插件为目标对象创建一个代理对象;AOP 为四大对象创建代理对象,代理对象就可以拦截到四大对象的每一个执行
使用
- 实现Interceptor接口并重写intercept()方法;
- 类上加上@Intercepts({@Signature(type=要拦截的目标对象,method=要拦截的方法,args=要拦截方法的参数)})
- 在配置文件中配置你编写的插件,<plugins
pageHelper分页插件
- 1:导入通用PageHelper坐标
- 2:在Mybatis核心配置文件中配置PageHelper插件
- 3:测试分页数据获取,PageHelper.startPage(1,2)
通用Mapper:解决单表增删改查
- 1:引入依赖 tk.mybatis.mapper.3.1.2
- 2:配置文件中完成配置
- 3:实体类设置主键
- 4:定义通用mapper
架构原理
API接口层:提供外不适用的接口API,开发人员通过本地API来操作数据库,接口层接收到调用请求就会调用数据处理层完成具体的数据处理
- 1:使用传统的Mybatis提供的API
- 2:使用Mapper代理的方式
数据处理层:负责SQL查找、解析、执行和执行结果映射处理等。主要目的是根据调用的请求完成一次数据库操作
框架支撑层:负责最基础的功能支撑,包括连接管理、事务管理、配置加载和缓存处理,都是公共的东西,抽取出来作为最基础的组件,为上层的数据处理层提供最基础的支撑。
组件
- SQLSession:作为MyBatis工作的主要顶层API,表示和数据交互的会话,完成必要数据库增删改查功能
- Executor:MyBatis执行器,是MyBatis调度的核心,负责SQL语句的生成和查询缓存的维护
- StatementHandler:封装JDBC Statement操作,负责对JDBC Statement的操作,如设置参数、将Statement结果集装成List集合
- ParameterHandler:负责对用户传递的参数转换成JDBC Statement所需要的参数
- ResultSetHandler:负责将JDBC返回的ResultSet结果集对象转换成List类型的集合
- TypeHandler:负责Java数据类型和JDBC数据类型之间的映射和转换
- MappedStatement:MappedStatement维护了一条<select|update|delete|insert>节点的封装
- SqlSource:负责根据用户传递的parameterObject,动态生成SQL语句,将信息封装到BoundSql对象中并返回
- BoundSql:表示动态生成的SQL语句及相应的参数信息
源码剖析(关注几个入口)
Resources.getResourceAsStream(“mybatis-config.xml”)
new SqlSessionFactoryBuilder().build(inputstream)
SqlSession接口,实现类 DefaultSqlSession(默认)和SqlSessionManger(废弃)
Executor接口(常用三个实现类)
- BatchExecutor:重用语句并执行批量更新
- ReuseExecutor:重用预处理语句prepared statements
- SimpleExecutor:普通执行器,默认