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 语句的解析与执行

一  叶  知  秋,奥  妙  玄  心

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:/a/622365.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

基于MetaGPT的智能体理论与实践-Task01

Task01: MetaGPT环境配置 学习教程&#xff1a;https://github.com/datawhalechina/hugging-multi-agent 1 环境准备 1.1 安装python3.9 通过&#xff1a;python3 --version, 查看此python版本为3.10.3 1.2 下载MetaGPT 开始&#xff0c;借用清华镜像&#xff0c;拉取…

CSS常用滤镜效果

CSS 提供了多种滤镜效果&#xff0c;可以通过 filter 属性应用于 HTML 元素。以下是一些常用的 CSS 滤镜效果&#xff1a; 一、灰度 (Grayscale) 将图像转换为灰度图像。值在 0%&#xff08;原始图像&#xff09;和 100%&#xff08;完全灰度&#xff09;之间。 filter: gra…

ORA-609频繁出现在alert.log,如何解决?

ORA-609就alertlog中比较常见的一个报错&#xff0c;虽然并没有太大的影响&#xff0c;但是频繁的出现在alert log也是很让人厌烦的事情&#xff0c;本文介绍如何排查解决ORA-609问题。 1.ORA-609官方定义 could not attach to incoming connection Cause Oracle process cou…

【话题】Agent AI智能体的未来

大家好&#xff0c;我是全栈小5&#xff0c;欢迎阅读小5的系列文章&#xff0c;这是《话题》系列文章 目录 背景一、Agent AI智能体的角色二、Agent AI智能体的发展路径三、Agent AI智能体可能带来的挑战文章推荐 背景 随着Agent AI智能体的智能化水平不断提高&#xff0c;它们…

centos7.6安装mysql

博客主页&#xff1a;花果山~程序猿-CSDN博客 文章分栏&#xff1a;MySQL之旅_花果山~程序猿的博客-CSDN博客 关注我一起学习&#xff0c;一起进步&#xff0c;一起探索编程的无限可能吧&#xff01;让我们一起努力&#xff0c;一起成长&#xff01; 目录 1.在网页中寻找mysql…

《一》Word文字编辑软件---架构设计分析

1&#xff0c;简单介绍 今天&#xff0c;我们来模拟offic软件中的word文档&#xff0c;运行如图&#xff1a; 运行程序后会出现主界面&#xff0c;顶端的菜单栏包括“文件”“编辑”“格式”“窗口”和“帮助五个主菜单。 菜单栏下面是工具栏&#xff0c;包含了系统常用的功能按…

深入理解C#中的IO操作:File类的详解

