1000字范文,内容丰富有趣,学习的好帮手!
1000字范文 > Mybatis源码学习第六课---核心层源码分析--binding模块

Mybatis源码学习第六课---核心层源码分析--binding模块

时间:2023-08-08 21:37:04

相关推荐

Mybatis源码学习第六课---核心层源码分析--binding模块

目录

一.一个核心问题

二 、binding模块分析

2.1、binging 模块核心组件关系如下图:

2.2、MapperRegistry解析

2.3、MapperProxyFactory

2.4MapperProxy

2.5MapperMethod

2.5.1SqlCommand

2.5.2 ParamNameResolver

2.5.3MethodSignature

2.5.4MapperMethod.execute()

三 、数据库执行流程分析

3.1流程分析

3.1.1 创建动态代理对象流程

3.1.2执行查询流程

四、总结

参考

一.一个核心问题

为什么通过mapper接口就可以执行数据库操作(mapper接口没有实现类型)。

Ans: 通过mapper接口动态代理的增强+ 配置文件解读。

动态代理增强就是binding模块的功能。

从表现来讲,bingding的主要功能是将面向mapper接口编程转换成session中对应的方法执行。

二 、binding模块分析

2.1、binging 模块核心组件关系如下图:

MapperRegistry : mapper 接口和对应动态代理工厂的注册中心。MapperProxyFactory : 用于生成mapper接口动态代理的实例对象;MapperProxy:实现了InvocationHandler接口,它是增强mapper接口的实现;MapperMethod:封装了Mapper接口中对应方法的信息,以及对应的sql语句的信息;它是mapper接口与映射配置文件中sql语句的桥梁;

2.2、MapperRegistry解析

MapperRegistry是 Mapper 接口及其对应的代理对象工厂的注册中心。

MapperRegistry中字段的含义和功能如下:

private final Configuration config;//Configuration对象,mybatis全局唯一的//记录了mapper接口与对应MapperProxyFactory之间的关系private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap();public MapperRegistry(Configuration config) {this.config = config;}

在Mybatis初始化过程中会读取配置文件以及mapper接口中的注解信息,并调用MapperRegistry.addMapper()方法将mapper接口的工厂类添加到mapper注册中心(MapperRegistry.knownMappers 集合)该集合的 key 是 Mapper 接口对应的 Class 对象, value 为 MapperProxyFactory 工厂对象,可以为 Mapper 接口创建代理对象, MapperProxyFactory 的实现马上就会分析到。 MapperRegistry.addMapper()方法的 部分实现如下:

//将mapper接口的工厂类添加到mapper注册中心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 {//实例化Mapper接口的代理工程类,并将信息添加至knownMappersknownMappers.put(type, new MapperProxyFactory<T>(type));//解析接口上的注解信息,并添加至configuration对象MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type);parser.parse();loadCompleted = true;} finally {if (!loadCompleted) {knownMappers.remove(type);}}}}

在需要执行某sql语句时候,会先调用 MapperRegistry.getMapper()方法获得Mapper接口的代理对象,例如:

TUserMapper mapper = sqlSession.getMapper(TUserMapper.class);mapper实际上是通过Jdk动态代理为TUserMapper接口生产代理对象。其代码如下所示:

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {//根据type获取 其动态代理工厂类final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);if (mapperProxyFactory == null) {throw new BindingException("Type " + type + " is not known to the MapperRegistry.");}try {//返回mapper接口的动态代理对象return mapperProxyFactory.newInstance(sqlSession);} catch (Exception e) {throw new BindingException("Error getting mapper instance. Cause: " + e, e);}}

2.3、MapperProxyFactory

MapperProxyFactory 主要负责创建代理对象, 其中核心字段的含义和功能如下:

//mapper接口的class对象private final Class<T> mapperInterface;//key是mapper接口中的某个方法的method对象,value是对应的MapperMethod,// MapperMethod对象不记录任何状态信息,所以它可以在多个代理对象之间共享private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap();

MapperProxyFactory.newInstance(SqlSession sqlSession)方法实现了创建mapperInterface接口的代理对象的功能,其具体代码如下:

public T newInstance(SqlSession sqlSession) {//每次调用都会创建新的MapperProxy对象final MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, mapperInterface, methodCache);return newInstance(mapperProxy);}protected T newInstance(MapperProxy<T> mapperProxy) {//创建实现了mapper接口的动态代理对象/*** jdk动态代理获得代理对象* 参数一 被代理对象的类加载器* 参数二 被代理对象的接口* 参数三 代理对象*/return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);}

2.4MapperProxy

MapperProxy 实现了 lnvocationHandler 接 口,在介绍 JDK 动态代理,该接口的实现是代理对象的核心逻辑,这里不再重复描述。 MapperProxy 中核心宇段的含义和功能 如下:

private final SqlSession sqlSession;//当前查询的sqlSession对象private final Class<T> mapperInterface;// Mapper接口对应的class对象key是mapper接口中的某个方法的method对象,value是对应的MapperMethod,// MapperMethod对象不记录任何状态信息,所以它可以在多个代理对象之间共享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;}

MapperProxy. invoke()方法是代理对象执行的主要逻辑, 实现如下:

/**** @param proxy 代理对象* @param method 被代理对象中方法* @param args 方法中饿入参* @return 代理对象* @throws Throwable*/@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {try {//如果目标方法继承自 Object ,则直接调用目标方法if (Object.class.equals(method.getDeclaringClass())) {return method.invoke(this, args);} else if (method.isDefault()) {// ..针对 Java7 以上版本对动态类型语言的支持if (privateLookupInMethod == null) {return invokeDefaultMethodJava8(proxy, method, args);} else {return invokeDefaultMethodJava9(proxy, method, args);}}} catch (Throwable t) {throw ExceptionUtil.unwrapThrowable(t);}//methodCache 缓存中获取MapperMethod 对象,如采缓存中没有,则创建新的 MapperMethod 对象并添加到缓存中final MapperMethod mapperMethod = cachedMapperMethod(method);//调用 MapperMethod.execute()方法执行 SQL 语句return mapperMethod.execute(sqlSession, args);}private MapperMethod cachedMapperMethod(Method method) {return puteIfAbsent(method,k -> new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));}

2.5MapperMethod

MapperMethod 中封装了Mapper接口中对应方法的信息,以及对应SQL语句的信息。我们可以把MapperMethod的看着连接Mapper接口以及映射配置文件中定义的SQL语句的桥梁。

MapperMethod中各个字段的信息如下:

private final SqlCommand command;//从configuration中获取方法的命名空间.方法名以及SQL语句的类型private final MethodSignature method;//封装mapper接口方法的相关信息(入参,返回类型);public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {mand = new SqlCommand(config, mapperInterface, method);this.method = new MethodSignature(config, mapperInterface, method);}

2.5.1SqlCommand

SqlCommand 是MapperMethod中定义的内部类,他使用name字段记录SQL语句的名称,使用 type 宇段( SqlCommandType 类型)记录了 SQL 语句的类型。 SqlCommandType 是枚举类 型,有效取值为 UNKNOWN、 INSERT、 UPDATE、 DELETE、 SELECT、 FLUSH。

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// 用于存储mapper.xml文件中的select、insert、update和delete节点,同时还包//含了这些节点的很多重要属性;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 {//如果如果mappedStatement不为空name = ms.getId();//获取sql的名称,命名空间+方法名称type = ms.getSqlCommandType();//获取sql语句的类型if (type == SqlCommandType.UNKNOWN) {throw new BindingException("Unknown execution method for: " + name);}}}public String getName() {return name;}public SqlCommandType getType() {return type;}//从configuration中获取mappedStatementprivate MappedStatement resolveMappedStatement(Class<?> mapperInterface, String methodName,Class<?> declaringClass, Configuration configuration) {String statementId = mapperInterface.getName() + "." + methodName;//sql语句的id为命名空间+方法名字if (configuration.hasStatement(statementId)) {return configuration.getMappedStatement(statementId);//从configuration中获取mappedStatement} 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;}}

2.5.2 ParamNameResolver

ParamNameResolver(Resolver[riˈzɑlvər]解析器; )解析mapper接口方法中的入参;

在 MethodSignature 中, 会使用 ParamNameResolver 处理 Mapper 接口中定义的方法的参数 列表。 ParamNameResolver 使用 name 宇段( SortedMap<Integer, String>类型)记录了参数在参 数列表中的位置索引与参数名称之间的对应关系,其中 key 表示参数在参数列表中的索引位置, value 表示参数名称,参数名称可以通过@Param 注解指定,如果没有指定@Param 注解,则使 用参数索引作为其名称。

2.5.3MethodSignature

MethodSignature(Signature [ˈsɪɡnətʃər] 明显特征;)封装mapper接口方法的相关信息(入参,返回类型);

MethodSignature 中核心内容如下:

private final boolean returnsMany;//返回参数是否为集合类型或数组private final boolean returnsMap;//返回参数是否为mapprivate final boolean returnsVoid;//返回值为空private final boolean returnsCursor;//返回值是否为游标类型private final boolean returnsOptional;//返回值是否为Optionalprivate final Class<?> returnType;//返回值类型private final String mapKey;//如果返回值类型是 Map,则该字段记录了作为 key 的列名private final Integer resultHandlerIndex;//用来标记该方法参数列表中 ResultHandler 类型参数的位置private final Integer rowBoundsIndex;//用来标记该方法参数列表中 RowBounds 类型参数的位置private final ParamNameResolver paramNameResolver;//该方法对应的 ParamNameResolver (方法入参的信息)对象public MethodSignature(Configuration configuration, Class<?> mapperInterface, Method method) {//通过类型解析器获取方法的返回值类型Type resolvedReturnType = TypeParameterResolver.resolveReturnType(method, mapperInterface);if (resolvedReturnType instanceof Class<?>) {this.returnType = (Class<?>) resolvedReturnType;} else if (resolvedReturnType instanceof ParameterizedType) {this.returnType = (Class<?>) ((ParameterizedType) resolvedReturnType).getRawType();} else {this.returnType = method.getReturnType();}//初始化返回值等字段this.returnsVoid = void.class.equals(this.returnType);this.returnsMany = configuration.getObjectFactory().isCollection(this.returnType) || this.returnType.isArray();this.returnsCursor = Cursor.class.equals(this.returnType);this.returnsOptional = Optional.class.equals(this.returnType);this.mapKey = getMapKey(method);this.returnsMap = this.mapKey != null;this.rowBoundsIndex = getUniqueParamIndex(method, RowBounds.class);this.resultHandlerIndex = getUniqueParamIndex(method, ResultHandler.class);this.paramNameResolver = new ParamNameResolver(configuration, method);}// 负责将 args[]数纽( 用户传入的实参列表)转换成 SQL 语句对应的参数列表,它是通过上面介绍的public Object convertArgsToSqlCommandParam(Object[] args) {return paramNameResolver.getNamedParams(args);}

2.5.4MapperMethod.execute()

介绍完 MapperMethod 中定义的内部类, 回到 MapperMethod 继续分析。 MapperMethod 中 最核心的方法是 execute()方法,它会根据 SQL 语句的类型调用 SqISession 对应的方法完成数据 库操作。 SqlSession 是 MyBatis 的核心组件之一,其具体实现后面会详细介绍,这里读者暂时只 需知道它负责完成数据库操作即可。

MapperMethod.execute()方法的具体实现如下:

public Object execute(SqlSession sqlSession, Object[] args) {Object result;//根据 SQL 语句的类型调用 SqlSession 对应的方法switch (command.getType()) {case INSERT: {//使用 ParamNameResolver 处理 args[]数组(用户传入的实参列表),将用户传入的 实参与// 指定参数名称关联起Object param = method.convertArgsToSqlCommandParam(args);result = rowCountResult(sqlSession.insert(command.getName(), param));break;}case UPDATE: {}case DELETE: {}case SELECT://处理返回值为 void 且 ResultSet 通过 ResultHandler 处理的方法if (method.returnsVoid() && method.hasResultHandler()) {executeWithResultHandler(sqlSession, args);result = null;} else if (method.returnsMany()) {///处理返回值为集合或数组的方法result = executeForMany(sqlSession, args);} else if (method.returnsMap()) {//处理返回值为 Map 的方法result = executeForMap(sqlSession, args);} else if (method.returnsCursor()) {//处理返回值为 Cursor 的方法result = executeForCursor(sqlSession, args);} else {//处理返回值为单一对象的方法Object param = method.convertArgsToSqlCommandParam(args);result = sqlSession.selectOne(command.getName(), param);if (method.returnsOptional()&& (result == null || !method.getReturnType().equals(result.getClass()))) {result = Optional.ofNullable(result);}}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;}

三 、数据库执行流程分析

3.1流程分析

@Testpublic void testManyParamQuery(){String resource = "mybatis-config.xml";InputStream inputStream = null;try {inputStream = Resources.getResourceAsStream(resource);} catch (IOException e) {e.printStackTrace();}// 1.读取mybatis配置文件创SqlSessionFactorysqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);// 2.获取sqlSession 方法级别的SqlSession sqlSession = sqlSessionFactory.openSession();3.获取对应mapperTUserMapper mapper = sqlSession.getMapper(TUserMapper.class);String email = "";Byte sex = 1;// 第三种方式用对象EmailSexBean esb = new EmailSexBean();esb.setEmail(email);esb.setSex(sex);List<TUser> list3 = mapper.selectByEmailAndSex3(esb);System.out.println("javabean:"+list3.size());}

3.1.1 创建动态代理对象流程

3.获取对应mapperTUserMapper mapper = sqlSession.getMapper(TUserMapper.class);

step1.SqlSession的唯一实现是DefaultSqlSession,getMapper方法如下:

@Overridepublic <T> T getMapper(Class<T> type) {return configuration.<T>getMapper(type, this);}

step2.调用Configuration的getMapper方法:

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {return mapperRegistry.getMapper(type, sqlSession);}/*mapper接口的动态代理注册中心*/protected final MapperRegistry mapperRegistry = new MapperRegistry(this);public MapperRegistry(Configuration config) {this.config = config;}

step3.调用MapperRegistry的getMapper方法:

public <T> T getMapper(Class<T> type, SqlSession sqlSession) {//根据type获取 其动态代理工厂类final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);if (mapperProxyFactory == null) {throw new BindingException("Type " + type + " is not known to the MapperRegistry.");}try {//返回mapper接口的动态代理对象return mapperProxyFactory.newInstance(sqlSession);} catch (Exception e) {throw new BindingException("Error getting mapper instance. Cause: " + e, e);}}

step4.调用MapperProxyFactory的newInstance:

public T newInstance(SqlSession sqlSession) {//每次调用都会创建新的MapperProxy对象final MapperProxy<T> mapperProxy = new MapperProxy(sqlSession, mapperInterface, methodCache);return newInstance(mapperProxy);}protected T newInstance(MapperProxy<T> mapperProxy) {//创建实现了mapper接口的动态代理对象/*** jdk动态代理获得代理对象* 参数一 被代理对象的类加载器* 参数二 被代理对象的接口* 参数三 代理对象*/return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);}

最后DEBUG可以看出来得到的mapper实际上是 mapper接口class对象的动态代理对象

3.1.2执行查询流程

List<TUser> list3 = mapper.selectByEmailAndSex3(esb);

step1.动态代理最终要调用的InvocationHandler——mapperProxy

/**** @param proxy 代理对象* @param method 被代理对象中方法* @param args 方法中饿入参* @return 代理对象* @throws Throwable*/@Overridepublic Object invoke(Object proxy, Method method, Object[] args) throws Throwable {try {//如果目标方法继承自 Object ,则直接调用目标方法if (Object.class.equals(method.getDeclaringClass())) {return method.invoke(this, args);} else if (method.isDefault()) {// ..针对 Java7 以上版本对动态类型语言的支持if (privateLookupInMethod == null) {return invokeDefaultMethodJava8(proxy, method, args);} else {return invokeDefaultMethodJava9(proxy, method, args);}}} catch (Throwable t) {throw ExceptionUtil.unwrapThrowable(t);}//methodCache 缓存中获取MapperMethod 对象,如采缓存中没有,则创建新的 MapperMethod 对象并添加到缓存中final MapperMethod mapperMethod = cachedMapperMethod(method);//调用 MapperMethod.execute()方法执行 SQL 语句return mapperMethod.execute(sqlSession, args);}

step2.获取MapperMethod对象

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封装了Mapper接口中对应方法的信息,以及对应的sql语句的信息,它是mapper接口与映射配置文件中sql语句的桥梁,MapperMethod对象不记录任何状态信息,所以它可以在多个代理对象之间共享。

SqlCommand:从configuration中获取方法的命名空间、方法名以及SQL语句的类型;MethodSignature:封装mapper接口方法的相关信息(入参、返回类型);ParamNameResolver:解析mapper接口方法中的入参。

step2-1.获取SqlCommand

step2-2.获取MethodSignature

step3.调用MapperMethod.execute执行sql

return mapperMethod.execute(sqlSession, args);

四、总结

step1.通过动态代理创建了代理对象MapperProxy

step2.MapperProxy最后调用的是MapperMethod的方法执行

最后的核心落在了MapperMethod,其封装了Mapper接口中对应方法的信息,以及对应的sql语句的信息,它是mapper接口与映射配置文件中sql语句的桥梁。

参考

1)享学课堂Lison老师笔记

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。