在应用代码中,如果不结合spring来使用mybatis,则需要通过SqlSession获取mapper接口对应的代理对象MapperProxy,然后通过该代理对象来调用并执行mapper接口的方法。使用示例如下:
String resource = "org/mybatis/example/mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
// 通过SqlSessionFactory获取sqlSession
SqlSession session = sqlSessionFactory.openSession();
try {
// 通过SqlSession对象获取BlogMapper接口的代理对象,
// 然后赋值给BlogMapper接口
BlogMapper mapper = session.getMapper(BlogMapper.class);
// 调用BlogMapper接口的方法来执行指定的数据库操作
Blog blog = mapper.selectBlog(101);
} finally {
session.close();
}
在mybatis内部是通过该MapperProxy代理对象来获取并执行该mapper接口方法所对应的SQL的。
由上面的例子可知,在应用代码中通过SqlSession来获取mapper接口BlogMapper的代理对象,并将该代理对象赋值到类型为BlogMapper的一个引用,通过该引用来调用BlogMapper接口的对应方法。
SqlSession获取mapper接口的代理对象的getMapper方法实现如下:调用configuration的getMapper方法,从configuration内部获取指定mapper接口类型的MapperProxy代理对象,其中mapper接口类型使用泛型T来表示。
public <T> T getMapper(Class<T> type) {
return configuration.<T>getMapper(type, this);
}
Configuration的getMapper方法实现如下:
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
// MapperRegistry类定义
public class MapperRegistry {
// 配置Configuration引用
private final Configuration config;
// 应用代码中的mapper接口和代理对象MapperProxy的工厂类MapperProxyFactory的映射,
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();
public MapperRegistry(Configuration config) {
this.config = config;
}
@SuppressWarnings("unchecked")
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
// 将mapper接口的类对象作为key
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
// 创建MapperProxy对象实例
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
...
}
从内部维护的mapperRegistry中获取该mapper接口对应的mapperProxy代理对象,由之前的文章分析可知,每个mapper接口对应的mapperProxy代理对象是在应用程序中,使用SqlSessionFactoryBuilder创建SqlSessionFactory对象实例时,解析mapper.xml配置文件,然后保存到SqlSessionFactory对象关联的配置Configuration的mapperRegistry中的。
由以上分析可知,应用代码通过SqlSession对象获取了mapper接口对应的代理对象MapperProxy,并在应用代码中赋值给了mapper接口的一个引用,然后通过该引用来调用mapper接口的方法,从而触发该代理对象MapperProxy来获取并执行该方法对应的SQL。MapperProxy的定义如下:
// 实现了InvocationHandler接口实现动态代理,目标类T每个方法的执行都会通过invoke来拦截
// 每个mapper对应一个MapperProxy,在spring中,针对每个mapper接口使用对应的MapperProxy对象来作为bean
public class MapperProxy<T> implements InvocationHandler, Serializable {
private static final long serialVersionUID = -6424540398559729838L;
// sqlSession引用,由该mapper的所有方法共享,在结合mybatis-spring中,是所有mapper的所有方法共享同一个sqlSessionTemplate引用
private final SqlSession sqlSession;
private final Class<T> mapperInterface;
private final Map<Method, MapperMethod> methodCache;
...
}
MapperProxy实现了JDK提供的InvocationHandler接口,InvocationHandler是JDK提供的用来实现动态代理的,即被代理的类的所有方法的执行都通过InvocationHandler接口定义的invoke方法来拦截。
MapperProxy的invoke的方法实现如下:
// args为mapper接口的代理对象的method方法被调用时,应用代码提供的参数值
@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);
}
// 根据目标类,即应用代码中的mapper接口的方法method,找到对应的mapperMethod
// MapperProxy的MapperMethod是懒加载的,即使用的时候才创建MapperMethod,然后存放到该MapperProxy的methodCache中
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
核心实现为通过cachedMapperMethod方法来创建该被调用的mapper接口的方法对应的MapperMethod,然后调用MapperMethod的execute方法来执行实际的SQL调用。
其中MapperMethod对象是在第一次调用该方法时创建,然后保存在MapperProxy,具体为MapperProxyFactory的一个类型为ConcurrentHashMap的methodCache缓存中,之后直接取出调用即可。
在mybatis内部设计当中mapper接口对应MapperProxy,mapper接口的方法对应MapperMethod。
MapperProxy的cachedMapperMethod的实现如下:
private MapperMethod cachedMapperMethod(Method method) {
// methodCache具体在MapperProxyFactory定义,用于缓存该mapper接口的方法对应的MapperMethod对象
// 这样由该MapperProxyFactory创建的MapperProxy对象共享这一个缓存,每个MapperProxy内部维护一个引用,
// 这个实现基础是每个mapper接口都对应一个MapperProxyFactory。
// 由于methodCache是ConcurrentHashMap,故是线程安全的。
MapperMethod mapperMethod = methodCache.get(method);
if (mapperMethod == null) {
mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
methodCache.put(method, mapperMethod);
}
return mapperMethod;
}
先从方法缓存methodCache中获取,如果存在该方法对应的MapperMethod则直接返回,否则创建一个MapperMethod对象。
MapperMethod类定义与对象实例的创建如下:在执行时,即调用execute方法时,通过SqlCommand对象来获取mapper接口的该方法对应的MappedStatement对象。
public class MapperMethod {
private final SqlCommand command;
private final MethodSignature method;
public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
// 在SqlCommand构造方法中,从configuration的mappedStatements集合中,
// 获取key为该mapper接口的方法method,value为对应的MappedStatement,即SQL语句包装器,
// 这样在下面执行execute方法时,可以直接通过command的name作为key,
// 从configuration的mappedStatements集合取出对应的mappedStatement对象即可
this.command = new SqlCommand(config, mapperInterface, method);
// 该mapper接口方法的签名
this.method = new MethodSignature(config, mapperInterface, method);
}
...
}
SqlCommand类定义:其中name为该mapper接口的方法的全限定名。通过SqlCommand的name属性作为key,可以从Configuration的mappedStatements字典中,取出该方法对应的MappedStatement对象。由之前的文章分析可知,Configuration的mappedStatements集合用于存放mapper.xml的增删查改对应的SQL语句。
SqlCommandType为SQL语句类型枚举:UNKNOWN, INSERT, UPDATE, DELETE, SELECT, FLUSH;
// 获取mapper接口的某个方法对应的mapper.xml中的id为该方法名的SQL操作节点对应的mappedStatement
// 使用name来关联这个mappedStatement对象在configuration的mappedStatements集合的key
public static class SqlCommand {
// mapper接口的方法全限定名,便于从Configuration的mappedStatements集合获取对应的SQL包装对象MappedStatement对象
private final String name;
// SQL语句的类型枚举
private final SqlCommandType type;
public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
final String methodName = method.getName(); // mapper接口方法名
final Class<?> declaringClass = method.getDeclaringClass();
// 获取该方法对应的MapperStatement对象
MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass,
configuration);
if (ms == null) {
if (method.getAnnotation(Flush.class) != null) {
name = null;
type = SqlCommandType.FLUSH;
} else {
throw new BindingException("Invalid bound statement (not found): "
+ mapperInterface.getName() + "." + methodName);
}
} else {
// name为configuration中的mappedStatements集合的某个元素的key
name = ms.getId();
type = ms.getSqlCommandType();
if (type == SqlCommandType.UNKNOWN) {
throw new BindingException("Unknown execution method for: " + name);
}
}
}
...
}
// MapperMethod的resolveMappedStatement方法
private MappedStatement resolveMappedStatement(Class<?> mapperInterface, String methodName,
Class<?> declaringClass, Configuration configuration) {
// statementId为方法全限定名,即类全限定名+方法名
String statementId = mapperInterface.getName() + "." + methodName;
// 创建SqlSessionFactory时,已经将mapper接口的方法对应的SQL操作,
// 生成了对应的MappedStatement对象存放到了configuration的mappedStatement集合中了
if (configuration.hasStatement(statementId)) {
return configuration.getMappedStatement(statementId);
} else if (mapperInterface.equals(declaringClass)) {
return null;
}
for (Class<?> superInterface : mapperInterface.getInterfaces()) {
if (declaringClass.isAssignableFrom(superInterface)) {
MappedStatement ms = resolveMappedStatement(superInterface, methodName,
declaringClass, configuration);
if (ms != null) {
return ms;
}
}
}
return null;
}
}
SQL的执行主要是通过MapperMethod的execute来实现的。由以上分析可知,MapperMethod通过SqlCommand来获取mapper接口的该方法对应的mapper.xml的增删查改节点的SQL,具体为SQL对应的MappedStatement对象。具体实现如下:
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
// 使用sqlSession来执行对应的SQL语句
switch (command.getType()) {
case INSERT: {
// param为一个HashMap,SQL参数占位符名和mapper接口方法的参数值的映射
Object param = method.convertArgsToSqlCommandParam(args);
// command.getName为SQL语句的id,param为SQL的参数和参数值对
// 交给sqlSession执行,其中sqlSession绑定一个数据库连接
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;
}
以insert插入数据为例:sqlSession.insert(command.getName(), param),以下为SqlSession的update方法定义,insert底层也是调用update方法来执行的。
具体为通过executor包的Executor来实现:使用SQL包装器MappedStatement和SQL参数parameter获取实际的执行SQL和完成动态SQL参数绑定,最后调用JDBC相关API来完成SQL的执行。
public int update(String statement, Object parameter) {
try {
dirty = true;
// statement为mapper接口的方法全限定名,
// 从configuration的mappedStatements中获取在创建sqlSessionFactory时填充进去的该statement对应的
// SQL语句包装类MappedStatement
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();
}
}
每个SqlSession都包含一个独立的Executor,避免不同SqlSession的相互影响,通过该executor来完成SQL的执行。
Executor在内部解析MappedStatement获取SQL语句并完成动态SQL参数值的绑定,以下为Executor接口的其中一个实现类SimpleExecutor的update方法实现,具体实现为:通过StatementHandler对象来完成MappedStatement内部的SQL解析提取,SQL参数值绑定,生成JDBC的预处理语句,以及最后通过JDBC的API执行该SQL语句。
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
// SQL语句处理器,将SQL语句对象ms和参数键值对映射parameter,赋值为其内部参数,以便之后的SQL语句参数赋值
StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
// 生成预处理语句,并且将参数值替换SQL的问号?参数,形成可执行的SQL
// 内部调用handler.prepare方法
stmt = prepareStatement(handler, ms.getStatementLog());
// 使用SQL语句处理器执行SQL完成更新,返回更新的数据库行数
return handler.update(stmt);
} finally {
closeStatement(stmt);
}
}
以下为SQL语句处理器StatementHandler接口的其中一个实现类PreparedStatementHandler的update方法实现:主要是JDBC的PrepareStatement的执行。
@Override
public int update(Statement statement) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
// SQL语句执行
ps.execute();
// 获取执行结果,即更新的数据库行数
int rows = ps.getUpdateCount();
Object parameterObject = boundSql.getParameterObject();
// 生成key信息,如插入数据产生行的id
KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
// 将key信息返回给应用代码
keyGenerator.processAfter(executor, mappedStatement, ps, parameterObject);
// 放回更新的行数
return rows;
}
通过以上分析可知,在应用代码中调用Mapper接口的某个方法来执行对应的数据库操作,在mybatis内部过程为: