【Mybatis学习】查询映射过程解析

本文介绍了Mybatis的功能架构,包括API接口层、数据处理层和基础支撑层,并深入探讨了JAVA动态代理的核心原理。重点讲解了mapper(DAO)到session如何通过statementId进行查询映射的过程,强调了namespace与DAO接口全限定名一致的重要性。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

1.功能架构

Mybatis的功能架构分为三层:

(1)API接口层:提供给外部使用的接口API,比如dao层接口。

(2)数据处理层:负责具体的SQL查找、SQL解析、SQL执行和执行结果映射处理等。它主要的目的是根据调用的请求完成一次数据库操作。

(3)基础支撑层:负责最基础的功能支撑,包括连接管理、事务管理、配置加载和缓存处理、日志,这些都是共用的东西,将他们抽取出来作为最基础的组件。为上层的数据处理层提供最基础的支撑。

2.核心原理:JAVA动态代理

       代理模式是常用的Java设计模式,它的特征是代理类与委托类有同样的接口,代理类主要负责为委托类预处理消息、过滤消息、把消息转发给委托类,以及事后处理消息等。代理类与委托类之间通常会存在关联关系,一个代理类的对象与一个委托类的对象关联,代理类的对象本身并不真正实现服务,而是通过调用委托类的对象的相关方法,来提供特定的服务。分为动态代理和静态代理。

       动态代理类的字节码在程序运行时由Java反射机制动态生成,无需程序员手工编写它的源代码。

实现方式:

  • JDK动态代理实现

           效率相对低,被代理类需实现对应的接口

  • cglib动态代理实现

          效率相对高,生成目标类的子类

public class UserProxy<T> implements InvocationHandler {
    private final SqlSession sqlSession;
    private final Class<T> mapperInterface;
 
    public UserProxy(SqlSession sqlSession, Class<T> mapperInterface) {
        this.sqlSession = sqlSession;
        this.mapperInterface = mapperInterface;
    }
 
    public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
        System.out.println(method.getName() + ":" + Arrays.toString(args));
        return null;
    }
}
  
public interface UserMapper {
    UserInfo getUserById(long id);
}
  
public class ProxyTest {
    public static void main(String[] args) {
        UserProxy userProxy = new UserProxy(null, null);
        UserMapper userMapper = (UserMapper)Proxy.newProxyInstance(ProxyTest.class.getClassLoader(),
                new Class[]{UserMapper.class},
                userProxy);
        System.out.println(userMapper.getUserById(1l));
    }
}
简单查询过程:

public class OrderInfoTest {
    static Logger log = LogManager.getLogger(OrderInfoTest.class);
    public static void main(String[] args) throws IOException {
        // 加载配置文件,并获取session
        String resource = "mybatis-config.xml";
        SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(Resources.getResourceAsStream(resource));
        SqlSession session = sessionFactory.openSession();
        // 获取可操作的接口
        OrderInfoMapper orderInfoMapper = session.getMapper(OrderInfoMapper.class); // 生成动态代理类
        //  执行查询
        // OrderInfo orderInfo = session.selectOne("mybatis.study.customer.mapper.OrderInfoMapper.getOrderInfoById", 1L);
        OrderInfo orderInfo = orderInfoMapper.getOrderInfoById(1l);
        System.out.println(orderInfo.getMoney());
    }
}

通过调用DAO接口的方法,与直接使用statementId标识的方式,结果是一致的,中间肯定是做了映射关系。

这就说明了为什么mapper文件中的namespace必须要与dao层接口的全限定名一致。下面看下映射的过程。

3.mapper(dao)到session使用statementId查询的映射过程

3.1 sqlSession创建过程主要类的说明

SqlSessionFactoryBuilder:用于创建SqlSessionFactory的实例,build方法入参配置文件的数据流
SqlSessionFactory是创建SqlSession实例的工厂接口,实现类有两个

默认的实现是DefaultSqlSessionFactory,调用openSession获取session对象,进行操作

/**
 *
 * 从配置文件获取环境、数据源、事务类型来创建 sqlSession
 *
 * @param execType 执行器类型
 * @param level 事务级别
 * @param autoCommit 是否自动提交
 * @return session
 */
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
    Transaction tx = null;
    try {
        final Environment environment = configuration.getEnvironment();  // 获取配置的环境
        final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);  // 环境的事务工厂, 默认事务管理ManagedTransactionFactory
        tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);  // 新建事务对象
        final Executor executor = configuration.newExecutor(tx, execType);    // 建立执行器
        return new DefaultSqlSession(configuration, executor, autoCommit);  // 返回默认的sqlSession
    } catch (Exception e) {
        closeTransaction(tx);
        throw ExceptionFactory.wrapException("Error opening session.  Cause: " + e, e);
    } finally {
        ErrorContext.instance().reset();
    }
}

而SqlSessionManager,实现了SqlSessionFactory和SqlSession接口,直接具有session的功能,内部封装 DefaultSqlSessionFactory,是DefaultSqlSessionFactory的加强版。

总之,通过上述的过程得到可操作的session,其中最重要的就是Configuration的构建,下面说明下Configuration的解析过程

