当前位置: 首页 > news >正文

《手写MyBatis框架全流程:从DOM4J解析到SQL执行原理剖析》

警示:该部分内容有难度,基础较弱的程序员可能有些部分是听不懂的,如果无法跟下来,可直接跳过,不影响后续知识点的学习。当然,如果你要能够跟下来,必然会让你加深对MyBatis框架的理解。

我们给自己的框架起个名:GodBatis(起名灵感来源于:my god!!! 我的天呢!)

一、dom4j解析XML文件

第一步:引入dom4j的依赖

<?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.group</groupId>
    <artifactId>parse-xml-by-dom4j</artifactId>
    <version>1.0-SNAPSHOT</version>
    <packaging>jar</packaging>

    <dependencies>
        <!--dom4j依赖-->
        <dependency>
            <groupId>org.dom4j</groupId>
            <artifactId>dom4j</artifactId>
            <version>2.1.3</version>
        </dependency>
        <!--jaxen依赖-->
        <dependency>
            <groupId>jaxen</groupId>
            <artifactId>jaxen</artifactId>
            <version>1.2.0</version>
        </dependency>
        <!--junit依赖-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13.2</version>
            <scope>test</scope>
        </dependency>
    </dependencies>

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

</project>

第二步:编写配置文件godbatis-config.xml

<?xml version="1.0" encoding="UTF-8" ?>

<configuration>
    <environments default="dev">
        <environment id="dev">
            <transactionManager type="JDBC"/>
            <dataSource type="POOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/jdbc"/>
                <property name="username" value="root"/>
                <property name="password" value="abc123"/>
            </dataSource>
        </environment>
        <mappers>
            <mapper resource="sqlmapper.xml"/>
        </mappers>
    </environments>
</configuration>

第三步:解析godbatis-config.xml

 @Test
    public void testGodBatisConfig() throws Exception{

        // 读取xml,获取document对象
        SAXReader saxReader = new SAXReader();
        Document document = saxReader.read(Thread.currentThread().getContextClassLoader().getResourceAsStream("godbatis-config.xml"));

        // 获取<environments>标签的default属性的值
        Element environmentsElt = (Element)document.selectSingleNode("/configuration/environments");
        String defaultId = environmentsElt.attributeValue("default");
        System.out.println(defaultId);

        // 获取environment标签
        Element environmentElt = (Element)document.selectSingleNode("/configuration/environments/environment[@id='" + defaultId + "']");

        // 获取事务管理器类型
        Element transactionManager = environmentElt.element("transactionManager");
        String transactionManagerType = transactionManager.attributeValue("type");
        System.out.println(transactionManagerType);

        // 获取数据源类型
        Element dataSource = environmentElt.element("dataSource");
        String dataSourceType = dataSource.attributeValue("type");
        System.out.println(dataSourceType);

        // 将数据源信息封装到Map集合
        Map<String,String> dataSourceMap = new HashMap<>();
        dataSource.elements().forEach(propertyElt -> {
            dataSourceMap.put(propertyElt.attributeValue("name"), propertyElt.attributeValue("value"));
        });

        dataSourceMap.forEach((k, v) -> System.out.println(k + ":" + v));

        // 获取sqlmapper.xml文件的路径
        Element mappersElt = (Element) document.selectSingleNode("/configuration/environments/mappers");
        mappersElt.elements().forEach(mapper -> {
            System.out.println(mapper.attributeValue("resource"));
        });
    }

运行结果:

第四步:编写配置文件sqlmapper.xml

<?xml version="1.0" encoding="UTF-8" ?>

