传统JDBC 的缺点 与 Mybatis 的解决方案
- 频繁的数据库连接的创建与销毁,没有使用连接池,浪费系统资源,从而影响系统性能;
解决: 通过配置文件配置连接池,复用数据库连接。 - sql 语句存在硬编码,实际应用会频繁改动 sql 代码,代码难以维护,没有将 sql 代码与 java 代码分来管理;
解决:将 sql 语句配置在 ***Mapper.xml 中。 - preparedStatement 向 sql 使用占位符的形式传递参数,条件难以确定;
解决:自动将 java 对象映射到 sql 中,通过 parameterType 指定。 - 结果集处理中也存在着硬编码的问题,sql 的变化导致解析代码的不确定性,难以维护。
解决:自动将 sql 执行结果映射到 java 对象中,通过将 resultType 或 resultMap 指定。
Mybatis 的插件原理
Mybatis 允许在已映射的 sql 语句执行过程中的某一点进行拦截调用。默认情况下,允许插件拦截的对象和对应的方法有:
- Executor:Mybatis 执行器,负责串联 StatementHandler,ParamterHandler,ResultSetHandler,执行增删查改操作。
可拦截的方法:update, query, flushStatements, commit, rollback, getTransaction, close, isClosed。 - ParameterHandler :负责处理 sql 参数。
可拦截的方法:getParameterObject, setParameters。 - ResultSetHandler:处理 sql 的返回集映射。
可拦截的方法:handleResultSets, handleOutputParameters。 - StatementHandler:使用 Statement 执行 sql 语句。
可拦截的方法:prepare, parameterize, batch, update, query。
一个插件对象的声明实例:
@Intercepts({@Signature( type= Executor.class, method = "query", args ={
MappedStatement.class, Object.class, RowBounds.class, ResultHandler.class
})})
public class SimplePlugin implements Interceptor {}
在 Mybatis 的操作过程中,会执行这四个对象的实例方法:
public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
return parameterHandler;
}
public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,
ResultHandler resultHandler, BoundSql boundSql) {
ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
return resultSetHandler;
}
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
return statementHandler;
}
public Executor newExecutor(Transaction transaction, ExecutorType executorType, boolean autoCommit) {
executorType = executorType == null ? defaultExecutorType : executorType;
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, autoCommit);
}
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
实例完成后,会调用 interceptorChain.pluginAll,这里的 interceptors 会保存在扫描配置文件时获取到的所有插件对象。
public class InterceptorChain {
private final List<Interceptor> interceptors = new ArrayList<>();
public Object pluginAll(Object target) {
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}
return target;
}
}
public interface Interceptor {
Object intercept(Invocation invocation) throws Throwable;
default Object plugin(Object target) {
return Plugin.wrap(target, this);
}
default void setProperties(Properties properties) {
}
}
实际上就是创建了一层一层的动态代理,形成了责任链。
public static Object wrap(Object target, Interceptor interceptor) {
// 获得interceptor配置的@Signature的type
Map<Class<?>, Set<Method>> signatureMap = getSignatureMap(interceptor);
// 当前代理类型
Class<?> type = target.getClass();
// 根据当前代理类型 和 @signature指定的type进行配对, 配对成功则可以代理
Class<?>[] interfaces = getAllInterfaces(type, signatureMap);
if (interfaces.length > 0) {
return Proxy.newProxyInstance(
type.getClassLoader(),
interfaces,
new Plugin(target, interceptor, signatureMap));
}
return target;
}
最后得到的对象:
getSignatureMap:根据Intercepts注解的 Signature 信息判定是否插件是否拦截。
分页插件的原理
分页插件就是利用 Mybatis 的分页插件,拦截待执行的 sql ,添加对应的分页语句和参数。
如: select * from user -> select t.* from (select * from user) t limit 0,10