JavaWeb——MyBatis框架之执行过程原理与解析(通过自定义MyBatis查询所有操作的实现来观察整个过程)

举报
Winter_world 发表于 2021/09/28 23:43:28 2021/09/28
【摘要】 目录 1 MyBatis执行过程分析 1.1 MyBatis执行查询所有的过程分析 1.2 MyBatis创建代理对象的分析 2 自定义MyBatis 2.1 根据MyBatisTest测试类中缺少的创建接口和类 2.2 解析XML工具类 2.3 补充SqlSessionFactoryBuilder类创建SqlS...

目录

1 MyBatis执行过程分析

1.1 MyBatis执行查询所有的过程分析

1.2 MyBatis创建代理对象的分析

2 自定义MyBatis

2.1 根据MyBatisTest测试类中缺少的创建接口和类

2.2 解析XML工具类

2.3 补充SqlSessionFactoryBuilder类创建SqlSessionFactory工厂

2.4 实现基于XML的查询所有操作


1 MyBatis执行过程分析

通过上一博文,我们了解了MyBatis的入门,知道了怎么搭建环境及最基本的使用,那么,本次我们结合上一博文的案例实战,进行更深入的分析MyBatis的执行过程,MyBatis使用代理dao方式进行增删改查时做了哪些事呢?其实就是两件:

  • 1)创建代理对象;
  • 2)在代理对象中调用selectList。

1.1 MyBatis执行查询所有的过程分析

1.2 MyBatis创建代理对象的分析

2 自定义MyBatis

在入门实战案例基础上修改,删除掉pom.xml中MyBatis的坐标,下面按步骤搞起:

2.1 根据MyBatisTest测试类中缺少的创建接口和类

MyBatisTest测试类中涉及的类如下,我们需要自定义,先创建,让MyBatisTest测试类不报错,然后再下一步中填充内容:

  • Class Resources
  • Class SqlSessionFactoryBuilder
  • Interface SqlSessionFactory
  • Interface SqlSession

为了便于观察,将MyBatisTest中的代码贴一下,整体实现思路就是如下注释的:


  
  1. public class MyBatisTest {
  2. //MyBatis入门案例
  3. @Test
  4. public void test() throws Exception{
  5. //1、读取配置文件
  6. InputStream in = Resources.getResourceAsStream("SqlMapConfig.xml");
  7. //2、创建SqlSessionFactory工厂
  8. SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
  9. SqlSessionFactory factory = builder.build(in);
  10. //3、使用工厂生产SqlSession对象
  11. SqlSession session = factory.openSession();
  12. //4、使用SqlSession创建Dao接口的代理对象
  13. UserDao userDao = session.getMapper(UserDao.class);
  14. //5、使用代理对象执行方法
  15. List<User> users = userDao.findAll();
  16. for (User user : users) {
  17. System.out.println(user);
  18. }
  19. //6、释放资源
  20. session.close();
  21. in.close();
  22. }
  23. }

1)创建Resources类


  
  1. //使用类加载器读取配置文件的类
  2. public class Resources {
  3. //根据传入的参数获取一个字节输入流
  4. public static InputStream getResourceAsStream(String filePath){
  5. return Resources.class.getClassLoader().getResourceAsStream(filePath);
  6. }
  7. }

2)创建SqlSessionFactoryBuilder类


  
  1. //用于创建一个SqlSessionFactory对象
  2. public class SqlSessionFactoryBuilder {
  3. //根据参数字节输入流,构建一个SqlSessionFactory工厂
  4. public SqlSessionFactory build(InputStream config){
  5. return null;
  6. }
  7. }

3)创建SqlSessionFactory接口


  
  1. public interface SqlSessionFactory {
  2. //用于打开一个新的SqlSession对象
  3. public SqlSession openSession();
  4. }

