Mybatis执行流程分析_自定义简易Mybatis框架

自定义简易Mybatis框架

Mybatis执行流程分析

  • Mybatis代码编写流程:
    在这里插入图片描述
  • Mybatis配置文件加载过程:在这里插入图片描述

需求分析及技术概述

  • 根据上述的功能结构图, 得出如下需求:
1. 需要具有配置文件加载模块.
2. 支持使用构建者进行SessionFactory构建.
3. 支持使用工厂模式构建Session对象.
4. 可以使用代理模式, 构建DAO接口实现类对象.
5. 使用反射机制, 封装ResultSet结果集.
6. 支持使用.xml文件编写SQL语句.
7. 支持使用注解方式进行SQL查询.
  • 项目技术要点
	1. 解析UserDao.xml; SqlMapConfig.xml需要使用dom4j, xpath技术2. 使用Configuration类(类似于Map结构)存储SqlMapConfig中配置信息3. SqlMapConfig中包含连接池, 使用DataSource类根据配置信息, 创建连接池4. 使用构建者, 工厂, 代理模式开发5. 使用Java反射机制, 自定义注解.

项目搭建(本项目只构建Select * from tableName; 主要用于理解Mybatis执行过程)

本项目使用maven进行构建, pom.xml文件需引入如下依赖包:

    <dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId><version>5.1.6</version></dependency><dependency><groupId>log4j</groupId><artifactId>log4j</artifactId><version>1.2.17</version></dependency>
<!--    使用dom4j与xpath解析xml--><dependency><groupId>dom4j</groupId><artifactId>dom4j</artifactId><version>1.6.1</version></dependency>
<!--    xpath技术--><dependency><groupId>jaxen</groupId><artifactId>jaxen</artifactId><version>1.1.6</version></dependency>

项目目录结构:
在这里插入图片描述
本项目分为如下几块内容:

配置文件加载(包括SqlMapConfig.xml; Mapper.xml, 注解).
配置数据源.
Session工厂
SqlSession

第一部分内容如下:
在这里插入图片描述

  • 配置文件加载类实体编写(用于封装SqlMapConfig.xml文件内容):
/*** 自定义Mybatis的配置文件* @author regotto*/
public class Configuration {private String driver;private String username;private String url;private String password;private Map<String, Mapper> mappers = new HashMap<>(16);//....属性get, set方法
}
  • 注解定义
/*** select语句的注解, 运行期有效, 用于修饰方法* @author regotto*/
@Retention(RetentionPolicy.RUNTIME)
@Target(ElementType.METHOD)
public @interface   Select {/*** 配置SQL语句* @return*/String value();
}
  • 工具类准备(Xml文件解析, 数据源配置, Sql执行结果集封装):
    • 获取XML配置文件的输入流:
public class Resources {public static InputStream getResourceAsStream(String filePath) {return Resources.class.getClassLoader().getResourceAsStream(filePath);}
}
    • Xml文件解析(将配置文件内容解析后, 并将其封装到Configuration实体中)
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)){String driver = propertyElement.attributeValue("value");cfg.setDriver(driver);}if("url".equals(name)){String url = propertyElement.attributeValue("value");cfg.setUrl(url);}if("username".equals(name)){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");//在mapper中只有两种属性: resource, classif(attribute != null){//表示有resource属性,用的是XML, 取出属性的值String mapperPath = attribute.getValue();//获取属性的值"com/itheima/dao/IUserDao.xml"//把映射配置文件的内容获取出来,封装成一个mapMap<String,Mapper> mappers = loadMapperConfiguration(mapperPath);//给configuration中的mappers赋值cfg.setMappers(mappers);}else{//表示没有resource属性,用的是注解 获取class属性的值String daoClassPath = mapperElement.attributeValue("class");//根据daoClassPath获取封装的必要信息Map<String, Mapper> mappers = loadMapperAnnotation(daoClassPath);//给configuration中的mappers赋值cfg.setMappers(mappers);}}//返回Configurationreturn 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语句和要封装的实体类全限定类名(ResultSet的类型))*/private static Map<String,Mapper> loadMapperConfiguration(String mapperPath)throws IOException {InputStream in = null;try{//定义返回值对象Map<String,Mapper> mappers = new HashMap<String,Mapper>(16);//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();//创建KeyString key = namespace+"."+id;//创建ValueMapper 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);//获取当前方法的返回值且返回值必须具有泛型(Generic泛型的意思)// List<User>Type type = method.getGenericReturnType();//判断type是不是参数化的类型if(type instanceof ParameterizedType){//强转ParameterizedType ptype = (ParameterizedType)type;//得到参数化类型中的实际类型参数, 因为像Map<K,V>这种存在多个泛型, 所以Type是数组Type[] types = ptype.getActualTypeArguments();//取出第一个, 我们的List<User>只有一个, 所以取出第一个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;}
}	
    • 数据源配置
