Spring集成Mybatis源码分析
准备示例代码
代码
@Configuration
@MapperScan(basePackages = {"com.tuling.mapper"})
@ComponentScan(basePackages = {"com.tuling"})
@Repository
public class MyBatisConfig { // =====> spring.xml
/**
* <mapper-scan basePackage=""/>
* SqlSessionFactoryBean
* 创建的过程会初始化 sqlsession 数据源 事务 mapperProxyFactory
* @return
*/
@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;
}
}
@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();
}
UserMapper.Xml
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.tuling.mapper.UserMapper">
<!-- Mybatis 是如何将 sql 执行结果封装为目标对象并返回的?都有哪些映射形式?-->
<resultMap id="result" type="user" >
<id column="id" jdbcType="BIGINT" property="id" />
<result column="user_name" jdbcType="VARCHAR" property="userName" />
<result column="create_time" jdbcType="DATE" property="createTime" />
</resultMap>
<select id="selectById" resultMap="result" >
select id,user_name,create_time from t_user where id=#{param1}
</select>
</mapper>
启动类:
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));
}
}
运行结果:
一、Spring集成了mybatis的哪些东西?
集成了SqlSessionFactory
我们看下怎么集成的?
实现了这个接口InitializingBean的,会在初始化时候,调用afterPropertiesSet
那么这里就相当于mybatis的
首先声明了个Configuration
相当于解析mybatis的全局配置文件
/**
* 判断configurationProperties不为空,
* 那么就调用targetConfiguration.set方法
* 把configurationProperties注入到Configuration对象中
*/
Optional.ofNullable(this.configurationProperties).
ifPresent(targetConfiguration::setVariables);
JDK8的新特性,相当于
if(this.configurationProperties!=null){
targetConfiguration.setVariables(this.configurationProperties)
}
真正的解析我们的配置(mybatis-config.xml)的document对象
xmlConfigBuilder.parse();
保存到configuration对象里去
最终利用简单工厂模式返回了
其实就是把mybatis的信息存到Configuration对象里去,后续执行数据库操作的东西都有了。
与Spring声明式事务的集成
Spring声明式事务在开启的时候,会获得connection对象,会存到事务同步管理器的
mybatis要用事务,去这里拿到connection就可以了。我们来看源码:还是之前的SqlSessionFactoryBean
这里会new SpringManagedTransactionFactory()
可以看见就是去事务同步管理器去拿Connection,这样mybatis和spring的声明式事务就集成了。
二、Mapper接口使用的jdk动态代理
UserMapper bean = (UserMapper) ioc.getBean("userMapper");
bean.selectById(1L)
@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);
return mapperMethod.execute(sqlSession, args);
}
最终
result = sqlSession.selectOne(command.getName(), param);
@Override
public <T> T selectOne(String statement, Object parameter) {
/**
* 因为在sqlSessionTemplate中的属性 sqlSessionproxy是sqlSession一个代理对象,所以会调用代理对象 SqlSessionInterceptor.invoke方法
*/
return this.sqlSessionProxy.selectOne(statement, parameter);
}
来到DefaultSqlSession调用mybatis的去进行数据库操作
三、怎么让Spring去管理Mapper动态代理?
源码跟踪
接下来我们看这个方法
processBeanDefinitions(beanDefinitions)
for (BeanDefinitionHolder holder : beanDefinitions) {
// 获取我们的bean定义
definition = (GenericBeanDefinition) holder.getBeanDefinition();
// 获取我们的bean定义的名称
String beanClassName = definition.getBeanClassName();
LOGGER.debug(() -> "Creating MapperFactoryBean with name '" + holder.getBeanName() + "' and '" + beanClassName
+ "' mapperInterface");
/**
* 进行真的偷天换日操作,也就是这二行代码是最最最最最重要的, 关乎我们 spring整合mybaits的整合 definition.setBeanClass(this.mapperFactoryBeanClass);
*/
// 设置ConstructorArgumentValues 会通过构造器初始化对象
definition.getConstructorArgumentValues().addGenericArgumentValue(beanClassName); // issue #59
// 设置成factoryBean
definition.setBeanClass(this.mapperFactoryBeanClass);
definition.getPropertyValues().add("addToConfig", this.addToConfig);