如何手写一个简易版MyBatis框架

1,575 阅读7分钟

如何手写一个简易版MyBatis框架

1.MyBatis简介

  • MyBatis 是支持定制化 SQL、存储过程以及高级映射的优秀的持久层框架

  • 也属于ORM框架: Object Relational Mapping

  • 用于实现面向对象编程语言里不同类型系统的数据之前的转换

  • MyBatis 避免了几乎所有的 JDBC 代码和手动设置参数以及获取结果集

  • MyBatis 可以对配置和原生Map使用简单的 XML 或注解,将接口和 Java 的 POJOs映射成数据库中的记录

2.手写框架我们要做哪些工作

拆解: MyBatis -> MySql

MyBatis底层封装的就是获取和操作数据库的过程

mp2.png

mp1.png 感兴趣的同学可以自己去阅读一下源码,MyBatis核心就是下面三个问题

一、MyBatis是如何获取数据库源

二、Mybatis是如何执行sql语句

三、MyBatis是如何执行数据库

下面用图解方式来看一下MyBatis的原理实现 MyBatis核心.png

介绍一下相关核心文件

mybatis-config.xml

XML 配置文件(configuration XML)中包含了对 MyBatis 系统的核心设置,包含获取数据库连接实例的数据源(DataSource)和决定事务作用域和控制方式的事务管理器(TransactionManager)

SqlSessionFactoryBuilder.java

SqlSessionFactoryBuilder通过类名就可以看出这个类的主要作用就是创建一个SqlSessionFactory,通过输入mybatis配置文件的字节流或者字符流,生成XMLConfigBuilderXMLConfigBuilder创建一个ConfigurationConfiguration这个类中包含了mybatis的配置的一切信息,mybatis进行的所有操作都需要根据Configuration中的信息来进行。

作用域(Scope)和生命周期

可以重用 SqlSessionFactoryBuilder 来创建多个 SqlSessionFactory 实例,但是最好还是不要让其一直存在以保证所有的 XML 解析资源开放给更重要的事情

这个类可以被实例化、使用和丢弃,一旦创建了 SqlSessionFactory,就不再需要它了。因此 SqlSessionFactoryBuilder 实例的最佳作用域是方法作用域(也就是局部方法变量)

SqlSessionFactory接口

sql会话工厂,用于创建SqlSession

作用域(Scope)和生命周期

SqlSessionFactory 一旦被创建就应该在应用的运行期间一直存在,没有任何理由对它进行清除或重建

最佳作用域是应用作用域。有很多方法可以做到,最简单的就是使用单例模式或者静态单例模式。

两种构建方式

使用xml

String resource = "org/mybatis/example/mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);

java代码构建

DataSource dataSource = BlogDataSourceFactory.getBlogDataSource();
TransactionFactory transactionFactory = new JdbcTransactionFactory();
Environment environment = new Environment("development", transactionFactory, dataSource);
Configuration configuration = new Configuration(environment);
configuration.addMapper(BlogMapper.class);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);

SqlSession接口

SqlSession是MyBatis的一个重要接口,定义了数据库的增删改查以及事务管理的常用方法。

SqlSession还提供了查找Mapper接口的有关方法

作用域(Scope)和生命周期

  • 每个线程都应该有它自己的 SqlSession 实例
  • SqlSession 的实例不是线程安全的,因此是不能被共享的,所以它的最佳的作用域是请求或方法作用域
  • 每次收到的 HTTP 请求,就可以打开一个 SqlSession,返回一个响应,就关闭它。 Mapper接口

承载了实际的业务逻辑,其生命周期比较短,由SqlSession创建,用于将Java对象和实际的SQL语句对应起来。

Mapper接口是指程序员自行定义的一个数据操纵接口,类似于通常所说的DAO接口。跟DAO不同的地方在于Mapper接口只需要程序员定义,不需要程序员去实现,MyBatis会自动为Mapper接口创建动态代理对象。Mapper接口的方法通常与Mapper配置文件中的select、insert、update、delete等XML结点存在一一对应关系。

3.开始实现

我们分5步实现:

