一、binding模块分析
1、为什么使用mapper接口就能操作数据库?
配置文件解读 +动态代理的增强
@Test// 快速入门
public void quickStart() throws IOException {
SqlSession sqlSession = sqlSessionFactory.openSession();
TUserMapper mapper = sqlSession.getMapper(TUserMapper.class);
TUser user = mapper.selectByPrimaryKey(2); // 4.执行查询语句并返回单条数据
System.out.println(user);
}
快速入门本质分析 ibatis方式
@Test
public void quickStartOriginal(){
// 2.获取sqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
// 3.执行查询语句并返回单条数据
TUser user = sqlSession.selectOne("com.chj.mybatis.mapper.TUserMapper.selectByPrimaryKey",1);
System.out.println(user.toString());
}
2、binding模块逻辑架构分析
MapperRegistry:mapper接口和对应的代理对象工厂的注册中心;
MapperProxyFactory:用于生成mapper接口动态代理的实例对象;
MapperProxy:实现了InvocationHandler接口,它是增强mapper接口的实现;
MapperMethod:封装了Mapper接口中对应方法的信息,以及对应的sql语句的信息;它是mapper接口与映射配置文件中sql语句的桥梁;
2.1、Mybatis初始化解析mapper.xml中的增删改查节点
接着第一阶段初始化的最后一步让我们从XMLMapperBuilder.bindMapperForNamespace()方法,解析select、insert、update、delete节点,开始入手吧!
private void configurationElement(XNode context) {
try {
//获取mapper节点的namespace属性
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
builderAssistant.setCurrentNamespace(namespace); //设置builderAssistant的namespace属性
cacheRefElement(context.evalNode("cache-ref"));//解析cache-ref节点
//重点分析 :解析cache节点----------------1-------------------
cacheElement(context.evalNode("cache"));
parameterMapElement(context.evalNodes("/mapper/parameterMap"));//解析parameterMap节点(已废弃)
//重点分析 :解析resultMap节点(基于数据结果去理解)----------------2-------------------
resultMapElements(context.evalNodes("/mapper/resultMap"));
sqlElement(context.evalNodes("/mapper/sql"));//解析sql节点
//重点分析 :解析select、insert、update、delete节点 ----------------3-------------------
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
}
}
解析select、insert、update、delete节点:
private void buildStatementFromContext(List<XNode> list) {
if (configuration.getDatabaseId() != null) {
buildStatementFromContext(list, configuration.getDatabaseId());
}
buildStatementFromContext(list, null);
}
//处理所有的sql语句节点并注册至configuration对象
private void buildStatementFromContext(List<XNode> list, String requiredDatabaseId) {
for (XNode context : list) {
//创建XMLStatementBuilder 专门用于解析sql语句节点
final XMLStatementBuilder statementParser = new XMLStatementBuilder(configuration, builderAssistant, context, requiredDatabaseId);
try {
//解析sql语句节点
statementParser.parseStatementNode();
} catch (IncompleteElementException e) {
configuration.addIncompleteStatement(statementParser);
}
}
}
解析配置的sql节点并通过builderAssistant实例化MappedStatement,并注册至configuration对象
public void parseStatementNode() {
//获取sql节点的id
String id = context.getStringAttribute("id");
String databaseId = context.getStringAttribute("databaseId");
......
//通过builderAssistant实例化MappedStatement,并注册至configuration对象
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
MappedStatement statement = statementBuilder.build();
configuration.addMappedStatement(statement);
return statement;
Configuration对象mapper文件中增删改查操作的注册中心:
protected final Map<String, MappedStatement> mappedStatements = new StrictMap<>("Mapped Statements collection");
public void addMappedStatement(MappedStatement ms) {
mappedStatements.put(ms.getId(), ms);
}
2.2、MapperRegistry代码解析
通过sqlSession.getMapper(TUserMapper.class);方法找到对应的源码默认实现代码SqlSessionManager与DefaultSqlSession都会调用Configuration对象的getMapper方法:
@Override
public <T> T getMapper(Class<T> type) {
return configuration.<T>getMapper(type, this);
}
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
MapperRegistry类是mapper接口和对应的代理对象工厂的注册中心:
public class MapperRegistry {
private final Configuration config;//config对象,mybatis全局唯一的
//记录了mapper接口与对应MapperProxyFactory之间的关系
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();
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);
}
}
org.apache.ibatis.binding.MapperProxyFactory.newInstance(MapperProxy<T> mapperProxy)
protected T newInstance(MapperProxy<T> mapperProxy) {
//创建实现了mapper接口的动态代理对象
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
//每次调用都会创建新的MapperProxy对象
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
接下来就是关于MapperProxy的具体实现逻辑处理部分代码,实现了InvocationHandler接口,它是增强mapper接口的实现,具体实现方法为invoke方法:
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
//如果是Object本身的方法不增强
return method.invoke(this, args);
} else if (isDefaultMethod(method)) {
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
// 1.从缓存中获取mapperMethod对象,如果缓存中没有,则创建一个,并添加到缓存中
final MapperMethod mapperMethod = cachedMapperMethod(method);
// 2.调用execute方法执行sql
return mapperMethod.execute(sqlSession, args);
}
2.3、解读MapperMethod
MapperMethod封装了Mapper接口中对应方法的信息,以及对应的sql语句的信息;它是mapper接口与映射配置文件中sql语句的桥梁;MapperMethod对象不记录任何状态信息,所以它可以在多个代理对象之间共享。
SqlCommand(内部类):从configuration中获取方法的命名空间,方法名以及SQL语句的类型
MethodSignature(内部类):封装mapper接口方法的相关信息(入参,返回类型)
ParamNameResolver:解析mapper接口方法中的入参
// 从缓存中获取mapperMethod对象,如果缓存中没有,则创建一个,并添加到缓存中
final MapperMethod mapperMethod = cachedMapperMethod(method);
private MapperMethod cachedMapperMethod(Method method) {
return methodCache.computeIfAbsent(method, k ->
new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
}
MapperMethod源码如下:
public class MapperMethod {
// 从configuration中获取方法的命名空间.方法名以及SQL语句的类型
private final SqlCommand command;
// 封装mapper接口方法的相关信息(入参,返回类型);
private final MethodSignature method;
public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
this.command = new SqlCommand(config, mapperInterface, method);
this.method = new MethodSignature(config, mapperInterface, method);
}
1)SqlCommand源码实现:
public static class SqlCommand {
//sql的名称,命名空间+方法名称
private final String name;
//获取sql语句的类型
private final SqlCommandType type;
public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
final String methodName = method.getName();//获取方法名称
final Class<?> declaringClass = method.getDeclaringClass();
//从configuration中获取mappedStatement
MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass,configuration);
if (ms == null) {
if(method.getAnnotation(Flush.class) != null){
name = null;
type = SqlCommandType.FLUSH