第四章 MyBatis源码分析笔记2

一、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;
       }

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值