【手写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执行、参数映射和结果映射,是一个很好的学习示例。

相关推荐
世转神风-2 小时前
qt-弹框提示-界面提醒
开发语言·qt·策略模式
500842 小时前
鸿蒙 Flutter 超级终端适配:多设备流转与状态无缝迁移
java·人工智能·flutter·华为·性能优化·wpf
codealy2 小时前
Spring 事务失效的八大场景深度解析
java·spring boot·后端·spring
计算衎2 小时前
基于python的FastAPI框架目录结构介绍、开发思路和标准开发模板总结
开发语言·python·fastapi
AM越.2 小时前
Java设计模式超详解--单例设计模式(含uml图)
java·设计模式·uml
wjykp2 小时前
第八章异常
开发语言·python
Neoest2 小时前
【Java 填坑日记】Excel里的“1.00“存入数据库解密后,Integer说它不认识:一次 NumberFormatException 翻车实录
java·数据库·excel
~patience~2 小时前
简单易懂的计数器(理解Qt的信号和槽机制)
开发语言·qt
小坏讲微服务2 小时前
Spring Boot 4.0 新特性整合 MyBatis-Plus 完整教程
java·spring boot·后端·spring cloud·微服务·mybatis·mybatis plus