在使用mybatis時(shí),我們通常直接調(diào)用接口dao(也就是常說的mapper)中的方法呛梆,然后應(yīng)用會(huì)自動(dòng)調(diào)用xml文件中對(duì)應(yīng)標(biāo)簽(select |update| insert|delete)中id與dao中方法名相同的sql,dao通過xml文件中的namespace做映射师幕,調(diào)用dao即是調(diào)用xml中的sql隆圆。是怎么實(shí)現(xiàn)的呢?
SqlSessionFactoryBean
mybatis 通常與spring 一起使用俭识,配置SqlSessionFactoryBean
<bean class="org.mybatis.spring.SqlSessionFactoryBean">
<!-- 實(shí)例化sqlSessionFactory時(shí)需要使用上述配置好的數(shù)據(jù)源以及SQL映射文件 -->
<property name="dataSource" ref="mailaDataSource"/>
<property name="mapperLocations" value="classpath*:mybatis/maila/**/*.xml"/>
<property name="configLocation" value="classpath:mybatis/sqlMapConfig.xml"/>
<property name="typeAliasesPackage" value="com.maila.biz.center.biz.entity,com.maila88.**.entity"/>
</bean>
首先我們來看一下SqlSessionFactoryBean,該bean 實(shí)現(xiàn)了InitializingBean接口洞渔,初始化完成時(shí)會(huì)調(diào)用afterPropertiesSet方法
public void afterPropertiesSet() throws Exception {
notNull(dataSource, "Property 'dataSource' is required");
notNull(sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
state((configuration == null && configLocation == null) || !(configuration != null && configLocation != null),
"Property 'configuration' and 'configLocation' can not specified with together");
this.sqlSessionFactory = buildSqlSessionFactory();
}
rotected SqlSessionFactory buildSqlSessionFactory() throws IOException {
Configuration configuration;
XMLConfigBuilder xmlConfigBuilder = null;
...
}
buildSqlSessionFactory方法內(nèi)容很多套媚,大致上就是加載xml(xml包含sql的xml和setting設(shè)置的xml),解析xml等磁椒,包裝mybatis的configuration堤瘤。這里不做過多的介紹,這里我們?cè)敿?xì)介紹一樣dao與xml的映射問題浆熔。在buildSqlSessionFactory方法中有這么一段代碼:
if (!isEmpty(this.mapperLocations)) {
for (Resource mapperLocation : this.mapperLocations) {
if (mapperLocation == null) {
continue;
}
try {
//mapperLocation路徑轉(zhuǎn)換為xmlMappperBuilder
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
configuration, mapperLocation.toString(), configuration.getSqlFragments());
//解析xml
xmlMapperBuilder.parse();
} catch (Exception e) {
throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
} finally {
ErrorContext.instance().reset();
}
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Parsed mapper file: '" + mapperLocation + "'");
}
}
} else {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Property 'mapperLocations' was not specified or no matching resources found");
}
}
上述代碼把<propertyname="mapperLocations"value="classpath*:mybatis/maila/**/*.xml"/>
轉(zhuǎn)化為xmlMapperBuilder本辐,并加以parse();
parse()
public void parse() {
if (!configuration.isResourceLoaded(resource)) {
//設(shè)置xml中的標(biāo)簽元素
configurationElement(parser.evalNode("/mapper"));
configuration.addLoadedResource(resource);
//mapper 與namespace 綁定
bindMapperForNamespace();
}
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
private void configurationElement(XNode context) {
try {
String namespace = context.getStringAttribute("namespace");
if (namespace == null || namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
builderAssistant.setCurrentNamespace(namespace);
cacheRefElement(context.evalNode("cache-ref"));
cacheElement(context.evalNode("cache"));
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
resultMapElements(context.evalNodes("/mapper/resultMap"));
sqlElement(context.evalNodes("/mapper/sql"));
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
} catch (Exception e) {
throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e);
}
}
這里我們?cè)敿?xì)看一下mapper 與namespace的綁定
private void bindMapperForNamespace() {
String namespace = builderAssistant.getCurrentNamespace();
if (namespace != null) {
Class<?> boundType = null;
try {
boundType = Resources.classForName(namespace);
} catch (ClassNotFoundException e) {
//ignore, bound type is not required
}
if (boundType != null) {
if (!configuration.hasMapper(boundType)) {
// Spring may not know the real resource name so we set a flag
// to prevent loading again this resource from the mapper interface
// look at MapperAnnotationBuilder#loadXmlResource
configuration.addLoadedResource("namespace:" + namespace);
configuration.addMapper(boundType);
}
}
}
}
綁定做了幾件事情
- 取得當(dāng)前的namaspace(即mapper接口)的全限定路徑名
- 反射得到類
- 類添加至mybatis configuration -- configuration.addMapper(boundType)
addMapper方法:
protected final MapperRegistry mapperRegistry = new MapperRegistry(this);
public <T> void addMapper(Class<T> type) {
mapperRegistry.addMapper(type);
}
MapperRegistry的addMapper方法:
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();
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);
}
}
}
}
knownMappers 以類為key,MapperProxyFactory 為value医增,也就是說:mybatis 為每個(gè)dao接口都創(chuàng)建了一個(gè)代理工廠慎皱。
public class MapperProxyFactory<T> {
private final Class<T> mapperInterface;
private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>();
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<T>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
}
代理工廠實(shí)例化負(fù)責(zé)實(shí)例化每個(gè)接口dao,并用一個(gè)private final Map<Method, MapperMethod> methodCache = new ConcurrentHashMap<Method, MapperMethod>();
儲(chǔ)存dao中的方法調(diào)用叶骨,MapperMethod又是一個(gè)很大的內(nèi)容宝冕,后續(xù)再談。
至此邓萨,我們知道了mybatis configuration中有個(gè)mapperRegistry,mapperRegistry中有一個(gè)Map菊卷,保存了所有dao和他的代理缔恳。