文章目录 一、File类的概述二、File类的常用方法2.1 File.Exists(string path)2.2 File.Create(string path)2.3 File.WriteAllText(string path, string contents)2.4 File.ReadAllText(string path)2.5 File.Copy(string sourceFilePath, string destFilePath, bool overwrit…

ONVIF系列一:ONVIF介绍

感谢博主OceanStar的学习笔记&#xff0c;ONVIF系列二和系列三中安装操作过程及代码实现参考了这位博主的博客。 ONVIF系列&#xff1a; ONVIF系列一&#xff1a;ONVIF介绍 ONVIF系列二&#xff1a;Ubuntu安装gSOAP、生成ONVIF代码框架 ONVIF系列三&#xff1a;ONVIF客户端实现…

【QT】QT环境搭建

本专栏内容为&#xff1a;QT学习专栏 通过本专栏的深入学习&#xff0c;你可以了解并掌握QT。 &#x1f493;博主csdn个人主页&#xff1a;小小unicorn ⏩专栏分类&#xff1a;QT &#x1f69a;代码仓库&#xff1a;小小unicorn的代码仓库&#x1f69a; &#x1f339;&#x1f…

无刷电机中对霍尔同步电角度的理解

在霍尔无刷电机驱动时需要进行初始电角度定位&#xff0c;根据ST电机库提供的方法首先我们要测量同步电角度。 在自己的驱动程序中也使用了ST的方法来测量同步电角度&#xff0c;然后根据当前霍尔的状态再确定启动时的电角度 之前用上面的方法测量同步电角度&#xff0c;程序…

Web 安全漏洞之文件上传

目录 文件上传漏洞及危害 文件名 HTML 和 SVG 软链 服务器磁盘 防御方法 网络安全学习路线 &#xff08;2024最新整理&#xff09; 学习资料的推荐 1.视频教程 2.SRC技术文档&PDF书籍 3.大厂面试题 特别声明&#xff1a; 文件上传漏洞及危害 文件上传漏洞…

JDK的串行收集器介绍与优化指南-02

对象的生命周期 对象的生命周期 在Java中,对象的生命周期通常包括以下几个阶段,这些阶段与JVM的内存管理和垃圾收集机制密切相关。 创建阶段 (1)为对象分配存储空间:当使用new关键字或其他方式(如反射、克隆、反序列化等)创建一个对象时,JVM首先会在堆内存中为其分配…

基于Springboot的大学生平时成绩量化管理系统(有报告)。Javaee项目,springboot项目。

演示视频&#xff1a; 基于Springboot的大学生平时成绩量化管理系统&#xff08;有报告&#xff09;。Javaee项目&#xff0c;springboot项目。 项目介绍&#xff1a; 采用M&#xff08;model&#xff09;V&#xff08;view&#xff09;C&#xff08;controller&#xff09;三…

在ComfyUI中通过IC-Light实现画面重新打光

&#x1f30c;背景 上一篇文章我们简单介绍了下IC-Light这个新的工程&#xff0c;可以实现为画面重新打光&#xff0c;让前景更好的融入到新的背景&#xff0c;这一篇我们就来一起简单了解下如何实现插件的安装和使用。 首先&#xff0c;我们看下最终能实现的效果&#xff1a…

5.nginx常用命令和日志定时切割

一. nginx常用的相关命令介绍 1.强制关闭nginx: ./nginx -s stop 2.优雅的关闭nginx: ./nginx -s quit 3.检查配置文件是否正确&#xff1a; ./nginx -t 4.查看nginx版本&#xff1a; ./nginx -v 5.查看nginx版本相关的配置环境信息&#xff1a;./nginx -V 6.nginx帮助信…

SpringCloud微服务01-MybatisPlus-Docker

https://b11et3un53m.feishu.cn/wiki/MWQIw4Zvhil0I5ktPHwcoqZdnec 一、微服务介绍 单体架构所有功能集群在一个架构中&#xff0c;难以维护复杂需求 微服务之间是不同的TomCat要跨服务查询&#xff0c; 学习是如何拆分单体架构为微服务 二、MybatisPlus 1.快速入门 ①入门…

老黄终于不穿皮衣了,分享一个AI换装AI试衣软件!

用AI实现在线试衣&#xff0c;或者在线换装&#xff0c;这不是一个新概念&#xff0c;肯定有人这么想过&#xff0c;但并不是所有人能都能轻松做到啊&#xff01; 今天就来分享一个人人都可以实现的方法&#xff0c;而且是那种傻瓜式的不用付钱的那种&#xff0c;甚至可以把软件…

Tkinter组件:Scrollbar-滚动条

Tkinter组件&#xff1a;Scrollbar Scrollbar&#xff08;滚动条&#xff09;组件用于滚动一些组件的可见范围&#xff0c;根据方向可分为垂直滚动条和水平滚动条。Scrollbar 组件常常被用于实现文本、画布和列表框的滚动。 1. 何时使用 Scrollbar 组件&#xff1f; Scrollb…

2000-2022年上市公司供应链效率数据(含原始数据+结果)

2000-2022年上市公司供应链效率数据&#xff08;含原始数据结果&#xff09; 1、时间&#xff1a;2000-2022年 2、指标&#xff1a;年份、股票代码、省份、城市、区县、省份代码、城市代码、区县代码、首次上市年份、上市状态、股票简称、行业名称、行业代码、库存周转率、供…

【qt】数值的输入与输出

数值的输入与输出 一.与c中的输入与输出的区别二.QString转数值三.数值转QString1.number()2.asprintf() 四.小项目1.总价和进制2.QSpinBox代替3.QSlider滑动块4.QScrollBar滚动条5.QDial表盘6.QLcdnumber lcd显示 五.总结一下下 一.与c中的输入与输出的区别 在c中我们一般通过…