<mapper namespace="car">
    <insert id="insertCar">
        insert into t_car(id,car_num,brand,guide_price,produce_time,car_type) values(null,#{carNum},#{brand},#{guidePrice},#{produceTime},#{carType})
    </insert>
    <select id="selectCarByCarNum" resultType="org.example1.pojo.Car">
        select id,car_num carNum,brand,guide_price guidePrice,produce_time produceTime,car_type carType from t_car where car_num = #{carNum}
    </select>
</mapper>

第五步:解析sqlmapper.xml

@Test
public void testSqlMapper() throws Exception{
    // 读取xml,获取document对象
    SAXReader saxReader = new SAXReader();
    Document document = saxReader.read(Thread.currentThread().getContextClassLoader().getResourceAsStream("sqlmapper.xml"));

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

    // 获取sql id
    mapperElt.elements().forEach(statementElt -> {
        // 标签名
        String name = statementElt.getName();
        System.out.println("name:" + name);
        // 如果是select标签,还要获取它的resultType
        if ("select".equals(name)) {
            String resultType = statementElt.attributeValue("resultType");
            System.out.println("resultType:" + resultType);
        }
        // sql id
        String id = statementElt.attributeValue("id");
        System.out.println("sqlId:" + id);
        // sql语句
        String sql = statementElt.getTextTrim();
        System.out.println("sql:" + sql);
    });
}

二、GodBatis

手写框架之前,如果没有思路,可以先参考一下mybatis的客户端程序,通过客户端程序来逆推需要的类,参考代码:

@Test
public void testInsert(){
    SqlSession sqlSession = null;
    try {
        // 1.创建SqlSessionFactoryBuilder对象
        SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
        // 2.创建SqlSessionFactory对象
        SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(Resources.getResourceAsStream("mybatis-config.xml"));
        // 3.创建SqlSession对象
        sqlSession = sqlSessionFactory.openSession();
        // 4.执行SQL
        Car car = new Car(null, "111", "宝马X7", "70.3", "2010-10-11", "燃油车");
        int count = sqlSession.insert("insertCar",car);
        System.out.println("更新了几条记录:" + count);
        // 5.提交
        sqlSession.commit();
    } catch (Exception e) {
        // 回滚
        if (sqlSession != null) {
            sqlSession.rollback();
        }
        e.printStackTrace();
    } finally {
        // 6.关闭
        if (sqlSession != null) {
            sqlSession.close();
        }
    }
}

@Test
public void testSelectOne(){
    SqlSession sqlSession = null;
    try {
        // 1.创建SqlSessionFactoryBuilder对象
        SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
        // 2.创建SqlSessionFactory对象
        SqlSessionFactory sqlSessionFactory = sqlSessionFactoryBuilder.build(Resources.getResourceAsStream("mybatis-config.xml"));
        // 3.创建SqlSession对象
        sqlSession = sqlSessionFactory.openSession();
        // 4.执行SQL
        Car car = (Car)sqlSession.selectOne("selectCarByCarNum", "111");
        System.out.println(car);
        // 5.提交
        sqlSession.commit();
    } catch (Exception e) {
        // 回滚
        if (sqlSession != null) {
            sqlSession.rollback();
        }
        e.printStackTrace();
    } finally {
        // 6.关闭
        if (sqlSession != null) {
            sqlSession.close();
        }
    }
}

第一步:IDEA中创建模块,导入依赖

模块:godbatis(创建普通的Java Maven模块,打包方式jar),引入相关依赖

<?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.example1</groupId>
    <artifactId>godbatis</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
    <!--依赖-->
    <dependencies>
        <!--dom4j依赖-->
        <dependency>
            <groupId>org.dom4j</groupId>
            <artifactId>dom4j</artifactId>
            <version>2.1.3</version>
        </dependency>
        <!--jaxen依赖-->
        <dependency>
            <groupId>jaxen</groupId>
            <artifactId>jaxen</artifactId>
            <version>1.2.0</version>
        </dependency>
        <!--junit依赖-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13.2</version>
            <scope>test</scope>
        </dependency>
        <!--mysql驱动依赖-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.30</version>
        </dependency>
    </dependencies>
</project>

第二步:资源工具类,方便获取指向配置文件的输入流

package org.example1.utils;

import java.io.InputStream;

/**
 * godbatis框架提供的一个工具类。
 * 这个工具类专门完成“类路径”中资源的加载。

 */
public class Resources {
    /**
     * 工具类的构造方法都是建议私有化的。
     * 因为工具类中的方法都是静态的,不需要创建对象就能调用。
     * 为了避免new对象,所有构造方法私有化。
     * 这只是一种编程习惯。
     */
    private Resources(){}

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

第三步:定义SqlSessionFactoryBuilder类

SqlSessionFactoryBuilder 主要用来创建 SqlSessionFactory 实例。在 MyBatis 里,要想操作数据库,就必须先构建一个 SqlSessionFactory 对象,而 SqlSessionFactoryBuilder 就承担着这个构建任务。它可以从多种资源里解析配置信息,像 XML 文件、Java 对象等,然后根据这些配置信息创建 SqlSessionFactory 实例。

提供一个无参数构造方法,再提供一个build方法,该build方法要返回SqlSessionFactory对象

package org.god.core;

import java.io.InputStream;

/**
 * SqlSessionFactory对象构建器

 */
public class SqlSessionFactoryBuilder {

    /**
     * 创建构建器对象
     */
    public SqlSessionFactoryBuilder() {
    }


    /**
     * 获取SqlSessionFactory对象
     * 该方法主要功能是:读取godbatis核心配置文件,并构建SqlSessionFactory对象
     * @param inputStream 指向核心配置文件的输入流
     * @return SqlSessionFactory对象
     */
    public SqlSessionFactory build(InputStream inputStream){
        // 解析配置文件,创建数据源对象
        // 解析配置文件,创建事务管理器对象
        // 解析配置文件,获取所有的SQL映射对象
        // 将以上信息封装到SqlSessionFactory对象中
        // 返回
        return null;
    }
}

第四步:分析SqlSessionFactory类中有哪些属性

SqlSessionFactory 是一个工厂类,其主要作用是创建 SqlSession 实例。SqlSession 是 MyBatis 里用于和数据库进行交互的核心对象,借助它能够执行 SQL 语句、管理事务等。SqlSessionFactory 负责管理数据库连接池、配置信息等,确保 SqlSession 能够正确地和数据库进行交互。

1. 核心方法:openSession()

  • 作用创建并返回一个 SqlSession 对象(类似打开一个数据库连接,用于执行 SQL)。

  • 代码逻辑

    public SqlSession openSession() {
        transaction.openConnection();  // 开启数据库连接
        return new SqlSession(this);   // 创建会话对象
    }

2. 事务管理器的配置(setTransaction/getTransaction

  • 作用动态设置或获取事务管理器(如 JDBC 事务、自定义事务)。

  • 代码逻辑

    private Transaction transaction;  // 面向接口编程,可灵活切换事务实现
    
    public void setTransaction(Transaction transaction) {
        this.transaction = transaction;
    }
    public Transaction getTransaction() {
        return transaction;
    }

3. SQL 语句存储(setMappedStatements/getMappedStatements

  • 作用存储所有映射的 SQL 语句(比如 XML 或注解中定义的 SQL)。

  • 代码逻辑

    private Map<String, MappedStatement> mappedStatements;  // key是SQL的ID
    
    public void setMappedStatements(Map<String, MappedStatement> mappedStatements) {
        this.mappedStatements = mappedStatements;
    }
    public Map<String, MappedStatement> getMappedStatements() {
        return mappedStatements;
    }

4. 构造方法(SqlSessionFactory

  • 作用初始化工厂时注入事务管理器和 SQL 配置

  • 你的代码逻辑

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

第五步:定义JDBCTransaction

事务管理器最好是定义一个接口,然后每一个具体的事务管理器都实现这个接口。

package org.example1.core;

import java.sql.Connection;

/**
 * 事务管理器接口。
 * 所有的事务管理器都应该遵循该规范。
 * JDBC事务管理器,MANAGED事务管理器都应该实现这个接口。
 * Transaction事务管理器:提供管理事务方法。
 */
public interface Transaction {
    /**
     * 提交事务
     */
    void commit();

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

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

    /**
     * 真正的开启数据库连接。
     */
    void openConnection();

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

JdbcTransaction 实现类

package org.example1.core;

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

/**
 * JDBC事务管理器(godbatis框架目前只对JdbcTransaction进行实现。)

 */
public class JdbcTransaction implements Transaction{

    /**
     * 数据源属性
     * 经典的设计:面向接口编程。
     */
    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();
            }
        }
    }
}

ManagedTransaction实现类

package org.example1.core;

import java.sql.Connection;

/**
 * MANAGED事务管理器。(godbatis对这个类就不再实现了。)
 *

 */
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;
    }
}

第六步:事务管理器中需要数据源,定义UNPOOLEDDataSource

数据源是获取connection对象的

POOLED        UNPOOLED        JNDI

所有的数据源都要实现JDK的规范,javax.sql.DataSource

UnPooledDataSource类

package org.example1.core;

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对象。
 */
public class UnPooledDataSource implements javax.sql.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 {
        Connection connection = DriverManager.getConnection(url, username, password);
        return connection;
    }

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

    @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;
    }

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

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

