在讲述原理之前,我们需要了解一些基础的知识,Java的代理技术,如果不理解为什么的话,我们想一下,为什么mapper仅仅是一个姐恶口,而不是一个包含逻辑的实现类,一个借口是没有办法去执行的,那么是怎么执行的呢,所以有必要理解一下动态代理。
构建SqlSessionFactory的过程:
SqlSessionFactory是mybatis的核心类之一,起追重要的功能就是提供mybatis的核心接口SqlSession,所以,我么和你首先要创建SqlSessionFactory为我们提供配置文件和相关的参数,其构造的方式为Builder够造方式,可以通过SqlSessionFactoryBuilder去构建,分为两步:
第一步:通过ConfigBuilder类解析配置文件,读出参数的配置,并将读取的数据存入到Configuration类中,这个类包含了几乎所有的mybatis的相关的配置。
第二步:使用Configuration对象创建SqlSessionFactory。Mybatis中的SqlSessionFactory是一个接口,这里采用的是抽象工厂,为此,我们需要找到其实现类,一般的实现类为默认的DefaultSqlSessionFactory类,大部分情况下没有必要自己去创建一个新的SqlSessionFactory的实现类。Builder构造模式对于复杂的对象而言直接构造是困难的,而且写出来的代码很不容易维护,而且弹性比较差,对此我们可以使用builder构建模式一步步有秩序的构建,例如全局的配置参数等,交给了Configuration类,DefaultSqlSessionFactory类就可以构建一个复杂的SqlSessionFactory类。
1.构建COnfiguration
configuration类的作用是很重要的,它的作用有以下几个方面:
1.基础的为读入配置的xml文件和mapper的映射的xml文件。
2.初始化基础的配置,比如mybatis的别名,类型转换等,一些重要的对象,插件,映射器等等。创建为单例,为后续创建SqlSessionFactory提供了配置的参数。
3.执行一些重要的对象方法,初始化配置信息。一般其初始化的内容有:
properties全局参数;settings设置;typeAliases别名;type Handler类型处理器;ObjectFactory对象;plugin插件;environment环境;DataBaseProvider 数据库标识;Mapper映射器;
2.剖析映射器内部组成
一般的,一个映射器有三部分组成:
1.MappedStatement,它保存映射器的一个节点,(如selectinsert|delete|update)包括许多我饿们配置的SQL和sql的id,缓存信息、resultmap、paramterType,resultType等的重要的配置内容。
2.SqlSource,它是提供BoundSql对象的地方,它是MappedStatement的一个属性。
3.BoundSql,它是建立SQL和参数的地方。有三个常用的属性,SQL,parameterObject、parameterMappings。
MappedStatement对象涉及的东西较多,一般不去修改它,因为容易产生不必要的错误,S起来Source是一个接口,他的主要作用是根据参数和其它的规则组装拼接Sql,包括动态的sql语句,
这些都很复杂。对于参数和SQL而言,主要的规则都反映在BoundSql类对象上,在插件中往往需要拿到它进而可以拿到它运行当前的sql和参数以及参数的规则,作出适当的修改。
对于BoundSql,其有三个主要的属性,parameterMappings,parameterObject和sql。
其中parameterObject为参数本身,可以通过传递对象,pojo,map或者@Param注解的参数。
(1)传递简单对象:比如当我们传递int类型时,Mybatis会把参数变为Integer对象进行传递,类似的如long,float等也是如此。
(2)如果传递的是pojo或者是map,那么这个parameterObject旧手机我们传入的pojo或者是map不变。
(3)当传递多个参数如果没有@Param注解,那么mybatis就会把parameterObject变为一个Map<String,Object>对象,其键值的关系是按照吮吸来规划的,所以在编写的时候我们可以使用#{param1}或者#{1}进行引用第一个参数。
(4)如果使用了@Param注解,那么mybatis也会把parameterObject转换为一个Map<String,Object>对象,只是将数字键值改会为注解的值。
parameterMappings是一个List,每一个元素都是ParameterMapping的对象,这个对象会描述我们的参数,参数包括属性,名称,表达式,javaType,jdbcType等重要的信息,我们一般不需要去修改这里,其可以实现参数和SQL的结合,以便于PreparedStatement能够通过它找到parameterObject对象的属性并设置参数,确保程序能够正常运行。
sql 属性就是我们书写在映射器里面的一条sql语句,大多数时候我许修改这个属性,只有在使用插件的情况下,我们可以进行改写。这个一定要谨慎,如果操作不当,会使得整个框架瘫痪。
3.构建SqlSessionFactory:
当我们有了Configuration对象之后,就可以较为轻松的构建SqlSessionFactory了,我们可以通过下面的方式来构建:
sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
这样的化,mybatis会根据Configuration类中的配置的信息构建一个SqlSessionFactory对象。
4.SqlSession运行过程:
SqlSession是一个接口,它的使用并不复杂,构建的SqlSessionFactory可以轻易的拿到SqlSession,SqlSession给出了查询,插入,更新和删除的方法,在旧版的iBatis中,常常使用这些接口,而在新版的Mybatis 中建议我们使用Mapper。(1)映射的动态代理:
其是通过JDK的动态代理来实现的(代码在org.appache.ibatis.binding包中):
public class MapperProxyFactory<T> {
private final Class<T> mapperInterface;
private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>();
public MapperProxyFactory(Class<T> mapperInterface) {
this.mapperInterface = mapperInterface;
}
public Class<T> getMapperInterface() {
return mapperInterface;
}
public Map<Method, MapperMethod> getMethodCache() {
return methodCache;
}
@SuppressWarnings("unchecked")
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
}
这里看到动态代理对接口的绑定,将代理的方法放置到代理类MapperProxy类中:
public class MapperProxy<T> implements InvocationHandler, Serializable {
````````
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else if (isDefaultMethod(method)) {
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
private MapperMethod cachedMapperMethod(Method method) {
MapperMethod mapperMethod = methodCache.get(method);
if (mapperMethod == null) {
mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
methodCache.put(method, mapperMethod);
}
return mapperMethod;
}
```````````
}
虽与上面的invoke方法,一旦mapper是一个代理对象,那么它就会运行到invoke方法里面,首先会判断是不是一个类,这里显然Mapper是一个接口而不是一个类,所以判定失败,就会生成MapperMethod对象,他是通过cachedMapperMethod方法对其进行初始化的,然后执行execute方法,把sqlSession和当前的参数传递进去。让我们看一下这个execute方法:
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;
}
可以看到,通过获取commandType类型的命令的类型,结合switch选择来执行对应的处理,我们通过其中的一个例子来分析,我们分析一下返回多条记录的过程,也就是select情况下的executeForMany方法执行的过程:
private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
List<E> result;
Object param = method.convertArgsToSqlCommandParam(args);
if (method.hasRowBounds()) {
RowBounds rowBounds = method.extractRowBounds(args);
result = sqlSession.<E>selectList(command.getName(), param, rowBounds);
} else {
result = sqlSession.<E>selectList(command.getName(), param);
}
// issue #510 Collections & arrays support
if (!method.getReturnType().isAssignableFrom(result.getClass())) {
if (method.getReturnType().isArray()) {
return convertToArray(result);
} else {
return convertToDeclaredCollection(sqlSession.getConfiguration(), result);
}
}
return result;
}
可以看到,传入的参数为SqlSession的类型,看到实现之后可以理解,实际上最后通过sqlSession对象去运行对象的SQL,因为映射器的xml文件的命名空间对应的就是这个接口的路径,那么根据全路径和方法的名称便可以绑定起来,通过动态代理的技术,然这个接口运行起来,之后使用命令模式,使得SqSession能够执行对应的方法,有了这层封装,我们仅需要提供一个接口,不必再去实现这个接口了。
(2)SqlSession下的四个重要的对象:
其实Mapper的执行过程是通过Executor、StatementHandler、ParammeterHandler和ResultHandler等来执行对应的sql。StatementHandler的作用是使用数据库的Statement(PreparedStatementHandler)执行操作,他起到了一个承上启下的作用,是四大对象的核心。而ParameterHandler用于SQL对参数的处理,ResultHandler是进行最后数据集的封装返回处理。
1.执行器
其是一个真正和数据库进行交互的部件,在mybatis的三种执行器中,我们可以在mybatis的配置文件中进行选择,可以通过settings元素的属性进行配置,配置defaultExecutorType来完成。
SIMPLE,简易执行器。REUSE,是一种执行器重用预处理语句。BATCH执行器重用语句和批量更新,针对批量转用的执行器。
首先通过源码看一下Mybatis是如何生成的:
在Configuration类中:
public Executor newExecutor(Transaction transaction, ExecutorType executorType) {
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);
}
executor = (Executor) interceptorChain.pluginAll(executor);
return executor;
}
Mybatis将根据配置的类型去确定需要的创建的三种执行器的哪一种,在创建对象后会执行如下代码:
interceptorChain.pluginAll(executor);
这里将mybatis构建一层层的动态代理对象,在调度真实的WExecutor之前,执行配置插件的代码可以被修改。下面以SIMPLE执行器SimpleExecutor的查询方法作为例子进行分析:
public class SimpleExecutor extends BaseExecutor {
public SimpleExecutor(Configuration configuration, Transaction transaction) {
super(configuration, transaction);
}
@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);
}
}
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.<E>query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
@Override
protected <E> Cursor<E> doQueryCursor(MappedStatement ms, Object parameter, RowBounds rowBounds, BoundSql boundSql) throws SQLException {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, null, boundSql);
Statement stmt = prepareStatement(handler, ms.getStatementLog());
return handler.<E>queryCursor(stmt);
}
@Override
public List<BatchResult> doFlushStatements(boolean isRollback) throws SQLException {
return Collections.emptyList();
}
private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException {
Statement stmt;
Connection connection = getConnection(statementLog);
stmt = handler.prepare(connection, transaction.getTimeout());
handler.parameterize(stmt);
return stmt;
}
}
显然可以看出,Mybatis根据Configuration来构建StatementHandler,然后使用prepareStatement方法,对SQL编译并对参数进行初始化,我们看它实现的过程,它调用了StaementHandler的prepare()进行了预编译和基础设置,然后通过StatementHandler的额parammeterize()方法来设置参数并执行,resultHandler在组装查询结果返回给调用者来完成一次查询。其中发挥主要作用的时StatementHandler。
2.数据库会话器
看一下数据库会话器是如何再Configuraiton类里面生成的:
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;
}
可以看到StatementHandler接口的实现类为一个RoutingStatementHandler对象,它实现了接口StatementHandler。和Executor一样,用代理对象进行一层层的封装。RoutingStatementHandler不是真正的服务对象,看源码会发现,其实通过适配器模式进行调度的,其对应Executor‘的三种模式,分别有SimpleStatementHandler,PreparedStatementHandler,CallableStatementHandler三种。看一下源码:
public class RoutingStatementHandler implements StatementHandler {
private final StatementHandler delegate;
public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
switch (ms.getStatementType()) {
case STATEMENT:
delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case PREPARED:
delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
case CALLABLE:
delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql);
break;
default:
throw new ExecutorException("Unknown statement type: " + ms.getStatementType());
}
}
@Override
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
return delegate.prepare(connection, transactionTimeout);
}
@Override
public void parameterize(Statement statement) throws SQLException {
delegate.parameterize(statement);
}
@Override
public void batch(Statement statement) throws SQLException {
delegate.batch(statement);
}
@Override
public int update(Statement statement) throws SQLException {
return delegate.update(statement);
}
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
return delegate.<E>query(statement, resultHandler);
}
@Override
public <E> Cursor<E> queryCursor(Statement statement) throws SQLException {
return delegate.queryCursor(statement);
}
@Override
public BoundSql getBoundSql() {
return delegate.getBoundSql();
}
@Override
public ParameterHandler getParameterHandler() {
return delegate.getParameterHandler();
}
}
可以看到,对象的适配器对象为delegate,其是一个StatementHandler的接口对象,构造方法根据配置来适配对应的StatementHanler对象,它的作用是给实现类对象的使用提供一个统一的,简易使用的适配器,此为对象的适配器模式。之后看一下是怎么执行查询的?
我们以PreparedStatementHandler为例,看一下prepare,parameterize和query方法是如何调度的。
prepare方法:
@Override
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException {
ErrorContext.instance().sql(boundSql.getSql());
Statement statement = null;
try {
statement = instantiateStatement(connection);
setStatementTimeout(statement, transactionTimeout);
setFetchSize(statement);
return statement;
} catch (SQLException e) {
closeStatement(statement);
throw e;
} catch (Exception e) {
closeStatement(statement);
throw new ExecutorException("Error preparing statement. Cause: " + e, e);
}
}
除了一些基本的配置,如设置超时,获取最大行数设置外,instancetiateStatement()方法是对SQL进行 预编译,然后调用了prepareStatementHandler类里面的上述的预编译方法,其中其调用了java.sql中的prepareStatement方法:
@Override
protected Statement instantiateStatement(Connection connection) throws SQLException {
String sql = boundSql.getSql();
if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) {
String[] keyColumnNames = mappedStatement.getKeyColumns();
if (keyColumnNames == null) {
return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS);
} else {
return connection.prepareStatement(sql, keyColumnNames);
}
} else if (mappedStatement.getResultSetType() != null) {
return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY);
} else {
return connection.prepareStatement(sql);
}
}
然后执行器Executor就会调用parameterize()方法器设置参数,它的代码如下:
@Override
public void parameterize(Statement statement) throws SQLException {
parameterHandler.setParameters((PreparedStatement) statement);
}
这个时候,它调用的ParameterHandler去完成的,那么看一下其是怎么完成查询的,不过在这里先了解一下StatementHandler中的查询方法:
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
return resultSetHandler.<E> handleResultSets(ps);
}
在执行参数和SQL都被prepare()方法预编译,参数在parameterize()方法上已经进行了设置,所以到这里已经很简单了,我们只要执行SQL,然后返回结果就可以了,执行之后我们看到了ResultSetHandler对结果进行封装和返回。
所以,我们再来梳理一遍这个过程哈:
首先,Executor会调用StatementHandler的prepare()方法预编译sql语句,同时设置一些基本的运行参数,然后用prepareize()方法启动ParameterHandler设置参数,完成预编译,跟着就是执行查询,而update()也是这样的,如果需要查询,我们就会用ResultSetHandler封装返回结果给调用者。
3.参数处理器
首先看一下参数处理器声明的接口,其功能就是完成对预编译参数的设置:
public interface ParameterHandler {
Object getParameterObject();
void setParameters(PreparedStatement ps)
throws SQLException;
}
其中,第一个方法的作用是返回参数对象,setParameters()方法的作用是设置预编译SQL语句的参数,其实现类为一个默认的DefaultParameterHandler,看一下第二个方法的实现:
@Override
public void setParameters(PreparedStatement ps) {
ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
if (parameterMappings != null) {
for (int i = 0; i < parameterMappings.size(); i++) {
ParameterMapping parameterMapping = parameterMappings.get(i);
if (parameterMapping.getMode() != ParameterMode.OUT) {
Object value;
String propertyName = parameterMapping.getProperty();
if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
value = boundSql.getAdditionalParameter(propertyName);
} else if (parameterObject == null) {
value = null;
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
value = parameterObject;
} else {
MetaObject metaObject = configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
}
TypeHandler typeHandler = parameterMapping.getTypeHandler();
JdbcType jdbcType = parameterMapping.getJdbcType();
if (value == null && jdbcType == null) {
jdbcType = configuration.getJdbcTypeForNull();
}
try {
typeHandler.setParameter(ps, i + 1, value, jdbcType);
} catch (TypeException e) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
} catch (SQLException e) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
}
}
}
}
}
可以看到,他还是从parameterObject对象中其参数,然后使用typeHandler进行参数处理,如果有设置在配置文件中,那么就会根据签名住v额的typeHandler对参数进行预处理,而其也是在mybatis初始化的时候注册在Configuration类中的,当我们需要的的时候就可以完成参数的设置了。
4.结果处理器
根据上面的分析可以了解到,它是用来组装结果集进行返回的,我们再来看看结果处理器(ResultSetHandler)的接口的定义,代码清单如下:public interface ResultSetHandler {
<E> List<E> handleResultSets(Statement stmt) throws SQLException;
<E> Cursor<E> handleCursorResultSets(Statement stmt) throws SQLException;
void handleOutputParameters(CallableStatement cs) throws SQLException;
}
其中,handleOutputParameters()方法是处理存储过程输出参数的,我们暂时不需要管他,重点看一下handleResultSet方法,他是包装结果集的,Mybatis同样为我们提供一个DefaultResultSetHandler类,在默认的情况下都是通过这个类进行处理的,这个类较为复杂,它涉及使用JAVASSIST和CGLIB作为延迟加载,然后通过typeHandler和ObjectFactory进行组装结果再返回,基本上改动这一部分的概率很小。
以上就是大体的运行流程的浅析,如果想要了解具体是怎么运行,怎么设计的,还需要我们返回我们的代码中继续理解。里面一些优秀的设计模式,一些优秀的设计理念是很值得我们去学习的。