//获得一个Connection对象
public class DataSource {public static Connection getConnection(Configuration configuration) {try {Class.forName(configuration.getDriver());return DriverManager.getConnection(configuration.getUrl(),configuration.getUsername(),configuration.getPassword());} catch (Exception e) {throw new RuntimeException(e);}}
}
    • 结果集封装(Sql执行后的ResultSet封装)
/*** @author regotto* 负责执行SQL语句,并且封装结果集*/
public class Executor {public <E> List<E> selectList(Mapper mapper, Connection conn) {PreparedStatement pstm = null;ResultSet rs = null;try {//1.取出mapper中的数据// select * from userString queryString = mapper.getQueryString();//com.regotto.domain.UserString resultType = mapper.getResultType();Class domainClass = Class.forName(resultType);//2.获取PreparedStatement对象pstm = conn.prepareStatement(queryString);//3.执行SQL语句,获取结果集rs = pstm.executeQuery();//4.封装结果集List<E> list = new ArrayList<E>();//定义返回值while(rs.next()) {//实例化要封装的实体类对象E obj = (E)domainClass.newInstance();//取出结果集的元信息:ResultSetMetaDataResultSetMetaData rsmd = rs.getMetaData();//取出总列数int columnCount = rsmd.getColumnCount();//遍历总列数for (int i = 1; i <= columnCount; i++) {//获取每列的名称,列名的序号是从1开始的String columnName = rsmd.getColumnName(i);//根据得到列名,获取每列的值Object columnValue = rs.getObject(columnName);//给obj赋值:使用Java内省机制(借助PropertyDescriptor实现属性的封装, 底层还是反射机制)//获取对应domainClass这个类中的columnName的所有操作//要求:实体类的属性和数据库表的列名保持一种PropertyDescriptor pd = new PropertyDescriptor(columnName,domainClass);//获取它的写入方法(即setXXX(...))Method writeMethod = pd.getWriteMethod();//把获取的列的值,给对象赋值(即obj.setXXX(columnValue))writeMethod.invoke(obj,columnValue);}//把赋好值的对象加入到集合中list.add(obj);}return list;} catch (Exception e) {throw new RuntimeException(e);} finally {release(pstm,rs);}}private void release(PreparedStatement pstm,ResultSet rs){if(rs != null){try {rs.close();}catch(Exception e){e.printStackTrace();}}if(pstm != null){try {pstm.close();}catch(Exception e){e.printStackTrace();}}}
}

第二部分内容:
在这里插入图片描述

