MyBatis 会先解析这些 XML 文件,通过 XML 文件里面的命名空间 (namespace)跟 DAO 建立关系;然后 XML 中的每段 SQL 会有一个id 跟 DAO 中的接口进行关联。
首先我们要知道每个基于 MyBatis 的应用都是以一个 SqlSessionFactory
的实例为中心的,SqlSessionFactory
的实例可以通过 SqlSessionFactoryBuilder
获得。
但 SqlSessionFactory
是一个接口,它里面其实就两个方法:openSession
、getConfiguration
其中,openSession
方法是为了获取一个 SqlSession
对象,完成必要数据库增删改查功能。但是,SqlSessionFactory
属性太少了,所以需要 getConfiguration
的配合;来配置mapper
映射文件、SQL 参数、返回值类型、缓存等属性。
/** * Creates an {@link SqlSession} out of a connection or a DataSource * * @author Clinton Begin */ public interface SqlSessionFactory { SqlSession openSession(); SqlSession openSession(boolean autoCommit); SqlSession openSession(Connection connection); SqlSession openSession(TransactionIsolationLevel level); SqlSession openSession(ExecutorType execType); SqlSession openSession(ExecutorType execType, boolean autoCommit); SqlSession openSession(ExecutorType execType, TransactionIsolationLevel level); SqlSession openSession(ExecutorType execType, Connection connection); Configuration getConfiguration(); }
可以看到 getConfiguration
是属于 Configuration
类的一个方法。你可以把它当成一个配置管家。MyBatis 所有的配置信息都维持在 Configuration
对象之中,基本每个对象都会持有它的引用。
但日常开发中我们都是将 MyBatis 与 Spring 一起使用的,所以把实例化交给 Spring 处理。
因此我们可以看下 org.MyBatis.spring.SqlSessionFactoryBean
,它实现了 InitializingBean
接口。这说明,在这个类被实例化之后会调用到 afterPropertiesSet()
。它只有一个方法
public void afterPropertiesSet() throws Exception { this.sqlSessionFactory = buildSqlSessionFactory(); }
而这个 afterPropertiesSet
方法只有一个动作,就是 buildSqlSessionFactory
。它可以分为两部分来看:
- 1、从配置文件的
property
属性中加载各种组件,解析配置到configuration
中 - 2、加载 mapper 文件,解析 SQL 语句,封装成
MappedStatement
对象,配置到configuration
中。
mapper 接口方法是怎样被调用到的?
大致有如下两种方式:
- MyBatis 提供的 API
使用 MyBatis 提供的 API 进行操作,通过获取 SqlSession
对象,然后根据 Statement Id 和参数来操作数据库。
String statement = "com.mmzsblog.business.DAO.MemberMapper.getMemberList";
List<Member> result = sqlsession.selectList(statement);
- mapper 接口
定义 Mapper 接口,并在里面定义一系列业务数据操作方法。在 Service 层通过注入 mapper 属性,调用其方法就可以执行数据库操作。
public interface MemberMapper { List<Member> getMemberList(); } @Service public class MemberServiceImpl implements MemberService{ @Resource private MemberMapper memberMapper; @Override public List<Member> getMemberList() { return memberMapper.getMemberList(); } }
Mapper 接口的代理创建过程
首先我们会配置需要扫描的基本包路径
通过注解的方式配置@MapperScan({"com.mmzsblog.business.DAO"})
或者xml的方式配置:
<bean class="org.MyBatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.mmzsblog.business.DAO" />
<property name="sqlSessionFactoryBeanName" value="sqlSessionFactory"></property>
</bean>
开始扫描
来到 org.MyBatis.spring.mapper.MapperScannerConfigurer
这个类,可以看到它实现了几个接口
其中的重点是 BeanDefinitionRegistryPostProcessor
。它可以动态的注册 Bean 信息,方法为 postProcessBeanDefinitionRegistry()
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) { if (this.processPropertyPlaceHolders) { this.processPropertyPlaceHolders(); } // 创建ClassPath扫描器,设置属性,然后调用扫描方法 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); // 创建ClassPath扫描器,设置属性,然后调用扫描方法 scanner.registerFilters(); scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ",; \t\n")); }
ClassPathMapperScanner
继承自 Spring 中的类 ClassPathBeanDefinitionScanner
,所以它的 scan 方法会调用到父类 ClassPathBeanDefinitionScanner
的 scan 方法
public class ClassPathBeanDefinitionScanner extends ClassPathScanningCandidateComponentProvider { …… public int scan(String... basePackages) { // int beanCountAtScanStart = this.registry.getBeanDefinitionCount(); this.doScan(basePackages); if (this.includeAnnotationConfig) { AnnotationConfigUtils.registerAnnotationConfigProcessors(this.registry); } return this.registry.getBeanDefinitionCount() - beanCountAtScanStart; } …… }