一,mybatis基本用法
1,mybatis的常用api
// inputStream是配置文件流
SqlSessionFactory factory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = factory.openSession();
// UserDao声明与mapper.xml文件的映射关系
UserDao userDao = sqlSession.getMapper(UserDao.class);
2,spring与mybatis的整合
<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xmlns:context="http://www.springframework.org/schema/context"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd">
<!--加载配置文件-->
<context:property-placeholder location="jdbc.properties"/>
<!--配置数据源,这里只进行一个简单的配置-->
<bean id="dataSource" class="com.alibaba.druid.pool.DruidDataSource">
<property name="driverClassName" value="${jdbc.driver}"/>
<property name="url" value="${jdbc.url}"/>
<property name="username" value="${jdbc.username}"/>
<property name="password" value="${jdbc.password}"/>
</bean>
<!--为mybatis的sqlSessionFactory注入数据源-->
<bean class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="dataSource" ref="dataSource"/>
<!--注入数据源后,sqlSessionFactory就可以创建sqlSession对象了,然后我们需要配置mapper文件的位置-->
<property name="mapperLocations" value="classpath:com/zs/dao/mapper/*.xml"/>
</bean>
<!--上面配置了mapper文件的位置,我们之前创建dao对象时,使用sqlSession的getMapper(dao.class)来创建对象
那么使用spring来创建对象,spring如何找到dao接口的位置呢?-->
<bean class="org.mybatis.spring.mapper.MapperScannerConfigurer">
<property name="basePackage" value="com.zs.dao"/>
</bean>
</beans>
二,spring整合mybatis需要解决的问题
1,如何创建SqlSessionFactory和SqlSession对象?
2,如何获取UserDao对象?
实际上,当spring和mybatis整合后,我们并没有直接用到SqlSessionFactory和SqlSession的api。
要做的工作仅限于编写sql的mapper文件和Mapper接口定义文件,然后在Service中注入Mapper接口,通过注入的mapper就可以操作数据库了,如下图:
sql配置文件:
Mapper接口文件:
三,Spring如何创建SqlSessionFactory
SqlSessionFactory的创建要归功于SqlSessionFactoryBean,SqlSessionFactoryBean继承了三个接口
1,FactoryBean接口:实现了该接口的类,在调getBean的时候会返回该工厂返回的实例对象,也就是再调一次getObject方法返回工厂的实例。
继承这个接口,就会实现getObject方法。当通过getBean获取SqlSessionFactory时,实际上调用SqlSessionFactoryBean的getObject获取的:
public SqlSessionFactory getObject() throws Exception {
if (this.sqlSessionFactory == null) {
afterPropertiesSet();
}
return this.sqlSessionFactory;
}
这里有个问题,SqlSessionFactory何时创建?
这要归功于SqlSessionFactoryBean实现的另外一个接口InitializingBean。
2,InitializingBean接口,在spring初始化时,实现了InitializingBean的类都会被实例化,然后调用afterPropertiesSet。
于是在afterPropertiesSet方法中,spring完成了SqlSessionFactory的创建。
四,spring如何屏蔽SqlSessionFactory,SqlSession,通过依赖注入Mapper接口,就可以操作数据库?
1,Mapper对象的创建
原来,有另外一个FactoryBean在起作用,它就是MapperFactoryBean,MapperFactoryBean也实现了FactoryBean和InitializingBean接口。
MapperFactoryBean继承了SqlSessionDaoSupport,SqlSessionDaoSupport继承DaoSupport,DaoSupport实现了InitializingBean接口,让我们开看看它这接口的实现:
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()方法,一个是调用initDao方法。checkDaoConfig方法在DaoSupport是抽象方法,让我看看它在MapperFactoryBean的实现:
/**
* {@inheritDoc}
*/
@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 (Throwable t) {
logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", t);
throw new IllegalArgumentException(t);
} finally {
ErrorContext.instance().reset();
}
}
}
该方法主要是检查dao的配置,主要是检验sqlSessionFactory和mapperInterface属性不能为空,以及检测接口对于的映射文件是否存在,如果存在,那么就把它添加到configuration里面去,注册mapper。
2,获取Mapper对象
因为MapperFactoryBean实现了FactoryBean,所以在依赖注入Mapper时调用了MapperFactoryBean.getObject方法,该方法的实现如下:
/**
* {@inheritDoc}
*/
public T getObject() throws Exception {
return getSqlSession().getMapper(this.mapperInterface);
}
熟悉的味道,熟悉的api。
3,但是,这里又有一个问题:难道每一个Mapper我都要去创建一个MapperFactoryBean吗?当然不是。
spring为了整合mybatis,提供了一个扩展类:MapperScannerConfigurer。
MapperScannerConfigurer实现了BeanDefinitionRegistryPostProcessor接口,如果MapperScannerConfigurer实现了该接口,那么说明在application初始化的时候该接口会被调用,具体实现:
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
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));
}
这里我们重点关注三个主要的方法,分别是
processPropertyPlaceHolders();
scanner.registerFilters();
scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));
① processPropertyPlaceHolders属性处理
执行属性的处理,简单的说,就是把xml中${XXX}中的XXX替换成属性文件中的相应的值
② 根据配置属性生成过滤器
scanner.registerFilters();方法会根据配置的属性生成对应的过滤器,然后这些过滤器在扫描的时候会起作用。
③ 扫描java文件
scanner.scan(StringUtils.tokenizeToStringArray(this.basePackage, ConfigurableApplicationContext.CONFIG_LOCATION_DELIMITERS));