Framework Implementation

SqlSessionFactoryBuilder

public class SqlSessionFactoryBuilder {

    private Configuration configuration;

    public SqlSessionFactoryBuilder() {
        configuration = new Configuration();
    }

    public SqlSessionFactory build(InputStream inputStream) throws DocumentException, PropertyVetoException, ClassNotFoundException {
        XMLConfigerBuilder xmlConfigerBuilder = new XMLConfigerBuilder(configuration);
        Configuration conf = xmlConfigerBuilder.parseConfiguration(inputStream);
        return new DefaultSqlSessionFactory(conf);
    }

}

SqlSessionFactory

public interface SqlSessionFactory {

    SqlSession openSession();

}

SqlSession

public interface SqlSession {

    <E> List<E> selectList(String statementId, Object ...params) throws Exception;

    <T> T selectOne(String statementId, Object ...params) throws Exception;

    void close() throws Exception;
}

DefaultSqlSession

@AllArgsConstructor
public class DefaultSqlSession implements SqlSession {

    private Configuration configuration;

    private Executor simpleExecutor = new SimpleExecutor();

    public DefaultSqlSession(Configuration configuration) {
        this.configuration = configuration;
    }

    @Override
    public <E> List<E> selectList(String statementId, Object... params) throws Exception {
        MappedStatement mappedStatement = configuration.getMappedStatementMap().get(statementId);
        return simpleExecutor.query(configuration, mappedStatement, params);
    }

    @Override
    public <T> T selectOne(String statementId, Object... params) throws Exception {
        List<Object> objects = selectList(statementId, params);
        if (objects.size() == 1) {
            return (T) objects.get(0);
        }
        throw new RuntimeException("DefaultSqlSession selectOne result not unique: " + statementId);
    }

    @Override
    public void close() throws Exception {
        simpleExecutor.close();
    }

    @Override
    public <T> T getMapper(Class<?> mapperClass) {
        Object proxyInstance = Proxy.newProxyInstance(DefaultSqlSession.class.getClassLoader(), new Class[]{mapperClass}, new InvocationHandler() {
            @Override
            public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
                String methodName = method.getName();
                if (method.getDeclaringClass() == Object.class) {
                    return method.invoke(this, args);
                }
                String className = method.getDeclaringClass().getName();
                String statementId = className + "." + methodName;
                Type genericReturnType = method.getGenericReturnType();
                if(genericReturnType instanceof ParameterizedType){
                    List<Object> objects = selectList(statementId, args);
                    return objects;
                }
                return selectOne(statementId,args);

            }
        });
        return (T) proxyInstance;
    }
}

Class Definition and Annotation Description

AllArgsConstructor: Used to generate constructor method containing all fields, simplifying code. Indicates that a DefaultSqlSession object can be directly constructed with all fields.

DefaultSqlSession: Implements SqlSession interface, serving as MyBatis’s core session management class. Contains instances of Configuration and Executor.

Property Description

  • private Configuration configuration: Saves configuration information, such as MappedStatement mappings, responsible for managing SQL metadata.
  • private Executor simpleExecutor = new SimpleExecutor(): Default executor, used to execute SQL statements and return results. Initialized as SimpleExecutor instance.

Working Principles Summary

Query Flow:

  • User calls method through Mapper interface
  • Dynamic proxy intercepts call, generates statementId based on method signature
  • Call selectList or selectOne to execute query
  • Return query results

Core Components:

  • Configuration: Manages configuration information
  • MappedStatement: Describes SQL statement and its mapping information
  • Executor: Responsible for executing SQL and returning results
  • Dynamic Proxy: Dynamically generates Mapper interface implementation, simplifying user calls

Exception Handling:

  • When query results are not unique, selectOne method throws exception. This constraint ensures single query results are always clear.

Executor Interface

public interface Executor {

    <E> List<E> query(Configuration configuration, MappedStatement mappedStatement, Object[] params) throws Exception;

    void close() throws Exception;
}

SimpleExecutor Implementation Class

public class SimpleExecutor implements Executor {