PooledDataSource 类

package org.example1.core;


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

/**
 * 数据源的实现类:POOLED
 * 使用godbatis框架内置的数据库连接池来获取Connection对象。(这个不实现了。)

 */
public class PooledDataSource implements javax.sql.DataSource{

    @Override
    public Connection getConnection() throws SQLException {
        // 从数据库连接池当中获取Connection对象。(这个数据库连接池是我godbatis框架内部封装好的。)
        return null;
    }

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

    @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;
    }

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

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

JNDIDataSource类

package org.example1.core;

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

/**
 * 数据源的实现类:JNDI
 * 使用第三方的数据库连接池获取Connection对象。(这个不实现了。)

 */
public class JNDIDataSource implements javax.sql.DataSource{
    @Override
    public Connection getConnection() throws SQLException {
        return null;
    }

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

    @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;
    }

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

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

第七步:定义MappedStatement

package org.example1.core;

/**
 * 普通的java类。POJO,封装了一个SQL标签。
 * 一个MappedStatement对象对应一个SQL标签。
 * 一个SQL标签中的所有信息封装到MappedStatement对象当中。
 * 面向对象编程思想。
 */
public class MappedStatement {
    /**
     * sql语句
     */
    private String sql;

    /**
     * 要封装的结果集类型。有的时候resultType是null。
     * 比如:insert delete update语句的时候resultType是null。
     * 只有当sql语句是select语句的时候resultType才有值。
     */
    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() {
    }
}