1.读取mybatis-config.xml配置文件

2.构建SqlSessionFactory

3.打开sqlsession

4.获取Mapper接口对象

5.调用Mapper接口对象的方法操作数据库 文件结构图如下

mtbatis类结构.png

3.1 准备工作

新建maven项目,导入依赖:

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.example</groupId>
    <artifactId>mybatis-write</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>8</maven.compiler.source>
        <maven.compiler.target>8</maven.compiler.target>
    </properties>

    <dependencies>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.26</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>RELEASE</version>
            <scope>compile</scope>
        </dependency>
    </dependencies>

    <build>
        <resources>
            <resource>
                <directory>src/main/java</directory>
                <includes>
                    <include>**/*.xml</include>
                </includes>
            </resource>
            <resource>
                <directory>src/main/resources</directory>
                <includes>
                    <include>**/*.*</include>
                </includes>
            </resource>
        </resources>
    </build>

</project>

主实现方法测试类:

package com.qinghong;

import com.qinghong.entity.UUserInfo;
import com.qinghong.mapper.UUserInfoMapper;
import com.qinghong.mybatis.session.MySqlSession;
import com.qinghong.mybatis.session.MySqlSessionFactory;
import com.qinghong.mybatis.session.MySqlSessionFactoryBuilder;

import java.io.InputStream;

public class Test {
    public static void main(String[] args) {
        // 读取mybatis-config.xml配置文件
        InputStream inputStream = Test.class.getClassLoader().getResourceAsStream("mybatis-config.xml");

        // 构建SqlSessionFactory
        MySqlSessionFactory mySqlSessionFactory = new MySqlSessionFactoryBuilder().build(inputStream);

        // 打开sqlsession
        MySqlSession mySqlSession = mySqlSessionFactory.openSession();

        // 获取Mapper接口对象
        UUserInfoMapper uUserInfoMapper = mySqlSession.getMappwe(UUserInfoMapper.class);

        // 操作数据库
        UUserInfo uUserInfo = uUserInfoMapper.selectByPrimaryKey(1);

        System.out.println(uUserInfo.getId());
    }
}

mybatis-config.xml

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>

    <environments default="development">
        <environment id="development">
            <transactionManager type="JDBC" />
            <dataSource type="POOLED">
                <property name="driver" value="com.cj.mysql.jdbc.Driver" />
                <property name="url" value="jdbc:mysql://localhost:3306/ssm?serverTimezone=UTC" />
                <property name="username" value="root" />
                <property name="password" value="123456" />
            </dataSource>
        </environment>
    </environments>
    <mappers>
        <mapper resource="com/qinghong/mapper/UUserInfoMapper.xml" />
    </mappers>
</configuration>

UUserInfoMapper.java

package com.qinghong.mapper;

import com.qinghong.entity.UUserInfo;

public interface UUserInfoMapper {

    UUserInfo selectByPrimaryKey(Integer id);
}

UUserInfoMapper.xml

<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
        "http://mybatis.org/dtd/mybatis-3-config.dtd">
<mapper namespace="com.qinghong.mapper.UUserInfoMapper">

    <select id="selectByPrimaryKey" parameterType="java.lang.Integer" resultType="com.qinghong.entity.UUserInfo">
        select * from account where id = #{id}
    </select>

</mapper>

UUserInfo.java

package com.qinghong.entity;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import lombok.ToString;

@Data
@AllArgsConstructor
@ToString
@NoArgsConstructor
public class UUserInfo {
    Integer id;
    String name;
    Integer money;
}

3.2 构建Session工厂和初始化对象信息

依据MyBatis官方源码,我们也需要创建类似SqlSessionFactoryBuilderSqlSessionFactorySqlSession的类;

SqlSessionFactory是生成SqlSession的工厂类,SqlSessionFactory又依赖Configuration

MySqlSessionFactoryBuilder.java

public class MySqlSessionFactoryBuilder {

    public MySqlSessionFactory build(InputStream inputStream) {
        MyConfiguration myConfiguration = new XMLConfigBuilder(inputStream).parse();
        return new MySqlSessionFactory(myConfiguration);
    }
}

