希望读者们能将这个基础的流程熟读于心
String resource = "org/mybatis/example/mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
try (SqlSession session = sqlSessionFactory.openSession()) {
BlogMapper mapper = session.getMapper(BlogMapper.class);
Blog blog = mapper.selectBlog(101);
}
场景驱动,如果用myBatis进行一次简单的插入操作,其流程是怎么样的?
Mapper.insert()
->MapperProxy.invoke()
->MapperMethod.invoke()
->Executor.excute()
->statementHandler.update()
->statment.excute()
之所以是用插入而不用根据代表的select,原因是select有处理数据集映射成对象实例的过程,为避免篇幅过长,此章节不讨论该过程。
上节我们讨论过了,Mapper是代理类,任何执行的方法会进入其Proxy类的invoke()方法。
如果对此不了解的话,可以去复习一下JDK动态代理的内容。
场景驱动,当我们执行Mapper.insert()这方法的时候,会进入红框的部分。
获得在缓存中的MapperMethod,如果缓存中没有就直接new一个mapperMethod
final MapperMethod mapperMethod = cachedMapperMethod(method);
private final Map<Method, MapperMethod> methodCache;
private MapperMethod cachedMapperMethod(Method method) {
return methodCache.computeIfAbsent(method, k -> new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()));
}
然后直接MapperMethod.excute()。
注意下,这次excute函数并没有存入proxy作为参数,也就是说,它不用Proxy实例来执行该次插入操作。先不用及,我们先讨论一下MapperMethod是什么东西。
现在进入到MethodMapper.excute(SqlSession sqlSession, Object[] args)方法中。
看到command.getType()不用想就知道判断现在是crud的哪款。
调到MethodSignature.convertArgsToSqlCommandParam()。顾名思义,将Args转换为sql命令。
这是一个脏活累活,有兴趣的读者可以自行了解。我们需要知道的是param这个字段已经转换成了sql语句。
那么,我们继续调到sqlSession.insert(SqlCommand.getName(), param),这是回调哦。
DefaultSqlSession.insert(),在update()那里,直接调用了Executor的update(MappedStament, Object)。现在已经出现了Stament字眼了,离真正的数据库插入应该不远了。
随便说一下,wrapCollection(parameter)是一个集合,存放着#{}里面的数,而MappedStatement存放在各种各样的信息,包括和数据库连接的配置信息、MyBatis的配置信息。
@Override
public int insert(String statement, Object parameter) {
return update(statement, parameter);
}
@Override
public int update(String statement, Object parameter) {
try {
dirty = true;
MappedStatement ms = configuration.getMappedStatement(statement);
return executor.update(ms, wrapCollection(parameter));
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error updating database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
如果是启动了二级缓存,该Executor的实现类就是CachingExecutor,否则就是SimpleExecutor。但对于insert而言,都是在SimpleEXecutor里面执行。
好家伙,没有update方法,那应该是在去父类BaseExecutor中。
逮到!其执行子类doUpdate()
红框第一行是创建StatementHandler
第二行是创建jdbc的statement,是从数据库连接池获得Connection然后再获得Statement。
第三行是真正去statement执行
下张图,熟悉的preparedStament的读者。
其实是preparedStament的ps = conn.prepareStatement(sql);
和ps.setString(); 之类的,preparedStatement已经准备完毕,等待excute()!
插入操作是需要填入参数,所以用PreparedStatement,防止sql注入攻击嘛。
执行完毕,返回熟悉的rows
顾名思义,是Method的包装类。
public class MapperMethod {
private final SqlCommand command;
private final MethodSignature method;
SqlCommand,name是..., type是枚举类,用于确定增删改查的。
public static class SqlCommand {
private final String name;
private final SqlCommandType type;
public enum SqlCommandType {
UNKNOWN, INSERT, UPDATE, DELETE, SELECT, FLUSH;
}
MethodSignature,这个是Method的配置,就是returnType时候啥那些的。
public static class MethodSignature {
private final boolean returnsMany;
private final boolean returnsMap;
private final boolean returnsVoid;
private final boolean returnsCursor;
private final boolean returnsOptional;
private final Class<?> returnType;
private final String mapKey;
private final Integer resultHandlerIndex;
private final Integer rowBoundsIndex;
private final ParamNameResolver paramNameResolver;
这个MapperMethod与其说是Mapper接口的包装,不如直接说是mapper.xml中的method的内存映射,而原来的Mapper接口是作为MapperProxy的methodCache的key,而value是这MapperMethod,这样java接口就和mapper.xml文件映射起来了。
说说链路中我觉得比较重要的东西。
这个Mybatis坏得很呢,创建了Mapper接口,但重来都不用它们的实例,当然,它们是实例也是接口,没有实现类,空空如也。
它们都是有Mapper接口作为工具人,仅仅用来确定调用者调用哪一个方法,然后用有个Map<Method, MapperMethod> 作为JAVA接口与xml文件的映射。
我们知道真正有用的是mapper.xml文件,它们也用这个巧妙的跳过了Mapper接口,同时也给Spring大佬自己的mapper接口实例。
至于Spring怎么将Mapper收入自己的IOC容器,我想应该可以从MapperScan这个注解入手。
下节,我们主要分析MyBatis作为Orm框架最重要的对象映射,怎么将数据库找那个查询获得的ResultSet转换为JOPO。
原文:https://www.cnblogs.com/zhoujianyi/p/14326482.html