第八步:完善SqlSessionFactory类

package org.god.core;

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

/**
 * SqlSession工厂对象,使用SqlSessionFactory可以获取会话对象

 */
public class SqlSessionFactory {
    private TransactionManager transactionManager;
    private Map<String, GodMappedStatement> mappedStatements;

    public SqlSessionFactory(TransactionManager transactionManager, Map<String, GodMappedStatement> mappedStatements) {
        this.transactionManager = transactionManager;
        this.mappedStatements = mappedStatements;
    }

    public TransactionManager getTransactionManager() {
        return transactionManager;
    }

    public void setTransactionManager(TransactionManager transactionManager) {
        this.transactionManager = transactionManager;
    }

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

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

第九步:完善SqlSessionFactoryBuilder中的build方法

package org.example1.core;

import org.dom4j.Document;
import org.dom4j.Element;
import org.dom4j.Node;
import org.dom4j.io.SAXReader;
import org.example1.utils.Resources;


import javax.sql.DataSource;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * SqlSessionFactory构建器对象。
 * 通过SqlSessionFactoryBuilder的build方法来解析
 * godbatis-config.xml文件,然后创建SqlSessionFactory对象。

 */
public class SqlSessionFactoryBuilder {

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

    /**
     * 解析godbatis-config.xml文件,来构建SqlSessionFactory对象。
     * @param in 指向godbatis-config.xml文件的一个输入流。
     * @return SqlSessionFactory对象。
     */
    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 defaultId = environments.attributeValue("default");
            Element environment = (Element) document.selectSingleNode("/configuration/environments/environment[@id='" + defaultId + "']");
            Element transactionElt = environment.element("transactionManager");
            Element dataSourceElt = environment.element("dataSource");
            List<String> sqlMapperXMLPathList = new ArrayList<>();
            List<Node> nodes = document.selectNodes("//mapper"); // 获取整个配置文件中所有的mapper标签
            nodes.forEach(node -> {
                Element mapper = (Element) node;
                String resource = mapper.attributeValue("resource");
                sqlMapperXMLPathList.add(resource);
            });
            // 获取数据源对象
            DataSource dataSource = getDataSource(dataSourceElt);
            // 获取事务管理器
            Transaction transaction = getTransaction(transactionElt,dataSource);
            // 获取mappedStatements
            Map<String, MappedStatement> mappedStatements = getMappedStatements(sqlMapperXMLPathList);
            // 解析完成之后,构建SqlSessionFactory对象。
            factory = new SqlSessionFactory(transaction, mappedStatements);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return factory;
    }