MySqlSessionFactoryBuilder.java

package com.qinghong.mybatis.session;

import com.qinghong.mybatis.executor.MyExecutor;
import com.qinghong.mybatis.mapping.MyConfiguration;

public class MySqlSessionFactory {

    private MyConfiguration myConfiguration;

    public MySqlSessionFactory() {
    }

    public MySqlSessionFactory(MyConfiguration myConfiguration) {
        this.myConfiguration = myConfiguration;
    }

}

根据mybatis-config.xml配置文件的结构我们又需要在里面引入MyEnvironment类型成员变量MyEnvironment,用来存放数据库相关信息,同时引入MyMapperStatementMap集合变量,对应的一个Mapper.xml里的方法可能对应多个

MyConfiguration.java

package com.qinghong.mybatis.mapping;

import lombok.Data;

import java.util.Map;

@Data
public class MyConfiguration {

    // mybatis-config.xml
    private MyEnvironment myEnvironment;

    // xxMapper.xml
    private Map<String , MyMapperStatement> mapperStatement;
}

MyEnvironment.java

package com.qinghong.mybatis.mapping;

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@AllArgsConstructor
@NoArgsConstructor
public class MyEnvironment {

    private String driver;

    private String url;

    private String username;

    private String password;

}

MyMapperStatement.java

package com.qinghong.mybatis.mapping;

import lombok.Data;

@Data
public class MyMapperStatement {

    private String namespace;

    private String id;

    private String parameterType;

    private String resultType;

    private String sql;
}

引入解析xml文件的两个类:

XMLConfigBuilder.java

package com.qinghong.mybatis.parsing;

import com.qinghong.mybatis.mapping.MyConfiguration;
import com.qinghong.mybatis.mapping.MyEnvironment;
import com.qinghong.mybatis.mapping.MyMapperStatement;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;

import java.io.InputStream;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.ConcurrentHashMap;

public class XMLConfigBuilder {

    private XPathParser parser;

    public XMLConfigBuilder(InputStream inputStream){
        this.parser = new XPathParser(inputStream);
    }

    // 配置文件解析
    public MyConfiguration parse(){
        Node dataSourceNode = parser.xNode("/configuration/environments/environment");

        // 数据源属性配置信息
        Properties properties = new Properties();
        NodeList childNodes = dataSourceNode.getChildNodes();
        for (int i = 0; i < childNodes.getLength(); i++) {
            Node item = childNodes.item(i);
            if (item.getNodeType() == Node.ELEMENT_NODE){
                properties.setProperty(item.getAttributes().getNamedItem("name").getNodeValue(),item.getAttributes().getNamedItem("value").getNodeValue());
            }
        }

        // Mapper映射文件配置信息
        Map<String , MyMapperStatement> mapperStatementMap = new ConcurrentHashMap<>();
        Node mappersNode = parser.xNode("/configuration/mappers");
        NodeList mapperNodeList = mappersNode.getChildNodes();
        for (int i = 0; i < mapperNodeList.getLength(); i++) {
            Node mapperNode = mapperNodeList.item(i);
            if (mapperNode.getNodeType() == Node.ELEMENT_NODE){
                // mapper.xml配置
                String resource = mapperNode.getAttributes().getNamedItem("resource").getNodeValue();
                // 解析mapper.xml文件
                InputStream inputStream = this.getClass().getClassLoader().getResourceAsStream(resource);

                this.parser = new XPathParser(inputStream);

                Element element = parser.getDocument().getDocumentElement();
                String namespace = element.getAttribute("namespace");

                NodeList sqlNodeList = element.getChildNodes();

                for (int j = 0; j < sqlNodeList.getLength(); j++) {
                    Node sqlNode = sqlNodeList.item(j);
                    if (sqlNode.getNodeType() == Node.ELEMENT_NODE){
                        String id = "";
                        String resultType = "";
                        String parameterType = "";

                        Node idNode = sqlNode.getAttributes().getNamedItem("id");
                        if (null == idNode){
                            throw new RuntimeException("sql is null");
                        }else {
                            id = sqlNode.getAttributes().getNamedItem("id").getNodeValue();
                        }
                        Node resultTypeNode = sqlNode.getAttributes().getNamedItem("resultType");
                        if (null != resultTypeNode){
                            resultType = sqlNode.getAttributes().getNamedItem("resultType").getNodeValue();
                        }
                        Node parameterTypeNode = sqlNode.getAttributes().getNamedItem("parameterType");
                        if (null != parameterTypeNode){
                            parameterType = sqlNode.getAttributes().getNamedItem("parameterType").getNodeValue();
                        }

                        String sql = sqlNode.getTextContent();

                        MyMapperStatement mapperStatement = new MyMapperStatement();
                        mapperStatement.setId(id);
                        mapperStatement.setNamespace(namespace);
                        mapperStatement.setParameterType(parameterType);
                        mapperStatement.setResultType(resultType);
                        mapperStatement.setSql(sql);
                        mapperStatementMap.put(namespace+"."+id,mapperStatement);
                    }
                }
            }
        }

        // 把解析出来的赋值给MyConfiguration
        MyConfiguration catConfiguration = new MyConfiguration();

        MyEnvironment catEnvironment = new MyEnvironment();
        catEnvironment.setDriver(properties.getProperty("driver"));
        catEnvironment.setPassword(properties.getProperty("password"));
        catEnvironment.setUsername(properties.getProperty("username"));
        catEnvironment.setUrl(properties.getProperty("url"));

        catConfiguration.setMyEnvironment(catEnvironment);
        catConfiguration.setMapperStatement(mapperStatementMap);

        return catConfiguration;

    }
}

