GitHub address
Source resolve the address
https://github.com/erlieStar/mybatis-3
debug source code with project
https://github.com/erlieStar/mybatis-examples
About dynamic proxy Mybatis stage, mainly related to the four components
- Executor: cache and secondary cache implemented in the Executor
- StatementHandler: Use Statement or perform operations PrepareStatement JDBC provided, play the role of connecting
- ParameterHandler: pre-compiled SQL parameter settings
- ResultSetHandler: result set returned to the database (the ResultSet) encapsulated, the type of user-specified return entities
This process dynamic proxy stages of execution are included in this figure, in order to recover the code can get
Returns the dynamic proxy object
Examples spend a dynamic proxy to debug the process, step by step chase
sqlSession = sqlSessionFactory.openSession(true);
BookMapper bookMapper = sqlSession.getMapper(BookMapper.class);
int num = bookMapper.deleteById(1);
Previous article comes sqlSessionFactory implementation class is DefaultSqlSessionFactory, so openSession returns DefaultSqlSession, chasing getMapper method
Catch up MapperRegistry class, not during initialization saved mapper interface objects and the corresponding mapping MapperProxyFactory do?
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();
You got it, it seems from this back into the proxy object mapper interface
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
I found to generate the proxy of the work to MapperProxyFactory class, all right, then chase
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
protected T newInstance(MapperProxy<T> mapperProxy) {
// 实现了mapper接口的动态代理对象
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
This chase to finally over, this dynamic proxy function of Proxy.newProxyInstance see the following articles do not understand
Mybatis write-only interface, why run?
Look Proxy.newProxyInstance () method in the last parameter is valid? MapperProxy, the original proxy class mapper interface is MapperProxy ah.
Perform dynamic proxy approach
When performing the following operation will jump invoke MapperProxy class () function
int num = bookMapper.deleteById(1);
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 代理以后,所有Mapper方法调用时,都会执行这个invoke方法
try {
// 如果是Object本身的方法不增强
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else if (isDefaultMethod(method)) {
// 针对java7以上版本对动态类型语言的支持,暂不分析
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
// 从缓存中获取mapperMethod对象,如果缓存中没有,则创建一个,并添加到缓存中
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
Perform operations to the MapperMethod class, continue to chase
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {
case INSERT: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
case UPDATE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
}
case DELETE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
break;
}
case SELECT:
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
result = executeForMap(sqlSession, args);
} else if (method.returnsCursor()) {
result = executeForCursor(sqlSession, args);
} else {
// 处理返回为单一对象的情况
// 通过参数解析器解析参数
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
}
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for: " + command.getName());
}
if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
throw new BindingException("Mapper method '" + command.getName()
+ " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
}
return result;
}
Depending on the type of sql statements, the implementation of the right to a Sqlsession
DefaultSqlSession to the Executor (actuators)
@Override
public int update(String statement, Object parameter) {
try {
dirty = true;
// statement为命名空间+方法名
MappedStatement ms = configuration.getMappedStatement(statement);
return executor.update(ms, wrapCollection(parameter));
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error updating database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
Then in turn calls through CachingExecutor and SimpleExecutor
because we found DefaultSqlSession incoming Executor returned by the following method
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
executorType = executorType == null ? defaultExecutorType : executorType;
// 防止粗心大意的人将defaultExecutorType设为null
executorType = executorType == null ? ExecutorType.SIMPLE : executorType;
Executor executor;
if (ExecutorType.BATCH == executorType) {
executor = new BatchExecutor(this, transaction);
} else if (ExecutorType.REUSE == executorType) {
executor = new ReuseExecutor(this, transaction);
} else {
executor = new SimpleExecutor(this, transaction);
}
// 开启二级缓存,用装饰器模式装饰一下
if (cacheEnabled) {
executor = new CachingExecutor(executor);
}
// 创建代理对象,插件在这里起作用
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
The method is passed executorType defaultExecutorType
protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;
And cacheEnabled (secondary cache control) The default value is true, will be used with decorative SimpleExecutor CachingExecutor
The method then enters SimpleExecutor of doUpate
@Override
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.update(stmt);
} finally {
closeStatement(stmt);
}
}
We created a PreparedStatementHandler, as can be seen from MappedStatement statementType type class as PREPARED
mappedStatement.statementType = StatementType.PREPARED;
And obtained from a PreparedStatement PreparedStatementHandler
handler.parameterize(stmt);
Next PreparedStatementHandler parameter settings in SQL ParameterHandler
To update method SimpleStatementHandler
@Override
public int update(Statement statement) throws SQLException {
String sql = boundSql.getSql();
Object parameterObject = boundSql.getParameterObject();
KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
int rows;
if (keyGenerator instanceof Jdbc3KeyGenerator) {
statement.execute(sql, Statement.RETURN_GENERATED_KEYS);
rows = statement.getUpdateCount();
keyGenerator.processAfter(executor, mappedStatement, statement, parameterObject);
} else if (keyGenerator instanceof SelectKeyGenerator) {
statement.execute(sql);
rows = statement.getUpdateCount();
keyGenerator.processAfter(executor, mappedStatement, statement, parameterObject);
} else {
statement.execute(sql);
rows = statement.getUpdateCount();
}
return rows;
}
Finally see JDBC native wording
statement.execute()
Thus, the removal process is completed
The overall flow chart executed by the following
Detailed Executor
SimpleExecutor: the default configuration, use the PreparedStatement object to access the database, each visit must create a new PreparedStatement object
ReuseExecutor: using PreparedStatement object to access the database, reuses the statement object access
BatchExecutor: execute multiple SQL statements to achieve the ability to
to sum up
Call link below
MapperProxy: interception Mapper method
MapperMethod: Depending on the type of call DefaultSqlSession statement
DefaultSqlSession: the implementation of the right to Executor
Executor: generating StatementHandler
ParameterHandler: The StatementHandler the generated SQL pre-compiler
ResultSetHandler: the database returns a result set (ResultSet) were package, the type of user-specified return entities
Reference blog
[1]https://www.jianshu.com/p/46c6e56d9774