Framework Implementation
SqlSession Related
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 Related
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