    /**
     * 解析所有的SqlMapper.xml文件,然后构建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");
                    // 这里进行了namespace和id的拼接,生成最终的sqlId
                    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 transactionElt 事务管理器标签元素
     * @param dataSource 数据源对象
     * @return
     */
    private Transaction getTransaction(Element transactionElt, DataSource dataSource) {
        Transaction transaction = null;
        String type = transactionElt.attributeValue("type").trim().toUpperCase();
        if (Const.JDBC_TRANSACTION.equals(type)) {
            transaction = new JdbcTransaction(dataSource, false); // 默认是开启事务的,将来需要手动提交的。
        }
        if (Const.MANAGED_TRANSACTION.equals(type)) {
            transaction = new ManagedTransaction();
        }
        return transaction;
    }

    /**
     * 获取数据源对象
     * @param dataSourceElt 数据源标签元素
     * @return
     */
    private DataSource getDataSource(Element dataSourceElt) {
        Map<String,String> map = new HashMap<>();
        // 获取所有的property
        List<Element> propertyElts = dataSourceElt.elements("property");
        propertyElts.forEach(propertyElt -> {
            String name = propertyElt.attributeValue("name");
            String value = propertyElt.attributeValue("value");
            map.put(name, value);
        });

        DataSource dataSource = null;
        String type = dataSourceElt.attributeValue("type").trim().toUpperCase();
        if (Const.UN_POOLED_DATASOURCE.equals(type)) {
            dataSource = new UnPooledDataSource(map.get("driver"), map.get("url"), map.get("username"), map.get("password"));
        }
        if (Const.POOLED_DATASOURCE.equals(type)) {
            dataSource = new PooledDataSource();//没有实现
        }
        if (Const.JNDI_DATASOURCE.equals(type)) {
            dataSource = new JNDIDataSource();//没有实现
        }
        return dataSource;
    }


}

第十步:在SqlSessionFactory中添加openSession方法

package org.example1.core;

import java.util.Map;

/**
 * SqlSessionFactory对象:
 *      一个数据库对应一个SqlSessionFactory对象。
 *      通过SqlSessionFactory对象可以获取SqlSession对象。(开启会话)
 *      一个SqlSessionFactory对象可以开启多个SqlSession会话。

 */
public class SqlSessionFactory {

    /**
     * 事务管理器属性
     * 事务管理器是可以灵活切换的。
     * 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;
    }

    /**
     * 获取Sql会话对象。
     * @return
     */
    public SqlSession openSession(){
        // 开启会话的前提是开启连接。(连接打开了)
        transaction.openConnection();
        // 创建SqlSession对象
        SqlSession sqlSession = new SqlSession(this);
        return sqlSession;
    }

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

    public SqlSessionFactory() {
    }
}

第十一步:编写SqlSession类

package org.example1.core;

import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;

/**
 * 专门负责执行SQL语句的会话对象
 */
public class SqlSession {
    private SqlSessionFactory factory;

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

