【手写Mybatis | version0.0.1 附带源码 项目文档】

Easy-mybatis version 0.0.1

  1. 配置加载(SqlSessionFactoryBuilder + Resources) 业务代码调用SqlSessionFactoryBuilder.build(Resources.getResourceAsStream("mybatis-config.xml"))

    • Resources加载类路径下的mybatis-config.xml,返回输入流;

    • 建造者解析 XML,初始化EasyMybatisUNPOOLEDDataSource(数据源);

    • 基于数据源创建EasyMybatisJDBCTransaction(事务管理器);

    • 解析 Mapper XML(如UserMapper.xml),生成EasyMybatisMappedStatement并放入 Map;

    • 组装SqlSessionFactory(包含事务管理器 + SQL 映射 Map)。

  2. 获取操作入口(SqlSessionFactory) 调用SqlSessionFactory.openSession(),创建DefaultSqlSession实例(注入事务管理器和 SQL 映射 Map)。

  3. 执行 SQL(DefaultSqlSession) 调用defaultSqlSession.selectOne("com.xxx.UserMapper.selectById", 1)

    • 通过 SQL id 从mappedStatements中获取对应的EasyMybatisMappedStatement

    • TransactionManager中获取Connection

    • 预编译 SQL(PreparedStatement ps = conn.prepareStatement(ms.getSql()));

    • 设置参数(ps.setInt(1, 1));

    • 执行 SQL(ResultSet rs = ps.executeQuery());

    • ResultSet封装为User对象并返回。

  4. 事务控制(TransactionManager)

    • 若操作成功:调用defaultSqlSession.commit() → 代理调用TransactionManager.commit()

    • 若操作失败:调用defaultSqlSession.rollback() → 代理调用TransactionManager.rollback()

    • 最终调用close()关闭连接,释放资源。

源码

