MyBatis——模拟MyBatis框架

一、dom4j 解析 XML 文件

在 dom4j 中,DOMReader SAXReader 是两种不同的 XML 解析器。

它们的主要区别在于解析 XML 的方式和所提供的功能:

  1. DOMReader

    • DOMReader 使用 DOM(Document Object Model)模型来表示整个 XML 文档,将整个 XML 文档加载到内存中,以树形结构的方式表示整个文档。

    • 优点:可以随机访问和修改文档中的任何部分,方便对文档进行增删改查操作。

    • 缺点:由于将整个文档加载到内存中,对于大型 XML 文档会占用较多的内存,可能导致性能问题。

  2. SAXReader

    • SAXReader 使用 SAX(Simple API for XML)解析器,采用事件驱动的方式逐行读取和解析 XML 文档,不需要将整个文档加载到内存中。

    • 优点:适合处理大型 XML 文档,因为不需要一次性加载整个文档,可以减少内存占用。

    • 缺点:相对于 DOM 模型,SAX 模型不支持随机访问和修改文档的能力,只能顺序读取文档内容并响应特定事件。

选择使用 DOMReader 还是 SAXReader 取决于具体的需求。

如果需要频繁地对文档进行修改或随机访问,适合使用 DOMReader

而如果处理大型文档或只需顺序读取文档内容,那么 SAXReader 是更好的选择。

 

  • 解析核心配置文件

// 创建 SAXReader 对象
SAXReader saxReader = new SAXReader();
// 通过 ClassLoader 加载 xml 文件
InputStream is = ClassLoader.getSystemClassLoader()
    						.getResourceAsStream("mybatis-config.xml");
// 读 xml 文件,返回 document 对象,document 对象是文档对象,代表整个 xml 文件
Document document = saxReader.read(is);

// 获取文档中的根标签
/*
	Element rootElement = document.getRootElement();
	String rootElementName = rootElement.getName();
	System.out.println("根结点:" + rootElementName);
*/

// 获取 default 环境 id
// xpath 是做标签路径匹配的,能够快速定位 xml 中的元素
// 从根下找 configuration 标签,然后找 configuration 下的 environments 标签
String xpath = "/configuration/environments";
// Element 是 Node 的子类,方法更多,使用更便捷
Element environments = (Element) document.selectSingleNode(xpath);
// System.out.println(environments);
// 获取属性值
String defaultEnvironmentId = environments.attributeValue("default");
// System.out.println("默认环境id :" + defaultEnvironmentId);

// 获取具体环境
xpath = "//configuration/environments/environment[@id='" + defaultEnvironmentId + "']";
Element environment = (Element) document.selectSingleNode(xpath);
// System.out.println(environment);

// 获取 environment 下的 transactionManager 结点
// element - 获取孩子结点
Element transactionManager = environment.element("transactionManager");
String transactionManagerType = transactionManager.attributeValue("type");
System.out.println("transactionManagerType : " + transactionManagerType);

// 获取 dataSource 结点
Element dataSource = environment.element("dataSource");
String dataSourceType = dataSource.attributeValue("type");
System.out.println("dataSourceType : " + dataSourceType);

// 获取 dataSource 下的所有子节点
List<Element> propertyEles = dataSource.elements();
// 遍历
propertyEles.forEach(propertyEle -> {
    String name = propertyEle.attributeValue("name");
    String value = propertyEle.attributeValue("value");
    System.out.println(name + " : " + value);
});

// 获取所有 mapper 标签
// 不想从根下开始,想从任意位置开始获取所有标签需要这样写
xpath = "//mapper";
List<Node> mappers = document.selectNodes(xpath);
// 遍历
mappers.forEach(mapper -> {
    Element mapperEle= (Element) mapper;
    String resource = mapperEle.attributeValue("resource");
    System.out.println(resource);
});

 

  • 解析 SqlMapper 文件 
SAXReader saxReader = new SAXReader();
InputStream is = ClassLoader.getSystemClassLoader()
    						.getResourceAsStream("CarMapper.xml");
Document document = saxReader.read(is);

// 获取 namespace
String xpath = "/mapper";
Element mapper = (Element) document.selectSingleNode(xpath);
String namespace = mapper.attributeValue("namespace");
System.out.println(namespace);