4)创建SqlSession接口


  
  1. //自定义MyBatis中和数据库交互的核心类
  2. //可以创建dao接口的代理对象
  3. public interface SqlSession {
  4. //根据参数创建一个代理对象
  5. //daoInterfaceClass为dao的接口字节码
  6. <T> T getMapper(Class<T> daoInterfaceClass);
  7. //释放资源
  8. void close();
  9. }

2.2 解析XML工具类

此处不作为重点,主要用来解析主配置文件,把里面的内容填充到DefaultSqlSession所需要的地方:

1)在com.winter.mybatis.utils包下新建XMLConfigBuilder类:


  
  1. //用于解析配置文件
  2. public class XMLConfigBuilder {
  3. /**
  4. * 解析主配置文件,把里面的内容填充到DefaultSqlSession所需要的地方
  5. * 使用的技术:
  6. * dom4j+xpath
  7. */
  8. public static Configuration loadConfiguration(InputStream config){
  9. try{
  10. //定义封装连接信息的配置对象(mybatis的配置对象)
  11. Configuration cfg = new Configuration();
  12. //1.获取SAXReader对象
  13. SAXReader reader = new SAXReader();
  14. //2.根据字节输入流获取Document对象
  15. Document document = reader.read(config);
  16. //3.获取根节点
  17. Element root = document.getRootElement();
  18. //4.使用xpath中选择指定节点的方式,获取所有property节点
  19. List<Element> propertyElements = root.selectNodes("//property");
  20. //5.遍历节点
  21. for(Element propertyElement : propertyElements){
  22. //判断节点是连接数据库的哪部分信息
  23. //取出name属性的值
  24. String name = propertyElement.attributeValue("name");
  25. if("driver".equals(name)){
  26. //表示驱动
  27. //获取property标签value属性的值
  28. String driver = propertyElement.attributeValue("value");
  29. cfg.setDriver(driver);
  30. }
  31. if("url".equals(name)){
  32. //表示连接字符串
  33. //获取property标签value属性的值
  34. String url = propertyElement.attributeValue("value");
  35. cfg.setUrl(url);
  36. }
  37. if("username".equals(name)){
  38. //表示用户名
  39. //获取property标签value属性的值
  40. String username = propertyElement.attributeValue("value");
  41. cfg.setUsername(username);
  42. }
  43. if("password".equals(name)){
  44. //表示密码
  45. //获取property标签value属性的值
  46. String password = propertyElement.attributeValue("value");
  47. cfg.setPassword(password);
  48. }
  49. }
  50. //取出mappers中的所有mapper标签,判断他们使用了resource还是class属性
  51. List<Element> mapperElements = root.selectNodes("//mappers/mapper");
  52. //遍历集合
  53. for(Element mapperElement : mapperElements){
  54. //判断mapperElement使用的是哪个属性
  55. Attribute attribute = mapperElement.attribute("resource");
  56. if(attribute != null){
  57. System.out.println("使用的是XML");
  58. //表示有resource属性,用的是XML
  59. //取出属性的值
  60. String mapperPath = attribute.getValue();//获取属性的值"com/winter/dao/IUserDao.xml"
  61. //把映射配置文件的内容获取出来,封装成一个map
  62. Map<String, Mapper> mappers = loadMapperConfiguration(mapperPath);
  63. //给configuration中的mappers赋值
  64. cfg.setMappers(mappers);
  65. }else{
  66. // System.out.println("使用的是注解");
  67. // //表示没有resource属性,用的是注解
  68. // //获取class属性的值
  69. // String daoClassPath = mapperElement.attributeValue("class");
  70. // //根据daoClassPath获取封装的必要信息
  71. // Map<String,Mapper> mappers = loadMapperAnnotation(daoClassPath);
  72. // //给configuration中的mappers赋值
  73. // cfg.setMappers(mappers);
  74. }
  75. }
  76. //返回Configuration
  77. return cfg;
  78. }catch(Exception e){
  79. throw new RuntimeException(e);
  80. }finally{
  81. try {
  82. config.close();
  83. }catch(Exception e){
  84. e.printStackTrace();
  85. }
  86. }
  87. }
  88. /**
  89. * 根据传入的参数,解析XML,并且封装到Map中
  90. * @param mapperPath 映射配置文件的位置
  91. * @return map中包含了获取的唯一标识(key是由dao的全限定类名和方法名组成)
  92. * 以及执行所需的必要信息(value是一个Mapper对象,里面存放的是执行的SQL语句和要封装的实体类全限定类名)
  93. */
  94. private static Map<String,Mapper> loadMapperConfiguration(String mapperPath)throws IOException {
  95. InputStream in = null;
  96. try{
  97. //定义返回值对象
  98. Map<String,Mapper> mappers = new HashMap<String,Mapper>();
  99. //1.根据路径获取字节输入流
  100. in = Resources.getResourceAsStream(mapperPath);
  101. //2.根据字节输入流获取Document对象
  102. SAXReader reader = new SAXReader();
  103. Document document = reader.read(in);
  104. //3.获取根节点
  105. Element root = document.getRootElement();
  106. //4.获取根节点的namespace属性取值
  107. String namespace = root.attributeValue("namespace");//是组成map中key的部分
  108. //5.获取所有的select节点
  109. List<Element> selectElements = root.selectNodes("//select");
  110. //6.遍历select节点集合
  111. for(Element selectElement : selectElements){
  112. //取出id属性的值 组成map中key的部分
  113. String id = selectElement.attributeValue("id");
  114. //取出resultType属性的值 组成map中value的部分
  115. String resultType = selectElement.attributeValue("resultType");
  116. //取出文本内容 组成map中value的部分
  117. String queryString = selectElement.getText();
  118. //创建Key
  119. String key = namespace+"."+id;
  120. //创建Value
  121. Mapper mapper = new Mapper();
  122. mapper.setQueryString(queryString);
  123. mapper.setResultType(resultType);
  124. //把key和value存入mappers中
  125. mappers.put(key,mapper);
  126. }
  127. return mappers;
  128. }catch(Exception e){
  129. throw new RuntimeException(e);
  130. }finally{
  131. in.close();
  132. }
  133. }
  134. }

