关于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操作 之后只需要将容器回收就可以了

原文地址:https://www.cnblogs.com/yuztmt/p/13629827.html