1、Mybatis简介
MyBatis 是一款优秀的持久层框架,它支持自定义 SQL、存储过程以及高级映射。在项目中通过配置不同的mapper和xml,然后即可方便的对数据库进行操作。对于Mybatis的使用方法大家应该都很熟悉,本文不再赘述,本文重点叙述Mybatis的原理,详细分析Mybatis如何完成从mapper到sql的转化过程。下文会重点叙述两个方面:
- 启动时候Mybatis如何扫描并解析Mapper
- 运行时如何完成从对Mapper方法的调用转化成Sql
2、Mybatis扫描Mapper过程
2.1、Mapper扫描过程源码分析
对于dataSource和SqlSessionFactory大家应该都比较了解,我们不难猜测MapperScannerConfigurer主要完成对Mapper的扫描和解析。
<bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close">
<property name="driverClassName">
<value>${jdbc.driverClassName}</value>
</property>
<property name="url">
<value>${jdbc.url}</value>
</property>
<property name="username">
<value>${jdbc.username}</value>
</property>
<property name="password">
<value>${jdbc.password}</value>
</property>
</bean>
//负责加载解析mapper对应的xml配置,将mapper和mappedStatements等配置信息保存到DefaultSqlSessionFactory的configuration属性
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource" />
<property name="mapperLocations" value="classpath*:*.xml" />
</bean>
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage"
value="xxx.dao"/>
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"/>
</bean>
<!--事务管理器-->
<bean id="transactionManager" class="org.springframework.jdbc.datasource.DataSourceTransactionManager">
<property name="dataSource" ref="sourceDataSource"></property>
</bean>
MapperScannerConfigurer实现BeanDefinitionRegistryPostProcessor接口,在对Bean实例化之前,通过回调postProcessBeanDefinitionRegistry方法register Mapper对应的BeanDefinition。最终将Mapper对应的Bean包装成MapperFactoryBean,并指定调用父类SqlSessionDaoSupport的setSqlSessionFactory方法给子类MapperFactoryBean添加对应的sqlSessionFactory属性。上述过程最终将Mapper转化成了MapperFactoryBean,如果对FactoryBean比较了解的话,应该知道后续我们通过@Autowired注解注入Mapper时候,会调用MapperFactoryBean的getObject()方法构造Mapper的实例。
public class MapperScannerConfigurer implements BeanDefinitionRegistryPostProcessor, InitializingBean, ApplicationContextAware, BeanNameAware {
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) {
if (this.processPropertyPlaceHolders) {
processPropertyPlaceHolders();
}
ClassPathMapperScanner scanner = new ClassPathMapperScanner(registry);
//扫描指定的mapper
scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
}
}
public class ClassPathMapperScanner extends ClassPathBeanDefinitionScanner {
@Override
public Set<BeanDefinitionHolder> doScan(String... basePackages) {
//调用父类方法扫描xml配置的mapper路径,返回对应的beanDefinitions
Set<BeanDefinitionHolder> beanDefinitions = super.doScan(basePackages);
if (beanDefinitions.isEmpty()) {
logger.warn("No MyBatis mapper was found in '" + Arrays.toString(basePackages) + "' package. Please check your configuration.");
} else {
//上述beanDefinitions 包装成MapperFactoryBean
processBeanDefinitions(beanDefinitions);
}
return beanDefinitions;
}
private void processBeanDefinitions(Set<BeanDefinitionHolder> beanDefinitions) {
GenericBeanDefinition definition;
for (BeanDefinitionHolder holder : beanDefinitions) {
definition = (GenericBeanDefinition) holder.getBeanDefinition();
// 将mapper对应的beanDefinitions包装成MapperFactoryBean
MapperFactoryBean
definition.getConstructorArgumentValues().addGenericArgumentValue(definition.getBeanClassName()); // issue #59
definition.setBeanClass(this.mapperFactoryBean.getClass());
definition.getPropertyValues().add("addToConfig", this.addToConfig);
boolean explicitFactoryUsed = false;
//如果xml配置了SqlSessionFactory调用父类SqlSessionDaoSupport的setSqlSessionFactory方法给子类MapperFactoryBean添加对应的sqlSession属性,最终每个Mapper都会创建一个SqlSessionTemplate
if (StringUtils.hasText(this.sqlSessionFactoryBeanName)) {
definition.getPropertyValues().add("sqlSessionFactory", new RuntimeBeanReference(this.sqlSessionFactoryBeanName));
explicitFactoryUsed = true;
} else if (this.sqlSessionFactory != null) {
definition.getPropertyValues().add("sqlSessionFactory", this.sqlSessionFactory);
explicitFactoryUsed = true;
}
//如果xml配置了SqlSessionTemplate,调用父类SqlSessionDaoSupport的setSqlSessionTemplate方法给子类MapperFactoryBean添加对应的sqlSession属性,此时所有Mapper共享一个SqlSessionTemplate(线程安全)
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);
}
}
}
}
public abstract class SqlSessionDaoSupport extends DaoSupport {
private SqlSession sqlSession;
private boolean externalSqlSession;
public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
if (!this.externalSqlSession) {
this.sqlSession = new SqlSessionTemplate(sqlSessionFactory);
}
}
public void setSqlSessionTemplate(SqlSessionTemplate sqlSessionTemplate) {
this.sqlSession = sqlSessionTemplate;
this.externalSqlSession = true;
}
}
2.2、MapperFactoryBean构造真实Mapper
上文已经叙述过Spring依赖注入Mapper时候,会调用getObject方法来返回真实的实例,然后经由一系列过程调用MapperRegistry的getMapper方法
public class MapperFactoryBean<T> extends SqlSessionDaoSupport implements FactoryBean<T> {
@Override
public T getObject() throws Exception {
//上文已经知道getSqlSession()会返回SqlSessionTemplate的一个实例
return getSqlSession().getMapper(this.mapperInterface);
}
}
public abstract class SqlSessionDaoSupport extends DaoSupport {
private SqlSession sqlSession;
private boolean externalSqlSession;
public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
if (!this.externalSqlSession) {
this.sqlSession = new SqlSessionTemplate(sqlSessionFactory);
}
}
}
public class SqlSessionTemplate implements SqlSession, DisposableBean {
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;
//又是使用动态代理,对sqlSessionProxy方法的调用最终都会调用SqlSessionInterceptor的invoke方法
this.sqlSessionProxy = (SqlSession) newProxyInstance(
SqlSessionFactory.class.getClassLoader(),
new Class[] { SqlSession.class },
new SqlSessionInterceptor());
}
@Override
public <T> T getMapper(Class<T> type) {
return getConfiguration().getMapper(type, this);
}
//返回sqlSessionFactory对应的Configuration
public Configuration getConfiguration() {
return this.sqlSessionFactory.getConfiguration();
}
}
MapperRegistry的getMapper方法会调用MapperProxyFactory的newInstance方法,该方法通过jdk动态代理来代理Mapper,后续对该Mapper所有方法的调用都会调用MapperProxy的invoke方法,至此完成了启动时Spring对Mapper的加载过程。
public class MapperRegistry {
private final Configuration config;
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<Class<?>, MapperProxyFactory<?>>();
public MapperRegistry(Configuration config) {
this.config = config;
}
@SuppressWarnings("unchecked")
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);
}
}
}
public class MapperProxyFactory<T> {
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
}
3、Mapper到sql的转化过程
通过上文我们知道在调用Mapper.insert(其他方法类似)时,会通过动态代理调用MapperProxy.invoke方法,该方法从缓存中获取MapperMethod,如果不存在就创建一个MapperMethod并缓存,然后调用MapperMethod.execute方法
@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)) {
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
//从缓存中获取MapperMethod,如果不存在就创建一个MapperMethod并缓存
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
MapperMethod的execute方法又会调用SqlSessionTemplate的同名方法,上文提到由于sqlSessionProxy是代理对象,对其任何方法调用,会调用SqlSessionInterceptor的invoke方法
public class MapperMethod {
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {
case INSERT: {
Object param = method.convertArgsToSqlCommandParam(args);
//此处sqlSession为启动过程创建的SqlSessionTemplate
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;
}
}
public class SqlSessionTemplate implements SqlSession, DisposableBean {
@Override
public int insert(String statement, Object parameter) {
return this.sqlSessionProxy.insert(statement, parameter);
}
}
SqlSessionInterceptor的invoke方法首先获取一个SqlSession(DefaultSqlSession),然后通过反射调用DefaultSqlSession的对应方法,如果当前不存在事务,则直接commit。
private class SqlSessionInterceptor implements InvocationHandler {
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//getSqlSession方法是后续Spring处理事务的核心,这里叙述下其过程:如果当前不存在事务,则每次都重新创建一个SqlSession;如果当前存在事务,则从Spring Transaction Manager获取,并将获取的SqlSession缓存到当前线程。这里判断是否存在、获取或者缓存SqlSession都是基于ThreadLocal的,后续在Spring事务博客中会详细介绍
SqlSession sqlSession = SqlSessionUtils.getSqlSession(
SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType,
SqlSessionTemplate.this.exceptionTranslator);
try {
//反射调用DefaultSqlSession的同名方法
Object result = method.invoke(sqlSession, args);
//如果当前不存在事务,则直接commit
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;
} catch (Throwable t) {
Throwable unwrapped = unwrapThrowable(t);
if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
// release the connection to avoid a deadlock if the translator is no loaded. See issue #22
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
sqlSession = null;
Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped);
if (translated != null) {
unwrapped = translated;
}
}
throw unwrapped;
} finally {
if (sqlSession != null) {
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
}
}
}
}
上文已经提到,启动过程中在构造sqlSessionFactory时候,会解析mapper对应的xml,并将配置信息缓存到configuration。我们知道在每个xml文件中需要配置一个namespace,然后还有很多sql,每个sql都对应的id,上述信息都会缓存到configuration的mappedStatements中。其中key=namespace+id,value是每条sql详细的配置信息。在获取到我们对应的MappedStatement后,然后调用executor.update方法
public class DefaultSqlSession implements SqlSession {
@Override
public int update(String statement, Object parameter) {
try {
dirty = true;
//这里的statement就是xml配置的[namespace+id]从configuration获取指定的MappedStatement
MappedStatement ms = configuration.getMappedStatement(statement);
return executor.update(ms, wrapCollection(parameter));
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error updating database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
}
SimpleExecutor的doUpdate方法构造StatementHandler和prepareStatement,然后调用andler.update(stmt),最终会调用SimpleStatementHandler的update方法。到这个方法我们终于看到具体的sql了,通过标准的Jdbc接口操作数据库。
public class SimpleExecutor extends BaseExecutor {
@Override
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(this, ms, parameter, RowBounds.DEFAULT, null, null);
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.update(stmt);
} finally {
closeStatement(stmt);
}
}
}
@Override
public int update(Statement statement) throws SQLException {
String sql = boundSql.getSql();
Object parameterObject = boundSql.getParameterObject();
KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
int rows;
if (keyGenerator instanceof Jdbc3KeyGenerator) {
statement.execute(sql, Statement.RETURN_GENERATED_KEYS);
rows = statement.getUpdateCount();
keyGenerator.processAfter(executor, mappedStatement, statement, parameterObject);
} else if (keyGenerator instanceof SelectKeyGenerator) {
statement.execute(sql);
rows = statement.getUpdateCount();
keyGenerator.processAfter(executor, mappedStatement, statement, parameterObject);
} else {
statement.execute(sql);
rows = statement.getUpdateCount();
}
return rows;
}