• 关于mybatis的理解,简单实现手写及源码


    mybatis现在已经是一大主流框架,相比hibernate来说 mybatis的使用更加广泛 当然这也有mybatis入手简单的原因,不过也无法改变使用的人越来越多

    我门都知道jdbc的连接数据库方式 其实不论是mybatis也好还是其他的用于数据库连接的对象映射框架都是基于JDBC来进行封装的

    下面是一段jdbc的代码 我们可以通过解读这段代码来逐步了解mybatis

     1      Connection connection = null;
     2         PreparedStatement preparedStatement = null;
     3         ResultSet resultSet = null;
     4         try {
     5             // 加载数据库驱动
     6             Class.forName("com.mysql.jdbc.Driver");
     7             // 通过驱动管理类获取数据库链接
     8             connection =
     9                     DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8", "root", "root1234");
    10             // 定义sql语句?表示占位符
    11             String sql = "select * from user where username = ?";
    12             // 获取预处理statement
    13             preparedStatement = connection.prepareStatement(sql);
    14             // 设置参数,第一个参数为sql语句中参数的序号(从1开始),第二个参数为设置的参数值
    15             preparedStatement.setString(1, "王五");
    16             // 向数据库发出sql执行查询,查询出结果集
    17             resultSet = preparedStatement.executeQuery();
    18             // 遍历查询结果集
    19             User user = new User();
    20             while (resultSet.next()) {
    21                 int id = resultSet.getInt("id");
    22                 String username = resultSet.getString("username");
    23                 // 封装User
    24                 user.setUsername(username);
    25                 user.setId(id);
    26             }
    27             System.out.println(user);
    28         } catch (Exception e) {
    29             e.printStackTrace();
    30         } finally {
    31             // 释放资源
    32             if (resultSet != null) {
    33                 try {
    34                     resultSet.close();
    35                 } catch (SQLException e) {
    36                     e.printStackTrace();
    37                 }
    38             }
    39             if (preparedStatement != null) {
    40                 try {
    41                     preparedStatement.close();
    42                 } catch (SQLException e) {
    43                     e.printStackTrace();
    44                 }
    45             }
    46 
    47         }

    首先

    // 加载数据库驱动  8.0以后的驱动为com.mysql.cj.jdbc.Driver
    Class.forName("com.mysql.jdbc.Driver");
    // 通过驱动管理类获取数据库链接
    connection =DriverManager.getConnection("jdbc:mysql://localhost:3306/mybatis?characterEncoding=utf-8", "root", "root1234");

    这段我想使用过mybatis的应该都不会陌生 就是jdbc配置文件中配置的driver url user password

    mybatis通过解析xml配置文件中的<dataSource>标签内的<property>标签内容来获取这些驱动信息,之后将其封装用于连接数据库时的构建连接

    再往下

           // 定义sql语句?表示占位符
                String sql = "select * from user where username = ?";
                // 获取预处理statement
                preparedStatement = connection.prepareStatement(sql);
                // 设置参数,第一个参数为sql语句中参数的序号(从1开始),第二个参数为设置的参数值
                preparedStatement.setString(1, "王五");
                // 向数据库发出sql执行查询,查询出结果集
                resultSet = preparedStatement.executeQuery();
                // 遍历查询结果集
                User user = new User();
                while (resultSet.next()) {
                    int id = resultSet.getInt("id");
                    String username = resultSet.getString("username");
                    // 封装User
                    user.setUsername(username);
                    user.setId(id);
                }
                System.out.println(user);

    这一段就是具体的sql操作了

    对照一个mapper.xml来看

       <select id="findByCondition" resultType="com.lagou.pojo.User" paramterType="com.lagou.pojo.User">
            select * from user where id = #{id} and username = #{username}
        </select>

    mybatis将定义sql语句放在了select标签中

    这时我们会发现没有看到mybatis的statement预处理对象 其实statement对象是被mybatis封装了 这个后面会说到

    然后就是参数和返回结果的设置 mybatis是用paramterType标明了参数和返回对象类型 具体的封装过程并没有看到

    后返回结果 我们发现mybatis会多出一个id这个属性

    用过的都知道id需要同方法名 这样为了方便精准定位 具体怎么定位 我们稍后再说

    最后是释放资源 也被mybatis封装了

            // 释放资源
                if (resultSet != null) {
                    try {
                        resultSet.close();
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
                }
                if (preparedStatement != null) {
                    try {
                        preparedStatement.close();
                    } catch (SQLException e) {
                        e.printStackTrace();
                    }
                }

    所以mybatis的使用我们只需要定义需要定义的内容即可 不必重复进行其他操作

    下面我们来谈谈mybatis是怎么进行这些操作的

    参考jdbc代码 首先我们要解析驱动 ,同时因为mapper.xml这些也是写在配置文件中 所以我们还需要解析这些配置文件 那么就需要两个解析类来解析这两个配置文件

     1 package com.lagou.config;
     2 
     3 import com.lagou.io.Resources;
     4 import com.lagou.pojo.Configuration;
     5 import com.mchange.v2.c3p0.ComboPooledDataSource;
     6 import org.dom4j.Document;
     7 import org.dom4j.DocumentException;
     8 import org.dom4j.Element;
     9 import org.dom4j.io.SAXReader;
    10 
    11 import java.beans.PropertyVetoException;
    12 import java.io.InputStream;
    13 import java.util.List;
    14 import java.util.Properties;
    15 
    16 public class XMLConfigBuilder {
    17 
    18     private Configuration configuration;
    19 
    20     public XMLConfigBuilder() {
    21         this.configuration = new Configuration();
    22     }
    23 
    24     /**
    25      * 该方法就是使用dom4j对配置文件进行解析,封装Configuration
    26      */
    27     public Configuration parseConfig(InputStream inputStream) throws DocumentException, PropertyVetoException {
    28 
    29         Document document = new SAXReader().read(inputStream);
    30         //<configuration>
    31         Element rootElement = document.getRootElement();
    32         List<Element> list = rootElement.selectNodes("//property");
    33         Properties properties = new Properties();
    34         for (Element element : list) {
    35             String name = element.attributeValue("name");
    36             String value = element.attributeValue("value");
    37             properties.setProperty(name,value);
    38         }
    39 
    40         ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();
    41         comboPooledDataSource.setDriverClass(properties.getProperty("driver"));
    42         comboPooledDataSource.setJdbcUrl(properties.getProperty("url"));
    43         comboPooledDataSource.setUser(properties.getProperty("username"));
    44         comboPooledDataSource.setPassword(properties.getProperty("password"));
    45 
    46         configuration.setDataSource(comboPooledDataSource);
    47 
    48         //mapper.xml解析: 拿到路径--字节输入流---dom4j进行解析
    49         List<Element> mapperList = rootElement.selectNodes("//mapper");
    50 
    51         for (Element element : mapperList) {
    52             String mapperPath = element.attributeValue("resource");
    53             InputStream resourceAsSteam = Resources.getResourceAsSteam(mapperPath);
    54             XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(configuration);
    55             xmlMapperBuilder.parse(resourceAsSteam);
    56 
    57         }
    58 
    59 
    60 
    61 
    62         return configuration;
    63     }
    64 
    65 
    66 }

    首先这个类来解析config配置 将取道的dataSource封装到Configuration对象中 方便之后取出

     1 package com.lagou.config;
     2 
     3 import com.lagou.pojo.Configuration;
     4 import com.lagou.pojo.MappedStatement;
     5 import org.dom4j.Document;
     6 import org.dom4j.DocumentException;
     7 import org.dom4j.Element;
     8 import org.dom4j.io.SAXReader;
     9 
    10 import java.io.InputStream;
    11 import java.util.ArrayList;
    12 import java.util.List;
    13 
    14 public class XMLMapperBuilder {
    15 
    16     private Configuration configuration;
    17 
    18     public XMLMapperBuilder(Configuration configuration) {
    19         this.configuration =configuration;
    20     }
    21 
    22     public void parse(InputStream inputStream) throws DocumentException {
    23 
    24         Document document = new SAXReader().read(inputStream);
    25         Element rootElement = document.getRootElement();
    26 
    27         String namespace = rootElement.attributeValue("namespace");
    28 
    29         List<Element> selectList = rootElement.selectNodes("//select");
    30         List<Element> insertList = rootElement.selectNodes("//insert");
    31         List<Element> updateList = rootElement.selectNodes("//update");
    32         List<Element> deleteList = rootElement.selectNodes("//delete");
    33 
    34         ArrayList<Element> list = new ArrayList<>();
    35         list.addAll(selectList);
    36         list.addAll(insertList);
    37         list.addAll(updateList);
    38         list.addAll(deleteList);
    39         for (Element element : list) {
    40             String id = element.attributeValue("id");
    41             String resultType = element.attributeValue("resultType");
    42             String paramterType = element.attributeValue("paramterType");
    43             String sqlText = element.getTextTrim();
    44             MappedStatement mappedStatement = new MappedStatement();
    45             mappedStatement.setId(id);
    46             mappedStatement.setResultType(resultType);
    47             mappedStatement.setParamterType(paramterType);
    48             mappedStatement.setSql(sqlText);
    49             String key = namespace+"."+id;
    50             configuration.getMappedStatementMap().put(key,mappedStatement);
    51 
    52         }
    53 
    54     }
    55 
    56 
    57 }

    然后时通过这个类来mapper配置文件中的属性也放到Configuration对象中 ,这里就要谈到map的key值问题了 从代码中可以看出是通过namespace+.+id来得到的 至于namespace是哪里定义的

    就是mapper.xml的最外层配置的namespace属性中,通常我们的设置是mapper接口的全路径

    之后我们就可以通过Configuration对象取出driver信息以及sql信息了 这时我们只需要statement对象来进行操作就行了 最后释放

    这里这样写没什么问题 但是比较笨 而且每次活得对象每次释放的行为太过繁琐和重复

    那么我们就需要给他封装一个可以执行sql的执行器 Executor

    然后让Executor去帮我们完成这些工作 我们取到结果,那么Executor对象中将这些取到后操作 那么换一个接口不是要重新封装一次

    所以我们通过jdk的动态代理对象的方式为mapper接口生成代理对象 并返回

     1 public <T> T getMapper(Class<?> mapperClass, String type) {
     2         // 使用JDK动态代理来为Dao接口生成代理对象,并返回
     3 
     4         Object proxyInstance = Proxy.newProxyInstance(DefaultSqlSession.class.getClassLoader(), new Class[]{mapperClass}, new InvocationHandler() {
     5             @Override
     6             public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
     7                 // 底层都还是去执行JDBC代码 //根据不同情况,来调用selctList或者selectOne
     8                 // 准备参数 1:statmentid :sql语句的唯一标识:namespace.id= 接口全限定名.方法名
     9                 // 方法名:findAll
    10                 String methodName = method.getName();
    11                 String className = method.getDeclaringClass().getName();
    12 
    13                 String statementId = className+"."+methodName;
    14 
    15                 // 准备参数2:params:args
    16                 // 获取被调用方法的返回值类型
    17                 Type genericReturnType = method.getGenericReturnType();
    18                 if (null != type || "".equals(type)) {
    19                     // 判断是否进行了 泛型类型参数化
    20                     if (genericReturnType instanceof ParameterizedType && "select".equals(type)) {
    21                         List<Object> objects = selectList(statementId, args);
    22                         return objects;
    23                     }
    24                     if ("insert".equals(type)) {
    25                         Integer result = insert(statementId, args);
    26                         return result;
    27                     }
    28                     if ("update".equals(type)) {
    29                         Integer result = update(statementId, args);
    30                         return result;
    31                     }
    32                     if ("delete".equals(type)) {
    33                         Integer result = delete(statementId, args);
    34                         return result;
    35                     }
    36                     return selectOne(statementId,args);
    37                 }
    38 
    39                 return null;
    40 
    41             }
    42         });
    43 
    44         return (T) proxyInstance;
    45     }

    我这里是通过type参数来判断该方法要执行什么操作的 不过我后来考虑可以直接取mapper标签来进行这一步操作 这样的话 就不会因为输入错误的type导致操作偏差了(因为我写修改的方法是忘了改delete的type所以测试update的时候执行了delete操作)

    之后我们再在Executor中去具体执行jdbc操作就可以直接返回结果了

    执行完之后关于释放连接的问题 mybatis是通过sqlSession来控制的

    mybatis通过工厂设计模式的方法 去实现sqlSessionFactory来产出一个个sqlSession对象作为容器 在容器内执行上面的Executor操作 之后只需要将容器回收就可以了

  • 相关阅读:
    【手机网络游戏 编程】C#异步socketAPI调用 处理数据的流程
    【已解决】unity4.2.0f4 导出Android工程报错:Error building Player: ArgumentException: Illegal characters in path. [unity导出android工程 报错,路径含有非法字符]
    【已解决】Android微信开放平台,申请移动应用的 应用签名 如何获取
    【keytool jarsigner工具的使用】Android 使用JDK1.7的工具 进行APK文件的签名,以及keystore文件的使用
    JavaScript闭包浅谈
    C#接口和抽象类的区别
    .NET中的垃圾回收
    访问修饰符(public,private,protected,internal,sealed,abstract)
    C#.NET里面抽象类和接口有什么区别?
    在 ASP.NET 中执行 URL 重写的方法
  • 原文地址:https://www.cnblogs.com/yuztmt/p/13629827.html
Copyright © 2020-2023  润新知