一、學習之前思考
在學習mybatis之前,我是首先帶着三個疑問去學習mybatis:
(1)數據庫鏈接怎樣管理:與數據庫鏈接的線程池創建,sql執行等(個人理解再牛逼的orm框架,最後都需要轉成
mysql(本次實例中是使用mysql數據)的原生的sql語句,最後執行的肯定是sql執行語句)
(2)java的dao層怎樣操作sql語句:一個dao接口,爲什麼可以直接執行sql語句,
(3)數據結果是怎樣封裝的
由上述三個疑問,總結下面的mybatis的orm框架大體圖:
二、結構化分析
由上述的描述,總結了下面的mybatis的需要實現的功能的結構化圖:
三、DAO層容器
2.1、dao層注入
(1)怎樣獲取dao??
通過註解@MapperScan獲取掃描到的包,而真正去處理的是MapperScannerRegistrar。
(2)MapperScannerRegistrar是怎樣處理掃描到的dao接口的??
MapperScannerRegistrar 主要處理過程核心方法:registerBeanDefinitions
MapperScannerRegistrar.registerBeanDefinitions:
@Override
public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
//獲取註解的屬性
AnnotationAttributes annoAttrs = AnnotationAttributes.fromMap(importingClassMetadata.getAnnotationAttributes(MapperScan.class.getName()));
//通過spring上下文實例化一個掃描器
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
// 非空判斷,設置resourceLoader,3.1之後不需要檢測
if (resourceLoader != null) {
scanner.setResourceLoader(resourceLoader);
}
//Annotation 註解類設置
Class<? extends Annotation> annotationClass = annoAttrs.getClass("annotationClass");
if (!Annotation.class.equals(annotationClass)) {
scanner.setAnnotationClass(annotationClass);
}
//接口標記類加載
Class<?> markerInterface = annoAttrs.getClass("markerInterface");
if (!Class.class.equals(markerInterface)) {
scanner.setMarkerInterface(markerInterface);
}
//bean name自動生成類加載
Class<? extends BeanNameGenerator> generatorClass = annoAttrs.getClass("nameGenerator");
if (!BeanNameGenerator.class.equals(generatorClass)) {
scanner.setBeanNameGenerator(BeanUtils.instantiateClass(generatorClass));
}
//MapperFactoryBean類加載-----這個就是代理dao的BeanFactory
Class<? extends MapperFactoryBean> mapperFactoryBeanClass = annoAttrs.getClass("factoryBean");
if (!MapperFactoryBean.class.equals(mapperFactoryBeanClass)) {
scanner.setMapperFactoryBean(BeanUtils.instantiateClass(mapperFactoryBeanClass));
}
//設置掃描器數據庫管理模板和sqlSessionFactory
scanner.setSqlSessionTemplateBeanName(annoAttrs.getString("sqlSessionTemplateRef"));
scanner.setSqlSessionFactoryBeanName(annoAttrs.getString("sqlSessionFactoryRef"));
List<String> basePackages = new ArrayList<String>();
for (String pkg : annoAttrs.getStringArray("value")) {
if (StringUtils.hasText(pkg)) {
basePackages.add(pkg);
}
}
for (String pkg : annoAttrs.getStringArray("basePackages")) {
if (StringUtils.hasText(pkg)) {
basePackages.add(pkg);
}
}
for (Class<?> clazz : annoAttrs.getClassArray("basePackageClasses")) {
basePackages.add(ClassUtils.getPackageName(clazz));
}
scanner.registerFilters();
scanner.doScan(StringUtils.toStringArray(basePackages));//核心方法,具體掃描所做的邏輯都在此方法中
}
ClassPathMapperScanner.doScan:
protected Set<BeanDefinitionHolder> doScan(String... basePackages) {
Assert.notEmpty(basePackages, "At least one base package must be specified");
Set<BeanDefinitionHolder> beanDefinitions = new LinkedHashSet<BeanDefinitionHolder>();
for (String basePackage : basePackages) {
//掃描包,由於包掃描得到了所有的class,去掉一些非spring config的類,得到ScannedGenericBeanDefinition類型的BeanDefinition
Set<BeanDefinition> candidates = findCandidateComponents(basePackage);
for (BeanDefinition candidate : candidates) {
//獲取需要註解的bean的生命週期,如:singleton
ScopeMetadata scopeMetadata = this.scopeMetadataResolver.resolveScopeMetadata(candidate);
//設置bean的生命週期
candidate.setScope(scopeMetadata.getScopeName());
//bean名字自動初始化
String beanName = this.beanNameGenerator.generateBeanName(candidate, this.registry);
if (candidate instanceof AbstractBeanDefinition) {
//如果是繼承抽象的AbstractBeanDefinition,在進一步初始化,此處初始化主要是爲了設置默認值
postProcessBeanDefinition((AbstractBeanDefinition) candidate, beanName);
}
//同上一步一樣,如果該bean繼承AnnotatedBeanDefinition接口,設置默認初始值
if (candidate instanceof AnnotatedBeanDefinition) {
AnnotationConfigUtils.processCommonDefinitionAnnotations((AnnotatedBeanDefinition) candidate);
}
if (checkCandidate(beanName, candidate)) {
//賦給bean定的持有者
BeanDefinitionHolder definitionHolder = new BeanDefinitionHolder(candidate, beanName);
//判斷當前的bean對應scope註解的那種形式,mybatis 沒有使用任何形式故結果返回的還是definitionHolder
definitionHolder =
AnnotationConfigUtils.applyScopedProxyMode(scopeMetadata, definitionHolder, this.registry);
beanDefinitions.add(definitionHolder);
//註冊bean
registerBeanDefinition(definitionHolder, this.registry);
}
}
}
return beanDefinitions;
}
ClassPathMapperScanner.processBeanDefinitions:
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
GenericBeanDefinition definition;//mapperFactoryBean 註冊的是此bean,最終我們每個dao都轉換成mapperFactoryBean,注意一個dao對應一個mapperFactoryBean
for (BeanDefinitionHolder holder : beanDefinitions) {
definition = (GenericBeanDefinition) holder.getBeanDefinition();
if (logger.isDebugEnabled()) {
logger.debug("Creating MapperFactoryBean with name '" + holder.getBeanName()
+ "' and '" + definition.getBeanClassName() + "' mapperInterface");
}
//definition設置參數,即被代理的bean,查看mapperFactoryBean構造器可以看到,只有一個參數mapperInterface------代理bean構造器初始化
definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName());
definition.setBeanClass(this.mapperFactoryBean.getClass());//設置beanClass,這個值代理對象bean
//代理添加參數addToConfig
definition.getPropertyValues().add("addToConfig", this.addToConfig);
boolean explicitFactoryUsed = false;
if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
//如果當前的被代理的bean所指定的sqlSessionFactory有名字,將當前sqlSessionFactory作爲參數添加到代理bean中
definition.getPropertyValues().add("sqlSessionFactory", new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
explicitFactoryUsed = true;
} else if (this.sqlSessionFactory != null) {
//爲空直接使用當前的sqlSessionFactory
definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
explicitFactoryUsed = true;
}
//同sqlSessionFactory一樣原理
if (StringUtils.hasText(this.sqlSessionTemplateBeanName)) {
if (explicitFactoryUsed) {
logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
}
definition.getPropertyValues().add("sqlSessionTemplate", new RuntimeBeanReference(this.sqlSessionTemplateBeanName));
explicitFactoryUsed = true;
} else if (this.sqlSessionTemplate != null) {
if (explicitFactoryUsed) {
logger.warn("Cannot use both: sqlSessionTemplate and sqlSessionFactory together. sqlSessionFactory is ignored.");
}
definition.getPropertyValues().add("sqlSessionTemplate", this.sqlSessionTemplate);
explicitFactoryUsed = true;
}
if (!explicitFactoryUsed) {
if (logger.isDebugEnabled()) {
logger.debug("Enabling autowire by type for MapperFactoryBean with name '" + holder.getBeanName() + "'.");
}
//設置根據類型自動注入
definition.setAutowireMode(AbstractBeanDefinition.AUTOWIRE_BY_TYPE);//這行代碼,,,,根據type實例化的,,,配置多數據源的時候這個地方就會出問題
}
}
}
(3)總結
一句話:dao層注入,dao接口->definition->BeanDefinitionHolder->MapperFactoryBean,是通過MapperFactoryBean來代理bean的
2.2、xml與dao綁定
上面我們已經注入了dao的MapperFactoryBean代理bean,每一個dao都有一個對應的MapperFactoryBean代理對象,現在就是想知道xml或者sql語句怎麼和MapperFactoryBean聯繫在一起的。
2.2.1、xml文件掃面映射
由於掃描xml文件是在sqlSessionFactory中掃描的,所以我是先從sqlSessionFactory 的bean注入代碼去了解的:
javaconfig的SqlSessionFactory:
@Bean(name="sqlSessionFactory1")
@Primary
public SqlSessionFactory sqlSessionFactory1() throws Exception {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setDataSource(testOne);
List<Resource> resources=new ArrayList<>();
resources.addAll(Arrays.asList(new PathMatchingResourcePatternResolver().getResources("classpath:mybatis/*/person.xml")));
factoryBean.setMapperLocations(resources.toArray(new Resource[resources.size()]));
return factoryBean.getObject();
}
核心方法SqlSessionFactoryBean.buildSqlSessionFactory:
protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
Configuration configuration;//整個SqlSessionFactory的核心,沒有他SqlSessionFactory就沒法去往後操作的,主要是保存SqlSessionFactory的一些配置信息
XMLConfigBuilder xmlConfigBuilder = null;
if (this.configuration != null) {//如果不爲空直接解析xml文件
configuration = this.configuration;
if (configuration.getVariables() == null) {
configuration.setVariables(this.configurationProperties);//這個不知道什麼參數可以在這裏設置,你要是懂,可留言告知我
} else if (this.configurationProperties != null) {
configuration.getVariables().putAll(this.configurationProperties);
}
} else if (this.configLocation != null) {
xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
configuration = xmlConfigBuilder.getConfiguration();
} else {
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration");
}
configuration = new Configuration();
if (this.configurationProperties != null) {
configuration.setVariables(this.configurationProperties);
}
}
if (this.objectFactory != null) {
configuration.setObjectFactory(this.objectFactory);
}
if (this.objectWrapperFactory != null) {
configuration.setObjectWrapperFactory(this.objectWrapperFactory);
}
if (this.vfs != null) {
configuration.setVfsImpl(this.vfs);
}
if (hasLength(this.typeAliasesPackage)) {
String[] typeAliasPackageArray = tokenizeToStringArray(this.typeAliasesPackage,
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
for (String packageToScan : typeAliasPackageArray) {
configuration.getTypeAliasRegistry().registerAliases(packageToScan,
typeAliasesSuperType == null ? Object.class : typeAliasesSuperType);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Scanned package: '" + packageToScan + "' for aliases");
}
}
}
if (!isEmpty(this.typeAliases)) {
for (Class<?> typeAlias : this.typeAliases) {
configuration.getTypeAliasRegistry().registerAlias(typeAlias);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Registered type alias: '" + typeAlias + "'");
}
}
}
if (!isEmpty(this.plugins)) {
for (Interceptor plugin : this.plugins) {
configuration.addInterceptor(plugin);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Registered plugin: '" + plugin + "'");
}
}
}
if (hasLength(this.typeHandlersPackage)) {
String[] typeHandlersPackageArray = tokenizeToStringArray(this.typeHandlersPackage,
ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS);
for (String packageToScan : typeHandlersPackageArray) {
configuration.getTypeHandlerRegistry().register(packageToScan);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Scanned package: '" + packageToScan + "' for type handlers");
}
}
}
if (!isEmpty(this.typeHandlers)) {
for (TypeHandler<?> typeHandler : this.typeHandlers) {
configuration.getTypeHandlerRegistry().register(typeHandler);
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Registered type handler: '" + typeHandler + "'");
}
}
}
if (this.databaseIdProvider != null) {//fix #64 set databaseId before parse mapper xmls
try {
configuration.setDatabaseId(this.databaseIdProvider.getDatabaseId(this.dataSource));
} catch (SQLException e) {
throw new NestedIOException("Failed getting a databaseId", e);
}
}
if (this.cache != null) {
configuration.addCache(this.cache);
}
if (xmlConfigBuilder != null) {
try {
xmlConfigBuilder.parse();
if (LOGGER.isDebugEnabled()) {
LOGGER.debug("Parsed configuration file: '" + this.configLocation + "'");
}
} catch (Exception ex) {
throw new NestedIOException("Failed to parse config resource: " + this.configLocation, ex);
} finally {
ErrorContext.instance().reset();
}
}
if (this.transactionFactory == null) {
this.transactionFactory = new SpringManagedTransactionFactory();
}
configuration.setEnvironment(new Environment(this.environment, this.transactionFactory, this.dataSource));
if (!isEmpty(this.mapperLocations)) {
for (Resource mapperLocation : this.mapperLocations) {
if (mapperLocation == null) {
continue;
}
try {
XMLMapperBuilder xmlMapperBuilder = new XMLMapperBuilder(mapperLocation.getInputStream(),
configuration, mapperLocation.toString(), configuration.getSqlFragments());//此處是重點,解析了sql的xml文件
xmlMapperBuilder.parse();//解析xml文件,注意,這裏解析返回的會有72中mybatis已經規定好的類型別名(我們在定義別名的時候注意不要重複)
} 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");
}
}
return this.sqlSessionFactoryBuilder.build(configuration);
}
真正解析類:XMLMapperBuilder.parse()
public void parse() {
if (!configuration.isResourceLoaded(resource)) {//資源如果未加載,開始解析
configurationElement(parser.evalNode("/mapper"));//解析mapper節點---對應xml文件<mapper></mapper>
configuration.addLoadedResource(resource);
bindMapperForNamespace();//命名空間綁定:namespace="bootdemo.dao.db1.PersonDaoOne",此處會生成一個對應的MapperProxyFactory 來代理MapperProxyFactoryBean
}
parsePendingResultMaps();
parsePendingCacheRefs();
parsePendingStatements();
}
xml解析方法實現:XMLMapperBuilder.configurationElement
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);//設置命名空間名稱---留個疑問:爲什麼命名空間必須是dao類路徑
cacheRefElement(context.evalNode("cache-ref"));//mapper緩存設置
cacheElement(context.evalNode("cache"));
parameterMapElement(context.evalNodes("/mapper/parameterMap"));//xml文件定的參數map,對應xml標籤:<parameterMap></parameterMap>
resultMapElements(context.evalNodes("/mapper/resultMap"));//xml文件定的結果DTO對應map,對應xml標籤:<resultMap></resultMap>
sqlElement(context.evalNodes("/mapper/sql"));//xml文件定的結果sql標籤,對應xml標籤:<sql></sql>
//增刪改語句解析,增刪改對應的映射類爲:XMLStatementBuilder,解析成的XMLStatementBuilder放在configuration中
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
} catch (Exception e) {
throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e);
}
}
生成MapperProxyFactory:XMLMapperBuilder.bindMapperForNamespace----實際上xml文件是由MapperRegissry來映射的
private void bindMapperForNamespace() {
String namespace = builderAssistant.getCurrentNamespace();
if (namespace != null) {
Class<?> boundType = null;
try {
boundType = Resources.classForName(namespace);
} catch (ClassNotFoundException e) {
}
if (boundType != null) {
if (!configuration.hasMapper(boundType)) {
configuration.addLoadedResource("namespace:" + namespace);//空間命名添加到容器中
configuration.addMapper(boundType);//將MapperProxyFactory 代理類添加到容器中
}
}
}
}
//添加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);
}
}
}
}
總結:基於上述的代碼,我們已經將整個的xml配置文件解析到configuration,並賦給SqlSessionFactory屬中的屬性:xml->configuration->SqlSessionFactory
四、調用方怎樣調用
由第二部分介紹了,一個dao層怎麼變成一個bean實例的(MapperFactoryBean),第三部分介紹了怎樣xml文件怎樣
生成java實例(MapperRegissry),接下來就是怎麼將兩者聯繫在一起和怎樣去執行方法的。
4.1、xml和注入的dao bean關聯
java的類dao生成了MapperFactoryBean實例,從MapperFactoryBean源碼可以看到MapperFactoryBean繼承了FactoryBean,所以他是一個Bean工廠類,我們可以看到他的bean獲取實例方法getObject()。
MapperFactoryBean.getObject():獲取實例對象
@Override
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
對於採用SqlSessionTemplate模板管理的getMapper方法:
public <T> T getMapper(Class<T> type) {
return getConfiguration().getMapper(type, this);
}
MapperRegistry.getMapper():
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);
}
}
protected T newInstance(MapperProxy<T> mapperProxy) {
//MapperProxy 又是一個代理對象,最終我們生成的bean是由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);
}
總結:MapperFactoryBean.getObject()->SqlSession.getMapper()->MapperRegistry.getMapper()->MapperProxyFactory.newInstance()->MapperProxy代理,最終獲取到實例
4.2、MapperProxy怎樣去執行dao中的方法
首先看MapperProxy代理類的invoke是怎樣執行的方法的。
MapperProxy.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)) {
//如果直接實現代理類,直接運行方法----據說是JDK 1.8屬性
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
//獲取需要執行的方法mapper,從Configuration容器中獲取到對應的MapperMethod:
//MapperMethod.SqlCommand 靜態類,其構造器有個方法resolveMappedStatement爲後去mapper 接口
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);//真正執行的接口
}
sql執行接口MapperMethod.execute():
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);
}
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;
}
以查詢爲例,真正查詢的接sql:BaseExecutor.query()
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();//執行查詢
return resultSetHandler.<E> handleResultSets(ps);//結果封裝到dto中
}
有啥疑問請指出,聯繫方式qq:158479841
拒絕轉載!!!!!!