XPathParser.java

package com.qinghong.mybatis.parsing;


import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.xml.sax.ErrorHandler;
import org.xml.sax.InputSource;
import org.xml.sax.SAXException;
import org.xml.sax.SAXParseException;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.xpath.XPath;
import javax.xml.xpath.XPathConstants;
import javax.xml.xpath.XPathExpressionException;
import javax.xml.xpath.XPathFactory;
import java.io.InputStream;

@Data
@AllArgsConstructor
public class XPathParser {

    private XPath xPath;

    private final Document document;

    public XPathParser(InputStream inputStream){
        this.xPath = createXpath();
        this.document = createDocument(new InputSource(inputStream));
    }


    private XPath createXpath() {
        XPathFactory factory = XPathFactory.newInstance();
        return factory.newXPath();
    }

    public Document createDocument(InputSource inputSource){
        try {
            DocumentBuilderFactory factory =DocumentBuilderFactory.newInstance();
            factory.setNamespaceAware(false);
            factory.setIgnoringComments(true);
            factory.setIgnoringElementContentWhitespace(false);
            factory.setCoalescing(false);
            factory.setExpandEntityReferences(true);
            DocumentBuilder builder =factory.newDocumentBuilder();
            builder.setErrorHandler(new ErrorHandler() {
                @Override
                public void warning(SAXParseException exception) throws SAXException {
                    throw exception;
                }

                @Override
                public void error(SAXParseException exception) throws SAXException {
                    throw exception;
                }

                @Override
                public void fatalError(SAXParseException exception) throws SAXException {
                    exception.printStackTrace();
                }
            });
            return builder.parse(inputSource);
        } catch (Exception e) {
            throw new RuntimeException("error");
        }
    }

    // 根据表达式解析xml节点
    public Node xNode(String expression){
        Node node = null;
        try {
            xPath.evaluate(expression,document, XPathConstants.NODE);
        } catch (XPathExpressionException e) {
            e.printStackTrace();
        }
        return node;
    }
}

执行代码debug可得:

mp3.png 此时xml文件内容已解析完毕;

3.3 构建Session会话

下面是源码中SqlSession结构

mp4.png configuration可由sqlSessionFactory传入,只需要创建一个执行器类 MySqlSession.java

package com.qinghong.mybatis.session;

import com.qinghong.mapper.UUserInfoMapper;
import com.qinghong.mybatis.executor.MyExecutor;
import com.qinghong.mybatis.mapping.MyConfiguration;
import com.qinghong.mybatis.mapping.MyMapperStatement;
import com.qinghong.mybatis.proxy.MapperProxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.List;

public class MySqlSession {

    private MyConfiguration myConfiguration;

    private MyExecutor myExecutor;

    public MySqlSession(MyConfiguration myConfiguration, MyExecutor myExecutor) {
        this.myConfiguration = myConfiguration;
        this.myExecutor = myExecutor;
    }

    public <T> T getMappwe(Class<T> clazz) {
        MapperProxy mapperProxy = new MapperProxy(this);
        return (T) Proxy.newProxyInstance(clazz.getClassLoader(),new Class<?>[]{clazz}, mapperProxy);
    }

    public <T> T selectOne(String statementKey,Object[] args) {
        MyMapperStatement  mapperStatement = myConfiguration.getMapperStatement().get(statementKey);
        List<T> resultList = myExecutor.query(mapperStatement, args);
        if (resultList != null && resultList.size()>1){
            throw new RuntimeException("more than one");
        }else {
            return resultList.get(0);
        }
    }

    public void selectList(Object[] args) {
    }
}

MySqlSessionFactory.java加入新方法:

package com.qinghong.mybatis.session;

import com.qinghong.mybatis.executor.MyExecutor;
import com.qinghong.mybatis.mapping.MyConfiguration;

public class MySqlSessionFactory {

    private MyConfiguration myConfiguration;

    public MySqlSessionFactory() {
    }

    public MySqlSessionFactory(MyConfiguration myConfiguration) {
        this.myConfiguration = myConfiguration;
    }

    public MySqlSession openSession() {
        MyExecutor executor = new MyExecutor(myConfiguration);
        return new MySqlSession(myConfiguration,executor);
    }
}

3.4 初始化DataSource和Executer

源码运行: mp5.png executor中又包含DataSource MyDataSourceInterface.java

package com.qinghong.pool;

import javax.sql.DataSource;
import java.io.PrintWriter;
import java.sql.Connection;
import java.sql.SQLException;
import java.sql.SQLFeatureNotSupportedException;
import java.util.logging.Logger;

public interface MyDataSourceInterface extends DataSource{
    @Override
    default Connection getConnection() throws SQLException {
        return null;
    }

    @Override
    default Connection getConnection(String username, String password) throws SQLException {
        return null;
    }

    @Override
    default <T> T unwrap(Class<T> iface) throws SQLException {
        return null;
    }

    @Override
    default boolean isWrapperFor(Class<?> iface) throws SQLException {
        return false;
    }

    @Override
    default PrintWriter getLogWriter() throws SQLException {
        return null;
    }

    @Override
    default void setLogWriter(PrintWriter out) throws SQLException {

    }

    @Override
    default void setLoginTimeout(int seconds) throws SQLException {

    }

    @Override
    default int getLoginTimeout() throws SQLException {
        return 0;
    }

    @Override
    default Logger getParentLogger() throws SQLFeatureNotSupportedException {
        return null;
    }
}

方法使用default修饰,实现类可以不用实现所有类,简化代码 MyDataSource.java

package com.qinghong.pool;

import com.qinghong.mybatis.mapping.MyEnvironment;

import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

public class MyDataSource implements MyDataSourceInterface{

    private MyEnvironment myEnvironment;

    private List<Connection> pool;

    private Connection conn = null;

    private static MyDataSource instance = null;

    private static final int POOL_SIZE = 15;

    private MyDataSource(MyEnvironment myEnvironment){
        this.myEnvironment = myEnvironment;
        pool = new ArrayList<Connection>(POOL_SIZE);
        this.createConnection();
    }

    public static MyDataSource getInstance(MyEnvironment myEnvironment){
        if (instance == null){
            instance = new MyDataSource(myEnvironment);
        }
        return instance;
    }

    public synchronized Connection getConnection(){
        if (pool.size() > 0){
            Connection conn = pool.get(0);
            pool.remove(conn);
            return conn;
        }else {
            return null;
        }
    }

