Mybatis与Spring整合源码阅读
前言
大家都知道Spring中mybatis的调用是接口形式,那么SpringIOC是不会注入接口形式的bean。那么它是怎么和Mybatis进行集成的呢?
本篇需要对SpringIOC的源码有一定的了解才可以,否则是不太好理解的
本文所用的代码:
mybatis-config.xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd">
<configuration>
<settings>
<setting name="mapUnderscoreToCamelCase" value="true"/>
</settings>
</configuration>
User类:
@Component
public class User {
private Long id ;
private String userName ;
private Date createTime;
public User() {
//System.out.println("user加载");
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public Date getCreateTime() {
return createTime;
}
public void setCreateTime(Date createTime) {
this.createTime = createTime;
}
@Override
public String toString() {
return "User{" +
"id=" + id +
", userName='" + userName + '\'' +
", createTime=" + createTime +
'}';
}
}
UserMapper接口
public interface UserMapper {
User selectById(Long id);
List<User> selectAllUser();
}
配置类
@Configuration
@MapperScan(basePackages = {"com.tuling.mapper"})
@ComponentScan(basePackages = {"com.tuling"})
@Repository
public class MyBatisConfig { // =====> spring.xml
/**
创建的过程会初始化 sqlsession 数据源 事务 mapperProxyFactory
* @return
* @throws IOException
*/
@Bean // ===== > <bean class="org.mybatis.spring.SqlSessionFactoryBean">
public SqlSessionFactoryBean sqlSessionFactory( ) throws IOException {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setDataSource(dataSource());
// 设置 MyBatis 配置文件路径
factoryBean.setConfigLocation(new ClassPathResource("mybatis/mybatis-config.xml"));
// 设置 SQL 映射文件路径
factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mybatis/mapper/*.xml"));
factoryBean.setTypeAliases(User.class);
return factoryBean;
}
public DataSource dataSource() {
DruidDataSource dataSource = new DruidDataSource();
dataSource.setUsername("root");
dataSource.setPassword("123456");
dataSource.setDriverClassName("com.mysql.jdbc.Driver");
dataSource.setUrl("jdbc:mysql://localhost:3306/mybatis_example");
return dataSource;
}
}
调用:
public class MainStarter {
public static void main(String[] args) {
// 记载spring上下文
AnnotationConfigApplicationContext ioc = new AnnotationConfigApplicationContext(MyBatisConfig.class);
UserMapper bean = (UserMapper) ioc.getBean("userMapper");
System.out.println(bean.selectById(1L));
}
}
一、SqlSessionFactoryBean
之前在SSM框架中我们在配置文件里有过
<bean class="org.mybatis.spring.SqlSessionFactoryBean" id="sqlSessionFactory">
<!-- 指定spring中的数据源 -->
<property name="dataSource" ref="dataSource"></property>
<property name="configLocation" value="classpath:mybatis-config.xml"></property>
<property name="mapperLocations" value="classpath:cn/tulingxueyuan/mapper/*.xml"></property>
</bean>
当ioc容器加载完bean定义后,配置文件中的
@Bean // ===== > <bean class="org.mybatis.spring.SqlSessionFactoryBean">
public SqlSessionFactoryBean sqlSessionFactory( ) throws IOException {
SqlSessionFactoryBean factoryBean = new SqlSessionFactoryBean();
factoryBean.setDataSource(dataSource());
// 设置 MyBatis 配置文件路径
factoryBean.setConfigLocation(new ClassPathResource("mybatis/mybatis-config.xml"));
// 设置 SQL 映射文件路径
factoryBean.setMapperLocations(new PathMatchingResourcePatternResolver().getResources("classpath:mybatis/mapper/*.xml"));
factoryBean.setTypeAliases(User.class);
return factoryBean;
}
SqlSessionFactoryBean这个类我们看下
实现了这个接口的类,之前说过会在初始化调用他的
void afterPropertiesSet() throws Exception;
中间省略一些,在这里可以对mybatis进行一些配置
解析我们的像UserMapper文件
/**
* 通过建造者模式构建我们的SqlSessionFactory对象 默认是DefaultSqlSessionFactory
*/
return this.sqlSessionFactoryBuilder.build(targetConfiguration);
以便后续mybitis进行调用
最后存到Configuration对象里去,后续执行数据库操作的东西都有了。
和声明式事务的集成
当我们项目中存在这样代码时,mybitis和Spring是怎么做的呢?
开启事务是在动态代理中去开启,开启事务时就会获得Connection对象,会放到事务同步管理器的
这里使用了TheadLocal进行了存储,本地线程副本,mybatis在进行操作数据库时,只要拿到这个Connection就行了。比如说Spring去回滚,mybatis只要拿到Connection就算是集成上了。
那我们看下源码,afterPropertiesSet方法
从这里面去获得数据源
在构建SqlSessionFactoryBean会new一个mybatis-spring适配的新的事务工厂。mybatis获得Connection时就会从这里获取
二、Mapper接口是如何和Spring进行集成的?
1.自动注入的Mapper接口是怎样进行集成的?
代码如下:
Mapper接口底层实现是jdk动态代理。
Mybatis源码
最后来到这里
2.怎么实现的呢?
给大家看个例子
代码如下(示例):
@Component
public class MyBeanDefinitionRegistryPostProcessor implements BeanDefinitionRegistryPostProcessor {
@Override
public void postProcessBeanDefinitionRegistry(BeanDefinitionRegistry registry) throws BeansException {
// 并不是直接注册动态代理, 而是注册FactoryBean
// 扫描所有mapper接口{ 所有mapper接口类
GenericBeanDefinition beanDefinition=new GenericBeanDefinition();
beanDefinition.setBeanClass(MyFactoryBean.class);
//传 UserMapper.class到MyFactoryBean的mapperInterFace属性里
beanDefinition.getPropertyValues().addPropertyValue("mapperInterFace", UserMapper.class);
registry.registerBeanDefinition("tuserMapper",beanDefinition);
// 扫描所有mapper接口 }
}
@Override
public void postProcessBeanFactory(ConfigurableListableBeanFactory beanFactory) throws BeansException {
}
}
public class MyFactoryBean implements FactoryBean {
public Class getMapperInterFace() {
return mapperInterFace;
}
public void setMapperInterFace(Class mapperInterFace) {
this.mapperInterFace = mapperInterFace;
}
// 动态传进来
private Class mapperInterFace;
@Override
public Object getObject() throws Exception {
System.out.println("进入factoryBean的getObject方法");
return (UserMapper) Proxy.newProxyInstance(mapperInterFace.getClassLoader(), new Class[]{mapperInterFace}, (proxy, method, args1) -> {
// 查询数据库... mybatis
System.out.println("查询");
return null;
});
}
@Override
public Class<?> getObjectType() {
return mapperInterFace;
}
public MyFactoryBean() {
System.out.println("加载");
}
}
这就是简化版的调用,源码里也是这么调用的
讲解:
因Usermapper是接口,所以需要重写Spring的isCandidateComponent
方法,
拿到bean定义修改beanclass
那么就可以用FactoryBean进行偷天换日了。那么看源码
通过builder注册了
void registerBeanDefinitions(AnnotationAttributes annoAttrs, BeanDefinitionRegistry registry, String beanName) {
/**
* 创建bean定义构造器 通过够构造器来构建出我们的bean定义<MapperScannerConfigurer> 应用到的设计模式[建造者模式]
*/
BeanDefinitionBuilder builder = BeanDefinitionBuilder.genericBeanDefinition(MapperScannerConfigurer.class);
}
和mybatis进行集成,MapperScannerConfigurer类
会来到这个方法
调用scan方法
调用父类方法
注意看这个方法
进行了重写
判断的是只有接口类型的并且是顶级的,加入到集合中,注意和Spring是不同的
扫描到了UserMapper接口
进入这个方法,创建动态代理要指定为哪个类创建
definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName)
进行了偷天换日,我们看下mapperFactoryBeanClass
最终我们实例化UserMapper就是我们的MapperFactoryBean类型。然后会为我们的UserMapper绑定个属性sqlSessionTemplate
,因为毕竟要和数据库进行交互。
实际上是就是为我们的MapperFactoryBean添加一个sqlSessionTemplate的属性)
然后SpringIoc在实例话我们的MapperFactoryBean的时候会经历populate()方法为UserMapper(MapperFactoryBean)
的sqlSessionTemplate赋值(调用set方法),这个是根据byType自动装配的
最终会在mybatis里创建动态代理
工厂bean的返回结果:
之后的
查询数据库的功能就可以交给mybatis去做了,当我selectOne时,
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
/**
* 判断我们的方法是不是我们的Object类定义的方法,若是直接通过反射调用
*/
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else if (method.isDefault()) { //是否接口的默认方法
/**
* 调用我们的接口中的默认方法
*/
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
/**
* 真正的进行调用,做了二个事情
* 第一步:把我们的方法对象封装成一个MapperMethod对象(带有缓存作用的)
*/
final MapperMethod mapperMethod = cachedMapperMethod(method);
/**
*通过sqlSessionTemplate来调用我们的目标方法
* 那么我们就需要去研究下sqlSessionTemplate是什么初始化的
* 我们知道spring 跟mybatis整合的时候,进行了偷天换日
* 把我们mapper接口包下的所有接口类型都变为了MapperFactoryBean
* 然后我们发现实现了SqlSessionDaoSupport,我们还记得在整合的时候,
* 把我们EmployeeMapper(案例class类型属性为MapperFactoryBean)
* 的注入模型给改了,改成了by_type,所以会调用SqlSessionDaoSupport
* 的setXXX方法进行赋值,从而创建了我们的sqlSessionTemplate
* 而在实例化我们的sqlSessionTemplate对象的时候,为我们创建了sqlSessionTemplate的代理对象
* this.sqlSessionProxy = (SqlSession) newProxyInstance(SqlSessionFactory.class.getClassLoader(),
new Class[] { SqlSession.class }, new SqlSessionInterceptor());
*/
return mapperMethod.execute(sqlSession, args);
来到了mybatis的selectOne的方法了。
至此Spring就集成了mybatis。