    /**
     * 执行insert语句,向数据库表当中插入记录。
     * @param sqlId sql语句的id
     * @param pojo 插入的数据。
     * @return
     */
    public int insert(String sqlId, Object pojo){
        int count = 0;
        try {
            // JDBC代码,执行insert语句,完成插入操作。
            Connection connection = factory.getTransaction().getConnection();
            // insert into t_user values(#{id},#{name},#{age})
            String godbatisSql = factory.getMappedStatements().get(sqlId).getSql();
            // insert into t_user(id,name,age) values(?,?,?)
            String sql = godbatisSql.replaceAll("#\\{[a-zA-Z0-9_$]*}", "?");
            PreparedStatement ps = connection.prepareStatement(sql);
            // 给?占位符传值
            // 难度是什么:
            // 第一:你不知道有多少个?
            // 第二:你不知道该将pojo对象中的哪个属性赋值给哪个 ?
            // ps.String(第几个问号, 传什么值); // 这里都是setString,所以数据库表中的字段类型要求都是varchar才行。这是godbatis比较失败的地方。
            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;
                // 有属性名id,怎么获取id的属性值呢?调用getId()方法
                String getMethodName = "get" + propertyName.toUpperCase().charAt(0) + propertyName.substring(1);
                Method getMethod = pojo.getClass().getDeclaredMethod(getMethodName);
                Object propertyValue = getMethod.invoke(pojo);
                ps.setString(index, propertyValue.toString());
                index++;
            }

            // 执行SQL语句
            count = ps.executeUpdate();
        } catch (Exception e) {
            e.printStackTrace();
        }
        return count;
    }

    /**
     * 执行查询语句,返回一个对象。该方法只适合返回一条记录的sql语句。
     * @param sqlId
     * @param param
     * @return
     */
    public Object selectOne(String sqlId, Object param){
        Object obj = null;
        try {
            Connection connection = factory.getTransaction().getConnection();
            MappedStatement mappedStatement = factory.getMappedStatements().get(sqlId);
            // 这是那个DQL查询语句
            // select * from t_user where id = #{id}
            String godbatisSql = mappedStatement.getSql();
            String sql = godbatisSql.replaceAll("#\\{[a-zA-Z0-9_$]*}", "?");
            PreparedStatement ps = connection.prepareStatement(sql);
            // 给占位符传值
            ps.setString(1, param.toString());
            // 查询返回结果集
            ResultSet rs = ps.executeQuery();
            // 要封装的结果类型。
            String resultType = mappedStatement.getResultType(); // org.god.ibatis.pojo.User
            // 从结果集中取数据,封装java对象
            if (rs.next()) {
                // 获取resultType的Class
                Class<?> resultTypeClass = Class.forName(resultType);
                // 调用无参数构造方法创建对象
                obj = resultTypeClass.newInstance(); // Object obj = new User();
                // 给User类的id,name,age属性赋值
                // 给obj对象的哪个属性赋哪个值。
                /*
                mysql> select * from t_user where id = '1111';
                +------+----------+------+
                | id   | name     | age  |
                +------+----------+------+
                | 1111 | zhangsan | 20   |
                +------+----------+------+
                解决问题的关键:将查询结果的列名作为属性名。
                列名是id,那么属性名就是:id
                列名是name,那么属性名就是:name
                 */
                ResultSetMetaData rsmd = rs.getMetaData();
                int columnCount = rsmd.getColumnCount();
                for (int i = 0; i < columnCount; i++) {
                    String propertyName = rsmd.getColumnName(i + 1);
                    // 拼接方法名
                    String setMethodName = "set" + propertyName.toUpperCase().charAt(0) + propertyName.substring(1);
                    // 获取set方法
                    Method setMethod = resultTypeClass.getDeclaredMethod(setMethodName, String.class);
                    // 调用set方法给对象obj属性赋值
                    setMethod.invoke(obj, rs.getString(propertyName));
                }
            }

        } catch (Exception e) {
            e.printStackTrace();
        }
        return obj;
    }

    // 局部测试
    public static void main(String[] args) {
        String sql = "insert into t_user values(#{id},#{name},#{age})";
        int fromIndex = 0;
        int index = 1;
        while(true){
            int jingIndex = sql.indexOf("#", fromIndex);
            if (jingIndex < 0) {
                break;
            }
            System.out.println(index);
            index++;
            int youKuoHaoIndex = sql.indexOf("}", fromIndex);
            String propertyName = sql.substring(jingIndex + 2, youKuoHaoIndex).trim();
            System.out.println(propertyName);
            fromIndex = youKuoHaoIndex + 1;
        }
    }

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

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

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

}





