项目——通过自动回复机器人学Mybatis(七)

时间:2020-12-24 21:41:53

接口式编程

以下是sqlSession执行sql的一般方式
SqlSession session = sqlSessionFactory.openSession();
try {
Blog blog = (Blog) session.selectOne("org.mybatis.example.BlogMapper.selectBlog", 101);
} finally {
session.close();
}

诚然这种方式能够正常工作,并且对于使用旧版本 MyBatis 的用户来说也比较熟悉,不过现在有了一种更直白的方式。使用对于给定语句能够合理描述参数和返回值的接口(比如说BlogMapper.class),你现在不但可以执行更清晰和类型安全的代码,而且还不用担心易错的字符串字面值以及强制类型转换。

例如:

SqlSession session = sqlSessionFactory.openSession();
try {
BlogMapper mapper = session.getMapper(BlogMapper.class);
Blog blog = mapper.selectBlog(101);
} finally {
session.close();
}
这种方式就是接口式编程



定义一个与Command.xml相对应的接口ICommand
修改Command.xml的namespace:
<mapper namespace="com.csdn.dao.ICommand">
这样就形成了对应关系



等Mybatis+Spring时,会充分发挥接口式编程首先Configuration.xml中的数据源将托付给Spring容器管理,sqlSession也一样,DBAccess将消失,还有CommandDao中sqlSession调用的接口式编程的代码统统由Spring自动实现,CommandDao也将消失,而ICommand将取代它成为新的Dao层





接口式编程原理

项目——通过自动回复机器人学Mybatis(七)
这部分的原理其实就是动态代理,详情见我关于设计模式——"代理模式"的博客



现在跟踪一下源码,看看它内部是怎么实现的

DBAccess:
public SqlSession getSqlSession() throws Exception{
//通过配置文件获取数据库连接信息
Reader reader = Resources.getResourceAsReader("com/csdn/config/Configuration.xml");
//通过配置信息构建sqlSessionFactory
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(reader);
//通过sqlSessionFactory打开一个数据库会话
SqlSession sqlSession = sessionFactory.openSession();
return sqlSession;
}
看了一下build()源码,发现这里的sqlSessionFactory创建的是DefaultSqlSession的实例

DefaultSqlSession的GetMapper()方法:
@Override
public <T> T getMapper(Class<T> type) {
return configuration.<T>getMapper(type, this);
}
我们传入的参数是ICommand.class,由于泛型的作用,getMapper返回值类型是ICommand


继续看getMapper方法的实现
@SuppressWarnings("unchecked")
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);
}
}
可以看到是mapperProxyFactory代理工厂生产了一个代理对象


而代理工厂的来历:final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);type是ICommand.class,看看knownMappers的来历:
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();

由于addMapper方法,knownMappers填充了key为type,MapperProxyFactory为value的键值对
  public <T> void addMapper(Class<T> type) {
if (type.isInterface()) {
if (hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
knownMappers.put(type, new MapperProxyFactory<T>(type));
// It's important that the type is added before the parser is run
// otherwise the binding may automatically be attempted by the
// mapper parser. If the type is already known, it won't try.
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}




回到上面的mapperProxyFactory.newInstance(sqlSession);
看newInstance源码:
@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);
}
先调用下面那个的newInstance方法,在调用上面那个newInstance方法,它的返回值T就是我们最后得到的ICommand对象


这是JDK提供的代理类方法
Proxy.newProxyInstance


当我们调用iCommand.queryCommandList(comm)时将执行mapperProxy中的invoke()

MapperProxy:
public class MapperProxy<T> implements InvocationHandler, Serializable {


.................略..............
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (Object.class.equals(method.getDeclaringClass())) {
try {
return method.invoke(this, 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;
}

}
iCommand.queryCommandList(comm)时invoke()方法中传入的参数method就是queryCommandList(),args就是comm

但是这段代码不回执行
 if (Object.class.equals(method.getDeclaringClass())) {
try {
return method.invoke(this, args);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
因为Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy),学过动态代理的人应该知道此方法的第一个应该是:被代理实例.getClassLoader()而不是:接口.getClassLoader(),
所以if判断为false执行的将只有
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);



进入mapperMethod类:
public class MapperMethod {

private final SqlCommand command;
private final MethodSignature method;

public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
this.command = new SqlCommand(config, mapperInterface, method);
this.method = new MethodSignature(config, method);
}

public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
if (SqlCommandType.INSERT == command.getType()) {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
} else if (SqlCommandType.UPDATE == command.getType()) {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
} else if (SqlCommandType.DELETE == command.getType()) {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
} else if (SqlCommandType.SELECT == command.getType()) {
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 {
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
}
} else if (SqlCommandType.FLUSH == command.getType()) {
result = sqlSession.flushStatements();
} else {
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;
}................................
因为我们执行的方法是select类型,且返回值是List
将进入:
else if (method.returnsMany()) {
result = executeForMany(sqlSession, args);


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.<E>selectList......