    /**
     * 创建原始数据库连接
     */
    public void createConnection(){
        for (int i = 0; i < POOL_SIZE; i++) {
            try {
                Class.forName(myEnvironment.getDriver());
                conn = DriverManager.getConnection(myEnvironment.getUrl(), myEnvironment.getUsername(), myEnvironment.getPassword());
                pool.add(conn);
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }

        }
    }

    /**
     * 用完放回连接池
     * @param conn
     */
    public synchronized void release(Connection conn){
        pool.add(conn);
    }

    /**
     * 关闭链接
     */
    public synchronized void closePool(){
        for (int i = 0; i < pool.size(); i++) {
             conn = pool.get(i);
            try {
                conn.close();
            } catch (SQLException throwables) {
                throwables.printStackTrace();
            }
            pool.remove(i);
        }
    }
}

MyExecutor.java

public class MyExecutor {

    private DataSource dataSource;

    public MyExecutor(MyConfiguration myConfiguration) {
        dataSource = MyDataSource.getInstance(myConfiguration.getMyEnvironment());
    }
}

3.5 动态获取Mapper接口对象和动态调用invoke方法

使用泛型和静态代理技术

代理对象MapperProxy.java

package com.qinghong.mybatis.proxy;

import com.qinghong.mybatis.session.MySqlSession;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Map;

/**
 * 代理对象
 */
public class MapperProxy implements InvocationHandler {

    private MySqlSession mySqlSession;

    public MapperProxy(MySqlSession mySqlSession) {
        this.mySqlSession = mySqlSession;
    }

    @Override
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        Class<?> clazz = method.getReturnType();
        // 如何返回类型是集合类型的子类
        if (Collection.class.isAssignableFrom(clazz)){
            // 表示查询多条数据
            mySqlSession.selectList(args);
        }else if (Map.class.isAssignableFrom(clazz)){
            // 表示要返回Map集合
        }else {
            // 返回对象数据
            // statementKey: namespace . selectid
            String statementKey = method.getDeclaringClass().getName()+"."+method.getName();
            return mySqlSession.selectOne(statementKey,args);
        }
        return null;
    }
}

MySqlSession中新增动态查找接口方法getMapper和查询数据方法selectOneselectList

package com.qinghong.mybatis.session;

import com.qinghong.mapper.UUserInfoMapper;
import com.qinghong.mybatis.executor.MyExecutor;
import com.qinghong.mybatis.mapping.MyConfiguration;
import com.qinghong.mybatis.mapping.MyMapperStatement;
import com.qinghong.mybatis.proxy.MapperProxy;

import java.lang.reflect.InvocationHandler;
import java.lang.reflect.Proxy;
import java.util.List;

public class MySqlSession {

    private MyConfiguration myConfiguration;

    private MyExecutor myExecutor;

    public MySqlSession(MyConfiguration myConfiguration, MyExecutor myExecutor) {
        this.myConfiguration = myConfiguration;
        this.myExecutor = myExecutor;
    }

    public <T> T getMapper(Class<T> clazz) {
        MapperProxy mapperProxy = new MapperProxy(this);
        return (T) Proxy.newProxyInstance(clazz.getClassLoader(),new Class<?>[]{clazz}, mapperProxy);
    }

    /**
     * 查询单条数据
     * @param statementKey
     * @param args
     * @param <T>
     * @return
     */
    public <T> T selectOne(String statementKey,Object[] args) {
        // statementKey: namespace . selectid
        MyMapperStatement  mapperStatement = myConfiguration.getMapperStatement().get(statementKey);
        List<T> resultList = myExecutor.query(mapperStatement, args);
        if (resultList != null && resultList.size()>1){
            throw new RuntimeException("more than one");
        }else {
            return resultList.get(0);
        }
    }

    public void selectList(Object[] args) {
    }
}

MyExecuter.java类中新增查询数据底层query方法和处理结果集方法handlerResultSet

package com.qinghong.mybatis.executor;

import com.qinghong.mybatis.mapping.MyConfiguration;
import com.qinghong.mybatis.mapping.MyMapperStatement;
import com.qinghong.pool.MyDataSource;
import com.qinghong.reflection.ReflectionUtil;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;

