mybatis 中的註解
正常使用 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