2)在com.winter.cfg包下创建XMLConfigBuilder类需要的Configuration类和Mapper类:

Configuration】:


  
  1. public class Configuration {
  2. private String driver;
  3. private String url;
  4. private String username;
  5. private String password;
  6. private Map<String,Mapper> mappers;
  7. public String getDriver() {
  8. return driver;
  9. }
  10. public void setDriver(String driver) {
  11. this.driver = driver;
  12. }
  13. public String getUrl() {
  14. return url;
  15. }
  16. public void setUrl(String url) {
  17. this.url = url;
  18. }
  19. public String getUsername() {
  20. return username;
  21. }
  22. public void setUsername(String username) {
  23. this.username = username;
  24. }
  25. public String getPassword() {
  26. return password;
  27. }
  28. public void setPassword(String password) {
  29. this.password = password;
  30. }
  31. public Map<String, Mapper> getMappers() {
  32. return mappers;
  33. }
  34. public void setMappers(Map<String, Mapper> mappers) {
  35. this.mappers.putAll(mappers); //此处需要使用追加方式,避免覆盖掉
  36. }
  37. }

【Mapper类】:用于封装执行的SQL语句和结果类型的全限定类名


  
  1. //用于封装执行的SQL语句和结果类型的全限定类名
  2. public class Mapper {
  3. private String queryString;//sql
  4. private String resultType;// 实体类的全限定类名
  5. public String getQueryString() {
  6. return queryString;
  7. }
  8. public void setQueryString(String queryString) {
  9. this.queryString = queryString;
  10. }
  11. public String getResultType() {
  12. return resultType;
  13. }
  14. public void setResultType(String resultType) {
  15. this.resultType = resultType;
  16. }
  17. }

2.3 补充SqlSessionFactoryBuilder类创建SqlSessionFactory工厂

