模拟MyBatis自定义 的实现
总结 : 实现 curd 增删改查 与 使用 增删改查 引导的是如何实现的 主配置 文件的配置 : 分析的是 : ===== MyBatis 原理的 分析流程 的是 =======- :
有了 主配置文件 的 连接数据库的 4个基本 与 mappers 的 里第二个映射xml 里 有了 就是xml配置的sql 语句 封装实体 全限定类名 于是 这改为框架就 开始 创建 JDBC 的事务了 Connection 对象
于是实体类 与。数据的 用的还是:***** 反射 ***** -----证明 反射是 框架的灵魂
解析配置文件用的是的xml 解析 用得到的 dom4j的技术在 API有的 为所欲为 的 SqlSession 会创建的是 Dao 的 代理对象 代理模式 3个参数 :----- 基于JDK 的接口实现的 方式 : 代理对象 与被 代理对象 使用的是 同一个 接口字节码数组
如何代理 :与增强的方法 就是SqlSession对象的的 好多的方法 :
还从认识API的4个里获取: 2个接口 2个类 :
Class Resources Class SqlSessionFactoryBuild: interface SqlSessionFactory: ======= 用的 是 反射 : 基于 (验证) interface SqlSession ===== 用到的是 动态代理 JDK的 基于接口来实现的 ===== 下图是 对于 Mybatisde 流程的分析
==== 主 配置文件 ======== : 数据库连接池的 : Environment 事务管理 : transactionManager 的type 是JDBC 的 就是 连库的 : 数据资源 类型: ------ dataSource 的type 是 POOLED 数据库连接池的 4个要素 : 的 property : 1:driver 2: url 3:username 4: password mappers 里 好多的 : 也是说 可以有好多 的 映射文件 ======= 而Resources 的文件是 === 如下
Resources 的配置文件 在UNity里可以通过Rwsources.Load来获取
这样我们就能不通过拖拽,直接写程序的方式来生成对象,是不是很高大尚呢?
====== 增删改查 的crud 过于sql语句的 DML 的 操作 :
是基于 动态代理 与反射来 实现的
主 配置文件 ======== : 数据库连接池的 : 到========
在Mybatis环境的初始化过程中,SqlSessionFactoryBuilder会调用XMLConfigBuilder读取所有的MybatisMapConfig.xml和所有的*Mapper.xml文件,构建Mybatis运行的核心对象Configuration对象,然后将该Configuration对象作为参数构建一个SqlSessionFactory对象。
其中XMLConfigBuilder在构建Configuration对象时,也会调用XMLMapperBuilder用于读取*Mapper文件,而XMLMapperBuilder会使用XMLStatementBuilder来读取和build所有的SQL语句。
在这个过程中,有一个相似的特点,就是这些Builder会读取文件或者配置,然后做大量的XpathParser解析、配置或语法的解析、反射生成对象、存入结果缓存等步骤,这么多的工作都不是一个构造函数所能包括的,因此大量采用了Builder模式来解决。
对于builder的具体类,方法都大都用build*开头,比如SqlSessionFactoryBuilder为例,它包含以下方法:
*工厂 模式 :=========可以看到,该Factory的openSession方法重载了很多个,分别支持autoCommit、Executor、Transaction等参数的输入,来构建核心的SqlSession对象。 在DefaultSqlSessionFactory的默认工厂实现里,有一个方法可以看出工厂怎么产出一个产品
Mybatis至少遇到了以下的设计模式的使用:
Builder模式,例如SqlSessionFactoryBuilder、XMLConfigBuilder、XMLMapperBuilder、XMLStatementBuilder、CacheBuilder; 工厂模式,例如SqlSessionFactory、ObjectFactory、MapperProxyFactory; 单例模式,例如ErrorContext和LogFactory; 代理模式,Mybatis实现的核心,比如MapperProxy、ConnectionLogger,用的jdk的动态代理;还有executor.loader包使用了cglib或者javassist达到延迟加载的效果; 组合模式,例如SqlNode和各个子类ChooseSqlNode等; 模板方法模式,例如BaseExecutor和SimpleExecutor,还有BaseTypeHandler和所有的子类例如IntegerTypeHandler; 适配器模式,例如Log的Mybatis接口和它对jdbc、log4j等各种日志框架的适配实现; 装饰者模式,例如Cache包中的cache.decorators子包中等各个装饰者的实现; 迭代器模式,例如迭代器模式PropertyTokenizer;
-!!!! maven构建项目的时候 ,不用 骨架
!!! pom 文件里 不用myBaties 的依赖还从认识API的4个里获取: 2个接口 2个类 :
Class Resources Class SqlSessionFactoryBuild: interface SqlSessionFactory: ======= 用的 是 反射 : 基于 (验证) interface SqlSession ===== 用到的是 动态代理 JDK的 基于接口来实现的
用的反射 : 创建 使用类加载器 读取时的
:
dao 接口 pojo 实体类 Resources 里的 配置文件 : 主配置文件 : Mapper 映射 文件:
mapper 映射文件 :
<?xml version="1.0" encoding="UTF-8"?> <mapper namespace="com.fhw.dao.IUserDao"> <!--配置查询所有--> <select id="findAll" resultType="com.fhw.domain.User"> select * from user </select> </mapper>只留下测试类 的这些 API 的 就会报错 :开始你的 自定义构建 : 找到解决完这些 报错的 的就会 成功了 !!!
用于解析配置文件 以 支持 xml 与注解 使用 dom4j 与 xpath 解析xml 的
package com.fhw.mybatis.utils; /*import com.fhw.mybatis.annotations.Select;*/ import com.fhw.mybatis.cfg.Configuration; import com.fhw.mybatis.cfg.Mapper; import org.apache.ibatis.io.Resources; import org.dom4j.Attribute; import org.dom4j.Document; import org.dom4j.Element; import org.dom4j.io.SAXReader; import java.io.IOException; import java.io.InputStream; import java.lang.reflect.Method; import java.lang.reflect.ParameterizedType; import java.lang.reflect.Type; import java.util.HashMap; import java.util.List; import java.util.Map; /** * 用于解析配置文件 */ public class XMLConfigBuilder { /** * 解析主配置文件,把里面的内容填充到DefaultSqlSession所需要的地方 * 使用的技术: * dom4j+xpath */ public static Configuration loadConfiguration(InputStream config){ try{ //定义封装连接信息的配置对象(mybatis的配置对象) Configuration cfg = new Configuration(); //1.获取SAXReader对象 SAXReader reader = new SAXReader(); //2.根据字节输入流获取Document对象 Document document = reader.read(config); //3.获取根节点 Element root = document.getRootElement(); //4.使用xpath中选择指定节点的方式,获取所有property节点 List<Element> propertyElements = root.selectNodes("//property"); //5.遍历节点 for(Element propertyElement : propertyElements){ //判断节点是连接数据库的哪部分信息 //取出name属性的值 String name = propertyElement.attributeValue("name"); if("driver".equals(name)){ //表示驱动 //获取property标签value属性的值 String driver = propertyElement.attributeValue("value"); cfg.setDriver(driver); } if("url".equals(name)){ //表示连接字符串 //获取property标签value属性的值 String url = propertyElement.attributeValue("value"); cfg.setUrl(url); } if("username".equals(name)){ //表示用户名 //获取property标签value属性的值 String username = propertyElement.attributeValue("value"); cfg.setUsername(username); } if("password".equals(name)){ //表示密码 //获取property标签value属性的值 String password = propertyElement.attributeValue("value"); cfg.setPassword(password); } } //取出mappers中的所有mapper标签,判断他们使用了resource还是class属性 List<Element> mapperElements = root.selectNodes("//mappers/mapper"); //遍历集合 for(Element mapperElement : mapperElements){ //判断mapperElement使用的是哪个属性 Attribute attribute = mapperElement.attribute("resource"); if(attribute != null){ System.out.println("使用的是XML"); //表示有resource属性,用的是XML //取出属性的值 String mapperPath = attribute.getValue();//获取属性的值"com/itheima/dao/IUserDao.xml" //把映射配置文件的内容获取出来,封装成一个map Map<String,Mapper> mappers = loadMapperConfiguration(mapperPath); //给configuration中的mappers赋值 cfg.setMappers(mappers); }/*else{ *//* System.out.println("使用的是注解"); //表示没有resource属性,用的是注解 //获取class属性的值 String daoClassPath = mapperElement.attributeValue("class"); //根据daoClassPath获取封装的必要信息 Map<String,Mapper> mappers = loadMapperAnnotation(daoClassPath); //给configuration中的mappers赋值 cfg.setMappers(mappers);*//* }*/ } //返回Configuration return cfg; }catch(Exception e){ throw new RuntimeException(e); }finally{ try { config.close(); }catch(Exception e){ e.printStackTrace(); } } } /** * 根据传入的参数,解析XML,并且封装到Map中 * @param mapperPath 映射配置文件的位置 * @return map中包含了获取的唯一标识(key是由dao的全限定类名和方法名组成) * 以及执行所需的必要信息(value是一个Mapper对象,里面存放的是执行的SQL语句和要封装的实体类全限定类名) */ private static Map<String,Mapper> loadMapperConfiguration(String mapperPath)throws IOException { InputStream in = null; try{ //定义返回值对象 Map<String,Mapper> mappers = new HashMap<String,Mapper>(); //1.根据路径获取字节输入流 in = Resources.getResourceAsStream(mapperPath); //2.根据字节输入流获取Document对象 SAXReader reader = new SAXReader(); Document document = reader.read(in); //3.获取根节点 Element root = document.getRootElement(); //4.获取根节点的namespace属性取值 String namespace = root.attributeValue("namespace");//是组成map中key的部分 //5.获取所有的select节点 List<Element> selectElements = root.selectNodes("//select"); //6.遍历select节点集合 for(Element selectElement : selectElements){ //取出id属性的值 组成map中key的部分 String id = selectElement.attributeValue("id"); //取出resultType属性的值 组成map中value的部分 String resultType = selectElement.attributeValue("resultType"); //取出文本内容 组成map中value的部分 String queryString = selectElement.getText(); //创建Key String key = namespace+"."+id; //创建Value Mapper mapper = new Mapper(); mapper.setQueryString(queryString); mapper.setResultType(resultType); //把key和value存入mappers中 mappers.put(key,mapper); } return mappers; }catch(Exception e){ throw new RuntimeException(e); }finally{ in.close(); } } /** * 根据传入的参数,得到dao中所有被select注解标注的方法。 * 根据方法名称和类名,以及方法上注解value属性的值,组成Mapper的必要信息 * @param daoClassPath * @return */ /* private static Map<String,Mapper> loadMapperAnnotation(String daoClassPath)throws Exception{ //定义返回值对象 Map<String,Mapper> mappers = new HashMap<String, Mapper>(); //1.得到dao接口的字节码对象 Class daoClass = Class.forName(daoClassPath); //2.得到dao接口中的方法数组 Method[] methods = daoClass.getMethods(); //3.遍历Method数组 for(Method method : methods){ //取出每一个方法,判断是否有select注解 boolean isAnnotated = method.isAnnotationPresent(Select.class); if(isAnnotated){ //创建Mapper对象 Mapper mapper = new Mapper(); //取出注解的value属性值 Select selectAnno = method.getAnnotation(Select.class); String queryString = selectAnno.value(); mapper.setQueryString(queryString); //获取当前方法的返回值,还要求必须带有泛型信息 Type type = method.getGenericReturnType();//List<User> //判断type是不是参数化的类型 if(type instanceof ParameterizedType){ //强转 ParameterizedType ptype = (ParameterizedType)type; //得到参数化类型中的实际类型参数 Type[] types = ptype.getActualTypeArguments(); //取出第一个 Class domainClass = (Class)types[0]; //获取domainClass的类名 String resultType = domainClass.getName(); //给Mapper赋值 mapper.setResultType(resultType); } //组装key的信息 //获取方法的名称 String methodName = method.getName(); String className = method.getDeclaringClass().getName(); String key = className+"."+methodName; //给map赋值 mappers.put(key,mapper); } } return mappers; } */ }测试类:
public static void main(String[] args)throws Exception { //1.读取配置文件 InputStream in = Resources.getResourceAsStream("SqlMapConfig01.xml"); //2.创建SqlSessionFactory工厂 SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder(); SqlSessionFactory factory = builder.build(in); //3.使用工厂生产SqlSession对象 SqlSession session = factory.openSession(); //4.使用SqlSession创建Dao接口的代理对象 IUserDao userDao = session.getMapper(IUserDao.class); //5.使用代理对象执行方法 List<User> users = userDao.findAll(); for(User user : users){ System.out.println(user); } //6.释放资源 // session.close(); in.close(); }package com.fhw.mybatis.cfg.Configuration
package com.fhw.mybatis.cfg; import java.util.HashMap; import java.util.Map; /** * 自定义mybatis的配置类 */ public class Configuration { private String driver; private String url; private String username; private String password; private Map<String,Mapper> mappers = new HashMap<String,Mapper>(); public Map<String, Mapper> getMappers() { return mappers; } public void setMappers(Map<String, Mapper> mappers) { this.mappers.putAll(mappers);//此处需要使用追加的方式 } public String getDriver() { return driver; } public void setDriver(String driver) { this.driver = driver; } public String getUrl() { return url; } public void setUrl(String url) { this.url = url; } public String getUsername() { return username; } public void setUsername(String username) { this.username = username; } public String getPassword() { return password; } public void setPassword(String password) { this.password = password; } }mapper类 :
package com.fhw.mybatis.cfg; /** * 用于封装执行的SQL语句和结果类型的全限定类名 */ public class Mapper { private String queryString;//SQL private String resultType;//实体类的全限定类名 public String getQueryString() { return queryString; } public void setQueryString(String queryString) { this.queryString = queryString; } public String getResultType() { return resultType; } public void setResultType(String resultType) { this.resultType = resultType; } }`解析主配置文件的原理
解析 的 这样 配置文件
此刻 需要一个连接对象 一获取资源 与Mapper 语句
package com.fhw.mybatis.utils; import com.fhw.mybatis.cfg.Configuration; import java.sql.Connection; import java.sql.DriverManager; /** * 用于创建数据源的工具类 */ public class DataSourceUtil { /** * 用于获取一个连接 * @param cfg * @return */ public static Connection getConnection(Configuration cfg){ try { Class.forName(cfg.getDriver()); return DriverManager.getConnection(cfg.getUrl(), cfg.getUsername(), cfg.getPassword()); }catch(Exception e){ throw new RuntimeException(e); } } }主配置文件 的改制 : 主配置文件的里mappers里 使用的是Class属性url到该接口
注解 要注意的的一点 mappers(映射器)========------ 用于注解开发 ; 注意:此种方法要求 mapper 接口名称和 mapper 映射文件名称相同,且放在同一个目录中。 接着在该 接口里实现 自定义注解的 sql 语句 :
package com.fhw.dao; import com.fhw.domain.User; /*import com.fhw.mybatis.annotations.Select;*/ import java.util.List; /* * 用户的持久层接口 */ public interface IUserDao { /** * 查询所有操作 * @return */ @Select("select * from user") List<User> findAll(); }我们要把 XMLConfigBuilder 代码里的 注解的 给 放行了
该 代码片段是 :有2段
else{ System.out.println("使用的是注解"); //表示没有resource属性,用的是注解 //获取class属性的值 String daoClassPath = mapperElement.attributeValue("class"); //根据daoClassPath获取封装的必要信息 Map<String,Mapper> mappers = loadMapperAnnotation(daoClassPath); //给configuration中的mappers赋值 cfg.setMappers(mappers); } /** * 根据传入的参数,得到dao中所有被select注解标注的方法。 * 根据方法名称和类名,以及方法上注解value属性的值,组成Mapper的必要信息 * @param daoClassPath * @return */ private static Map<String,Mapper> loadMapperAnnotation(String daoClassPath)throws Exception{ //定义返回值对象 Map<String,Mapper> mappers = new HashMap<String, Mapper>(); //1.得到dao接口的字节码对象 Class daoClass = Class.forName(daoClassPath); //2.得到dao接口中的方法数组 Method[] methods = daoClass.getMethods(); //3.遍历Method数组 for(Method method : methods){ //取出每一个方法,判断是否有select注解 boolean isAnnotated = method.isAnnotationPresent(Select.class); if(isAnnotated){ //创建Mapper对象 Mapper mapper = new Mapper(); //取出注解的value属性值 Select selectAnno = method.getAnnotation(Select.class); String queryString = selectAnno.value(); mapper.setQueryString(queryString); //获取当前方法的返回值,还要求必须带有泛型信息 Type type = method.getGenericReturnType();//List<User> //判断type是不是参数化的类型 if(type instanceof ParameterizedType){ //强转 ParameterizedType ptype = (ParameterizedType)type; //得到参数化类型中的实际类型参数 Type[] types = ptype.getActualTypeArguments(); //取出第一个 Class domainClass = (Class)types[0]; //获取domainClass的类名 String resultType = domainClass.getName(); //给Mapper赋值 mapper.setResultType(resultType); } //组装key的信息 //获取方法的名称 String methodName = method.getName(); String className = method.getDeclaringClass().getName(); String key = className+"."+methodName; //给map赋值 mappers.put(key,mapper); } } return mappers; }得到接口的所有方法 遍历 接口的哪一个方法 方法上有 注解 的 注解的value属性: 就是那条sql 语句
mapper 的结果类型 resultType 也很巧妙 : //获取当前方法的返回值,还要求必须带有泛型信息 Type type = method.getGenericReturnType();//List Generic在java里是 泛型的意思 :
/得到参数化类型中的实际类型参数 Type[] types = ptype.getActualTypeArguments() 得到实际类型参数 就是pojo 的 //组装key的信息 //获取方法的名称 mappers 就起来 了 ;