  • SqlSession工厂(此部分使用工厂模型, 构建者模型):
    • SqlSession工厂接口:
/*** SqlSessionFactory, 制定Session* @author regotto*/
public interface SqlSessionFactory {/*** 返回一个SqlSession对象* @return sqlSession*/SqlSession openSession();
}
    • 工厂建造器:
/*** 根据配置文件创建SqlSessionFactory* @author regotto*/
public class SqlSessionFactoryBuilder {public SqlSessionFactory build(InputStream inputStream) {Configuration configuration = XMLConfigBuilder.loadConfiguration(inputStream);return new DefaultSqlSessionFactory(configuration);}
}
    • 默认Session工厂:
/*** 默认的SqlSessionFactory实现* @author regotto*/
public class DefaultSqlSessionFactory implements SqlSessionFactory {private Configuration configuration;public DefaultSqlSessionFactory(Configuration configuration) {this.configuration = configuration;}/*** 创建一个默认的Session* @return*/@Overridepublic SqlSession openSession() {return new DefaultSqlSession(configuration);}
}

第三部分内容:
在这里插入图片描述

  • SqlSession
    • Mapper实体:
/***封装XXXDao.xml中的resultType和SQL语句* @author regotto*/
public class Mapper {private String resultType;private String queryString;//get, set方法...
    • SqlSession接口:
public interface SqlSession {/*** 通过tClass获得代理对象* @param daoInterfaceClass* @param <T>* @return 代理对象*/<T> T getMapper(Class<T> daoInterfaceClass);/*** 关闭Session资源*/void close();
}
    • MapperProxy(生成Dao接口代理类):
/*** 代理对象* @author regotto*/
public class MapperProxy implements InvocationHandler {/*** String内存储*/private Map<String, Mapper> mappers;private Connection connection;public MapperProxy(Map<String, Mapper> mappers, Connection connection) {this.mappers = mappers;this.connection = connection;}/*** 方法增强, 调用selectList* 在此处执行SQL, 对数据库进行操作* @param proxy 代理对象* @param method 执行方法* @param args 执行方法的参数* @return Object 返回代理对象* @throws Throwable*/@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {String methodName = method.getName();//获取方法所在的类名String className = method.getDeclaringClass().getName();String key = className + "." + methodName;//查看XXXDao.xml中是否有代理对象对应的类和方法Mapper mapper = mappers.get(key);if (mapper == null) {throw new IllegalArgumentException("传入的Class对象错误");}return new Executor().selectList(mapper, connection);}
}
    • 默认SqlSession实现:
/*** 默认的Session* @author regotto*/
public class DefaultSqlSession implements SqlSession {/*** 封装SqlMapConfig配置信息* 包含DataSource, Mappers(封装XXXDao.xml的namespace, sql标签的id, returnType, sql语句)*/private Configuration configuration;/*** 数据库连接对象*/private Connection connection;public DefaultSqlSession(Configuration configuration) {this.configuration = configuration;this.connection = DataSource.getConnection(configuration);}/*** 创建代理对象* @param daoInterfaceClass 需要创建代理对象的class* @param <T> 针对所有的class* @return 代理对象*/@Overridepublic <T> T getMapper(Class<T> daoInterfaceClass) {return (T) Proxy.newProxyInstance(daoInterfaceClass.getClassLoader(),new Class[]{daoInterfaceClass},new MapperProxy(configuration.getMappers(), connection));}/*** 释放资源*/@Overridepublic void close() {if (connection != null) {try {connection.close();} catch (SQLException e) {e.printStackTrace();}}}
}

测试类:

/*** @author regotto*/
public class MybatisTest {public static void main(String[] args) throws Exception {InputStream inputStream = Resources.getResourceAsStream("SqlMapConfig.xml");SqlSessionFactoryBuilder sessionFactoryBuilder = new SqlSessionFactoryBuilder();SqlSessionFactory sessionFactory = sessionFactoryBuilder.build(inputStream);SqlSession sqlSession = sessionFactory.openSession();//获得代理对象UserDao userDao = sqlSession.getMapper(UserDao.class);List<User> users = userDao.findAll();for (User u: users) {System.out.println(u);}sqlSession.close();inputStream.close();}
}

总结

断点调试, 理解Mybatis执行过程, 根据Mybatis执行过程, 模拟实现. 遇到问题, 理清思路, debug调试.

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/468551.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

给大家推荐一个优质Linux内核技术公众号-Linux阅码场

作为一个Linux 技术公众号的作者&#xff0c;我觉得有义务推荐优秀的公众号&#xff0c;推广内容&#xff0c;希望对大家的学习有所帮助~Linux阅码场是一个专注Linux内核和系统编程与调试调优技术的公众号&#xff0c;它的文章云集了国内众多知名企业一线工程师的心得。无论你工…

图数据库_ONgDB图数据库与Spark的集成

快速探索图数据与图计算图计算是研究客观世界当中的任何事物和事物之间的关系&#xff0c;对其进行完整的刻划、计算和分析的一门技术。图计算依赖底于底层图数据模型&#xff0c;在图数据模型基础上计算分析Spark是一个非常流行且成熟稳定的计算引擎。下面文章从ONgDB与Spark的…

2019 高考填报志愿建议

2019 高考填报志愿建议1、城市很关键&#xff0c;在大城市上学和小地方上学会有很大的不同&#xff0c;现在很多毕业生毕业后会往北上广深跑&#xff0c;很多原因是因为这里的就业机会多&#xff0c;薪资比内地好太多了&#xff0c;如果你大学就能在这样的地方上学&#xff0c;…

韦东山:闲鱼与盗版更配,起诉到底绝不和解!

之前很多人问&#xff0c;我和韦老师是什么关系&#xff0c;我们是本家人&#xff0c;至于更深的关系&#xff0c;我也不知道&#xff0c;这次事件受老师委托&#xff0c;帮忙发文支持&#xff0c;看到的朋友&#xff0c;也希望给予转发支持&#xff0c;感激不尽~大学的时候&am…

Windows下搭建FTP服务器

一、什么是ftp? FTP 是File Transfer Protocol&#xff08;文件传输协议&#xff09;的英文简称&#xff0c;而中文简称为“文传协议”。用于Internet上的控制文件的双向传输。同时&#xff0c;它也是一个应用程序&#xff08;Application&#xff09;。基于不同的操作系统有不…

Redis常见数据类型_Redis通用指令

Redis常见数据类型 redis本身就是一个Map结构, 所有数据都采用key:value的形式, redis中的数据类型指的是value的类型, key部分永远是字符串 string(类似Java String) string基本操作: set key value //存储数据 get key value //获取数据 del key value //删除数据 mset …

我关注的学习公众号

在这个激烈竞争的社会&#xff0c;职场上还有“一招鲜”么&#xff1f;面对35岁的“魔咒”&#xff0c;提升自我的路很多&#xff0c;学习是其中最为捷径的一条。只有不断学习新知识&#xff0c;才能保持进步。今天为大家整理了8个公众号&#xff0c;分别为各个领域的佼佼者&am…

如何让开关打开_安卓手机如何打开USB调试模式

点击上方“手机互联” 关注我吧&#xff01;什么是USB调试模式&#xff1f;USB调试模式是 安卓手机提供的一个用于开发工作的功能。使用该功能可在设备和安卓手机之间复制数据、在移动设备上安装应用程序、读取日志数据等等。默认情况下&#xff0c;USB 调试模式是关闭的&#…

Redis持久化_Redis事务_Redis删除策略

Redis持久化 Redis包含3中持久化方案: RDB, AOF, RDB与AOF混合使用 RDB RDB: 将内存中数据生成快照形式, 将其保存到.rdb文件中, 关注点是数据 使用命令执行RDB过程 在保存.rdb文件之前还需要修改redis.conf配置文件, 修改项如下: dbfilename dump.rdb //配置.rdb文件名, …

第一次线下活动总结

公众号建立有了一段时间了&#xff0c;今天是我们筹划的第一次线下聚会活动&#xff0c;活动发起人是公众号的一个读者&#xff0c;是我们的前辈&#xff0c;也是这次活动的赞助商&#xff0c;非常感谢&#xff0c;支付了聚餐了费用&#xff0c;这次第一届活动&#xff0c;当然…

初学者选黑卡还是微单_零基础,一篇读懂单反和微单

许多小白在选购相机时&#xff0c;常常会纠结选微单还是选单反。那么这次就来一篇通俗讲解&#xff1a;单反和微单有什么区别&#xff1f;谁更好&#xff1f;应该怎么选择&#xff1f;一、单反和微单有什么区别&#xff1f;在了解单反和微单的区别之前&#xff0c;我们先要了解…

Redis核心配置_Redis高级数据类型使用

Redis核心配置 服务端配置 daemonize yes|no //服务器是否已守护进程方式运行 bind 127.0.0.1 //绑定主机 port 6379 //设置端口 databases 16 //设置数据库数量 loglevel debug|verbose|notice|warning //设置日志级别 logfile 端口号.log //设置日志文件名 maxclients 1 //…

xcode8注释快捷键失效问题

1. 首先按上图的指示&#xff0c;查看Add Documentation后面的快捷键是不是optioncommand/。 2. 如果发现不是默认的快捷键&#xff0c;可按快捷键command&#xff0c;打开Xcode偏好设置窗口&#xff0c;选中Key Bindings&#xff0c;搜索Add Documentation&#xff0c;便可修…

vant组件搜索并选择_Vant Weapp - 有赞出品的免费开源微信小程序组件库

轻量可靠的小程序UI组件库&#xff0c;主流移动组件库 Vant 的微信小程序版本。Vant Weapp 和 Vant 的区别之前推荐过的移动端web组件库 Vant 是 Vue.js 版本的&#xff0c;其对内承载了有赞所有核心业务&#xff0c;对外有十多万开发者在使用&#xff0c;一直是业界主流的移动…

走了,又回来了

今天换了个大的办公室&#xff0c;从100平增加到了300平&#xff0c;从宝安到南山&#xff0c;从旧环境到新环境&#xff0c;不是新的开始&#xff0c;是新的环境和心情。突然有点感慨&#xff0c;那时候从科技园出发&#xff0c;跟HP从深圳坐高铁去广州&#xff0c;在广州小蛮…

Flink-Java版单词计数(批处理流处理)

创建工程 pom.xml文件依赖如下: <dependencies><dependency><groupId>org.apache.flink</groupId><artifactId>flink-java</artifactId><version>1.10.1</version></dependency><!--依赖的一些组件需要 Scala 环境…

怎么做批注_BIM平台是什么?有何用?怎么用?

原标题&#xff1a;BIM平台是什么&#xff1f;有何用&#xff1f;怎么用&#xff1f;随着BIM技术的深入应用&#xff0c;我们也不再拘泥于单单BIM软件的使用&#xff0c;在BIM技术的广泛应用之下&#xff0c;BIM平台也逐渐成为了BIM技术的最佳体现&#xff0c;也成为了众多工程…

Flink并行度优先级_集群操作常用指令_运行组件_任务提交流程_数据流图变化过程

Flink并行度优先级(从高到低) sum(1).setParallelism(1) env.setParallelism(1) ApacheFlinkDashboard任务添加并行度配置 flink-conf.yaml并行度配置 注: 处理输入输出时, 并行度默认为 1Flink集群常用指令 提交任务 run: 代表执行; c: 指定入口类; p: 并行度; host, post:…

招银网络笔试java_最新!!招银网络科技Java面经,整理附答案

作者&#xff1a;榨汁机2号 链接&#xff1a;https://www.nowcoder.com/discuss/1640193月12号现场面试的&#xff0c; 感觉好像所有人有3面的样子。到目前也没有消息&#xff0c;有消息的吱一声&#xff0c;让我早点死了这个心…..一面 1 Java的八大基本类型byte、short、int、…

FlinkAPI_Environment_输入源_算子转化流程

Flink Environment getExecutionEnvironment() 根据当前平台, 获取对应的执行环境, 若未设置并行度, 使用 flink-conf.yaml 中的并行度配置, 默认 1. StreamExecutionEnvironment env StreamExecutionEnvironment.getExecutionEnvironment();createLocalEnviroment() 创建本地…