补充的SqlSessionFactory类如下:


  
  1. //用于创建一个SqlSessionFactory对象
  2. public class SqlSessionFactoryBuilder {
  3. //根据参数字节输入流,构建一个SqlSessionFactory工厂
  4. public SqlSessionFactory build(InputStream config){
  5. Configuration cfg = XMLConfigBuilder.loadConfiguration(config);
  6. return new DefaultSqlSessionFactory(cfg);
  7. }
  8. }

这里涉及了两个实现类:

【DefaultSqlSessionFactory】:SqlSessionFactory接口的实现类,openSession方法用于创建一个新的操作数据库对象


  
  1. //SqlSessionFactory接口的实现类
  2. public class DefaultSqlSessionFactory implements SqlSessionFactory {
  3. private Configuration cfg;
  4. public DefaultSqlSessionFactory(Configuration cfg) {
  5. this.cfg = cfg;
  6. }
  7. //用于创建一个新的操作数据库对象
  8. @Override
  9. public SqlSession openSession() {
  10. return new DefaultSqlSession(cfg);
  11. }
  12. }

【DefaultSqlSession】:SqlSession接口的实现类,用于创建代理对象及关闭资源


  
  1. public class DefaultSqlSession implements SqlSession {
  2. private Configuration cfg;
  3. public DefaultSqlSession(Configuration cfg) {
  4. this.cfg = cfg;
  5. }
  6. //用于创建代理对象
  7. @Override
  8. public <T> T getMapper(Class<T> daoInterfaceClass) {
  9. return null;
  10. }
  11. //释放资源
  12. @Override
  13. public void close() {
  14. }
  15. }

到这里上述类之间就建立起来了联系:

  • 1)读取配置文件,用到io里面的Resources类;
  • 2)读出来的流交给构建者;
  • 3)构建者使用工具类构建了工厂对象;
  • 4)工厂对象的openSession提供了Session方法。

接下来需要在方法中实现创建代理对象,和查询所有的操作。

2.4 实现基于XML的查询所有操作

1)DefaultSqlSession实现类中getMapper方法创建代理对象


  
  1. public class DefaultSqlSession implements SqlSession {
  2. private Configuration cfg;
  3. private Connection connection;
  4. public DefaultSqlSession(Configuration cfg) {
  5. this.cfg = cfg;
  6. this.connection = DataSourceUtil.getConnection(cfg);
  7. }
  8. //用于创建代理对象
  9. @Override
  10. public <T> T getMapper(Class<T> daoInterfaceClass) {
  11. return (T)Proxy.newProxyInstance(daoInterfaceClass.getClassLoader(),
  12. new Class[]{daoInterfaceClass},new MapperProxy(cfg.getMappers(),connection));
  13. }
  14. //释放资源
  15. @Override
  16. public void close() {
  17. if(connection!=null){
  18. try {
  19. connection.close();
  20. } catch (SQLException throwables) {
  21. throwables.printStackTrace();
  22. }
  23. }
  24. }
  25. }

【MapperProxy类】:用于进行具体的方法增强,通过key获取Mapper对象,再创建Executor执行查询操作


  
  1. public class MapperProxy implements InvocationHandler {
  2. //key 是全限定类名+方法名
  3. private Map<String, Mapper> mappers;
  4. private Connection conn;
  5. public MapperProxy(Map<String, Mapper> mappers, Connection conn) {
  6. this.mappers = mappers;
  7. this.conn = conn;
  8. }
  9. //用于对方法进行增强
  10. //调用selectList方法
  11. @Override
  12. public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
  13. //1、获取方法名
  14. String methodName = method.getName();
  15. //2、获取方法所在类的名称
  16. String className = method.getDeclaringClass().getName();
  17. //3、组合key
  18. String key = className+"."+methodName;
  19. //4、获取mappers中的Mapper对象
  20. Mapper mapper = mappers.get(key);
  21. //5、判断是否有mapper
  22. if(mapper==null){
  23. throw new IllegalArgumentException("传入的参数有误");
  24. }
  25. //6、调用工具类执行查询所有
  26. return new Executor().selectList(mapper,conn);
  27. }
  28. }

