SET NAMES utf8mb4;
SET FOREIGN_KEY_CHECKS = 0 ;
DROP TABLE IF EXISTS ` account` ;
CREATE TABLE ` account` (
` id` char ( 20 ) CHARACTER SET utf8 COLLATE utf8_bin NOT NULL ,
` username` varchar ( 255 ) CHARACTER SET utf8 COLLATE utf8_bin NULL DEFAULT NULL ,
` money` int ( 20 ) NULL DEFAULT NULL ,
PRIMARY KEY ( ` id` ) USING BTREE
) ENGINE = InnoDB CHARACTER SET = utf8 COLLATE = utf8_bin ROW_FORMAT = Dynamic;
INSERT INTO ` account` VALUES ( '1' , 'A' , 100 ) ;
INSERT INTO ` account` VALUES ( '2' , 'B' , 100 ) ;
SET FOREIGN_KEY_CHECKS = 1 ;
一、手写Mybatis
1. 概念和基本分析
Mybatis是持久层框架的一种,对原生JDBC进行封装,并实现sql与逻辑的分离
JDBC的问题和解决思路 自定义Mybatis的思路
关键在于如何合理规定用户提供的xml配置文件,有了配置文件就好说了,直接解析,然后反射,拿到反射的东西,再使用JDBC完成操作
sqlMapConfig.xml:存放数据库配置信息,和mapper.xml的全路径
这个文件随意一点就好,因为是基本配置完成一次,就不需要动了 提供数据库配置信息和mapper.xml全路径就好
mapper.xml
这个文件就需要深思熟虑了,我们必须关心,那么多mapper.xml如何保证每一条sql语句不冲突,当执行动态sql时,如何封装用户传过来的条件参数。如何规定结果集封装的类型。 关于保证每一条sql不冲突的解决办法:
我们希望,每个mapper.xml文件的配置具有统一性,比如查询所有数据,我们希望都叫做< select id=“selectList”>.那么名字都一样如何准确定位这些sql呢? 所以我们决定在根标签定义唯一标识,它是整个mapper文件的id。起名为namespace
关于结果集封装的解决方案:我们要通过反射等技术,进行封装,就必须知道要反射什么类。所以需要结果集的sql,只需要指定resultType=“全限定路径”。我们就可以完成反射 关于使用用户传来的条件参数,我们依然通过全限定路径,然后根据属性名进行封装。使用形式就是#{属性名}
例子如下
< mapper namespace = " bank" >
< select id = " selectList" resultType = " com.yzpnb.entity.Account" >
select * from account
</ select>
< select id = " selectOne" resultType = " com.yzpnb.entity.Account" paramterType = " com.yzpnb.entity.Account" >
select * from account where id = #{id}
</ select>
</ mapper>
2. 使用端提供xml
使用端,就是平常使用mybatis框架的我们。主要负责引入mybatis的jar包,然后提供相关配置文件,然后使用即可,我们单独写一个模块作为使用端
1. 提供xml:这些xml的标签没有任何规定,只是我们解析时按照这个规则解析。而且毕竟是手写mybatis,当然最好规则和mybatis一样喽。所以和mybatis配置文件基本上一模一样
核心配置,指定数据库连接和mapper.xml文件全路径的配置 mapper.xml(只提供了select的,因为已经把很多种情况都涵盖了,update,delete等大同小异)
2. 其它操作就是引入自己实现的mybatis,使用提供的功能。但是现在还没写,所以接下来先写自定义MyBatis
3. 自定义MyBatis,并不断通过使用端测试
新建模块,实现自定义MyBatis,然后使用端引入
自定义MyBatis模块 使用端引入
为了节省篇幅,我直接将所有需要用到的jar,全部通过maven导入
3.1 加载配置文件
3.2 创建两个javaBean容器对象
两个容器对象,分别存储sql配置映射关系(MappedStatement),和核心配置(Configuration)
另外,MappedStatement是一个容器对象,相当于实体类,不是数组集合,而是单独存放一个sql映射的容器,一个sql映射对应一个MappedStatement对象
2. Configuration,核心配置类。存储数据源和保存每一个MappedStatement对象
3.3 使用dom4j解析配置文件,并封装,然后构建会话对象
提供SqlSessionFactoryBuilder类并通过build(InputStram in)方法。通过前面加载核心配置文件的输入流,使用dom4j解析配置文件,然后进行封装。最后创建SqlSessionFactory对象,生产sqlSession会话对象
SqlSessionFactory是一个接口,一个工厂设计模式,用来生产sqlSession会话对象,遵守开闭原则
1.我们要将从mapper.xml解析的东西封装到MappedStatement,然后将MappedStatement再封装到Configuration对象中,所以通过构建者模式,单独用一个类来构建
2. 我们要将最后从xml解析的东西都封装到Configuration对象中,所以通过构建者模式,单独用一个类来构建
3. 创建SqlSessionFactory接口,工厂设计模式,专门用来生产SqlSession对象,当然现在我们还不需要实现,先将规则放在那即可
4. DefaultSqlSessionFactory类实现SqlSessionFactory接口,生产sqlSession对象,SqlSession对象需要使用我们Configuration对象,所以将其封装进去,而Configuration对象需要进行构建,所以整个SqlSessionFactory都需要构建
5. 创建SqlSessionFactoryBuilder类,构建者模式,构建SqlSessionFactory
3.4 编写SqlSessionFactory接口和实现类,生产sqlSession
上面我们构建好了SqlSession对象需要的Configuration对象,并将其交给了SqlSession。接下来要生产sqlSession
1. 创建SqlSession对象,也是一个接口,和需要的实现类
SqlSession接口 实现类
2. SqlSessionFactory接口定义规范
2. DefaultSqlSessionFactory实现类,实现接口
3.5 编写SqlSession接口和实现类,定义对数据库crud操作
有了SqlSession对象,我将就可以通过SqlSession操作了,我们只模拟查询的操作,其它操作大同小异
遵循开闭原则,JDBC和SqlSession不可以耦合,所以我们继续抽出一层,这样实现的好处是,使用端,可以统一的封装,也可以自己单个实现,并且完全不耦合
Executor接口,它是更抽象的一层,所以名字叫query就好 我们可以自己提供一个实现,当然一般用户都自己去实现
可见query需要Configuration对象,所以我们在生产SqlSession时,需要给它传过去
3.6 编写Executor接口及实现类,执行JDBC代码
执行JDBC代码首先需要SQL语句,和动态参数,例如select * from account where id = ?。但是现在我们只有select * from account where id = #{id}。所以我们将其转换,包括#{id},像id这样的参数也有取出来,我们可以单独封装成一个对象BoundSql
我们需要借助一些工具类(我从mybatis源码中直接拿过来用)
参数名称封装类,ParameterMapping TokenHandler接口 ParameterMappingTokenHandler实现TokenHandler接口,负责解析sql 通用Token解析器,GenericTokenParser,通过开始标记,结束标记解析,干活的是上面TokenHandler接口的实现类
BoundSql实体类
接下来正式编写query,就是基本的通过反射拿到参数和返回类型,然后编写JDBC代码,还是很简单的。反射和JDBC都是java的基础,都是非常简单的概念,大家自己看注释吧。
提供getBoundSql方法,解析sql,封装BoundSql 提供getClassType方法,根据全限定类名,返回class对象 反射和执行JDBC的代码==>注册驱动,获取连接==>sql语句转换==>获取预处理对象 JDBC的preparedStatement==>如果有占位符,就设置参数==>执行sql 结果集封装
4. 使用端测试,功能优化
4.1 问题分析
虽然功能实现了,但是想一想,我们平常是在持久层(dao,mapper)编写相关代码,显然上面内种方式是不好的,每个方法,都需要写重复的代码,一般我们只是写一个接口,而不需要实现类
持久层接口
我们希望,只是定义接口,其它什么都不需要关心。很明显,又要用到代理设计模式
4.2 动态代理改造,持久层只需接口,无需实现类
那么我们什么都不写,怎么知道调用哪个sql,我们现在只知道是哪个持久层接口,接口中哪个方法。所以既然只知道持久层接口,那我们用接口名字当namespace不就好了。而< select id="">,id这个标识,就和接口中方法对应就好了
1. SqlSession提供方法,为持久层接口生成代理实现类
2. 实现类实现
二、Mybatis高级应用
三、Mybatis源码