    private Connection connection;

    @Override
    public <E> List<E> query(Configuration configuration, MappedStatement mappedStatement, Object[] params) throws Exception {
        connection = configuration.getDataSource().getConnection();
        String sql = mappedStatement.getSql();
        BoundSql boundSql = getBoundSql(sql);
        PreparedStatement preparedStatement = connection.prepareStatement(boundSql.getSqlText());
        String parameterType = mappedStatement.getParameterType();
        Class<?> parameterTypeClass = getClassType(parameterType);
        List<ParameterMapping> parameterMappingList = boundSql.getParameterMappingList();
        int n = 0;
        for (ParameterMapping pm : parameterMappingList) {
            String content = pm.getName();
            Field declaredField = parameterTypeClass.getDeclaredField(content);
            declaredField.setAccessible(true);
            Object object = declaredField.get(params[0]);
            preparedStatement.setObject(n + 1, object);
        }

        // Execute SQL
        ResultSet resultSet = preparedStatement.executeQuery();
        String resultType = mappedStatement.getResultType();
        Class<?> resultTypeClass = getClassType(resultType);
        List<Object> objects = new ArrayList<>();

        // Encapsulate result set
        while (resultSet.next()) {
            Object o = resultTypeClass.newInstance();
            // Metadata
            ResultSetMetaData metaData = resultSet.getMetaData();
            for (int i = 1; i <= metaData.getColumnCount(); i ++) {
                // Field name
                String columnName = metaData.getColumnName(i);
                // Field value
                Object value = resultSet.getObject(columnName);

                // Reflection based on database and entity completion
                PropertyDescriptor propertyDescriptor = new PropertyDescriptor(columnName, resultTypeClass);
                Method writeMethod = propertyDescriptor.getWriteMethod();
                writeMethod.invoke(o, resultTypeClass.getDeclaredField(columnName).getType().cast(value));
            }
            objects.add(o);
        }

        return (List<E>) objects;
    }

    private BoundSql getBoundSql(String sql) {
        // Marker processing class, configure marker parser to complete placeholder parsing
        ParameterMappingTokenHandler parameterMappingHandler = new ParameterMappingTokenHandler();
        GenericTokenParser genericTokenParser = new GenericTokenParser(
                "#{",
                "}",
                parameterMappingHandler
        );
        // Parsed SQL
        String parseSql = genericTokenParser.parse(sql);
        // Parameters inside #{}
        List<ParameterMapping> parameterMapping = parameterMappingHandler.getParameterMappings();
        BoundSql boundSql = new BoundSql(parseSql, parameterMapping);
        System.out.println("SimpleExecutor getBoundSql: " + boundSql.getSqlText());
        return boundSql;
    }

    private Class<?> getClassType(String parameterType) throws ClassNotFoundException {
        if(parameterType != null){
            return Class.forName(parameterType);
        }
        return null;
    }

    @Override
    public void close() throws Exception {
        connection.close();
    }
}

Class Purpose

The main purpose of SimpleExecutor class is:

  • Execute SQL queries based on passed Configuration and MappedStatement
  • Operate database through JDBC, and use reflection to map query results to specified type Java objects
  • Support parameter binding and dynamic SQL parsing (such as #{} placeholder handling)

Code Logic Analysis

Establish Database Connection:

connection = configuration.getDataSource().getConnection();

Get and Parse SQL:

  • Get SQL template with placeholders #{} through MappedStatement
  • Call getBoundSql method to replace #{} with ? and get parameter information

Create PreparedStatement and Bind Parameters:

  • Get parameter values through reflection based on parameter type information
  • Use JDBC’s PreparedStatement to complete SQL parameter setting

Execute Query and Process Result Set:

  • Get query results through ResultSet
  • Use reflection to dynamically build result objects, filling data from result set into specified Java types

Applicable Scenarios

This class is one of MyBatis’s core implementations, suitable for:

  • Implementing database operation encapsulation
  • Providing dynamic proxy-supported Mapper interfaces
  • Managing SQL query execution process