public class MyExecutor {

    private MyDataSource dataSource;

    public MyExecutor(MyConfiguration myConfiguration) {
        dataSource = MyDataSource.getInstance(myConfiguration.getMyEnvironment());
    }

    public <T> List<T> query(MyMapperStatement mapperStatement, Object[] args) {
        List<T> resultList = new ArrayList<>();
        Connection connection = null;
        PreparedStatement preparedStatement = null;
        ResultSet resultSet = null;
        try {
            connection = dataSource.getConnection();
            preparedStatement = connection.prepareStatement(mapperStatement.getSql());
            if (args[0] instanceof Integer){
                preparedStatement.setInt(1,(Integer) args[0]);
            }else  if (args[0] instanceof Double) {
                preparedStatement.setDouble(1,(Double) args[0]);
            }
            else  if (args[0] instanceof  String) {
                preparedStatement.setString(1,(String) args[0]);
            }
            resultSet = preparedStatement.executeQuery();

            // 查询后的结果需要处理,转换成一个对象返回
            handlerResultSet(resultSet,resultList,mapperStatement.getResultType());

        } catch (SQLException throwables) {
            throwables.printStackTrace();
        } finally {
            if (null != preparedStatement){
                try {
                    preparedStatement.close();
                }catch (SQLException e){
                    e.printStackTrace();
                }
            }
            if (null != connection){
                // 把连接归还到连接池,而不是关闭connection
                dataSource.release(connection);
            }
        }
        return resultList;
    }

    /**
     * 处理查询出来的ResuleSet
     * @param resultSet
     * @param resultList
     * @param resultType
     * @param <T>
     */
    private <T> void handlerResultSet(ResultSet resultSet, List<T> resultList,String resultType){
        try {
            Class<T> clazz = (Class<T>) Class.forName(resultType);
            while (resultSet.next()){
                // UserInfo
                T entity = clazz.newInstance();
                // 把从数据库查询出来的结果集字段的数据要设置到entity对象中去
                ReflectionUtil.setProToBeanFromResult(entity,resultSet);
                resultList.add((T)entity);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }finally {
            if (null != resultSet){
                try {
                    resultSet.close();
                }catch (SQLException e){
                    e.printStackTrace();
                }
            }
        }
    }
}

处理结果集时handlerResultSet用到反射工具类 ReflectionUtil.java

package com.qinghong.reflection;

import java.lang.reflect.Field;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.Locale;

public class ReflectionUtil {

    /**
     * 把结果集设置到Baen对象
     * @param entity
     * @param resultSet
     * @throws SQLException
     */
    public static void setProToBeanFromResult(Object entity, ResultSet resultSet) throws SQLException {
        ResultSetMetaData rsmd = resultSet.getMetaData();

        int count = rsmd.getColumnCount();
        Field[] decfields = entity.getClass().getDeclaredFields();
        for (int i = 0; i < count; i++) {
            String columnName = rsmd.getCatalogName(i + 1).replace("_", "").toUpperCase();
            for (int j = 0; j < decfields.length; j++) {
                String filedName = decfields[j].getName().toUpperCase();
                if (columnName.equalsIgnoreCase(filedName)){
                    if (decfields[j].getType().getSimpleName().equals("Integer")){
                        setProToBean(entity,decfields[j].getName(),resultSet.getInt(rsmd.getColumnName(i+1)));
                    }
                    break;
                }
            }
        }
    }

    /**
     * 属性值设置到Bean中
     * @param bean
     * @param name
     * @param value
     */
    private static void setProToBean(Object bean, String name, Object value) {
        try {
            Field field = bean.getClass().getDeclaredField("name");
            field.setAccessible(true);
            field.set(bean,value);
        } catch (NoSuchFieldException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        }

    }
}

同时别忘了把Sql语句解析一下,不然就会报如下错误

mp6.png 预编译时这个是id = #{id,jdbcType = INTEGER}应该替换成#{?},还需要写一个SQLTokenParser方法