// 获取 mapper 下的所有子节点
List<Element> elements = mapper.elements();
elements.forEach(element -> {
    String id = element.attributeValue("id");
    // 没有该属性的 Sql 语句则会返回一个 “null”
    String resultType = element.attributeValue("resultType");
    System.out.println("id : " + id + ",resultType : " + resultType);

    // 获取 Sql 语句(获取标签中的文本内容,去除前后空白)
    String sql = element.getTextTrim();
    System.out.println(sql);

    /**
     * MyBatis 封装了 JDBC,需要执行的是带 ? 的 SQL 语句,所以需要将以下 SQL 语句做转化
     * insert into t_car 
     * values(null,#{carNum},#{brand},#{guidePrice},#{produceTime},#{carType})
     *
     * insert into t_car values(null,?,?,?,?,?)
     */
    String newSql = sql.replaceAll("#\\{[0-9A-Za-z_$]*}", "?");
    System.out.println(newSql);
}

 

二、手写 GodBatis 

Jaxen 是一个用于 Java 平台的开源 XPath 库,它提供了在 XML 文档中执行 XPath 查询的功能。

Jaxen 的目标是提供一个简单、易用且高效的方式来解析和查询 XML 文档,使开发人员能够轻松地使用 XPath 表达式来定位和提取 XML 文档中的数据。

一些 Jaxen 库的特点包括:

  • 支持标准的 XPath 语法:Jaxen 遵循标准的 XPath 语法规范,可以执行常见的 XPath 查询操作,如按路径查找节点、筛选节点、使用谓词等。

  • 跨平台性:作为一个 Java 库,Jaxen 可以在不同的 Java 平台上运行,提供了对 XML 文档的跨平台查询能力。

  • 易于集成:Jaxen 提供了简洁的 API,使得开发人员可以轻松地将 XPath 功能集成到 Java 应用程序中。

  • 灵活性:Jaxen 支持不同类型的 XML 文档,如 DOM、SAX、JDOM 等,使得开发人员可以根据需求选择合适的 XML 解析器来进行 XPath 查询。

总的来说,Jaxen 是一个方便、灵活且功能丰富的 Java XPath 库,适用于需要在 Java 应用程序中对 XML 文档进行复杂查询和处理的场景。

 

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.god.ibatis</groupId>
    <artifactId>godBatis</artifactId>
    <version>1.0</version>

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

    <dependencies>
        <!--dom4j-->
        <dependency>
            <groupId>dom4j</groupId>
            <artifactId>dom4j</artifactId>
            <version>1.1</version>
        </dependency>
        <!--jexen-->
        <dependency>
            <groupId>jaxen</groupId>
            <artifactId>jaxen</artifactId>
            <version>1.2.0</version>
        </dependency>
        <!--junit-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.12</version>
            <scope>test</scope>
        </dependency>
        <!--mysql-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.30</version>
        </dependency>
    </dependencies>

</project>

Resources工具类  

package org.god.ibatis.utils;

import java.io.InputStream;

/**
 * 工具类:加载类路径中资源
 *
 * @author 秋玄
 * @version 1.0
 * @package org.god.ibatis.utils
 * @date 2022-09-26-07:50
 * @since 1.0
 */
public class Resources {
    /**
     * 工具类构造方法都是私有的
     * 因为工具类中的方法都是静态的,不需要创建对象就可以调用
     */
    private Resources() {
    }

    /**
     * 从类路径中加载资源
     * @param resource  类路径中的资源文件
     * @return          指向资源文件的输入流
     */
    public static InputStream getResourceAsStream(String resource){
        return ClassLoader.getSystemClassLoader().getResourceAsStream(resource);
    }
}

SqlSessioniFactoryBuilder  

package org.god.ibatis.core;

import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.Node;
import org.dom4j.io.SAXReader;
import org.god.ibatis.utils.Resources;

import javax.sql.DataSource;
import java.io.InputStream;
import java.util.*;

/**
 * SqlSessionFactory 构建器对象
 * 通过 SqlSessioniFactoryBuilder 的 build 方法解析
 * godbatis-config.xml 文件,创建 SqlSessionFactory 对象
 *
 * @author 秋玄
 * @version 1.0
 * @package org.god.ibatis.core
 * @date 2022-09-26-07:55
 * @since 1.0
 */
public class SqlSessioniFactoryBuilder {

    /**
     * 无参数构造方法
     */
    public SqlSessioniFactoryBuilder() {
    }

    /**
     * 解析 godbatis-config.xml 文件,构建 SqlSesionFactory 对象
     * @param in    指向 godbatis-config.xml 文件的一个输入流
     * @return      SqlSesionFactory 对象
     */
    public SqlSessionFactory build(InputStream in){
        SqlSessionFactory factory = null;
        try {
            // 解析核心配置文件 godbatis-config.xml
            SAXReader reader = new SAXReader();
            Document document = reader.read(in);

            Element environments = (Element) document.selectSingleNode("/configuration/environments");
            String dafaultId = environments.attributeValue("default");
            Element environment = (Element) document.selectSingleNode("/configuration/environments/environment[@id='"+ dafaultId +"']");
            Element transactionEle = environment.element("transactionManager");
            Element dataSourceEle = environment.element("dataSource");
            List<String> sqlMapperXMLPathList = new ArrayList<>();
            // “//mapper” -- 获取整个配置文件中的 mapper
            List<Node> nodes = document.selectNodes("//mapper");
            nodes.forEach(node -> {
                Element mapper = (Element) node;
                String resource = mapper.attributeValue("resource");
                sqlMapperXMLPathList.add(resource);
            });

            // 获取数据源
            DataSource dataSource = getDataSource(dataSourceEle);
            // 获取事务管理器
            Transaction transaction = getTransaction(transactionEle,dataSource);
            // 获取 mappedStatements
            Map<String,MappedStatement> mappedStatements = 
                			getMappedStatements(sqlMapperXMLPathList);

            // 构建 SqlSessionFactory 对象
            factory = new SqlSessionFactory(transaction,mappedStatements);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return factory;
    }

    /**
     * 解析所有的 SqlMapper 文件,构建 Map 集合
     * @param sqlMapperXMLPathList
     * @return
     */
    private Map<String, MappedStatement> getMappedStatements(List<String> sqlMapperXMLPathList) {
        Map<String,MappedStatement> mappedStatements = new HashMap<>();

        sqlMapperXMLPathList.forEach(sqlMapperXMLPath -> {
            try {
                SAXReader reader = new SAXReader();
                Document document = reader.read(
                    Resources.getResourceAsStream(sqlMapperXMLPath)
                );
                Element mapper  = (Element) document.selectSingleNode("mapper");
                String namespace = mapper.attributeValue("namespace");
                List<Element> elements = mapper.elements();
                elements.forEach(element -> {
                    String id = element.attributeValue("id");
                    String sqlId = namespace + "." + id;

                    String resultType = element.attributeValue("resultType");
                    String sql = element.getTextTrim();
                    MappedStatement mappedStatement = new MappedStatement(sql, resultType);

                    mappedStatements.put(sqlId,mappedStatement);
                });
            } catch (Exception e) {
                e.printStackTrace();
            }
        });

        return mappedStatements;
    }

    /**
     * 获取事务管理器
     * @param transactionEle    事务管理器标签元素
     * @param dataSource        数据源对象
     * @return                  事务管理器标签元素对应的事务管理器对象
     */
    private Transaction getTransaction(Element transactionEle, DataSource dataSource) {
        Transaction transaction = null;
        // type 可能的值:JDBC MANAGED
        String type = transactionEle.attributeValue("type").trim().toUpperCase();
        switch (type){
            case Const.JDBC_TRANSACTION:
                /* false:默认开启事务,需要手动提交 */
                transaction = new JdbcTransaction(dataSource,false);
                break;
            case Const.MANAGED_TRANSACTION:
                transaction = new ManagedTransaction();
                break;
        }
        return transaction;
    }

    /**
     * 获取数据源
     * @param dataSourceEle 数据源标签元素
     * @return              数据源标签元素对应的数据源对象
     */
    private DataSource getDataSource(Element dataSourceEle) {
        Map<String,String> map = new HashMap<>();

        // 获取所有 property
        List<Element> propertys = dataSourceEle.elements("property");
        propertys.forEach(propertyEle -> {
            String name = propertyEle.attributeValue("name");
            String value = propertyEle.attributeValue("value");
            map.put(name,value);
        });

        DataSource dataSource = null;
        // type 可能的值:UNPOOLED POOLED JNDI
        String type = dataSourceEle.attributeValue("type").trim().toUpperCase();
        switch (type){
            case Const.UN_POOLED_DATASOURCE:
                dataSource = new UnPooledDataSource(map.get("driver"),map.get("url"),map.get("username"), map.get("password"));
                break;
            case Const.POOLED_DATASOURCE:
                dataSource = new PooledDataSource();
                break;
            case Const.JNDI_DATASOURCE:
                dataSource = new JndiDataSource();
                break;
        }
        return dataSource;
    }
}

SqlSessionFactory  

package org.god.ibatis.core;

import java.util.Map;

/**
 * 一个数据库一般对应一个 SqlSessionFactory 对象
 * 通过 SqlSessionFactory 对象可以获取 SqlSession 对象(开启会话)
 * 一个 SqlSessionFactory 对象可以开启多个 SqlSession 会话
 *
 * @author 秋玄
 * @version 1.0
 * @package org.god.ibatis.core
 * @date 2022-09-26-08:01
 * @since 1.0
 */
public class SqlSessionFactory {
    /**
     * 事务管理器
     * 事务管理器是可以灵活切换的
     * SqlSessionFactory 类中的事务管理器应该是面向接口编程
     */
    private  Transaction transaction;

    /**
     * 存放 SQL 语句的 Map 集合
     * key 是 sqlId
     * value 是对应的 SQL 标签信息对象
     */
    private Map<String,MappedStatement> mappedStatements;

    public Transaction getTransaction() {
        return transaction;
    }

    public void setTransaction(Transaction transaction) {
        this.transaction = transaction;
    }

    public Map<String, MappedStatement> getMappedStatements() {
        return mappedStatements;
    }

    public void setMappedStatements(Map<String, MappedStatement> mappedStatements) {
        this.mappedStatements = mappedStatements;
    }

    /**
     * 获取 SqlSession 对象
     * @return SqlSession 对象
     */
    public SqlSession openSession(){
        // 开启连接
        transaction.openConnection();
        // 创建 SqlSession 对象
        /*
            this 指的是当前的 SqlSessionFactory 对象
            它包含了 transaction 和 mappedStatements
            同时对外提供了 getter 方法
         */
        SqlSession sqlSession = new SqlSession(this);
        return sqlSession;
    }

    public SqlSessionFactory(Transaction transaction, Map<String, MappedStatement> mappedStatements) {
        this.transaction = transaction;
        this.mappedStatements = mappedStatements;
    }

    public SqlSessionFactory() {
    }
}

全局常量  

package org.god.ibatis.core;

/**
 * 整个框架的常量类
 *
 * @author 秋玄
 * @version 1.0
 * @package org.god.ibatis.core
 * @date 2022-09-27-07:15
 * @since 1.0
 */
public class Const {
    public static final String UN_POOLED_DATASOURCE = "UNPOOLED";
    public static final String POOLED_DATASOURCE = "POOLED";
    public static final String JNDI_DATASOURCE = "JNDI";
    public static final String JDBC_TRANSACTION = "JDBC";
    public static final String MANAGED_TRANSACTION = "MANAGED";
}

Transaction 接口  

package org.god.ibatis.core;

import java.sql.Connection;

/**
 * 事务管理器接口
 * 所有的事务管理器都应该遵循此规范
 *      JDBC 事务管理器
 *      MANAGED 事务管理器
 * 事务管理器:提供控制事务的方法
 *
 * @author 秋玄
 * @version 1.0
 * @package org.god.ibatis.core
 * @date 2022-09-26-17:58
 * @since 1.0
 */
public interface Transaction {
    /**
     * 提交事务
     */
    void commit();

    /**
     * 回滚事务
     */
    void rollback();

    /**
     * 关闭事务
     */
    void close();

    /**
     * 开启数据库连接
     */
    void openConnection();

    /**
     * 获取数据库连接对象
     */
    Connection getConnection();
}

Transaction 接口实现类  

package org.god.ibatis.core;

import javax.sql.DataSource;
import java.sql.Connection;
import java.sql.SQLException;

/**
 * JDBC 事务管理器
 *
 * @author 秋玄
 * @version 1.0
 * @package org.god.ibatis.core
 * @date 2022-09-26-18:03
 * @since 1.0
 */
public class JdbcTransaction  implements Transaction{

    /**
     * 数据源属性
     * 所有的数据源都要实现 JDK 自带的规范:javax.sql.DataSource
     */
    private DataSource dataSource;

    /**
     * 自动提交标志
     *      true:自动提交
     *      false:不自动提交
     */
    private boolean autoCommit;

    /**
     * 连接对象
     */
    private Connection connection;

    @Override
    public Connection getConnection() {
        return connection;
    }

    /**
     * 创建事务管理器对象
     * @param dataSource
     * @param autoCommit
     */
    public JdbcTransaction(DataSource dataSource, boolean autoCommit) {
        this.dataSource = dataSource;
        this.autoCommit = autoCommit;
    }

    @Override
    public void commit() {
        try {
            connection.commit();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void rollback() {
        try {
            connection.rollback();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void close() {
        try {
            connection.close();
        } catch (SQLException e) {
            e.printStackTrace();
        }
    }

    @Override
    public void openConnection(){
        if (connection == null) {
            try {
                connection = dataSource.getConnection();
                // 开启事务
                connection.setAutoCommit(autoCommit);
            } catch (SQLException e) {
                e.printStackTrace();
            }
        }
    }
}
package org.god.ibatis.core;

import java.sql.Connection;

/**
 * MANAGED 事务管理器(不实现)
 *
 * @author 秋玄
 * @version 1.0
 * @package org.god.ibatis.core
 * @date 2022-09-26-18:04
 * @since 1.0
 */
public class ManagedTransaction implements Transaction{
    @Override
    public void commit() {

    }

    @Override
    public void rollback() {

    }

    @Override
    public void close() {

    }

    @Override
    public void openConnection() {

    }

    @Override
    public Connection getConnection() {
        return null;
    }
}

数据源  

package org.god.ibatis.core;

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

/**
 * 数据源实现类 UNPOOLED
 * 不使用连接池,每次都新建 Connection 对象
 *
 * @author 秋玄
 * @version 1.0
 * @package org.god.ibatis.core
 * @date 2022-09-26-18:27
 * @since 1.0
 */
public class UnPooledDataSource implements DataSource {

    private String url;
    private String username;
    private String password;

    /**
     * 创建一个数据源对象
     * @param driver
     * @param url
     * @param username
     * @param password
     */
    public UnPooledDataSource(String driver, String url, String username, String password) {
        try {
            // 直接注册驱动
            Class.forName(driver);
        } catch (ClassNotFoundException e) {
            throw new RuntimeException(e);
        }
        this.url = url;
        this.username = username;
        this.password = password;
    }

    @Override
    public Connection getConnection() throws SQLException {
        return DriverManager.getConnection(url,username,password);
    }

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

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

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

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

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

    }

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

    }

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

    @Override
    public Logger getParentLogger() throws SQLFeatureNotSupportedException {
        return null;
    }
}
package org.god.ibatis.core;

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;

/**
 * 数据源实现类:POOLED
 * 使用 godbatis 框架内置的数据库连接池来获取 Connection 对象(不实现)
 *
 * @author 秋玄
 * @version 1.0
 * @package org.god.ibatis.core
 * @date 2022-09-26-18:28
 * @since 1.0
 */
public class PooledDataSource implements DataSource {
    /**
     * 从数据连接池中获取 Connection 对象
     * 这个数据库连接池 godbatis 框架可以自己写一个连接池
     * @return
     * @throws SQLException
     */
    @Override
    public Connection getConnection() throws SQLException {
        return null;
    }

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

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

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

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

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

    }

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

    }

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

    @Override
    public Logger getParentLogger() throws SQLFeatureNotSupportedException {
        return null;
    }
}
package org.god.ibatis.core;

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;

/**
 * 数据源的实现:JNDI
 * 使用第三方的数据库连接池获取 Connection 对象(不实现)
 *
 * @author 秋玄
 * @version 1.0
 * @package org.god.ibatis.core
 * @date 2022-09-26-18:28
 * @since 1.0
 */
public class JndiDataSource implements DataSource {
    @Override
    public Connection getConnection() throws SQLException {
        return null;
    }

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

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

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

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

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

    }

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

    }

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

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

封装 SQL 标签

package org.god.ibatis.core;

/**
 * 普通 Java 类,用于封装一个 SQL 标签
 * 一个 MappedStatement 对象对应一个 SQL 标签
 *
 * @author 秋玄
 * @version 1.0
 * @package org.god.ibatis.core
 * @date 2022-09-26-08:21
 * @since 1.0
 */
public class MappedStatement {
    /**
     * sql 语句
     */
    private String sql;
    /**
     * 要封装的结果集类型
     * 当 sql 语句是 select 语句时 resultType 才有值
     * 其他情况都是 null
     */
    private String resultType;

    @Override
    public String toString() {
        return "MappedStatement{" +
                "sql='" + sql + '\'' +
                ", resultType='" + resultType + '\'' +
                '}';
    }

    public String getSql() {
        return sql;
    }

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

    public String getResultType() {
        return resultType;
    }

    public void setResultType(String resultType) {
        this.resultType = resultType;
    }

    public MappedStatement(String sql, String resultType) {
        this.sql = sql;
        this.resultType = resultType;
    }

    public MappedStatement() {
    }
}

执行 SQL 语句  

package org.god.ibatis.core;

import java.lang.reflect.Method;
import java.sql.*;

/**
 * 执行 SQL 语句的会话对象
 *
 * @author 秋玄
 * @version 1.0
 * @package org.god.ibatis.core
 * @date 2022-09-27-08:29
 * @since 1.0
 */
public class SqlSession {
    private SqlSessionFactory factory;

    public SqlSession(SqlSessionFactory factory) {
        this.factory = factory;
    }

    /**
     * 执行 insert 语句,向数据库表中插入记录
     * @param id        sql 语句的 id
     * @param pojo      插入的数据
     * @return          插入记录的数量
     */
    public int insert(String id,Object pojo){
        int count = 0;
        try {
            Connection connection = factory.getTransaction().getConnection();
            // insert into t_user values(#{id},#{name},#{age});
            String godBatisSql = factory.getMappedStatements().get(id).getSql();
            String sql = godBatisSql.replaceAll("#\\{[0-9A-Za-z_$]*}","?");
            PreparedStatement ps = connection.prepareStatement(sql);

            // 给占位符传值(局限性:这里都是 setString,所以要求数据库表中的字段类型都是 varchar 类型)
            // 将 pojo 的属性与占位符对应
            // 获取占位符的数量
            int fromIndex = 0;
            // 问号下标
            int index = 1;
            while (true){
                // # 的下标
                int jingIndex = godBatisSql.indexOf("#",fromIndex);
                // 找不到 # 结束循环
                if(jingIndex < 0){
                    break;
                }
                // } 的下标,# 与 } 中间的字符串包含了一个属性名
                int youKuoHaoIndex = godBatisSql.indexOf("}",fromIndex);
                String propertyName = godBatisSql.substring(jingIndex + 2,youKuoHaoIndex).trim();
                fromIndex = youKuoHaoIndex + 1;
                String getter = "get" + propertyName.toUpperCase().charAt(0) + propertyName.substring(1);
                Method getMethod = pojo.getClass().getDeclaredMethod(getter);
                Object propertyValue = getMethod.invoke(pojo);
                ps.setString(index,propertyValue.toString());
                index++;
            }
            count = ps.executeUpdate();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return count;
    }

    /**
     * 执行查询语句,返回一个对象
     * 只适合返回一条记录的 sql 语句
     * @param id
     * @param param
     * @return
     */
    public Object selectOne(String id,Object param){
        Object obj = null;

        try {
            Connection connection = factory.getTransaction().getConnection();
            String giodBatisSql = factory.getMappedStatements().get(id).getSql();
            String sql = giodBatisSql.replaceAll("#\\{[0-9A-Za-z_$]*}","?");
            PreparedStatement ps = connection.prepareStatement(sql);

            // 这里只对一个占位符的情况做处理
            ps.setString(1,param.toString());
            ResultSet resultSet = ps.executeQuery();

            String resultType = factory.getMappedStatements().get(id).getResultType();

            // 从结果集取数据封装对象
            if (resultSet.next()) {
                // 获取 resultType 的 class
                Class<?> clazz = Class.forName(resultType);
                // 调用无参数构造方法创建对象
                obj = clazz.newInstance();
                // 给属性赋值
                // 关键:将查询结果的列名作为属性名
                ResultSetMetaData metaData = resultSet.getMetaData();
                int columnCount = metaData.getColumnCount();
                for (int i = 1; i < columnCount + 1; i++) {
                    String propertyName = metaData.getColumnName(i);
                    // 拼接方法名
                    String setMethodName = "set" + propertyName.toUpperCase().charAt(0) + propertyName.substring(1);
                    // 获取 set 方法
                    Method setMethod = clazz.getDeclaredMethod(setMethodName, String.class);
                    // 调用 set 方法给对象 obj 赋值
                    setMethod.invoke(obj,resultSet.getString(propertyName));
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }

        return obj;
    }

    /**
     * 提交事务
     */
    public void commint(){
        factory.getTransaction().commit();
    }

    /**
     * 回滚事务
     */
    public void rollback(){
        factory.getTransaction().rollback();
    }

    /**
     * 关闭事务
     */
    public void close(){
        factory.getTransaction().close();
    }
}

打包到本地仓库  

使用 GodBatis 框架  

<dependency>
    <groupId>org.god.ibatis</groupId>
    <artifactId>godBatis</artifactId>
    <version>1.0</version>
</dependency>

三、思路 

Mybatis 核心流程:

(采用对 MyBatis 框架使用的过程逆推的方式)

首先需要创建一个 SqlSessionFactoryBuilder 对象

  • 通过 SqlSessionFactoryBuilder 对象的 build 方法解析核心配置文件,构建 SqlSessionFactory 对象(需要一个 InputStream 流对象指向核心配置文件)

  • 封装一个工具类 Resources 用于加载类路径中的资源

    • 其中的方法都是静态的,不需要对象就可以调用,所以工具类构造方法一般私有化,避免创建对象

  • 解析核心配置文件,创建具体的事务管理器对象、数据源对象以及存放 SQL 语句的 Map 集合。调用 SqlSessionFactory 的构造方法,传入事务管理器、Map 集合创建 SqlSessionFactory 对象

构建 SqlSessionFactory 对象

  • 一个数据库一般对应一个 SqlSessionFactory 对象

  • 通过 SqlSessionFactory 对象可以获取 SqlSession 对象(开启会话)

  • 一个 SqlSessionFactory 对象可以开启多个 SqlSession 会话

属性分析

在 SqlSessionFactoryBuilder 对象的 build 方法中需要构建一个 SqlSessionFactory 对象,并对其各个属性赋值,再将其作为返回值返回。

这个 SqlSessionFactory 对象需要封装的数据应该是 “事务管理器”、“存放 SQL 语句的 Map 集合”(使用 MappedStatement 类封装,具有 sql 及 resultType 两个属性)

由于事务管理器具有数据源属性,所以 SqlSessionFactory 对象可以通过其事务管理器属性获取数据源,故自身不需要数据源属性了,避免冗余

事务管理器

由于用户需要的事务管理器可能是 JDBC 事务管理器、MANAGED 事务管理器,所以这里采用面向接口编程的思想,抽取事务管理器接口,然后各种具体的事务管理器再对其方法做具体实现;使用时根据用户设置的属性值判断再创建具体的子类对象

子类需要实现的方法有:

  • 提交事务

  • 回滚事务

  • 关闭事务

  • 及其他方法(此处暂时未知,后续再做补充)

  • 开启数据库连接(后续添加的)

  • 获取数据库链接对象(后续添加的)

子类在具体实现接口方法时,要实现对事务的控制,则需要调用 Connectioin 对象的方法,而 Connection 对象需要通过数据源创建,所以 Transaction 对象需要有一个数据源的属性(此时因为事务管理器对象包含了数据源对象,所以 SqlSessionFactory 不需要有数据源属性)

为了保证执行事务的是同一个连接对象,所以给事务管理器对象添加一个 Connection 属性,并添加 openConnection、getConnection 方法,获取数据源中的连接对象

数据源

数据源与事务管理器类似,采用面向接口编程思想,实现 javax.sql.DataSource 接口,实现类的动态创建

在数据源对象中完成注册驱动、获取 Connection 连接对象,同时设置一个是否自动提交事务的标记

注册驱动只需要注册一次即可,所以在构造方法中完成

Connection 则在每一个调用 getConnection 方法时创建一个(UNPOOLED,不使用数据库连接池的实现)

封装 SqlSession 对象,完成配置文件中 SQL 语句的解析与执行

一  叶  知  秋,奥  妙  玄  心

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

qx_java_1024

祝老板生意兴隆,财源广进!

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值