【Executor类】:


  
  1. //负责执行SQL语句,并且封装结果集
  2. public class Executor {
  3. public <E> List<E> selectList(Mapper mapper, Connection conn) {
  4. PreparedStatement pstm = null;
  5. ResultSet rs = null;
  6. try {
  7. //1.取出mapper中的数据
  8. String queryString = mapper.getQueryString();//select * from user
  9. String resultType = mapper.getResultType();//com.itheima.domain.User
  10. Class domainClass = Class.forName(resultType);
  11. //2.获取PreparedStatement对象
  12. pstm = conn.prepareStatement(queryString);
  13. //3.执行SQL语句,获取结果集
  14. rs = pstm.executeQuery();
  15. //4.封装结果集
  16. List<E> list = new ArrayList<E>();//定义返回值
  17. while(rs.next()) {
  18. //实例化要封装的实体类对象
  19. E obj = (E)domainClass.newInstance();
  20. //取出结果集的元信息:ResultSetMetaData
  21. ResultSetMetaData rsmd = rs.getMetaData();
  22. //取出总列数
  23. int columnCount = rsmd.getColumnCount();
  24. //遍历总列数
  25. for (int i = 1; i <= columnCount; i++) {
  26. //获取每列的名称,列名的序号是从1开始的
  27. String columnName = rsmd.getColumnName(i);
  28. //根据得到列名,获取每列的值
  29. Object columnValue = rs.getObject(columnName);
  30. //给obj赋值:使用Java内省机制(借助PropertyDescriptor实现属性的封装)
  31. PropertyDescriptor pd = new PropertyDescriptor(columnName,domainClass);//要求:实体类的属性和数据库表的列名保持一种
  32. //获取它的写入方法
  33. Method writeMethod = pd.getWriteMethod();
  34. //把获取的列的值,给对象赋值
  35. writeMethod.invoke(obj,columnValue);
  36. }
  37. //把赋好值的对象加入到集合中
  38. list.add(obj);
  39. }
  40. return list;
  41. } catch (Exception e) {
  42. throw new RuntimeException(e);
  43. } finally {
  44. release(pstm,rs);
  45. }
  46. }
  47. private void release(PreparedStatement pstm,ResultSet rs){
  48. if(rs != null){
  49. try {
  50. rs.close();
  51. }catch(Exception e){
  52. e.printStackTrace();
  53. }
  54. }
  55. if(pstm != null){
  56. try {
  57. pstm.close();
  58. }catch(Exception e){
  59. e.printStackTrace();
  60. }
  61. }
  62. }
  63. }

【DataSourceUtil类】:用于创建数据源的工具类


  
  1. //用于创建数据源的工具类
  2. public class DataSourceUtil {
  3. //用于获取连接
  4. public static Connection getConnection(Configuration cfg){
  5. try {
  6. Class.forName(cfg.getDriver());
  7. return DriverManager.getConnection(cfg.getUrl(),cfg.getUsername(),cfg.getPassword());
  8. }catch (Exception e){
  9. throw new RuntimeException(e);
  10. }
  11. }
  12. }

【实现效果】:

终于搞完了,以上主要用来深入了解MyBatis的执行过程,博主也是根据教程一步步做下来的,需要源码的话传送门在此

作于202009081645,已归档

———————————————————————————————————

本文为博主原创文章,转载请注明出处!

若本文对您有帮助,轻抬您发财的小手,关注/评论/点赞/收藏,就是对我最大的支持!

祝君升职加薪,鹏程万里!

文章来源: winter.blog.csdn.net,作者:Winter_world,版权归原作者所有,如需转载,请联系作者。

原文链接:winter.blog.csdn.net/article/details/108462406

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。