三、GodBatis使用Maven打包

查看本地仓库中是否已经有jar包:

四、使用GodBatis

使用GodBatis就和使用MyBatis是一样的。

第一步:准备数据库表t_user1

第二步:创建模块,普通的Java Maven模块:godbatis-test

第三步:引入依赖

<?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.example1</groupId>
    <artifactId>godbatis-test</artifactId>
    <version>1.0-SNAPSHOT</version>

    <properties>
        <maven.compiler.source>17</maven.compiler.source>
        <maven.compiler.target>17</maven.compiler.target>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
    </properties>
    <!--依赖-->
    <dependencies>
        <!--godbatis依赖-->
        <dependency>
            <groupId>org.example1</groupId>
            <artifactId>godbatis</artifactId>
            <version>1.0</version>
        </dependency>
        <!--引入junit-->
        <dependency>
            <groupId>junit</groupId>
            <artifactId>junit</artifactId>
            <version>4.13.2</version>
            <scope>test</scope>
        </dependency>
        <!--mysql-->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <version>8.0.30</version>
        </dependency>
    </dependencies>
</project>

第四步:编写pojo类

package org.example1.pojo;

public class User {
    private String id;
    private String name;
    private String age;

    public User(String id, String name, String age) {
        this.id = id;
        this.name = name;
        this.age = age;
    }

    public User() {
    }

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

    public String getId() {
        return id;
    }

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

    public String getName() {
        return name;
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getAge() {
        return age;
    }

    public void setAge(String age) {
        this.age = age;
    }
}

第五步:编写核心配置文件:godbatis-config.xml

<?xml version="1.0" encoding="UTF-8" ?>

<configuration>
    <environments default="dev">
        <environment id="dev">
            <transactionManager type="JDBC"/>
            <dataSource type="UNPOOLED">
                <property name="driver" value="com.mysql.cj.jdbc.Driver"/>
                <property name="url" value="jdbc:mysql://localhost:3306/jdbc"/>
                <property name="username" value="root"/>
                <property name="password" value="abc123"/>
            </dataSource>
        </environment>
        <mappers>
            <mapper resource="UserMapper.xml"/>
        </mappers>
    </environments>
</configuration>

第六步:编写sql映射文件:UserMapper.xml

<?xml version="1.0" encoding="UTF-8" ?>

<mapper namespace="user">

    <insert id="insertUser">
        insert into t_user1 values(#{id},#{name},#{age})
    </insert>

    <select id="selectById" resultType="org.example1.pojo.User">
        select * from t_user1 where id = #{id}
    </select>

</mapper>

第七步:编写测试类

package oop;

import org.example1.core.SqlSession;
import org.example1.core.SqlSessionFactory;
import org.example1.core.SqlSessionFactoryBuilder;
import org.example1.pojo.User;
import org.example1.utils.Resources;
import org.junit.Test;

public class UserMapperTest {
    @Test
    public void testInsertUser(){
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("godbatis-config.xml"));
        SqlSession sqlSession = sqlSessionFactory.openSession();
        // 执行SQL
        User user = new User("3333", "孙悟空", "5000");
        int count = sqlSession.insert("user.insertUser", user);
        System.out.println(count);
        sqlSession.commit();
        sqlSession.close();
    }

    @Test
    public void testSelectById(){
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream("godbatis-config.xml"));
        SqlSession sqlSession = sqlSessionFactory.openSession();
        Object obj = sqlSession.selectOne("user.selectById", "3333");
        System.out.println(obj);
        sqlSession.close();
    }
}