3.2 配置文件的解析

  从上面的代码可以看到,配置文件的解析是通过XMLConfigBuilder实现的。

public class XMLConfigBuilder extends BaseBuilder {
 
    private boolean parsed;
    private final XPathParser parser;
    private String environment;
    private final ReflectorFactory localReflectorFactory = new DefaultReflectorFactory();
    private XMLConfigBuilder(XPathParser parser, String environment, Properties props) {
        super(new Configuration());
        ErrorContext.instance().resource("SQL Mapper Configuration");
        this.configuration.setVariables(props);
        this.parsed = false;
        this.environment = environment;
        this.parser = parser;
    }
 
    public Configuration parse() {
        if (parsed) {
            throw new BuilderException("Each XMLConfigBuilder can only be used once.");
        }
        parsed = true;
        parseConfiguration(parser.evalNode("/configuration"));
        return configuration;
    }
 
    private void parseConfiguration(XNode root) {
        try {
            propertiesElement(root.evalNode("properties"));  // 解析属性
            Properties settings = settingsAsProperties(root.evalNode("settings"));   // 解析设置项
            loadCustomVfs(settings);
            typeAliasesElement(root.evalNode("typeAliases"));   // 解析别名
            pluginElement(root.evalNode("plugins"));      // 解析插件
            objectFactoryElement(root.evalNode("objectFactory"));    // 解析对象工厂
            objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));   // objectWrapper工厂,可以对结果进行一些特殊的处理
            reflectorFactoryElement(root.evalNode("reflectorFactory"));  // 反射工厂
            settingsElement(settings);
            environmentsElement(root.evalNode("environments"));   // 解析环境
            databaseIdProviderElement(root.evalNode("databaseIdProvider"));   // 解析数据库提供厂商
            typeHandlerElement(root.evalNode("typeHandlers"));    // 解析配置的类型处理器
            mapperElement(root.evalNode("mappers"));    // 解析sql映射文件
        } catch (Exception e) {
            throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
        }
    }
}

通过mybatis配置文件和mapper文件的解析,

1.将mapper接口信息,注册到了mapperRegistry,  // 以class<T> 和代理工厂注册到mapper库


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);
  }
}

这样,针对session.getMapper(OrderInfoMapper.class)生成动态代理类,就对应上了

2.Statement信息注册到了mappedStatements

public final class MappedStatement {
 
    private String resource;
    private Configuration configuration;
    private String id;
    private Integer fetchSize;
    private Integer timeout;
    private StatementType statementType;
    private ResultSetType resultSetType;
    private SqlSource sqlSource;
    private Cache cache;
    private ParameterMap parameterMap;
    private List<ResultMap> resultMaps;
    private boolean flushCacheRequired;
    private boolean useCache;
    private boolean resultOrdered;
    private SqlCommandType sqlCommandType;
    private KeyGenerator keyGenerator;
    private String[] keyProperties;
    private String[] keyColumns;
    private boolean hasNestedResultMaps;
    private String databaseId;
    private Log statementLog;
    private LanguageDriver lang;
    private String[] resultSets;
  
    public BoundSql getBoundSql(Object parameterObject) {
        BoundSql boundSql = sqlSource.getBoundSql(parameterObject);
        List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
        if (parameterMappings == null || parameterMappings.isEmpty()) {
            boundSql = new BoundSql(configuration, boundSql.getSql(), parameterMap.getParameterMappings(), parameterObject);
        }
        for (ParameterMapping pm : boundSql.getParameterMappings()) {
            String rmId = pm.getResultMapId();
            if (rmId != null) {
                ResultMap rm = configuration.getResultMap(rmId);
                if (rm != null) {
                    hasNestedResultMaps |= rm.hasNestedResultMaps();
                }
            }
        }
        return boundSql;
    }
}

3.3 调用映射

session获取mapper,调用了Configuration中从mapper库中查询到MapperProxyFactory对象,调用方法,执行MapperProxy中invoke方法

public class MapperProxy<T> implements InvocationHandler, Serializable {
 
  private static final long serialVersionUID = -6424540398559729838L;
  private final SqlSession sqlSession;
  private final Class<T> mapperInterface;
  private final Map<Method, MapperMethod> methodCache;
 
  public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
    this.sqlSession = sqlSession;
    this.mapperInterface = mapperInterface;
    this.methodCache = methodCache;
  }
 
  @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;
  }

执行mapperMethod的execute方法

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, mapperInterface, method);
  }
 
  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;
  }
}
public static class SqlCommand {
 
  private final String name;
  private final SqlCommandType type;
 
  public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
    final String methodName = method.getName();
    final Class<?> declaringClass = method.getDeclaringClass();
    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 = ms.getId();
      type = ms.getSqlCommandType();
      if (type == SqlCommandType.UNKNOWN) {
        throw new BindingException("Unknown execution method for: " + name);
      }
    }
  }
}

4.总结

    主要是梳理了下从DAO接口到Mybatis查询的过程,解释了Mybatis采用的动态代理模式,将DAO接口的方法映射到session使用statementId查询的过程,Mybatis源码中涉及了很多工厂类和建造类,可以借鉴。







评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值