在解析源码之前,我们需要知道Mybatis如何独立使用
- 简历POJO
public class User(){
private String name;
private String age;
//省略set get
} - 建立Mapper
public Integerface UserMapper{
public void insertUser(User user);
public User getUser(Integer id);
} - 建立配置文件
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTDSQL Map Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <!-- 引入外部资源配置文件 --> <properties resource="jdbc.properties" /> <!-- 配置环境,制定数据库连接信息 --> <environments default="development"> <environment id="development"> <transactionManager type="JDBC" /> <dataSource type="POOLED"> <property name="driver" value="${jdbc.driver}"/> <property name="url" value="${jdbc.url}"/> <property name="username" value="${jdbc.username}"/> <property name="password" value="${jdbc.password}"/> </dataSource> </environment> </environments> <mappers> <mapper resource="com/test/dao/UserMapper.xml"/> </mappers> </configuration>
- 建立映射文件
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTDSQL Map Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <mapper namespace="Mapper.UserMapper"> <insert id="insertUser" parameterType="User"> INSERT INTO USER(NAME,AGE) VALUES(#{name},#{age})</insert> <select id="getUser" resultType="User" parameterType="java.lang.Integer"> select * from user where id=#{id}</select></configuration> - 建立测试类
public class TestUser {
privateUserMapper userDao;
@Before
public void setUp() throws Exception {
InputStream inputStream =Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory =new SqlSessionFactoryBuilder().build(inputStream);
// 是否自动提交
SqlSession session =sqlSessionFactory.openSession(true);
this.userDao =session.getMapper(UserMapper.class);
}
@Test
publicvoid run(){
System.out.println("-------------------------------------");
Useruser = this.userDao.queryUserById(1);
System.out.println(user);
System.out.println("-------------------------------------");
}
}
整理下上面的东西,mybatis独立使用需要提供
- 一个接口Mapper类,内容是数据库操作的方法
- 一个配置文件,内容是数据源信息
- 一个sqlSessionFacotry用来承载控制数据库回话的类
先看下spring如何配置
- spring的配置文件
<?xmlversionxmlversion="1.0"encoding="UTF-8"?> <beansxmlns:xsibeansxmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"xmlns="http://www.springframework.org/schema/beans" xmlns:aop="http://www.springframework.org/schema/aop"xmlns:context="http://www.springframework.org/schema/context" xmlns:tx="http://www.springframework.org/schema/tx"xmlns:cache="http://www.springframework.org/schema/cache" xmlns:p="http://www.springframework.org/schema/p"xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/aop http://www.springframework.org/schema/aop/spring-aop.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd http://www.springframework.org/schema/tx http://www.springframework.org/schema/tx/spring-tx.xsd http://www.springframework.org/schema/cache http://www.springframework.org/schema/cache/spring-cache.xsd"> <!-- 自动扫描 --> <context:component-scanbase-packagecontext:component-scanbase-package="com.hys.app.**.service,com.hys.app.**.dao,com.hys.app.**.action"/> <!-- 引入外置文件 --> <beanidbeanid="propertyConfigurer"class="org.springframework.beans.factory.config.PropertyPlaceholderConfigurer"> <propertynamepropertyname="location"value="classpath:jdbc.properties"/> </bean> <!--数据库连接池配置--> <beanidbeanid="dataSource"class="org.apache.commons.dbcp.BasicDataSource"destroy-method="close"> <propertynamepropertyname="driverClassName"value="${jdbc.driverClassName}"/> <propertynamepropertyname="url"value="${jdbc.url}"/> <propertynamepropertyname="username"value="${jdbc.username}"/> <propertynamepropertyname="password"value="${jdbc.password}"/> </bean> <!-- spring和MyBatis完美整合 --> <beanidbeanid="sqlSessionFactory"class="org.mybatis.spring.SqlSessionFactoryBean"> <!-- 指定数据源 --> <propertynamepropertyname="dataSource"ref="dataSource"/> <!-- 具体指定xml文件,可不配 --> <propertynamepropertyname="configLocation" value="classpath:mybatis-config.xml"/> </bean > <bean id="UserMapper" class="org.mybatis.Spring.mapper.MapperFactoryBean">
<property name="mapperInterface" value="test.mybatis.dao.UserMapper"> <peroperty name="sqlSessionFactory" ref="sqlSessionFactory">
</beans></bean>
-
Mybatis配置文件 <?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTDSQL Map Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <typeAliases> <typeAliase alias="User" type="test.mybatis.User"></typeAliase> </typeAliases> <mappers> <mapper resource="com/test/dao/UserMapper.xml"/> </mappers> </configuration>
<?xml version="1.0" encoding="UTF-8"?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTDSQL Map Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <typeAliases> <typeAlise></typeAliase </typeAliases> <mappers> <mapper resource="com/test/dao/UserMapper.xml"/> </mappers> </configuration>
- 测试
public class UserServiceTest{
public static void main(String args[]){
ApplicationContext context = new ClassPassXmlApplicationContext("test/mybatis/applicationContext.xml");
UserMapper userDao = (UserMapper)context.getBean("userMapper");
}
}
总结,对比spring整合方式 和 mybatis独立使用的方式,spring将数据源的配置囊括到spring的控制范围内,注册了SqlSessionFactoryBean类型的bean用于控制与数据库的会话,以及用于接口映射的MapperFacotryBean
源码分析
第一步 我们从控制会话的SqlSessionFactoryBean入手进行分析。
这个类实现了FacotryBean 接口 和 InitializingBean接口,这两个接口阅读过spring源码bean生成逻辑的应该非常熟悉。我们这里再回顾下
- FacotryBean :一旦某个bean实现的此接口,那么在spring bean获取的时候就会通过getBean方法获取bean实例。
- InitializingBean:实现此接口的bean会在bean初始化时调用afterPropertiesSet方法来进行bean的逻辑初始化。
好,我们定位到getObject方法
@Override
public SqlSessionFactory getObject() throws Exception {
if (this.sqlSessionFactory == null) {
afterPropertiesSet();
}
return this.sqlSessionFactory;
}
进入
afterPropertiesSet();//这里的afterPropertiesSet方法其实就是实现InitializingBean后的覆盖方法
看看afterPropertiesSset中的逻辑
@Override
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();
}
进入buildSqlSessionFacotry
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 {
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);
LOGGER.debug(() -> "Scanned package: '" + packageToScan + "' for aliases");
}
}
if (!isEmpty(this.typeAliases)) {
for (Class<?> typeAlias : this.typeAliases) {
configuration.getTypeAliasRegistry().registerAlias(typeAlias);
LOGGER.debug(() -> "Registered type alias: '" + typeAlias + "'");
}
}
if (!isEmpty(this.plugins)) {
for (Interceptor plugin : this.plugins) {
configuration.addInterceptor(plugin);
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);
LOGGER.debug(() -> "Scanned package: '" + packageToScan + "' for type handlers");
}
}
if (!isEmpty(this.typeHandlers)) {
for (TypeHandler<?> typeHandler : this.typeHandlers) {
configuration.getTypeHandlerRegistry().register(typeHandler);
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();
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());
xmlMapperBuilder.parse();
} catch (Exception e) {
throw new NestedIOException("Failed to parse mapping resource: '" + mapperLocation + "'", e);
} finally {
ErrorContext.instance().reset();
}
LOGGER.debug(() -> "Parsed mapper file: '" + mapperLocation + "'");
}
} else {
LOGGER.debug(() -> "Property 'mapperLocations' was not specified or no matching resources found");
}
return this.sqlSessionFactoryBuilder.build(configuration);
}
上面的代码比较多,但是逻辑其实比较简单,重要的代码只有下面这里两行
xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties);
configuration = xmlConfigBuilder.getConfiguration();
其余的都是spring为了加载mybatis的一些配置做的兼容,上面的逻辑主要是设置mybatis的相关配置信息,如果存在配置文件,则从配置文件加载配置信息。新建configuration类用于承载所有的配置项。并最后通过xmlConfigBuilder来创建sqlSessionFactory实例。
第二步,为了使用mybaits,我们新建了mapper映射类,MapperFacotryBean
查看MapperFactoryBean代码,发现同样实现了FactoryBean和InializingBean接口。
查找后发现当前类中没有afterProperties方法,层层追踪其父类,在DaoSupport中找到额afterProperties方法
public final void afterPropertiesSet() throws IllegalArgumentException, BeanInitializationException {
// Let abstract subclasses check their configuration.
checkDaoConfig();
// Let concrete implementations initialize themselves.
try {
initDao();
}
catch (Exception ex) {
throw new BeanInitializationException("Initialization of DAO failed", ex);
}
}
查看checkDaoConfig方法
@Override
protected void checkDaoConfig() {
super.checkDaoConfig();
notNull(this.mapperInterface, "Property 'mapperInterface' is required");
Configuration configuration = getSqlSession().getConfiguration();
if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
try {
configuration.addMapper(this.mapperInterface);
} catch (Exception e) {
logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", e);
throw new IllegalArgumentException(e);
} finally {
ErrorContext.instance().reset();
}
}
}
上面代码主要是实现了注册加入到映射类型中
因为MapperFactoryBean实现了Factorybean接口,所以当调用获取bean方法的时候调用的是getObject方法返回的实例。
/**
* {@inheritDoc}
*/
@Override
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
这段代码正式我们提供给mybatis独立使用的时候获取Mapper的方式。