说明
MyBatis版本:3.5.1
前言
通过上面的叙述我们已经知道我们与mybatis交互主要是通过配置文件或者配置对象,但是我们最终的目的是要操作数据库的,所以mybatis为我们提供了sqlSession这个对象来进行所有的操作,也就是说我们真正通过mybatis操作数据库只要对接sqlSession这个对象就可以了。在实际中,我们并不会直接使用sqlSession进行直接操作,而是使用了Mapper。
问题1:Mapper对象怎么来的?
我们可以使用SqlSession的getMapper进行获取:
@Override
public <T> T getMapper(Class<T> type) {
return configuration.getMapper(type, this);
}
说明:
通过调用DefaultSqlSession的getMapper方法并且传入一个类型对象获取,底层呢调用的是配置对象configuration的getMapper方法,configuration对象是我们在加载DefaultSqlSessionFactory时传入的。
然后我们再来看下这个配置对象的getMapper,传入的是类型对象(也就是我们自定义的Mapper.java对应的接口)和自身,也就是DefaultSqlSession。
public <T> T getMapper(Class<T> type, SqlSession sqlSession) { return mapperRegistry.getMapper(type, sqlSession); }
我们看到这个configuration的getMapper方法里调用的是mapperRegistry的getMapper方法,参数依然是类型对象和sqlSession。这里呢,我们要先来看下这个MapperRegistry即所谓Mapper注册器是什么。
public class MapperRegistry { private final Configuration config; private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>(); …… }
从这里我们可以知道其实啊这个MapperRegistry就是保持了一个Configuration对象和一个HashMap,而这个HashMap的key是类型对象,value呢是MapperProxyFactory。MapperRegistry对象是怎么来的,是在初始化Configuration对象时初始化了这个MapperRegistry对象的,代码大家可以去看。接下来我们继续看下这个MapperRegistry的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); } }
这里从knownMappers中获取key为类型的MapperProxyFactory对象。然后调用MapperProxyFactory对象的newInstance方法返回指定类型的Mapper,newInstance方法传入sqlSession对象。newInstance具体是做了什么呢?
public class MapperProxyFactory<T> { private final Class<T> mapperInterface; private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<>(); 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<>(sqlSession, mapperInterface, methodCache); return newInstance(mapperProxy); } }
这里可以看到MapperpProxyFactory直接new了一个MapperProxy,然后调用了重载方法newInstance。这里最终是Proxy.newProxyInstance动态代理了我们的mapper对象。
分析了这么多可以得出:通过sqlSesssion.getMapper(clazz)得到的Mapper对象是一个mapperProxy的代理类。
问题2:为什么调用mapper对象中的方法就能发出sql操作数据库?
通过上面我们知道了Mapper是一个个MapperProxy的代理类,对于JDK的动态代理,MapperProxy接口就要实现InvocationHandler接口:
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; }
当我们调用Mapper中接口定义的方法的时候,就会调用InvocationHandler的invoke方法:
@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) { return methodCache.computeIfAbsent(method, k -> new MapperMethod(mapperInterface, method, sqlSession.getConfiguration())); }
这里要关注的重点是MapperMethod:cachedMapperMethod返回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); 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; } //… }
可以看到execute对数据库进行了CRUD的操作。我看下SELECT的情况:
单条查询的时候是使用了sqlSession.selectOne,多条查询是使用了sqlSession.selectList:
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.selectList(command.getName(), param, rowBounds); } else { result = sqlSession.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; }
总结
到这里在结合上一篇文章我们就应该很好理解Mapper了:
通过SqlSession.getMapper会返回一个mapper的代理类MapperProxy,当调用mapper中的方法的时候,实际上是调用了代理类MapperProxy的invoke方法, 在invoke方法中会使用sqlSession进行数据库的操作,当然sqlSession并不直接操作数据库,而是由Executor和数据库打交道。
通过本篇文章,你应该能理解为什么Mapper是接口,我们没有具体的实现也能执行的原因了吧?
我就是我,是颜色不一样的烟火。
我就是我,是与众不同的小苹果。
à悟空学院:https://t.cn/Rg3fKJD
学院中有Spring Boot相关的课程!点击「阅读原文」进行查看!
SpringBoot视频:http://t.cn/A6ZagYTi
Spring Cloud视频:http://t.cn/A6ZagxSR
SpringBoot Shiro视频:http://t.cn/A6Zag7IV
SpringBoot交流平台:https://t.cn/R3QDhU0
SpringData和JPA视频:http://t.cn/A6Zad1OH
SpringSecurity5.0视频:http://t.cn/A6ZadMBe
Sharding-JDBC分库分表实战:http://t.cn/A6ZarrqS
分布式事务解决方案「手写代码」:http://t.cn/A6ZaBnIr
相关推荐
其中可能包含了Springboot应用的主类、Mybatis的Mapper接口和XML配置文件、业务逻辑服务类、以及使用Layui构建的前端界面HTML、CSS、JavaScript文件等。通过这些代码,系统实现了后台的增删改查操作,与前端界面的...
每个接口方法都对应一个XML中的SQL映射,通过MyBatis的自动代理机制,可以直接调用Mapper接口的方法执行SQL。 5. **MapperRegistry**:负责管理和注册Mapper,使得系统能够找到对应的Mapper XML文件,并将其与...
5. `Mapper`接口和对应的XML文件:MyBatis的SQL映射。 **项目实现** 1. **用户模块**:用户注册、登录,可能有权限管理。 2. **垃圾分类模块**:定义不同类型的垃圾,如可回收物、有害垃圾等,以及对应的分类规则。...
5. **配置MyBatis**:编写MyBatis的配置文件,`mybatis-config.xml`,设置数据库连接信息,引入Mapper XML文件的位置。 6. **编写实体类**:根据数据库表结构创建对应的Java实体类。 7. **编写Mapper接口**:定义...
1. **MyBatis配置**:在Mapper接口和XML文件中,定义分页查询的SQL语句,使用`LIMIT`和`OFFSET`(MySQL)或`ROWNUM`(Oracle)等关键字实现。 2. **传递参数**:Action接收来自前端的分页参数,如当前页数和每页记录...
MyBatis 是一款著名的 Java 持久层框架,它专注于 SQL 查询的处理,使得开发者能够更加关注 SQL 语句的编写,同时提供了...同时,熟悉 Java 垃圾回收机制也是面试中的重要部分,因为这关系到程序的内存管理和性能优化。
总的来说,"mybatis_jdk_proxy.zip"可能包含的是关于如何在MyBatis中利用JDK Proxy实现Mapper接口动态代理的教程或示例代码。通过学习这部分内容,你可以掌握如何在MyBatis中优雅地处理数据库操作,并理解Java内存...
- **MyBatis入门**:安装配置、创建映射文件、编写Mapper接口、使用SqlSession等。 - **MyBatis高级**:深入理解动态SQL、缓存机制、MyBatis插件、ResultMap的使用等。 - **实战项目**:通过实际项目锻炼,将理论...
开发者可以查看`web.xml`来了解SpringMVC的配置,` applicationContext.xml `来理解Spring的bean配置,以及MyBatis的配置文件`mybatis-config.xml`和映射文件`Mapper.xml`。此外,可能还有Java源码,包括SpringMVC的...
- 内存管理:了解堆栈内存分配,垃圾回收机制和JVM内存模型。 - 集合框架:ArrayList、LinkedList、HashMap、HashSet等数据结构的特性和使用场景。 - 多线程:线程的创建方式,同步机制(synchronized、Lock),...
总的来说,这个思维导图涵盖了从基础的编程概念(面向对象、Java基础)到实际开发中常用的工具(JavaScript、jQuery)以及数据库操作框架(MyBatis的Mapper接口开发)。对于初学者或者希望巩固基础知识的开发者来说...
为了高效管理垃圾处理流程,一个基于SSM(Spring、SpringMVC、MyBatis)架构的垃圾分类回收平台应运而生。本篇文章将深入探讨该平台的核心技术和实现原理。 首先,我们来了解一下SSM框架。SSM是Java开发Web应用的...
MyBatis允许开发者直接编写SQL,提供动态SQL支持,并通过Mapper接口与Java对象映射。熟悉XML配置和注解方式,以及事务管理,可以帮助你有效地使用MyBatis进行数据操作。 五、Redis Redis是一个高性能的键值存储...
3. MyBatis框架的缺点:(1)学习成本高、需要掌握SQL语句和Mapper文件配置、需要在Mapper文件中编写SQL语句等。 4. MyBatis框架适用场合:(1)大型项目中需要复杂的数据库交互、需要高效的数据库访问、需要灵活的...
3. **Dao**:数据访问层,通过MyBatis的Mapper接口与数据库进行通信。 4. **Controller**:控制器层,处理HTTP请求,调用Service并转发结果到视图层(JSP页面)。 5. **View**:视图层,JSP页面展示数据,如查询结果...
在本项目中,MyBatis可能被用来操作数据库,例如存储和检索垃圾分类的相关信息,如垃圾类型、回收时间、重量等。开发者可以通过编写Mapper接口和XML配置文件,定义SQL查询和更新操作。 关于SQL,这是用于管理关系...
通过Mapper接口,开发者可以便捷地执行SQL查询和更新操作。MyBatis与Spring结合使用时,可以实现自动事务管理。 4. **数据库设计**:智能垃圾分类系统可能需要存储垃圾种类、分类规则、用户信息、分类历史等数据。...
MyBatis通过Mapper接口和XML配置文件,将SQL与Java对象进行绑定,实现了数据的增删查改操作。 在数据库设计方面,本项目可能包含关于垃圾分类的知识库表,如垃圾类型、分类规则、题目库等。MySQL作为关系型数据库,...
例如,可以设置`eviction`属性来改变回收策略,如`FIFO`(先进先出)、`SOFT`(基于垃圾回收器状态和软引用)、`WEAK`(基于垃圾收集器状态和弱引用),默认是`LRU`。`flushInterval`属性可以设置刷新间隔,如`60000...
- **MyBatis**:与数据库交互,通过Mapper接口动态生成SQL,保持代码简洁。 - **数据库设计**:可能包含用户表、垃圾类型表、回收站表、预约表等,涉及关系模型设计和优化。 - **前端技术**:可能使用HTML5、CSS3...