复制代码
​
​
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
​
package com.rongx.core;
​
import java.sql.Connection;
import java.sql.SQLException;
import javax.sql.DataSource;
​
public class EasyMybatisJDBCTransaction implements TransactionManager {
    private Connection conn;
    private DataSource dataSource;
    private boolean autoCommit;
​
    public EasyMybatisJDBCTransaction(DataSource dataSource, boolean autoCommit) {
        this.dataSource = dataSource;
        this.autoCommit = autoCommit;
    }
​
    public void commit() {
        try {
            this.conn.commit();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
​
    public void rollback() {
        try {
            this.conn.rollback();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
​
    public void close() {
        try {
            this.conn.close();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
​
    public void openConnection() {
        try {
            this.conn = this.dataSource.getConnection();
            this.conn.setAutoCommit(this.autoCommit);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
​
    public Connection getConnection() {
        return this.conn;
    }
}
复制代码
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
​
package com.rongx.core;
​
public class EasyMybatisMappedStatement {
    private String sqlId;
    private String resultType;
    private String sql;
    private String parameterType;
    private String sqlType;
​
    public String toString() {
        return "GodMappedStatement{sqlId='" + this.sqlId + "', resultType='" + this.resultType + "', sql='" + this.sql + "', parameterType='" + this.parameterType + "', sqlType='" + this.sqlType + "'}";
    }
​
    public String getSqlId() {
        return this.sqlId;
    }
​
    public void setSqlId(String sqlId) {
        this.sqlId = sqlId;
    }
​
    public String getResultType() {
        return this.resultType;
    }
​
    public void setResultType(String resultType) {
        this.resultType = resultType;
    }
​
    public String getSql() {
        return this.sql;
    }
​
    public void setSql(String sql) {
        this.sql = sql;
    }
​
    public String getParameterType() {
        return this.parameterType;
    }
​
    public void setParameterType(String parameterType) {
        this.parameterType = parameterType;
    }
​
    public String getSqlType() {
        return this.sqlType;
    }
​
    public void setSqlType(String sqlType) {
        this.sqlType = sqlType;
    }
​
    public EasyMybatisMappedStatement(String sqlId, String resultType, String sql, String parameterType, String sqlType) {
        this.sqlId = sqlId;
        this.resultType = resultType;
        this.sql = sql;
        this.parameterType = parameterType;
        this.sqlType = sqlType;
    }
}
复制代码
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
​
package com.rongx.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;
import javax.sql.DataSource;
​
public class EasyMybatisUNPOOLEDDataSource implements DataSource {
    private String url;
    private String username;
    private String password;
​
    public EasyMybatisUNPOOLEDDataSource(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;
    }
​
    public Connection getConnection() throws SQLException {
        return DriverManager.getConnection(this.url, this.username, this.password);
    }
​
    public Connection getConnection(String username, String password) throws SQLException {
        return null;
    }
​
    public PrintWriter getLogWriter() throws SQLException {
        return null;
    }
​
    public void setLogWriter(PrintWriter out) throws SQLException {
    }
​
    public void setLoginTimeout(int seconds) throws SQLException {
    }
​
    public int getLoginTimeout() throws SQLException {
        return 0;
    }
​
    public Logger getParentLogger() throws SQLFeatureNotSupportedException {
        return null;
    }
​
    public <T> T unwrap(Class<T> iface) throws SQLException {
        return null;
    }
​
    public boolean isWrapperFor(Class<?> iface) throws SQLException {
        return false;
    }
}
复制代码
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
​
package com.rongx.core;
​
import java.io.InputStream;
​
public class Resources {
    public static InputStream getResourcesAsStream(String config) {
        return Thread.currentThread().getContextClassLoader().getResourceAsStream(config);
    }
}
复制代码
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
​
package com.rongx.core;
​
import java.lang.reflect.Constructor;
import java.lang.reflect.Method;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
​
public class SqlSession {
    private TransactionManager transactionManager;
    private Map<String, EasyMybatisMappedStatement> mappedStatements;
​
    public SqlSession(TransactionManager transactionManager, Map<String, EasyMybatisMappedStatement> mappedStatements) {
        this.transactionManager = transactionManager;
        this.mappedStatements = mappedStatements;
    }
​
    public void commit() {
        try {
            this.transactionManager.getConnection().commit();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
​
    public void rollback() {
        try {
            this.transactionManager.getConnection().rollback();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
​
    public void close() {
        try {
            this.transactionManager.getConnection().close();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
​
    public int insert(String sqlId, Object obj) {
        EasyMybatisMappedStatement easyMybatisMappedStatement = (EasyMybatisMappedStatement)this.mappedStatements.get(sqlId);
        Connection connection = this.transactionManager.getConnection();
        String easyMybatisbatisSql = easyMybatisMappedStatement.getSql();
        String sql = easyMybatisbatisSql.replaceAll("#\\{[a-zA-Z0-9_\\$]*}", "?");
        Map<Integer, String> map = new HashMap();
​
        int endIndex;
        for(int index = 1; easyMybatisbatisSql.indexOf("#") >= 0; easyMybatisbatisSql = easyMybatisbatisSql.substring(endIndex + 1)) {
            int beginIndex = easyMybatisbatisSql.indexOf("#") + 2;
            endIndex = easyMybatisbatisSql.indexOf("}");
            map.put(index++, easyMybatisbatisSql.substring(beginIndex, endIndex).trim());
        }
​
        try {
            PreparedStatement ps = connection.prepareStatement(sql);
            map.forEach((k, v) -> {
                try {
                    char var10000 = v.toUpperCase().charAt(0);
                    String getMethodName = "get" + var10000 + v.substring(1);
                    Method getMethod = obj.getClass().getDeclaredMethod(getMethodName);
                    ps.setString(k, getMethod.invoke(obj).toString());
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            });
            endIndex = ps.executeUpdate();
            ps.close();
            return endIndex;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
​
    public int update(String sqlId, Object obj) {
        EasyMybatisMappedStatement easyMybatisMappedStatement = (EasyMybatisMappedStatement)this.mappedStatements.get(sqlId);
        Connection connection = this.transactionManager.getConnection();
        String easyMybatisbatisSql = easyMybatisMappedStatement.getSql();
        String sql = easyMybatisbatisSql.replaceAll("#\\{[a-zA-Z0-9_\\$]*}", "?");
        Map<Integer, String> map = new HashMap();
​
        int endIndex;
        for(int index = 1; easyMybatisbatisSql.indexOf("#") >= 0; easyMybatisbatisSql = easyMybatisbatisSql.substring(endIndex + 1)) {
            int beginIndex = easyMybatisbatisSql.indexOf("#") + 2;
            endIndex = easyMybatisbatisSql.indexOf("}");
            map.put(index++, easyMybatisbatisSql.substring(beginIndex, endIndex).trim());
        }
​
        try {
            PreparedStatement ps = connection.prepareStatement(sql);
            map.forEach((k, v) -> {
                try {
                    char var10000 = v.toUpperCase().charAt(0);
                    String getMethodName = "get" + var10000 + v.substring(1);
                    Method getMethod = obj.getClass().getDeclaredMethod(getMethodName);
                    ps.setString(k, getMethod.invoke(obj).toString());
                } catch (Exception e) {
                    throw new RuntimeException(e);
                }
            });
            endIndex = ps.executeUpdate();
            ps.close();
            return endIndex;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
​
    public int delete(String sqlId, Object parameterObj) {
        EasyMybatisMappedStatement easyMybatisMappedStatement = (EasyMybatisMappedStatement)this.mappedStatements.get(sqlId);
        Connection connection = this.transactionManager.getConnection();
        String easyMybatisbatisSql = easyMybatisMappedStatement.getSql();
        String sql = easyMybatisbatisSql.replaceAll("#\\{[a-zA-Z0-9_\\$]*}", "?");
        Map<Integer, String> map = new HashMap();
​
        int endIndex;
        for(int index = 1; easyMybatisbatisSql.indexOf("#") >= 0; easyMybatisbatisSql = easyMybatisbatisSql.substring(endIndex + 1)) {
            int beginIndex = easyMybatisbatisSql.indexOf("#") + 2;
            endIndex = easyMybatisbatisSql.indexOf("}");
            map.put(index++, easyMybatisbatisSql.substring(beginIndex, endIndex).trim());
        }
​
        try {
            PreparedStatement ps = connection.prepareStatement(sql);
            if (map.size() == 1 && parameterObj != null) {
                ps.setString(1, parameterObj.toString());
            } else if (parameterObj != null) {
                map.forEach((k, v) -> {
                    try {
                        char var10000 = v.toUpperCase().charAt(0);
                        String getMethodName = "get" + var10000 + v.substring(1);
                        Method getMethod = parameterObj.getClass().getDeclaredMethod(getMethodName);
                        ps.setString(k, getMethod.invoke(parameterObj).toString());
                    } catch (Exception e) {
                        throw new RuntimeException(e);
                    }
                });
            }
​
            endIndex = ps.executeUpdate();
            ps.close();
            return endIndex;
        } catch (Exception e) {
            throw new RuntimeException(e);
        }
    }
​
    public Object selectOne(String sqlId, Object parameterObj) {
        EasyMybatisMappedStatement easyMybatisMappedStatement = (EasyMybatisMappedStatement)this.mappedStatements.get(sqlId);
        Connection connection = this.transactionManager.getConnection();
        String easyMybatisbatisSql = easyMybatisMappedStatement.getSql();
        String sql = easyMybatisbatisSql.replaceAll("#\\{[a-zA-Z0-9_\\$]*}", "?");
        PreparedStatement ps = null;
        ResultSet rs = null;
        Object obj = null;
​
        try {
            ps = connection.prepareStatement(sql);
            ps.setString(1, parameterObj.toString());
            rs = ps.executeQuery();
            if (rs.next()) {
                String resultType = easyMybatisMappedStatement.getResultType();
                Class<?> aClass = Class.forName(resultType);
                Constructor<?> con = aClass.getDeclaredConstructor();
                obj = con.newInstance();
                ResultSetMetaData rsmd = rs.getMetaData();
                int columnCount = rsmd.getColumnCount();
​
                for(int i = 1; i <= columnCount; ++i) {
                    String columnName = rsmd.getColumnName(i);
                    char var10000 = columnName.toUpperCase().charAt(0);
                    String setMethodName = "set" + var10000 + columnName.substring(1);
                    Method setMethod = aClass.getDeclaredMethod(setMethodName, aClass.getDeclaredField(columnName).getType());
                    setMethod.invoke(obj, rs.getString(columnName));
                }
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            if (rs != null) {
                try {
                    rs.close();
                } catch (SQLException e) {
                    throw new RuntimeException(e);
                }
            }
​
            try {
                ps.close();
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }
​
        return obj;
    }
​
    public List<Object> selectList(String sqlId, Object parameterObj) {
        EasyMybatisMappedStatement easyMybatisMappedStatement = (EasyMybatisMappedStatement)this.mappedStatements.get(sqlId);
        Connection connection = this.transactionManager.getConnection();
        String easyMybatisbatisSql = easyMybatisMappedStatement.getSql();
        String sql = easyMybatisbatisSql.replaceAll("#\\{[a-zA-Z0-9_\\$]*}", "?");
        PreparedStatement ps = null;
        ResultSet rs = null;
        List<Object> list = new ArrayList();
​
        try {
            ps = connection.prepareStatement(sql);
            if (parameterObj != null) {
                ps.setString(1, parameterObj.toString());
            }
​
            rs = ps.executeQuery();
            String resultType = easyMybatisMappedStatement.getResultType();
            Class<?> aClass = Class.forName(resultType);
            Constructor<?> con = aClass.getDeclaredConstructor();
            ResultSetMetaData rsmd = rs.getMetaData();
            int columnCount = rsmd.getColumnCount();
            Map<String, Class<?>> fieldTypeMap = new HashMap();
​
            for(int i = 1; i <= columnCount; ++i) {
                String columnName = rsmd.getColumnName(i);
​
                try {
                    fieldTypeMap.put(columnName, aClass.getDeclaredField(columnName).getType());
                } catch (Exception var35) {
                }
            }
​
            while(rs.next()) {
                Object obj = con.newInstance();
​
                for(int i = 1; i <= columnCount; ++i) {
                    String columnName = rsmd.getColumnName(i);
                    char var10000 = columnName.toUpperCase().charAt(0);
                    String setMethodName = "set" + var10000 + columnName.substring(1);
​
                    try {
                        Class<?> fieldType = (Class)fieldTypeMap.get(columnName);
                        Method setMethod = aClass.getDeclaredMethod(setMethodName, fieldType);
                        Object value = rs.getObject(columnName);
                        if (fieldType == String.class) {
                            setMethod.invoke(obj, rs.getString(columnName));
                        } else if (fieldType != Integer.class && fieldType != Integer.TYPE) {
                            if (fieldType != Long.class && fieldType != Long.TYPE) {
                                setMethod.invoke(obj, value);
                            } else {
                                setMethod.invoke(obj, rs.getLong(columnName));
                            }
                        } else {
                            setMethod.invoke(obj, rs.getInt(columnName));
                        }
                    } catch (Exception var36) {
                    }
                }
​
                list.add(obj);
            }
        } catch (Exception e) {
            throw new RuntimeException(e);
        } finally {
            if (rs != null) {
                try {
                    rs.close();
                } catch (SQLException e) {
                    throw new RuntimeException(e);
                }
            }
​
            try {
                ps.close();
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }
​
        return list;
    }
}
复制代码
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
​
package com.rongx.core;
​
import java.util.Map;
​
public class SqlSessionFactory {
    private TransactionManager transactionManager;
    private Map<String, EasyMybatisMappedStatement> mappedStatements;
​
    public SqlSessionFactory(TransactionManager transactionManager, Map<String, EasyMybatisMappedStatement> mappedStatements) {
        this.transactionManager = transactionManager;
        this.mappedStatements = mappedStatements;
    }
​
    public TransactionManager getTransactionManager() {
        return this.transactionManager;
    }
​
    public void setTransactionManager(TransactionManager transactionManager) {
        this.transactionManager = transactionManager;
    }
​
    public Map<String, EasyMybatisMappedStatement> getMappedStatements() {
        return this.mappedStatements;
    }
​
    public void setMappedStatements(Map<String, EasyMybatisMappedStatement> mappedStatements) {
        this.mappedStatements = mappedStatements;
    }
​
    public SqlSession openSession() {
        this.transactionManager.openConnection();
        SqlSession sqlSession = new SqlSession(this.transactionManager, this.mappedStatements);
        return sqlSession;
    }
}
复制代码
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
​
package com.rongx.core;
​
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import javax.sql.DataSource;
import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
​
public class SqlSessionFactoryBuilder {
    public SqlSessionFactory build(InputStream inputStream) throws DocumentException {
        SAXReader saxReader = new SAXReader();
        Document document = saxReader.read(inputStream);
        Element environmentsElt = (Element)document.selectSingleNode("/configuration/environments");
        String defaultEnv = environmentsElt.attributeValue("default");
        Element environmentElt = (Element)document.selectSingleNode("/configuration/environments/environment[@id='" + defaultEnv + "']");
        Element dataSourceElt = environmentElt.element("dataSource");
        DataSource dataSource = this.getDataSource(dataSourceElt);
        Element transactionManagerElt = environmentElt.element("transactionManager");
        TransactionManager transactionManager = this.getTransactionManager(transactionManagerElt, dataSource);
        Element mappers = environmentsElt.element("mappers");
        Map<String, EasyMybatisMappedStatement> mappedStatements = this.getMappedStatements(mappers);
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactory(transactionManager, mappedStatements);
        return sqlSessionFactory;
    }
​
    private Map<String, EasyMybatisMappedStatement> getMappedStatements(Element mappers) {
        Map<String, EasyMybatisMappedStatement> mappedStatements = new HashMap();
        mappers.elements().forEach((mapperElt) -> {
            try {
                String resource = mapperElt.attributeValue("resource");
                SAXReader saxReader = new SAXReader();
                Document document = saxReader.read(Resources.getResourcesAsStream(resource));
                Element mapper = (Element)document.selectSingleNode("/mapper");
                String namespace = mapper.attributeValue("namespace");
                mapper.elements().forEach((sqlMapper) -> {
                    String sqlId = sqlMapper.attributeValue("id");
                    String sql = sqlMapper.getTextTrim();
                    String parameterType = sqlMapper.attributeValue("parameterType");
                    String resultType = sqlMapper.attributeValue("resultType");
                    String sqlType = sqlMapper.getName().toLowerCase();
                    EasyMybatisMappedStatement easyMybatisMappedStatement = new EasyMybatisMappedStatement(sqlId, resultType, sql, parameterType, sqlType);
                    mappedStatements.put(namespace + "." + sqlId, easyMybatisMappedStatement);
                });
            } catch (DocumentException e) {
                throw new RuntimeException(e);
            }
        });
        return mappedStatements;
    }
​
    private TransactionManager getTransactionManager(Element transactionManagerElt, DataSource dataSource) {
        String type = transactionManagerElt.attributeValue("type").toUpperCase();
        TransactionManager transactionManager = null;
        if ("JDBC".equals(type)) {
            transactionManager = new EasyMybatisJDBCTransaction(dataSource, false);
        } else if ("MANAGED".equals(type)) {
        }
​
        return transactionManager;
    }
​
    private DataSource getDataSource(Element dataSourceElt) {
        Map<String, String> dataSourceMap = new HashMap();
        dataSourceElt.elements().forEach((propertyElt) -> dataSourceMap.put(propertyElt.attributeValue("name"), propertyElt.attributeValue("value")));
        String dataSourceType = dataSourceElt.attributeValue("type").toUpperCase();
        DataSource dataSource = null;
        if (!"POOLED".equals(dataSourceType)) {
            if ("UNPOOLED".equals(dataSourceType)) {
                dataSource = new EasyMybatisUNPOOLEDDataSource((String)dataSourceMap.get("driver"), (String)dataSourceMap.get("url"), (String)dataSourceMap.get("username"), (String)dataSourceMap.get("password"));
            } else if ("JNDI".equals(dataSourceType)) {
            }
        }
​
        return dataSource;
    }
}
复制代码
//
// Source code recreated from a .class file by IntelliJ IDEA
// (powered by FernFlower decompiler)
//
​
package com.rongx.core;
​
import java.sql.Connection;
import java.sql.SQLException;
import javax.sql.DataSource;
​
public class EasyMybatisJDBCTransaction implements TransactionManager {
    private Connection conn;
    private DataSource dataSource;
    private boolean autoCommit;
​
    public EasyMybatisJDBCTransaction(DataSource dataSource, boolean autoCommit) {
        this.dataSource = dataSource;
        this.autoCommit = autoCommit;
    }
​
    public void commit() {
        try {
            this.conn.commit();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
​
    public void rollback() {
        try {
            this.conn.rollback();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
​
    public void close() {
        try {
            this.conn.close();
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
​
    public void openConnection() {
        try {
            this.conn = this.dataSource.getConnection();
            this.conn.setAutoCommit(this.autoCommit);
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
​
    public Connection getConnection() {
        return this.conn;
    }
}

easy-mybatis 0.0.1 完整代码讲解

我将结合完整的代码,详细讲解这个简化版MyBatis框架的实现原理和工作流程。

项目整体架构

复制代码
easy-mybatis 0.0.1
├── 配置文件解析层 (SqlSessionFactoryBuilder)
├── 会话管理层 (SqlSessionFactory, SqlSession)
├── 数据操作层 (SqlSession的CRUD方法)
├── 事务管理层 (EasyMybatisJDBCTransaction)
├── 数据源层 (EasyMybatisUNPOOLEDDataSource)
├── 映射配置层 (EasyMybatisMappedStatement)
└── 资源工具层 (Resources)

1. 配置解析与初始化流程

1.1 Resources - 资源加载工具类

复制代码
package com.rongx.core;
​
import java.io.InputStream;
​
public class Resources {
    public static InputStream getResourcesAsStream(String config) {
        return Thread.currentThread().getContextClassLoader().getResourceAsStream(config);
    }
}

作用 :从类路径加载配置文件,为框架提供统一的资源访问入口。

1.2 SqlSessionFactoryBuilder - 工厂建造者

这是框架的启动入口,负责解析XML配置文件并构建SqlSessionFactory。

复制代码
public class SqlSessionFactoryBuilder {
    public SqlSessionFactory build(InputStream inputStream) throws DocumentException {
        // 1. 读取主配置文件
        SAXReader saxReader = new SAXReader();
        Document document = saxReader.read(inputStream);
        
        // 2. 解析环境配置
        Element environmentsElt = (Element)document.selectSingleNode("/configuration/environments");
        String defaultEnv = environmentsElt.attributeValue("default");
        Element environmentElt = (Element)document.selectSingleNode(
            "/configuration/environments/environment[@id='" + defaultEnv + "']");
        
        // 3. 创建数据源
        Element dataSourceElt = environmentElt.element("dataSource");
        DataSource dataSource = this.getDataSource(dataSourceElt);
        
        // 4. 创建事务管理器
        Element transactionManagerElt = environmentElt.element("transactionManager");
        TransactionManager transactionManager = this.getTransactionManager(transactionManagerElt, dataSource);
        
        // 5. 加载Mapper文件并解析SQL映射
        Element mappers = environmentsElt.element("mappers");
        Map<String, EasyMybatisMappedStatement> mappedStatements = this.getMappedStatements(mappers);
        
        // 6. 创建SqlSessionFactory
        SqlSessionFactory sqlSessionFactory = new SqlSessionFactory(transactionManager, mappedStatements);
        return sqlSessionFactory;
    }
    
    private Map<String, EasyMybatisMappedStatement> getMappedStatements(Element mappers) {
        Map<String, EasyMybatisMappedStatement> mappedStatements = new HashMap();
        mappers.elements().forEach((mapperElt) -> {
            try {
                // 读取Mapper XML文件
                String resource = mapperElt.attributeValue("resource");
                SAXReader saxReader = new SAXReader();
                Document document = saxReader.read(Resources.getResourcesAsStream(resource));
                
                // 解析Mapper中的SQL语句
                Element mapper = (Element)document.selectSingleNode("/mapper");
                String namespace = mapper.attributeValue("namespace");
                
                mapper.elements().forEach((sqlMapper) -> {
                    // 提取SQL配置信息
                    String sqlId = sqlMapper.attributeValue("id");
                    String sql = sqlMapper.getTextTrim();
                    String parameterType = sqlMapper.attributeValue("parameterType");
                    String resultType = sqlMapper.attributeValue("resultType");
                    String sqlType = sqlMapper.getName().toLowerCase(); // select/insert/update/delete
                    
                    // 创建MappedStatement并存储
                    EasyMybatisMappedStatement easyMybatisMappedStatement = 
                        new EasyMybatisMappedStatement(sqlId, resultType, sql, parameterType, sqlType);
                    mappedStatements.put(namespace + "." + sqlId, easyMybatisMappedStatement);
                });
            } catch (DocumentException e) {
                throw new RuntimeException(e);
            }
        });
        return mappedStatements;
    }
    
    private TransactionManager getTransactionManager(Element transactionManagerElt, DataSource dataSource) {
        String type = transactionManagerElt.attributeValue("type").toUpperCase();
        TransactionManager transactionManager = null;
        if ("JDBC".equals(type)) {
            // 创建JDBC事务管理器,autoCommit设置为false(手动提交)
            transactionManager = new EasyMybatisJDBCTransaction(dataSource, false);
        }
        return transactionManager;
    }
    
    private DataSource getDataSource(Element dataSourceElt) {
        // 提取数据源配置属性
        Map<String, String> dataSourceMap = new HashMap();
        dataSourceElt.elements().forEach((propertyElt) -> 
            dataSourceMap.put(propertyElt.attributeValue("name"), propertyElt.attributeValue("value")));
        
        String dataSourceType = dataSourceElt.attributeValue("type").toUpperCase();
        DataSource dataSource = null;
        if ("UNPOOLED".equals(dataSourceType)) {
            // 创建非池化数据源
            dataSource = new EasyMybatisUNPOOLEDDataSource(
                dataSourceMap.get("driver"), 
                dataSourceMap.get("url"), 
                dataSourceMap.get("username"), 
                dataSourceMap.get("password")
            );
        }
        return dataSource;
    }
}

1.3 EasyMybatisMappedStatement - SQL映射模型

复制代码
public class EasyMybatisMappedStatement {
    private String sqlId;          // SQL语句ID,如"selectUserById"
    private String resultType;     // 返回类型,如"com.example.User"
    private String sql;            // SQL语句,如"SELECT * FROM user WHERE id = #{id}"
    private String parameterType;  // 参数类型,如"java.lang.String"
    private String sqlType;        // SQL类型,如"select"
    
    // 构造函数和getter/setter
}

作用 :封装一个SQL语句的所有配置信息。

2. 数据源与事务管理

2.1 EasyMybatisUNPOOLEDDataSource - 非池化数据源

复制代码
public class EasyMybatisUNPOOLEDDataSource implements DataSource {
    private String url;
    private String username;
    private String password;
​
    public EasyMybatisUNPOOLEDDataSource(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;
    }
​
    public Connection getConnection() throws SQLException {
        // 每次调用都创建新连接(非池化)
        return DriverManager.getConnection(this.url, this.username, this.password);
    }
    
    // 其他DataSource接口方法(简单实现或返回null)
}

2.2 EasyMybatisJDBCTransaction - JDBC事务管理器

复制代码
public class EasyMybatisJDBCTransaction implements TransactionManager {
    private Connection conn;
    private DataSource dataSource;
    private boolean autoCommit;  // 通常设置为false,手动控制事务
​
    public void openConnection() {
        try {
            this.conn = this.dataSource.getConnection();
            this.conn.setAutoCommit(this.autoCommit);  // 设置手动提交
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
​
    public void commit() {
        try {
            this.conn.commit();  // 提交事务
        } catch (SQLException e) {
            throw new RuntimeException(e);
        }
    }
    
    // 其他事务操作方法...
}

3. 核心执行流程

3.1 SqlSessionFactory - 会话工厂

复制代码
public class SqlSessionFactory {
    private TransactionManager transactionManager;
    private Map<String, EasyMybatisMappedStatement> mappedStatements;
​
    public SqlSession openSession() {
        this.transactionManager.openConnection();  // 打开数据库连接
        SqlSession sqlSession = new SqlSession(this.transactionManager, this.mappedStatements);
        return sqlSession;
    }
}

3.2 SqlSession - 核心会话类(重点)

3.2.1 SQL参数处理机制

框架使用#{}作为参数占位符,需要将其转换为JDBC的?

复制代码
// 以insert方法为例,展示参数处理流程:
public int insert(String sqlId, Object obj) {
    // 1. 获取SQL映射配置
    EasyMybatisMappedStatement easyMybatisMappedStatement = this.mappedStatements.get(sqlId);
    
    // 2. 替换#{}为?
    String easyMybatisbatisSql = easyMybatisMappedStatement.getSql();
    String sql = easyMybatisbatisSql.replaceAll("#\\{[a-zA-Z0-9_\\$]*}", "?");
    
    // 3. 提取参数名映射关系
    Map<Integer, String> map = new HashMap();
    for(int index = 1; easyMybatisbatisSql.indexOf("#") >= 0; easyMybatisbatisSql = easyMybatisbatisSql.substring(endIndex + 1)) {
        int beginIndex = easyMybatisbatisSql.indexOf("#") + 2;  // 跳过"#{"
        int endIndex = easyMybatisbatisSql.indexOf("}");
        map.put(index++, easyMybatisbatisSql.substring(beginIndex, endIndex).trim());
    }
    
    // 4. 设置PreparedStatement参数
    try {
        PreparedStatement ps = connection.prepareStatement(sql);
        map.forEach((k, v) -> {
            try {
                // 通过反射调用getter方法获取参数值
                // 例如:#{userName} -> getUserName()
                char var10000 = v.toUpperCase().charAt(0);
                String getMethodName = "get" + var10000 + v.substring(1);
                Method getMethod = obj.getClass().getDeclaredMethod(getMethodName);
                ps.setString(k, getMethod.invoke(obj).toString());  // 设置为字符串
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        });
        int endIndex = ps.executeUpdate();
        ps.close();
        return endIndex;
    } catch (Exception e) {
        throw new RuntimeException(e);
    }
}
3.2.2 结果集映射机制

selectOne方法为例:

复制代码
public Object selectOne(String sqlId, Object parameterObj) {
    // 1. 执行查询
    PreparedStatement ps = connection.prepareStatement(sql);
    ps.setString(1, parameterObj.toString());  // 设置参数
    ResultSet rs = ps.executeQuery();
    
    // 2. 如果有结果,开始映射
    if (rs.next()) {
        String resultType = easyMybatisMappedStatement.getResultType();
        Class<?> aClass = Class.forName(resultType);  // 加载返回类型类
        
        // 3. 创建返回对象实例
        Constructor<?> con = aClass.getDeclaredConstructor();
        Object obj = con.newInstance();
        
        // 4. 获取ResultSet元数据
        ResultSetMetaData rsmd = rs.getMetaData();
        int columnCount = rsmd.getColumnCount();
        
        // 5. 遍历每一列,设置对象属性
        for(int i = 1; i <= columnCount; ++i) {
            String columnName = rsmd.getColumnName(i);
            // 生成setter方法名,如:user_name -> setUserName
            char var10000 = columnName.toUpperCase().charAt(0);
            String setMethodName = "set" + var10000 + columnName.substring(1);
            
            // 获取字段类型,调用对应的setter方法
            Method setMethod = aClass.getDeclaredMethod(setMethodName, 
                aClass.getDeclaredField(columnName).getType());
            setMethod.invoke(obj, rs.getString(columnName));
        }
        return obj;
    }
}
3.2.3 selectList方法的类型处理
复制代码
// selectList方法中处理不同类型字段的逻辑
if (fieldType == String.class) {
    setMethod.invoke(obj, rs.getString(columnName));
} else if (fieldType != Integer.class && fieldType != Integer.TYPE) {
    if (fieldType != Long.class && fieldType != Long.TYPE) {
        setMethod.invoke(obj, value);  // 其他类型直接设置
    } else {
        setMethod.invoke(obj, rs.getLong(columnName));  // Long类型
    }
} else {
    setMethod.invoke(obj, rs.getInt(columnName));  // Integer类型
}

4. 完整使用示例

4.1 配置文件

mybatis-config.xml:

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

UserMapper.xml:

复制代码
<mapper namespace="UserMapper">
    <select id="selectUserById" parameterType="java.lang.String" resultType="com.example.User">
        SELECT id, user_name, age, email FROM users WHERE id = #{id}
    </select>
    
    <insert id="insertUser" parameterType="com.example.User">
        INSERT INTO users(id, user_name, age, email) VALUES(#{id}, #{userName}, #{age}, #{email})
    </insert>
    
    <update id="updateUser" parameterType="com.example.User">
        UPDATE users SET user_name = #{userName}, age = #{age}, email = #{email} WHERE id = #{id}
    </update>
    
    <delete id="deleteUser" parameterType="java.lang.String">
        DELETE FROM users WHERE id = #{id}
    </delete>
</mapper>

4.2 Java实体类

复制代码
package com.example;

public class User {
    private String id;
    private String userName;
    private Integer age;
    private String email;
    
    // 必须有无参构造函数
    public User() {}
    
    // getter和setter方法(框架依赖这些方法)
    public String getId() { return id; }
    public void setId(String id) { this.id = id; }
    
    public String getUserName() { return userName; }
    public void setUserName(String userName) { this.userName = userName; }
    
    // 其他getter/setter...
}

4.3 使用代码

复制代码
public class Main {
    public static void main(String[] args) throws DocumentException {
        // 1. 读取配置文件
        InputStream inputStream = Resources.getResourcesAsStream("mybatis-config.xml");
        
        // 2. 创建SqlSessionFactory
        SqlSessionFactoryBuilder builder = new SqlSessionFactoryBuilder();
        SqlSessionFactory factory = builder.build(inputStream);
        
        // 3. 获取SqlSession
        SqlSession session = factory.openSession();
        
        try {
            // 4. 执行查询
            User user = (User) session.selectOne("UserMapper.selectUserById", "1001");
            System.out.println(user.getUserName());
            
            // 5. 执行插入
            User newUser = new User();
            newUser.setId("1002");
            newUser.setUserName("张三");
            newUser.setAge(25);
            newUser.setEmail("zhangsan@example.com");
            
            int rows = session.insert("UserMapper.insertUser", newUser);
            System.out.println("插入行数: " + rows);
            
            // 6. 提交事务
            session.commit();
            
        } catch (Exception e) {
            // 7. 异常时回滚
            session.rollback();
            e.printStackTrace();
        } finally {
            // 8. 关闭会话
            session.close();
        }
    }
}

5. 框架执行流程总结

复制代码
1. 初始化阶段:
   Resources.getResourcesAsStream() -> 加载配置文件
   ↓
   SqlSessionFactoryBuilder.build() -> 解析XML配置
   ↓
   创建DataSource -> EasyMybatisUNPOOLEDDataSource
   ↓
   创建TransactionManager -> EasyMybatisJDBCTransaction
   ↓
   解析Mapper文件 -> 创建EasyMybatisMappedStatement集合
   ↓
   创建SqlSessionFactory

2. 执行阶段:
   SqlSessionFactory.openSession() -> 打开连接
   ↓
   SqlSession.insert/update/delete/select() -> 执行SQL
   ↓
   参数处理:#{}替换为? + 反射调用getter
   ↓
   执行SQL:PreparedStatement.execute()
   ↓
   结果映射:ResultSet -> 反射调用setter
   ↓
   返回结果

3. 清理阶段:
   session.commit()/rollback() -> 提交/回滚事务
   ↓
   session.close() -> 关闭连接

6. 当前版本的局限性

  1. 数据类型支持有限

    • 参数设置只支持ps.setString(),所有参数都被转为字符串

    • 结果映射只支持String、Integer、Long等少数类型

  2. SQL处理简单

    • 不支持动态SQL

    • 不支持${}表达式

    • 参数名只能是简单的属性名,不支持嵌套

  3. 性能问题

    • 非池化数据源,每次操作都创建新连接

    • 大量使用反射,性能较低

    • 没有缓存机制

  4. 错误处理

    • 异常处理简单,直接包装为RuntimeException

    • 没有详细的错误信息

  5. 功能缺失

    • 不支持注解配置

    • 不支持插件/拦截器

    • 不支持多数据源

    • 不支持延迟加载

尽管有这些限制,但这个简化版本清晰地展示了MyBatis框架的核心原理:配置解析、SQL执行、参数映射和结果映射,是一个很好的学习示例。

相关推荐
Scott9999HH4 小时前
【IIoT流量实战】蒸汽管道阀门全关却仍有流量?用 Python 实现涡街信号 FFT 频谱分析与温压全补偿积算网关,深度拆解靠谱的涡街流量计厂家硬核技术标准
开发语言·python
腻害兔4 小时前
【若依项目-产品经理视角】深度拆解 RuoYi-Vue-Pro 商城模块:从商品管理到交易引擎,50 张表撑起一整套电商系统
java·大数据·vue.js·产品经理·ai编程
码智社5 小时前
AES加密原理详解及Java实现加解密实战
java·开发语言
AI云海5 小时前
python 列表、元组、集合和字典
开发语言·python
萧瑟余晖6 小时前
JDK 26 新特性详解
java·开发语言
马优晨6 小时前
Freemarker 完整讲解(后端 Java 模板引擎)
java·开发语言·freemarker·freemarker 完整讲解·freemarker模板引擎
人邮异步社区8 小时前
怎么把C语言学到精通?
c语言·开发语言
心平气和量大福大9 小时前
C#-WPF-控件-TextBox 数据绑定
开发语言·c#·wpf
ttwuai9 小时前
Cursor 生成 CRUD 后,Go 后台接口别只测 200:JWT、RBAC 和 tenant_id 怎么验
开发语言·后端·golang
维天说9 小时前
CLI-Switch 2026年3月版历史设计:Hook、TTY 隔离与 JSON 状态
java·服务器·json