前面我们描述的是XML的形式,这次我们来看注解的形式,跟前面一样先会去解析mybatis的配置文件,然后再解析mapper文件。我们这里采用的是注解形式也就不在看mapper文件的解析。前面解析也讲述过解析mybatis的配置文件这里不在重复,直接跳转到解析sql语句这里
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
//10.4自动扫描包下所有映射器
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);
} else {
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
if (resource != null && url == null && mapperClass == null) {
//10.1使用类路径
ErrorContext.instance().resource(resource);
InputStream inputStream = Resources.getResourceAsStream(resource);
//映射器比较复杂,调用XMLMapperBuilder
//注意在for循环里每个mapper都重新new一个XMLMapperBuilder,来解析
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url != null && mapperClass == null) {
//10.2使用绝对url路径
ErrorContext.instance().resource(url);
InputStream inputStream = Resources.getUrlAsStream(url);
//映射器比较复杂,调用XMLMapperBuilder
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url == null && mapperClass != null) {
//10.3使用java类名
Class> mapperInterface = Resources.classForName(mapperClass);
//直接把这个映射加入配置
configuration.addMapper(mapperInterface);
} else {
throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}
}
}
}
}
我们具体看configuration.addMappers(mapperPackage),解析注解的具体就在这里,mybatis的配置文件需要配置扫描哪些包下,这里的mapperPackage就是我们配置的包路径
public void addMapper(Class type) {
//mapper必须是接口!才会添加
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(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 {
//如果加载过程中出现异常需要再将这个mapper从mybatis中删除,这种方式比较丑陋吧,难道是不得已而为之?
if (!loadCompleted) {
knownMappers.remove(type);
}
}
}
}
进入MapperAnnotationBuilder注解的解析类中,首先还是会去加载类名对应的xml文件
public void parse() {
String resource = type.toString();
if (!configuration.isResourceLoaded(resource)) {
//加载类名对应的.xml文件
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();
}
我们这里具体看注解解析,进入parseStatement(method),可以看到有SelectKey、ResultMap等注解解析
void parseStatement(Method method) {
Class> parameterTypeClass = getParameterType(method);
LanguageDriver languageDriver = getLanguageDriver(method);
//获取方法上的注解
SqlSource sqlSource = getSqlSourceFromAnnotations(method, parameterTypeClass, languageDriver);
if (sqlSource != null) {
Options options = method.getAnnotation(Options.class);
final String mappedStatementId = type.getName() + "." + method.getName();
Integer fetchSize = null;
Integer timeout = null;
StatementType statementType = StatementType.PREPARED;
ResultSetType resultSetType = ResultSetType.FORWARD_ONLY;
SqlCommandType sqlCommandType = getSqlCommandType(method);
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
boolean flushCache = !isSelect;
boolean useCache = isSelect;
KeyGenerator keyGenerator;
String keyProperty = "id";
String keyColumn = null;
if (SqlCommandType.INSERT.equals(sqlCommandType) || SqlCommandType.UPDATE.equals(sqlCommandType)) {
// first check for SelectKey annotation - that overrides everything else
SelectKey selectKey = method.getAnnotation(SelectKey.class);
if (selectKey != null) {
keyGenerator = handleSelectKeyAnnotation(selectKey, mappedStatementId, getParameterType(method), languageDriver);
keyProperty = selectKey.keyProperty();
} else if (options == null) {
keyGenerator = configuration.isUseGeneratedKeys() ? new Jdbc3KeyGenerator() : new NoKeyGenerator();
} else {
keyGenerator = options.useGeneratedKeys() ? new Jdbc3KeyGenerator() : new NoKeyGenerator();
keyProperty = options.keyProperty();
keyColumn = options.keyColumn();
}
} else {
keyGenerator = new NoKeyGenerator();
}
if (options != null) {
flushCache = options.flushCache();
useCache = options.useCache();
fetchSize = options.fetchSize() > -1 || options.fetchSize() == Integer.MIN_VALUE ? options.fetchSize() : null; //issue #348
timeout = options.timeout() > -1 ? options.timeout() : null;
statementType = options.statementType();
resultSetType = options.resultSetType();
}
String resultMapId = null;
ResultMap resultMapAnnotation = method.getAnnotation(ResultMap.class);
if (resultMapAnnotation != null) {
String[] resultMaps = resultMapAnnotation.value();
StringBuilder sb = new StringBuilder();
for (String resultMap : resultMaps) {
if (sb.length() > 0) {
sb.append(",");
}
sb.append(resultMap);
}
resultMapId = sb.toString();
} else if (isSelect) {
resultMapId = parseResultMap(method);
}
assistant.addMappedStatement(
mappedStatementId,
sqlSource,
statementType,
sqlCommandType,
fetchSize,
timeout,
// ParameterMapID
null,
parameterTypeClass,
resultMapId,
getReturnType(method),
resultSetType,
flushCache,
useCache,
// TODO issue #577
false,
keyGenerator,
keyProperty,
keyColumn,
// DatabaseID
null,
languageDriver,
// ResultSets
null);
}
}
我们具体去看获取方法上的注解的sql语句,进入getSqlSourceFromAnnotations,主要就是获取注解上的sql字符串,并且把#替换成?
private SqlSource getSqlSourceFromAnnotations(Method method, Class> parameterType, LanguageDriver languageDriver) {
try {
Class extends Annotation> sqlAnnotationType = getSqlAnnotationType(method);
Class extends Annotation> sqlProviderAnnotationType = getSqlProviderAnnotationType(method);
if (sqlAnnotationType != null) {
if (sqlProviderAnnotationType != null) {
throw new BindingException("You cannot supply both a static SQL and SqlProvider to method named " + method.getName());
}
Annotation sqlAnnotation = method.getAnnotation(sqlAnnotationType);
//获取注解上到sql字符串,并且把#替换成?
final String[] strings = (String[]) sqlAnnotation.getClass().getMethod("value").invoke(sqlAnnotation);
//拼接字符串为sql语句
return buildSqlSourceFromStrings(strings, parameterType, languageDriver);
} else if (sqlProviderAnnotationType != null) {
Annotation sqlProviderAnnotation = method.getAnnotation(sqlProviderAnnotationType);
return new ProviderSqlSource(assistant.getConfiguration(), sqlProviderAnnotation);
}
return null;
} catch (Exception e) {
throw new BuilderException("Could not find value method on SQL annotation. Cause: " + e, e);
}
}
后面的执行过程也就跟前面一样了
现在并不是单独去使用mybatis,而是结合spring使用,所以这篇分析是怎么结合spring的。
首先得去配置mybatis的一些相关信息,数据源,sqlSessionFactory和mapper文件扫描类
因为需要整合spring,所以所有的创建对象都要交给spring管理,所以我们需要用spring的工厂类SqlSessionFactoryBean,它实现InitializingBean,结合spring的知识它的afterPropertiesSet方法会在bean初始化的时候执行
public class SqlSessionFactoryBean implements FactoryBean, InitializingBean, ApplicationListener {
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();
}
public SqlSessionFactory getObject() throws Exception {
if (this.sqlSessionFactory == null) {
afterPropertiesSet();
}
return this.sqlSessionFactory;
}
}
在该方法里创建了sqlSessionFactory,方法中主要去创建configuration并把相关信息设置,如果有配mybatis的配置文件,还会去解析配置信息。另外mybatis中是用TransactionFactory创建事务的,结合spring是默认会实例化SpringManagedTransaction Factory,Executor是需要TransactionFactory创建的,执行中需要去事务工厂拿到连接。因为两者都有事务管理,所以为了统一默认就用SpringManagedTransactionFactory解决共用数据库连接的问题。
protected SqlSessionFactory buildSqlSessionFactory() throws IOException {
Configuration configuration;
XMLConfigBuilder xmlConfigBuilder = null;
if (this.configuration != null) {
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.transactionFactory == null) {
this.transactionFactory = new SpringManagedTransactionFactory();
}
.......
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();
}
}
.....
return this.sqlSessionFactoryBuilder.build(configuration);
}
最后一句还是会去通过mybatis的sqlSessionFactoryBuilder.build(configuration)去创建mybatis的SqlSessionFactory。因为SqlSessionFactoryBean还是一个工厂类,最后通过getObject方法把DefaultSqlSessionFactory返回出去。
接下来看MapperScannerConfigurer怎么去扫描包创建代理对象,并把sql语句信息保存到configuration中
public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {
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));
}
}
该类实现了BeanDefinitionRegistryPostProcessor后置处理器,所以会在bean初始化的时候调用postProcessBeanDefinition Registry方法,在这里面就是会去扫描mapper接口和mapper文件,核心逻辑在doScan方法中
public Set doScan(String... basePackages) {
// 调用父类的doScan()方法,遍历basePackages中指定的所有包,扫描每个包下的Java文件并进行解析。
// 使用之前注册的过滤器进行过滤,得到符合条件的BeanDefinitionHolder对象
Set beanDefinitions = super.doScan(basePackages);
if (beanDefinitions.isEmpty()) {
logger.warn("No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration.");
} else {
// 处理扫描得到的BeanDefinitionHolder集合
processBeanDefinitions(beanDefinitions);
}
return beanDefinitions;
}
在processBeanDefinitions方法中会对doScan()方法中扫描到的BeanDefinition集合进行修改,主要是将其中记录的接口类型改造为MapperFactoryBean类型
private void processBeanDefinitions(Set beanDefinitions) {
GenericBeanDefinition definition;
for (BeanDefinitionHolder holder : beanDefinitions) {
definition = (GenericBeanDefinition) holder.getBeanDefinition();
if (logger.isDebugEnabled()) {
logger.debug("Creating MapperFactoryBean with name '" + holder.getBeanName()
+ "' and '" + definition.getBeanClassName() + "' mapperInterface");
}
// 将扫描到的接口类型作为构造方法的参数
definition.getPropertyValues().add("mapperInterface", definition.getBeanClassName());
// 将BeanDefinition中记录的Bean类型修改为MapperFactoryBean
definition.setBeanClass(this.mapperFactoryBean.getClass());
// 构造MapperFactoryBean的属性,将sqlSessionFactory、sqlSessionTemplate
// 等信息填充到BeanDefinition中
// 修改自动注入方式
}
}
MapperFactoryBean类的动态代理功能是通过实现了Spring提供的FactoryBean接口实现的,该接口是一个用于创建Bean对象的工厂接口,通过geObject()方法获取真实的对象。完成了可以直接将Mapper接口注入到 Service 层的Bean中,这样就不需要编写任何DAO实现的代码。
public class MapperFactoryBean extends SqlSessionDaoSupport implements FactoryBean {
public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
if (!this.externalSqlSession) {
this.sqlSession = new SqlSessionTemplate(sqlSessionFactory);
}
}
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
}
所以每个Mapper接口对应的实现类还是MyBatis提供的MapperProxy代理对象,MapperFactoryBean类只不过是包装了一下,让真正的对象能够注入到Spring容器中。所以Mapper接口对应的实现类是作为单例一直存在Spring容器中的,它的sql执行方式我前面讲述过。
但是在这里spring多封装了一层,从构造方法中可以看到这里的sqlSession不是mybatis的,而是SqlSessionTemplate,SqlSessionTemplate实现了SqlSession接口,在 MyBatis与Spring集成开发时,用来代替MyBatis中的DefaultSqlSession的功能。所以该类主要是用于获取 MapperProxy代理对象和执行SQL操作。
在mybatis中sqlSession,sql操作的相关方法是会话级别的不能共享,一次操作完成后就需要去关闭。而SqlSessionTemplate是线程安全的,可以在DAO之间共享使用,我们主要看SqlSessionTemplate是怎么代理的
public class SqlSessionTemplate implements SqlSession, DisposableBean {
// SqlSession 的代理类
private final SqlSession sqlSessionProxy;
public SqlSessionTemplate(SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
PersistenceExceptionTranslator exceptionTranslator) {
notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
notNull(executorType, "Property 'executorType' is required");
this.sqlSessionFactory = sqlSessionFactory;
this.executorType = executorType;
this.exceptionTranslator = exceptionTranslator;
// 创建SqlSession的代理类,给sqlSessionProxy属性赋值
this.sqlSessionProxy = (SqlSession) newProxyInstance(
SqlSessionFactory.class.getClassLoader(),
new Class[] { SqlSession.class },
new SqlSessionInterceptor());
}
}
在创建SqlSessionTemplate的时候内部会创建sqlSessionProxy代理对象,代理对象具体逻辑就在SqlSessionInterceptor中
private class SqlSessionInterceptor implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 通过SqlSessionUtils.getSqlSession()获取SqlSession对象,同一个事务共享SqlSession
SqlSession sqlSession = getSqlSession(
SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType,
SqlSessionTemplate.this.exceptionTranslator);
// 调用SqlSession对象的相应方法
Object result = method.invoke(sqlSession, args);
// 检测事务是否由Spring进行管理,并据此决定是否提交事务
if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
// force commit even on non-dirty sessions because some databases require
// a commit/rollback before calling close()
sqlSession.commit(true);
}
return result;
}
}
可以看到在这里面才是真正的去用mybatis的SqlSession去执行sql操作,并且会去提交事务,我们就不用每次执行完后还要手动提交一下了。从这里也可以看出原先mybatis是会把结果集放到缓存中的,而结合spring之后是把mybatis缓存去掉了