MyBatis-Spring
的应用以及源码
学会看官网,吃喝不用愁!入门
官网提供的是xml配置,那么就先以xml配置形式来分析。
所需的jar包就不多说了,直接看配置。
要和 Spring 一起使用 MyBatis,你需要在 Spring 应用上下文中定义至少两样东西:一个 SqlSessionFactory 和至少一个数据映射器类
这可以是任意 的 DataSource,配置它就和配置其它 Spring 数据库连接一样
要注意,所指定的映射器类必须是一个接口,而不是具体的实现类。在这个示例中注解被用来指定 SQL
语句,但是 MyBatis 的映射器 XML 文件也可以用(这里先不做解释)
public interface UserMapper {
@Select("SELECT * FROM users WHERE id = #{userId}")
User getUser(@Param("userId") String userId);
}
那么可以使用 MapperFactoryBean,像下面这样来把接口加入到 Spring 中
public class FooServiceImpl implements FooService {
private UserMapper userMapper;
public void setUserMapper(UserMapper userMapper) {
this.userMapper = userMapper;
}
public User doSomeBusinessStuff(String userId) {
return this.userMapper.getUser(userId);
}
现在问题来了,调用的是接口方法但是有具体的实现,那么这里肯定有动态代理
,下面就来分析源码。
在上面示例中,Spring注入的userMapper
肯定是一个实现UserMapper接口的代理类(从这里可看出一定是jdk
态代理)
private UserMapper userMapper;
userMapper
对应的bean是MapperFactoryBean,它实现了FactoryBean接口,因此注入userMapper
实则是MapperFactoryBean的getObject()
方法返回的代理对象
public T getObject() throws Exception {
// mapperInterface就是注入的数据映射器接口类
return this.getSqlSession().getMapper(this.mapperInterface);
}
先卖个关子,这里会调用MapperRegistry.getMapper
,根据数据映射器接口类去获取MapperProxyFactory(映射代理工厂),如果不存在则抛出异常。那么说明在注入userMapper之后
,肯定是注册
了一个对应的MapperProxyFactory
public T getMapper(Class type, SqlSession sqlSession) {
MapperProxyFactory mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
} else {
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception var5) {
throw new BindingException("Error getting mapper instance. Cause: " + var5, var5);
}
}
}
看到上面getMapper(Class
会用到SqlSession ,这是什么时候注册的呢?可以看下官网的解释SqlSessionFactoryBean,它实现了FactoryBean接口。
在基本的 MyBatis 中,session 工厂可以使用 SqlSessionFactoryBuilder 来创建。而在 MyBatis-Spring
中,则使用 SqlSessionFactoryBean 来替代。
但是Spring
获取的对象不是 SqlSessionFactoryBean 本身, 而它重写的 getObject()
返回的对象,其实就是 SqlSessionFactoryBuilder
详解看mybatis与spring的整合之SqlSessionFactoryBean
看一下在MapperFactoryBean注入SqlSessionFactoryBean都干了些什么,具体实现在父类SqlSessionDaoSupport中
SqlSessionTemplate 实现了SqlSession接口
public void setSqlSessionFactory(SqlSessionFactory sqlSessionFactory) {
if (!this.externalSqlSession) {
// 初始化sqlSession
this.sqlSession = new SqlSessionTemplate(sqlSessionFactory);
}
}
看上面的结构图,MapperFactoryBean最终实现了InitializingBean接口,它有一个afterPropertiesSet()
方法,就是在属性注入完成后执行的(一般在bean初始化完成后做某些事情,对应有个销毁方法)
afterPropertiesSet()
public final void afterPropertiesSet() throws IllegalArgumentException, BeanInitializationException {
this.checkDaoConfig();
try {
this.initDao();
} catch (Exception var2) {
throw new BeanInitializationException("Initialization of DAO failed", var2);
}
}
checkDaoConfig()
先判断sqlSession
(就是SqlSessionTemplate)是否注入,然后获取Configuration注册MapperProxyFactory
protected void checkDaoConfig() {
// 执行父类SqlSessionDaoSupport的方法,校验sqlSession是否不为空
super.checkDaoConfig();
Assert.notNull(this.mapperInterface, "Property 'mapperInterface' is required");
// configuration在SqlSessionFactoryBean中初始化的
Configuration configuration = this.getSqlSession().getConfiguration();
// 有的话就不注册
if (this.addToConfig && !configuration.hasMapper(this.mapperInterface)) {
try {
// 注册对应的MapperProxyFactory
configuration.addMapper(this.mapperInterface);
} catch (Exception var6) {
this.logger.error("Error while adding the mapper '" + this.mapperInterface + "' to configuration.", var6);
throw new IllegalArgumentException(var6);
} finally {
ErrorContext.instance().reset();
}
}
}
Configuration.addMapper
public void addMapper(Class type) {
// mapperRegistry在Configuration构造函数中初始化的
this.mapperRegistry.addMapper(type);
}
这里根据映射接口注册MapperProxyFactory
然后解析映射接口
:获取方法名、获取sql语句等mybatis核心组件之MapperAnnotationBuilder
public void addMapper(Class type) {
if (type.isInterface()) {
if (this.hasMapper(type)) {
throw new BindingException("Type " + type + " is already known to the MapperRegistry.");
}
boolean loadCompleted = false;
try {
// 根据映射接口注册MapperProxyFactory
this.knownMappers.put(type, new MapperProxyFactory(type));
// 解析映射接口方法以及注解
MapperAnnotationBuilder parser = new MapperAnnotationBuilder(this.config, type);
parser.parse();
loadCompleted = true;
} finally {
if (!loadCompleted) {
this.knownMappers.remove(type);
}
}
}
}
public T getObject() throws Exception {
return this.getSqlSession().getMapper(this.mapperInterface);
}
SqlsessionTemplate
public T getMapper(Class type) {
return this.getConfiguration().getMapper(type, this);
}
Configuration
public T getMapper(Class type, SqlSession sqlSession) {
return this.mapperRegistry.getMapper(type, sqlSession);
}
根据传入的映射接口拿到之前注册的MapperProxyFactory,进一步实例化出对象(实现映射接口的代理对象)
public T getMapper(Class type, SqlSession sqlSession) {
// 拿到映射接口对应MapperProxyFactory
MapperProxyFactory mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
} else {
try {
// 实例化对象
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception var5) {
throw new BindingException("Error getting mapper instance. Cause: " + var5, var5);
}
}
}
这里的sqlSession
就是SqlSessionTemplate
这里的mapperInterface
就是之前根据映射接口注册MapperProxyFactory时的映射接口
methodCache
是一个ConcurrentHashMap,用于缓存方法对应的MapperMethod,当调用同一方法时直接从缓存中拿
public T newInstance(SqlSession sqlSession) {
MapperProxy mapperProxy = new MapperProxy(sqlSession, this.mapperInterface, this.methodCache);
return this.newInstance(mapperProxy);
}
protected T newInstance(MapperProxy mapperProxy) {
// 返回代理对象(jdk动态代理)
return Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy);
}
从Proxy.newProxyInstance(this.mapperInterface.getClassLoader(), new Class[]{this.mapperInterface}, mapperProxy)
可以看出两点
newProxyInstance
的第二个参数只能传接口数组,所以mapperInterface
是接口,也就是说数据映射器只能是接口invoke
方法中执行userMapper.getUser(userId)
,执行的是代理对象的getUser(userId)
,会执行invok
MapperProxy.invok
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
// 如果方法是Object类的方法,则直接反射执行
if (Object.class.equals(method.getDeclaringClass())) {
try {
return method.invoke(this, args);
} catch (Throwable var5) {
throw ExceptionUtil.unwrapThrowable(var5);
}
} else {
// 获取MapperMethod
MapperMethod mapperMethod = this.cachedMapperMethod(method);
// 执行sql语句
return mapperMethod.execute(this.sqlSession, args);
}
}
tostring
,hashcode
等方法,是的话则直接反射执行这些方法private MapperMethod cachedMapperMethod(Method method) {
// 根据方法从缓存中获取
MapperMethod mapperMethod = (MapperMethod)this.methodCache.get(method);
if (mapperMethod == null) {
// 不存在则创建一个
mapperMethod = new MapperMethod(this.mapperInterface, method, this.sqlSession.getConfiguration());
// 放入缓存
this.methodCache.put(method, mapperMethod);
}
return mapperMethod;
}
mybatis核心组件之MapperMethod