IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁
 
   -> Java知识库 -> 手撸简易版Mybatis -> 正文阅读

[Java知识库]手撸简易版Mybatis

1.问题与分析

在没有orm框架时,可能我们大多数操作都停留在了对jdbc的操作上,也许我们查到一个user对象,可能需要画大量篇幅来写。如下

   public static void main(String[] args) {
        //声明变量
        Connection connection = null;
        PreparedStatement preparedStatement = null;
        ResultSet resultSet = null;

        try {
            //将jdbc包加载至JVM中
            Class.forName("com.mysql.cj.jdbc.Driver");
            //声明连接数据库的url(本处使用的是本地的MySQL数据库)
            connection = DriverManager.getConnection("jdbc:mysql://localhost:3306/test?serverTimezone=UTC", "root", "root");
            //编写需要操作的sql语句
            String sql = "select * from user";
            //通过连接对象创建编译对象
            preparedStatement = connection.prepareStatement(sql);
            //向编译对象发出sql指令
            resultSet = preparedStatement.executeQuery();
            //接受并处理返回的结果
            while (resultSet.next()) {
                System.out.println(resultSet.getInt("id") + " " + resultSet.getString("username"));
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //释放资源
            if (resultSet != null) {
                try {
                    resultSet.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if (preparedStatement != null) {
                try {
                    preparedStatement.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
            if (connection != null) {
                try {
                    connection.close();
                } catch (SQLException e) {
                    e.printStackTrace();
                }
            }
        }

    }

所以我们在针对不同对象、不同数据表时可能需要编写大量冗余的jdbc的连接与释放以及相同处理逻辑的代码。
总结一下有一下几点:
1、 数据库连接创建、释放频繁造成系统资源浪费,从?影响系统性能。
2、 Sql语句在代码中硬编码,造成代码不易维护,实际应?中sql变化的可能较?,sql变动需要改变java代码。
3、 使?preparedStatement向占有位符号传参数存在硬编码,因为sql语句的where条件不?定,可能多也可能少,修改sql还要修改代码,系统不易维护。
4、 对结果集解析存在硬编码(查询列名),sql变化导致解析代码变化,系统不易维护,如果能将数据 库记录封装成pojo对象解析比较方便。

2.解决思路

针对上述问题提出一下的解决思路
①使?数据库连接池初始化连接资源
②将sql语句抽取到xml配置?件中
③使?反射、内省等底层技术,?动将实体与表进?属性与字段的?动映射

3.自定义框架设计

通过对思路的抽象,做出以下的设计:

3.1 使用端:

提供核心配置文件:
sqlMapConfig.xml : 存放数据源信息,引?mapper.xml
Mapper.xml : sql语句的配置?件信息

3.2 框架端:

1.读取配置文件

读取完成以后以流的形式存在,我们不能将读取到的配置信息以流的形式存放在内存中,不好操作,可以创建javaBean来存储
(1)Configuration : 存放数据库基本信息、Map<唯?标识,Mapper> 唯?标识:namespace .id
(2)MappedStatement:sql语句、statement类型、输?参数java类型、输出参数java类型

2.解析配置文件

创建sqlSessionFactoryBuilder类:
方法:sqlSessionFactory build():
第?:使?dom4j解析配置?件,将解析出来的内容封装到Configuration和MappedStatement中
第?:创建SqlSessionFactory的实现类DefaultSqlSession

3.创建SqlSessionFactory

方法:openSession() : 获取sqlSession接?的实现类实例对象

4. 创建sqlSession接口及实现类

方法:
selectList(String statementId,Object param):查询所有
selectOne(String statementId,Object param):查询单个
具体实现:封装JDBC完成对数据库表的查询操作

自定义框架实现

首先我们先模拟出我们实现后的效果,再针对效果进行具体的实现。
使用自定义框架时我们需要配置数据源信息,在sqlMapConfig.xml中,如下:

<configuration>
    <!-- 数据库配置信息 -->
    <dataSource>
        <property name = "driverClass" value="com.mysql.cj.jdbc.Driver"></property>
        <property name = "jdbcUrl" value="jdbc:mysql://localhost:3306/test?serverTimezone=UTC"></property>
        <property name = "username" value="root"></property>
        <property name = "password" value="root"></property>
    </dataSource>

    <mapper resource="UserMapper.xml"></mapper>
</configuration>

实体对象与数据源的sql映射关系存放于mapper.xml文件中,上面代码中的标签定义的就是对sql映射关系的声明。

<mapper namespace="com.hunter.dao.IUserDao">

    <!--sql的唯一标识: namespace.id组成: statementId-->
    <select id="findAll" resultType="com.hunter.pojo.User">
        select * from user
    </select>

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

</mapper>

实体对象

public class User {
    private Integer id;
    //?户名
    private String username;

    public Integer getId() {
        return id;
    }

    @Override
    public String toString() {
        return "User{" +
                "id=" + id +
                ", username='" + username + '\'' +
                '}';
    }

    public void setId(Integer id) {
        this.id = id;
    }

    public String getUsername() {
        return username;
    }

    public void setUsername(String username) {
        this.username = username;
    }

调用测试方法:

public void test() throws Exception {
        InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(resourceAsStream);
        SqlSession sqlSession = factory.openSession();
        IUserDao userDao = sqlSession.getMapper(IUserDao.class);
        List<User> all = userDao.findAll();
        for (User user : all) {
            System.out.println(user);
        }
    }

我们传统的使用mybatis的流程如上所示,接下来我们根据分析的流程来着手对mybatis自定义框架的开发。
在mybatis的使用过程中mapper.xml是最核心的一个配置,定义了namespace和自定义的SQL语句,我们可以将其封装成MapperStatement,MapperStatement是以sql为维度的封装方式,其中类中会存储namespace的标识id,id主要由(namespace+自定义sql的id构成)。结构如下:

package com.hunter.pojo;

/**
 * @Classname MapperStatement
 * @Description 用于封装mapper.xml解析出来的内容
 * @Date 2021/7/22 21:58
 * @Created by Hunter
 */
public class MapperStatement {
    //id标识
    private String id;
    //返回类型
    private String returnType;
    //参数类型
    private String paramterType;
    //sql语句
    private String sql;

    public String getId() {
        return id;
    }

    public void setId(String id) {
        this.id = id;
    }

    public String getReturnType() {
        return returnType;
    }

    public void setReturnType(String returnType) {
        this.returnType = returnType;
    }

    public String getParamterType() {
        return paramterType;
    }

    public void setParamterType(String paramterType) {
        this.paramterType = paramterType;
    }

    public String getSql() {
        return sql;
    }

    public void setSql(String sql) {
        this.sql = sql;
    }
}

定义好了我们所需要的加载后的数据结构,接下来,我们需要通过读取数据库和mybatis的配置。该配置主要通过xml形式声明,所以我们需要一个初始化一个xml解析器,并把转换后的文件信息转化为配置实例。注意:这里的configuration我们使用的是建造者模式,对configuration有多道程序的赋值配置。
配置的结构如下:

package com.hunter.pojo;

import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;

/**
 * @Classname Configuration
 * @Description TODO
 * @Date 2021/7/22 22:15
 * @Created by haojie.liao
 */
public class Configuration {

    private DataSource dataSource;

    /**
     * statementId  value:封装好的mapperStatement对象
     */
    Map<String,MapperStatement> mapperStatementMap = new HashMap<>();;

    public DataSource getDataSource() {
        return dataSource;
    }

    public void setDataSource(DataSource dataSource) {
        this.dataSource = dataSource;
    }

    public Map<String, MapperStatement> getMapperStatementMap() {
        return mapperStatementMap;
    }

    public void setMapperStatementMap(Map<String, MapperStatement> mapperStatementMap) {
        this.mapperStatementMap = mapperStatementMap;
    }
}

这里的配置来主要包括数据库连接配置与加载mapper.xml的MapperStatement映射。
这里的configuration封装了我们的数据库信息与mybatis的mapper映射信息,我们在获取sqlsession时可以从configuration获取相关的连接以及mapper语句。而我们使用工厂类来封装sqlsession,每个获取数据库连接请求时都从工厂类获取session。具体的实现如下:


/**
 * @Classname SqlSessionFactoryBuilder
 * @Description TODO
 * @Date 2021/7/22 22:28
 * @Created by haojie.liao
 */
public class SqlSessionFactoryBuilder {
    public SqlSessionFactory build(InputStream in) throws DocumentException, PropertyVetoException {
        //1.使用dom4j解析配置文件,将解析出来的内容封装到Configuration中
        XMLConfigBuilder xmlConfigBuilder = new XMLConfigBuilder();
        Configuration configuration = xmlConfigBuilder.parseConfig(in);

        //2.创建sqlSessionFactory对象
        DefaultSqlSessionFactory defaultSqlSessionFactory = new DefaultSqlSessionFactory(configuration);
        return defaultSqlSessionFactory;

    }
}

XMLConfigBuilder的主要功能是对xml进行解析与configuration的构造等。

package com.hunter.config;

import com.hunter.io.Resources;
import com.hunter.pojo.Configuration;
import com.mchange.v2.c3p0.ComboPooledDataSource;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.beans.PropertyVetoException;
import java.io.InputStream;
import java.util.List;
import java.util.Properties;

/**
 * @Classname XMLConfigBuilder
 * @Description TODO
 * @Date 2021/7/22 22:30
 * @Created by haojie.liao
 */
public class XMLConfigBuilder {

    private Configuration configuration;

    public XMLConfigBuilder(){
        this.configuration = new Configuration();
    }

    /**
     * 使用dom4j对配置文件进行解析,封装Configuration的方法
     * @param inputStream
     * @return
     */
    public Configuration parseConfig(InputStream inputStream) throws DocumentException, PropertyVetoException {
        Document document = new SAXReader().read(inputStream);
        //<configuration>
        Element rootElement = document.getRootElement();
        List<Element> list = rootElement.selectNodes("//property");

        Properties properties = new Properties();;
        for (Element element : list) {
            String name = element.attributeValue("name");
            String value = element.attributeValue("value");
            properties.setProperty(name,value);
        }

        //设置c3p0连接
        ComboPooledDataSource comboPooledDataSource = new ComboPooledDataSource();
        comboPooledDataSource.setDriverClass(properties.getProperty("driverClass"));
        comboPooledDataSource.setJdbcUrl(properties.getProperty("jdbcUrl"));
        comboPooledDataSource.setUser(properties.getProperty("username"));
        comboPooledDataSource.setPassword(properties.getProperty("password"));



        configuration.setDataSource(comboPooledDataSource);

        //解析mapper.xml : 拿到路径——字节输入流——dom4j进行解析
        List<Element> mapperList = rootElement.selectNodes("//mapper");
        for (Element element : mapperList) {
            String mapperPath = element.attributeValue("resource");
            InputStream resourceAsStream = Resources.getResourceAsStream(mapperPath);
            XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(configuration);
            xmlMapperBuilder.parse(resourceAsStream);
        }

        return configuration;
    }
}

在前段我们主要做的是对sqlMapConfig.xml的解析(数据库连接信息以及mapper的声明信息)。
xmlMapperBuilder.parse(resourceAsStream)是对mapper.xml信息的解析并将解析后的sql封装到configuration中。

package com.hunter.config;

import com.hunter.pojo.Configuration;
import com.hunter.pojo.MapperStatement;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;

import java.io.InputStream;
import java.util.List;

/**
 * @Classname XMLMapperBuilder
 * @Description 解析mapper.xml文件
 * @Date 2021/7/22 22:46
 * @Created by haojie.liao
 */
public class XMLMapperBuilder {
    private Configuration configuration;

    public XMLMapperBuilder(Configuration configuration) {
        this.configuration = configuration;
    }

    /**
     * 解析mapper.xml文件
     * @param inputStream
     */
    public void parse(InputStream inputStream) throws DocumentException {
        Document document = new SAXReader().read(inputStream);
        Element rootElement = document.getRootElement();
        String namespace = rootElement.attributeValue("namespace");
        List<Element> selectList = rootElement.selectNodes("//select");
        if (selectList != null && selectList.size() > 0){
            for (Element element : selectList) {
                String id = element.attributeValue("id");
                String resultType = element.attributeValue("resultType");
                String paramterType = element.attributeValue("paramterType");
                String textTrim = element.getTextTrim();
                MapperStatement mapperStatement = new MapperStatement();
                mapperStatement.setId(id);
                mapperStatement.setReturnType(resultType);
                mapperStatement.setParamterType(paramterType);
                mapperStatement.setSql(textTrim);
                String key = namespace + "." + id;
                configuration.getMapperStatementMap().put(key,mapperStatement);

            }
        }
    }
}

这里需要注意的是,我们在对mapperStatement封装映射的时候,通过namespace+mapper标签语句的id构成唯一id。configuration中的mapperStatementMap存储的sql语句是未对其中标签进行解析的原始语句。如select id from user where id =#{id}, 并未对#{id}进行解析,当然这里的#{id}也只是个标记,具体的需要在真正执行时做替换填充。

我们对configuration进行配置赋值后,我们需要对session工厂获取连接上做处理。

/**
 * @Classname DefaultSqlSessionFactory
 * @Description TODO
 * @Date 2021/7/22 23:17
 * @Created by haojie.liao
 */
public class DefaultSqlSessionFactory implements SqlSessionFactory{

    private Configuration configuration;

    public DefaultSqlSessionFactory(Configuration configuration) {
        this.configuration = configuration;
    }

    @Override
    public SqlSession openSession() {
        return new DefaultSqlSession(configuration);
    }
}

sqlsession主要的参数其实就是configuration,这里主要sqlSession主要以open以及crud操作为主,暂略去close功能。

想象一下,我们使用sqlsession有什么功能,我们可以执行selectList或者selectOne操作等等。我们这里就用这两个操作为例。当然,我们得考虑为我们的Dao接口生成一个代理类,这样我们直接调用dao层就可以实现我们想要的功能了。

package com.hunter.sqlSession;

import java.beans.IntrospectionException;
import java.lang.reflect.InvocationTargetException;
import java.sql.SQLException;
import java.util.List;

/**
 * @Classname SqlSession
 * @Description TODO
 * @Date 2021/7/22 23:18
 * @Created by haojie.liao
 */
public interface SqlSession {

    //查询所有
    <E> List<E> selectList(String statementId, Object... params) throws SQLException, IllegalAccessException, IntrospectionException, InstantiationException, NoSuchFieldException, InvocationTargetException, ClassNotFoundException;

    //根据条件查询单个
    public <T> T selectOne(String statementId, Object... params) throws SQLException, IllegalAccessException, IntrospectionException, InstantiationException, ClassNotFoundException, InvocationTargetException, NoSuchFieldException;

    //为Dao接口生成代理实现类
    public <T> T getMapper(Class<?> mapperClass);

}

我们想象一下,如果我们通过Dao层直接获取接口数据要怎么做?这很常见,我们只需要告诉mybatis我们需要对哪个Dao做操作即可,如下

IUserDao userDao = sqlSession.getMapper(IUserDao.class);
        List<User> all = userDao.findAll();
        for (User user : all) {
            System.out.println(user);
        }

很明显我们需要对getMapper这个方法进行功能上的增强,换种专业术语就是做代理,那怎么做代理,代理什么功能。这里我们主要用jdk动态代理实现,因为我们的Dao层一般是接口化的,代理的功能我们挑选最简单的select功能。

那么下一个问题来了,我怎么通过接口类获取到Dao下面的方法以及sql呢?
使用过mybatis的人都知道,我们的dao层其实是与mapper.xml中的标签id映射的,而namespace也是我们dao层的全限定类名。
因此我们可以从configuration中获取到namespace,通过需要执行的操作获取到操作id,我们的唯一key是namespace+id从而可以确定到唯一的MapperStatement,里面封装了mapper操作标签的信息。
实现如下:

@Override
    public <T> T getMapper(Class<?> mapperClass) {
        //使用JDK动态代理为Dao生成动态代理类
        Object instance = Proxy.newProxyInstance(DefaultSqlSession.class.getClassLoader(), new Class[]{mapperClass}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                /**
                 * 准备参数: 1.statmentid:sql语句的唯一标识: namespace.id=接口全限定名.方法名
                 *
                 */
                String methodName = method.getName();
                String className = method.getDeclaringClass().getName();
                String statementId = className+ "." + methodName;

                //获取被调动方法的返回值类型
                Type returnType = method.getGenericReturnType();
                if (returnType instanceof ParameterizedType){
                    return selectList(statementId,args);
                }

                return selectOne(statementId,args);
            }
        });
        return (T) instance;
    }

接下来我们要如何实现selectList和selectOne,其实就是我们的告诉configuration我们需要执行的statementId还有我需要查询的参数信息。而我们的selectList方法会通过Executor去执行具体的sql操作。

我们来看一下Executor的接口信息(暂时只演示query方法)

package com.hunter.sqlSession;

import com.hunter.pojo.Configuration;
import com.hunter.pojo.MapperStatement;

import java.beans.IntrospectionException;
import java.lang.reflect.InvocationTargetException;
import java.sql.SQLException;
import java.util.List;

/**
 * @Classname Executor
 * @Description TODO
 * @Date 2021/7/22 23:26
 * @Created by haojie.liao
 */
public interface Executor {
    <E> List<E> query(Configuration configuration, MapperStatement mapperStatement, Object... params) throws SQLException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException, IntrospectionException, InstantiationException, InvocationTargetException;
}

虽然这个接口看似简单,但是我们总结一下执行一个query方法需要做什么
1.首先需要获取数据库连接
2.由于我们mapperStatement中的sql是未解析的所以需要最占位符等做转换。
3.对转化为占位符的sql做数据的填充
4.执行具体的sql
5.对结果集进行封装

这里的标签解析并转换为占位符其实使用mybatis原有的工具方法。

package com.hunter.utils;

/**
 * @author Clinton Begin
 */
public class GenericTokenParser {

  //开始标记
  private final String openToken;
  //结束标记
  private final String closeToken;
  //标记处理器
  private final TokenHandler handler;

  public GenericTokenParser(String openToken, String closeToken, TokenHandler handler) {
    this.openToken = openToken;
    this.closeToken = closeToken;
    this.handler = handler;
  }

  /**
   * 解析${}和#{}
   * @param text
   * @return
   * 该方法主要实现了配置文件、脚本等片段中占位符的解析、处理工作,并返回最终需要的数据。
   * 其中,解析工作由该方法完成,处理工作是由处理器handler的handleToken()方法来实现
   */
  public String parse(String text) {
    // 验证参数问题,如果是null,就返回空字符串。
    if (text == null || text.isEmpty()) {
      return "";
    }

    // 下面继续验证是否包含开始标签,如果不包含,默认不是占位符,直接原样返回即可,否则继续执行。
    int start = text.indexOf(openToken, 0);
    if (start == -1) {
      return text;
    }

   // 把text转成字符数组src,并且定义默认偏移量offset=0、存储最终需要返回字符串的变量builder,
    // text变量中占位符对应的变量名expression。判断start是否大于-1(即text中是否存在openToken),如果存在就执行下面代码
    char[] src = text.toCharArray();
    int offset = 0;
    final StringBuilder builder = new StringBuilder();
    StringBuilder expression = null;
    while (start > -1) {
     // 判断如果开始标记前如果有转义字符,就不作为openToken进行处理,否则继续处理
      if (start > 0 && src[start - 1] == '\\') {
        builder.append(src, offset, start - offset - 1).append(openToken);
        offset = start + openToken.length();
      } else {
        //重置expression变量,避免空指针或者老数据干扰。
        if (expression == null) {
          expression = new StringBuilder();
        } else {
          expression.setLength(0);
        }
        builder.append(src, offset, start - offset);
        offset = start + openToken.length();
        int end = text.indexOf(closeToken, offset);
        while (end > -1) {存在结束标记时
          if (end > offset && src[end - 1] == '\\') {//如果结束标记前面有转义字符时
            // this close token is escaped. remove the backslash and continue.
            expression.append(src, offset, end - offset - 1).append(closeToken);
            offset = end + closeToken.length();
            end = text.indexOf(closeToken, offset);
          } else {//不存在转义字符,即需要作为参数进行处理
            expression.append(src, offset, end - offset);
            offset = end + closeToken.length();
            break;
          }
        }
        if (end == -1) {
          // close token was not found.
          builder.append(src, start, src.length - start);
          offset = src.length;
        } else {
          //首先根据参数的key(即expression)进行参数处理,返回?作为占位符
          builder.append(handler.handleToken(expression.toString()));
          offset = end + closeToken.length();
        }
      }
      start = text.indexOf(openToken, offset);
    }
    if (offset < src.length) {
      builder.append(src, offset, src.length - offset);
    }
    return builder.toString();
  }
}

这里的ParameterMappingTokenHandler其实就是封装这占位符

package com.hunter.utils;

import java.util.ArrayList;
import java.util.List;




public class ParameterMappingTokenHandler implements TokenHandler {
	private List<ParameterMapping> parameterMappings = new ArrayList<ParameterMapping>();

	// context是参数名称 #{id} #{username}

	@Override
	public String handleToken(String content) {
		parameterMappings.add(buildParameterMapping(content));
		return "?";
	}

	private ParameterMapping buildParameterMapping(String content) {
		ParameterMapping parameterMapping = new ParameterMapping(content);
		return parameterMapping;
	}

	public List<ParameterMapping> getParameterMappings() {
		return parameterMappings;
	}

	public void setParameterMappings(List<ParameterMapping> parameterMappings) {
		this.parameterMappings = parameterMappings;
	}

}

package com.hunter.utils;

public class ParameterMapping {

    private String content;

    public ParameterMapping(String content) {
        this.content = content;
    }

    public String getContent() {
        return content;
    }

    public void setContent(String content) {
        this.content = content;
    }
}

接下来就很简单了,就是通过反射对查询的实体数据进行注入,执行sql语句和封装


```java
package com.hunter.sqlSession;

import com.hunter.config.BoundSql;
import com.hunter.pojo.Configuration;
import com.hunter.pojo.MapperStatement;
import com.hunter.utils.GenericTokenParser;
import com.hunter.utils.ParameterMapping;
import com.hunter.utils.ParameterMappingTokenHandler;
import com.hunter.utils.TokenHandler;
import javafx.geometry.Bounds;

import java.beans.IntrospectionException;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.sql.*;
import java.util.ArrayList;
import java.util.List;

/**
 * @Classname SimpleExecutor
 * @Description TODO
 * @Date 2021/7/22 23:28
 * @Created by haojie.liao
 */
public class SimpleExecutor implements Executor {
    @Override
    public <E> List<E> query(Configuration configuration, MapperStatement mapperStatement, Object... params) throws SQLException, ClassNotFoundException, NoSuchFieldException, IllegalAccessException, IntrospectionException, InstantiationException, InvocationTargetException {
        //1.注册驱动,获取连接
        Connection connection = configuration.getDataSource().getConnection();

        //2.获取sql语句并转换
        String sql = mapperStatement.getSql();
        BoundSql boundSql = getBoundSql(sql);

        //3.获取预处理对象:preparedStatement
        PreparedStatement preparedStatement = connection.prepareStatement(boundSql.getSqlText());

        //4.设置参数

        //4.1获取参数全路径
        String paramterType = mapperStatement.getParamterType();
        Class<?> parameterTypeClass = getClassType(paramterType);
        /*if (parameterTypeClass == null){
            return null;
        }*/
        //#{id}  #{name}  ParameterMapping中存放的是#{}中的值,这个值是与实体对应的,因此通过反射实体取到属性为id、name的值,然后赋值到ParameterMapping中
        //ps:注意,这里的params默认只有一个,如果有多个可以做扩展
        List<ParameterMapping> parameterMappingList = boundSql.getParameterMappingList();
        for (int i = 0; i < parameterMappingList.size(); i++) {
            ParameterMapping parameterMapping = parameterMappingList.get(i);
            String content = parameterMapping.getContent();

            //反射
            Field declaredField = parameterTypeClass.getDeclaredField(content);
            //暴力访问
            declaredField.setAccessible(true);
            Object o = declaredField.get(params[0]);

            preparedStatement.setObject(i+1,o);
        }

        //5.执行sql
        ResultSet resultSet = preparedStatement.executeQuery();
        String returnType = mapperStatement.getReturnType();
        Class<?> resultTypeClass = getClassType(returnType);


        ArrayList<Object> objects = new ArrayList<>();;

        //6.封装返回结果集
        while (resultSet.next()){
            ResultSetMetaData metaData = resultSet.getMetaData();
            Object instance = resultTypeClass.newInstance();
            for (int i = 1; i <= metaData.getColumnCount(); i++) {
                //字段名
                String columnName = metaData.getColumnName(i);
                //字段的值
                Object value = resultSet.getObject(columnName);

                //使用反射或内省,根据数据库表和实体的对应关系,完成封装
                PropertyDescriptor propertyDescriptor = null;
                try {
                    propertyDescriptor = new PropertyDescriptor(columnName, resultTypeClass);
                }catch (Exception e){
                    continue;
                }
                if (propertyDescriptor != null) {
                    Method writeMethod = propertyDescriptor.getWriteMethod();
                    writeMethod.invoke(instance, value);
                }
            }
            objects.add(instance);
        }

        return (List<E>) objects;
    }

    private Class<?> getClassType(String paramterType) throws ClassNotFoundException {
        if (paramterType != null){
            Class<?> aClass = Class.forName(paramterType);
            return aClass;
        }
        return null;
    }

    /**
     * 完成对#{}的解析工作: 1.将#{}使用?进行代替,
     * @param sql
     * @return
     */
    private BoundSql getBoundSql(String sql){
        //标记处理类:配置标记解析器来完成对占位符的解析处理工作
        ParameterMappingTokenHandler parameterMappingTokenHandler = new ParameterMappingTokenHandler();
        GenericTokenParser genericTokenParser = new GenericTokenParser("#{","}",parameterMappingTokenHandler);
        String parseSql = genericTokenParser.parse(sql);
        List<ParameterMapping> parameterMappings = parameterMappingTokenHandler.getParameterMappings();
        BoundSql boundSql = new BoundSql(parseSql,parameterMappings);
        return boundSql;
    }
}

大概的mybatis执行流程如下,我们做个试验测一下

@Test
    public void test() throws Exception {
        InputStream resourceAsStream = Resources.getResourceAsStream("sqlMapConfig.xml");
        SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(resourceAsStream);
        SqlSession sqlSession = factory.openSession();
        IUserDao userDao = sqlSession.getMapper(IUserDao.class);
        List<User> all = userDao.findAll();
        for (User user : all) {
            System.out.println(user);
        }
        System.out.println();
    }

在这里插入图片描述
查询成功,mybatis的原理其实不难,底层的实现还是通过jdbc对数据库进行操作,需要注意主要是对相关配置文件的解析与configuration的封装,并能对预处理的sql进行转换等。

  Java知识库 最新文章
计算距离春节还有多长时间
系统开发系列 之WebService(spring框架+ma
springBoot+Cache(自定义有效时间配置)
SpringBoot整合mybatis实现增删改查、分页查
spring教程
SpringBoot+Vue实现美食交流网站的设计与实
虚拟机内存结构以及虚拟机中销毁和新建对象
SpringMVC---原理
小李同学: Java如何按多个字段分组
打印票据--java
上一篇文章      下一篇文章      查看所有文章
加:2021-08-30 11:52:17  更:2021-08-30 11:52:29 
 
开发: C++知识库 Java知识库 JavaScript Python PHP知识库 人工智能 区块链 大数据 移动开发 嵌入式 开发工具 数据结构与算法 开发测试 游戏开发 网络协议 系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程
数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁

360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 -2024/11/23 13:09:24-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码