正常使用 mybatis时的写法:
AddressMapper mapper = session.getMapper(AddressMapper.class); (第一句)
Address address = mapper.queryById(101); (第二句)
Q1:mapper 如何 通过 sqlmap-XXX.xml 调用到 mysql?
第一句 实际返回的 是MapperProxy
实际执行时:会执行到MapperProxy的invoke方法:
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); }
MapperMethod 下面
一个是:SqlCommand
String statementName = mapperInterface.getName() + "." + method.getName();
MappedStatement ms = null;
if (configuration.hasStatement(statementName)) {
ms = configuration.getMappedStatement(statementName);
}
name = ms.getId();
type = ms.getSqlCommandType();
另一个是:
MethodSignature
用于说明方法的一些信息,主要有返回信息
最终 方法执行时:execute(SqlSession sqlSession, Object[] args)
会执行:
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
Address address = session.selectOne("org.mybatis.example.AddressMapper.queryById", 101);
statement 标识: 是 sqlmap-XXX.xml 下的namespace. id
/**
* Retrieve a single row mapped from the statement key and parameter.
* @param <T> the returned object type
* @param statement Unique identifier matching the statement to use.
* @param parameter A parameter object to pass to the statement.
* @return Mapped object
*/
<T> T selectOne(String statement, Object parameter);
底层基于
MappedStatement ms = configuration.getMappedStatement(statement);
MyBatis框架会把每一个节点(如:select节点、delete节点)生成一个MappedStatement类。
executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
## Q2 mybatis与spring集成 后 发生了什么?
集成后 XXXDAO 只要有接口就可以, 连实现都不用写了。
也就是框架帮我们做了
AddressMapper mapper = session.getMapper(AddressMapper.class);
调用时直接 调用所需方法即可。
从MapperScannerConfigurer 开始
* <bean class="org.mybatis.spring.mapper.MapperScannerConfigurer"> * <property name="basePackage" value="org.mybatis.spring.sample.mapper" /> * <!-- optional unless there are multiple session factories defined --> * <property name="sqlSessionFactoryBeanName" value="sqlSessionFactory" /> * </bean>
public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {
@Override public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) { if (this.processPropertyPlaceHolders) { processPropertyPlaceHolders(); } ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry); scanner.setAddToConfig(this.addToConfig); scanner.setAnnotationClass(this.annotationClass); scanner.setMarkerInterface(this.markerInterface); scanner.setSqlSessionFactory(this.sqlSessionFactory); scanner.setSqlSessionTemplate(this.sqlSessionTemplate); scanner.setSqlSessionFactoryBeanName(this.sqlSessionFactoryBeanName); scanner.setSqlSessionTemplateBeanName(this.sqlSessionTemplateBeanName); scanner.setResourceLoader(this.applicationContext); scanner.setBeanNameGenerator(this.nameGenerator); scanner.registerFilters(); scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS)); }
在Bean注册到容器之后, 实例化 Bean 之前 扫描basePakage 下的接口。 将其 转换为MapperFactoryBean
## 2 MapperFactoryBean
public abstract class DaoSupport implements InitializingBean { /** Logger available to subclasses */ protected final Log logger = LogFactory.getLog(getClass()); @Override public final void afterPropertiesSet() throws IllegalArgumentException, BeanInitializationException { // Let abstract subclasses check their configuration. checkDaoConfig(); // Let concrete implementations initialize themselves. try { initDao(); } catch (Exception ex) { throw new BeanInitializationException("Initialization of DAO failed", ex); } }
## Mybatis中的
public <T> void addMapper(Class<T> type) { mapperRegistry.addMapper(type); }
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 { knownMappers.put(type, new MapperProxyFactory<T>(type)); // It's important that the type is added before the parser is run // otherwise the binding may automatically be attempted by the // mapper parser. If the type is already known, it won't try.
下面两行是关键: MapperAnnotationBuilder parser = new MapperAnnotationBuilder(config, type); parser.parse(); loadCompleted = true; } finally { if (!loadCompleted) { knownMappers.remove(type); } } } }
public void parse() { String resource = type.toString(); if (!configuration.isResourceLoaded(resource)) { loadXmlResource(); configuration.addLoadedResource(resource); assistant.setCurrentNamespace(type.getName()); parseCache(); parseCacheRef(); Method[] methods = type.getMethods(); for (Method method : methods) { try { // issue #237 if (!method.isBridge()) { parseStatement(method); } } catch (IncompleteElementException e) { configuration.addIncompleteMethod(new MethodResolver(this, method)); } } } parsePendingMethods(); }
以CacheNamespace注解解析为例:
private void parseCache() { CacheNamespace cacheDomain = type.getAnnotation(CacheNamespace.class); if (cacheDomain != null) { Integer size = cacheDomain.size() == 0 ? null : cacheDomain.size(); Long flushInterval = cacheDomain.flushInterval() == 0 ? null : cacheDomain.flushInterval(); Properties props = convertToProperties(cacheDomain.properties()); assistant.useNewCache(cacheDomain.implementation(), cacheDomain.eviction(), flushInterval, size, cacheDomain.readWrite(), cacheDomain.blocking(), props); } }
## 涉及到的 Spring 知识点
### 1 spring容器、Bean配置信息、Bean 实现类 和应用程序 4者之间的关系
### 2 spring中处理bean的具体过程
### 3 Spring钩子方法和钩子接口的使用详解
https://www.jianshu.com/p/e22b9fef311c