说明
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 配置,里面包含了新增,修改,删除,查询,分页查询例子以及通过 网页访问的例子
根据项目模块来生成对应的Mapper,这里采用Maven构建项目。Mybatis Generator插件怎么使用请参考:https://my.oschina.net/boonya/blog/719502。更多代码生成器介绍请参考:...
IntellijIDEA中MybatisMapper自动注入警告的6种解决方案.docx
Mybatis Mapper.xml中字符串形式传参,逗号分隔 AND中拼接OR。
这是mybatis的生成mapper工具,可以生成bean、dao和mapper.xml.这是maven的项目的。
Spring+mvc+mybatis Mapper xml自动加载修改SQL不用重启tomcat,附带sql脚本,下载直接能运行,启动输入http://localhost:8080/aclome/
MybatisMapper主要实现只写dao接口,从而不用写到接口的实现类!
mybatis mapper 和xml文件生成
MyBatis Mapper映射文件
IntelliJ IDEA修改了mybatis mapper xml后自动热发布 热更新 热部署.zip Mybatis xml文件使用热加载检测不更新怎么办?看我的
MyBatis Mapper代理方式
里面包含说明使用方法,主要是mybatis生成mysql 数据库表并生成mapper对象和xml,此jar包请解压后,查看源码
本文详细介绍了在Mybatis框架 mapper.xml文件中parameterType传递参数常用的几种方式,以及如何实现的案列,同时#和$传参的区别。
MyBatis Mapper v2.2.1.zip
mybatis自动生成实体类和mapper文件。自动添加实体类注释。
Mapper用于映射SQL语句,可以说是MyBatis操作数据库的核心特性之一,这里我们来讨论Java的MyBatis框架中Mapper映射配置的使用及原理解析,包括对mapper的xml配置文件的读取流程解读.
本博客有零基础入门MyBatis,欢迎关注本博客。
1、我们的mapper层接口统一实现一个接口比如Mapper接口 2、Aspect切面定义时候切点匹配用this或者target eg: MethodInvocation methodInvocation = ExposeInvocationInterceptor.currentInvocation();这个句有值是...
mybatis修改了mapper xml不用重启tomcat,热发布热更新.zip
mybatis pojo mapper生成插件;在newFile.xml配置数据源地址,然后配置 <table tableName="TEST_MYBATIS" domainObjectName="TestMybatis"> </table> , <sqlMapGenerator targetPackage="com.ailk.ecs.orm.mybatis....