第八步:运行结果

五、总结MyBatis框架的重要实现原理

<?xml version="1.0" encoding="UTF-8" ?>

<mapper namespace="user">

    <insert id="insertUser">
        insert into t_user1 values(#{id},#{name},#{age})
    </insert>

    <select id="selectById" resultType="org.example1.pojo.User">
        select * from t_user1 where id = #{id}
    </select>

</mapper>

思考两个问题:

  • 为什么insert语句中 #{} 里填写的必须是属性名?

  • 为什么select语句查询结果列名要属性名一致?


文章转载自:

http://dndRsZMk.qxwgx.cn
http://OI5Pk0zh.qxwgx.cn
http://eltGo7Am.qxwgx.cn
http://8q6JXCXn.qxwgx.cn
http://MTyvrd8Q.qxwgx.cn
http://FyrSLkVc.qxwgx.cn
http://FqFVGVdf.qxwgx.cn
http://CGamxTQl.qxwgx.cn
http://HdyCbggu.qxwgx.cn
http://LBqHDjzv.qxwgx.cn
http://nZ6bp2zf.qxwgx.cn
http://zzZIi5ih.qxwgx.cn
http://1G2IUEDT.qxwgx.cn
http://ar47dL5r.qxwgx.cn
http://s0tn35Vu.qxwgx.cn
http://A7ha197q.qxwgx.cn
http://xuobeYvf.qxwgx.cn
http://JWmSU65t.qxwgx.cn
http://Cljngy6m.qxwgx.cn
http://0mv5xuA1.qxwgx.cn
http://s0Uo7qIV.qxwgx.cn
http://mz2AohiS.qxwgx.cn
http://KH87PvpJ.qxwgx.cn
http://AQxGJ1SL.qxwgx.cn
http://T5HuQ8D1.qxwgx.cn
http://tOk9rkAS.qxwgx.cn
http://UmeskOTC.qxwgx.cn
http://XMgn8FLs.qxwgx.cn
http://bk3YQ5Qe.qxwgx.cn
http://nIze4n7O.qxwgx.cn
http://www.dtcms.com/a/116368.html

相关文章:

  • 七、C++速通秘籍—静态多态(编译期)
  • 预测函数控制(PFC)——理论、应用与实践
  • 学透Spring Boot — 014. Spring MVC的自动配置
  • CANoe CAPL——CAN CAPL函数
  • jQuery 文本属性值
  • OceanBase生态2.0:如何实现“三分天下有其一”?
  • 应用层自定义协议与序列化
  • 【AI提示词】大学教授学术阅读(读论文)
  • 基于SpringBoot的售楼管理系统【附源码】
  • 记一次常规的网络安全渗透测试
  • SpringMVC与SpringCloud的区别
  • 区块链赋能知识产权保护:用技术捍卫创作者的权利
  • 下载安装Node.js及其他环境
  • 什么是异步?
  • ChatGPT-4o 在汉字显示上进步巨大
  • 解锁多邻国:全方位语言学习新体验
  • Gateway 网关 快速开始
  • NAT技术、代理服务器和内网穿透
  • Dubbo(36)如何进行Dubbo的性能调优?
  • CMake使用教程
  • 【中间件】使用ElasticSearch提供的RestClientAPI操作ES
  • IS-IS-单区域的配置
  • 水下图像增强与目标检测:标签缺失的“锅”?
  • 爬虫工程师杂活工具人
  • Databend Cloud Dashboard 全新升级:直击痛点,释放数据价值
  • 【36期获取股票数据API接口】如何用Python、Java等五种主流语言实例演示获取股票行情api接口之沪深A股当天逐笔大单交易数据及接口API说明文档
  • Java面试32-对Spring Cloud的理解
  • 美团Leaf分布式ID生成器:雪花算法原理与应用
  • macOS可视化桌面配置docker加速器
  • 找树左下角的值(DFS 深度优先搜索)| LeetCode 513