mybatis加载配置文件
mybatis框架执行的第一步就是加载配置文件,下面介绍mybatis是如何加载配置文件的。
// 1.定义mybatis_config文件地址
String resources = "mybatis_config.xml";
// 2.使用Java api读取mybatis配置文件,获取InputStreamReader Io流
Reader reader = Resources.getResourceAsReader(resources);
// 3.获取SqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
根据上面代码得知,在利用 SqlSessionFactoryBuilder 构建 SqlSessionFactory 工厂的时候,需要将 Reader 对象作为参数 SqlSessionFactoryBuilder().build(reader); 来读取 mybatis 的主配置文件。 Reader reader = Resources.getResourceAsReader(resources); mybatis 有自己的类(Resources)通过配置文件的路径来读取配置文件,最后生成Java中io包中的 java.io.Reader 类。
第一步、通过配置文件路径来读取配置文件
这里直接使用mybatis中的 Resources 类来读取然后获取 java.io.Reader 对象。源码如下:
// 2.使用Java api读取mybatis配置文件,获取InputStreamReader Io流
Reader reader = Resources.getResourceAsReader(resources);
第二步、获取SqlSessionFactory对象
SqlSessionFactoryBuilder 读取配置文件的内容来构造 SqlSessionFactory 对象。
具体步骤:见下面代码一步步往下执行。
// 1.定义mybatis_config文件地址
String resources = "mybatis_config.xml";
// 2.使用Java api读取mybatis配置文件,获取InputStreamReader Io流
Reader reader = Resources.getResourceAsReader(resources);
// 3.获取SqlSessionFactory
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader);
SqlSessionFactoryBuilder 类中执行过程
XMLConfigBuilder 类继承了 BaseBuilder,通过下面的 propertiesElement 方法将配置文件的内容都加载到 BaseBuilder 类中的 Configuration 属性对象中。
注意:parse 方法中的 parsed 变量的作用是只加载一次配置文件。
加载完成之后返回 XMLConfigBuilder 的 parse 方法返回 Configuration 对象,将Configuration 对象作为参数传递给 SqlSessionFactoryBuilder 类用来生成 DefaultSqlSessionFactory 对象。
这里就可以看出:mybatis中SqlSessionFactory接口 默认使用的实现类是 DefaultSqlSessionFactory。
这里配置文件就加载完成了,最后返回 DefaultSqlSessionFactory 对象,mybatis 的主配置文件都加载到 Configuration 对象中,最后作为 DefaultSqlSessionFactory 的属性 Configuration 的值,并且是不可修改的。
MybatisMapper接口绑定原理
Mapper既然是接口,没有被初始化如何被调用的?
答案:使用动态代理技术
根据mapper接口的 class 类型去 configuration 对象中找到注册的 mapper 接口
每一个mapper接口对应一个代理对象工厂 mapperProxyFactory 对象,由这个工厂去生产代理对象,这个工厂对象会缓存这个 mapper接口的所有方法。构建mapper 接口代理对象的时候会传入 sqlSession、mapper接口、接口方法缓存参数。
最后,如果是Object中定义的方法,直接执行。如toString(),hashCode()等。反之,会去methodCache 中查找对应的 MapperMethod 对象,最后去执行方法对应的 sql 语句。
大致原理分析:
SqlSession提供select/insert/update/delete方法,在旧版本中使用使用SqlSession接口的这些方法,但是新版的Mybatis中就会建议使用Mapper接口的方法。
映射器其实就是一个动态代理对象,进入到MapperMethod的execute方法就能简单找到SqlSession的删除、更新、查询、选择方法,从底层实现来说:通过动态代理技术,让接口跑起来,之后采用命令模式,最后还是采用了SqlSession的接口方法(getMapper()方法等到Mapper)执行SQL查询(也就是说Mapper接口方法的实现底层还是采用SqlSession接口方法实现的)。
创建会话对象SqlSession
前面已经得到 SqlSessionFactory 对象了。
SqlSession 的作用
SqlSession提供select/insert/update/delete方法,在旧版本中使用使用SqlSession接口的这些方法
每个线程都应该有它自己的 SqlSession 实例。SqlSession 的实例不是线程安全的,因此是不能被共享的。
SqlSession 因为线程不安全 所以不会共享
在创建sqlSession 对象的时候会指定对应的执行器 Executor 。
最后会返回 SqlSession 接口默认实现类 DefaultSqlSession 。
通过sqlSession 获取mapper接口
通过sqlSession 获取mapper接口后,执行接口中的方法会使用代理对象去执行对应的方法,
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
MyBatis中通过MapperProxy类实现动态代理,实现了InvocationHandler接口,invoke()方法中为通用的拦截逻辑。当我们调用自己Mapper接口中的方法时,其实就是在调用MapperProxy的invoke()方法。
// MapperProxy.java
public class MapperProxy<T> implements InvocationHandler, Serializable {
private static final long serialVersionUID = -6424540398559729838L;
private final SqlSession sqlSession;
// Mapper 接口
private final Class<T> mapperInterface;
// Mapper 接口中的每个方法都会生成一个MapperMethod对象, methodCache维护着他们的对应关系
private final Map<Method, MapperMethod> methodCache;
public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
this.sqlSession = sqlSession;
this.mapperInterface = mapperInterface;
this.methodCache = methodCache;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
// 如果是Object中定义的方法,直接执行。如toString(),hashCode()等
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else if (isDefaultMethod(method)) {
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
// 将 Method 转换成 MapperMethod 并保存到 methodCache 中
final MapperMethod mapperMethod = cachedMapperMethod(method);
// 这里面将会执行 Mapper接口对应的 SQL
return mapperMethod.execute(sqlSession, args);
}
private MapperMethod cachedMapperMethod(Method method) {
MapperMethod mapperMethod = methodCache.get(method);
if (mapperMethod == null) {
// 通过 Configuration 获取对应的 MappedStatement
// 通过 MappedStatement 获取 MapperMethod 需要的各种信息
mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
methodCache.put(method, mapperMethod);
}
return mapperMethod;
}
}
通过源码得知,最后通过 mapperMethod 对象来执行代理方法,也就是执行对应的 sql 语句。
public class MapperMethod {
// 里面有两个属性
// name: 要执行的方法名,如com.example.demo.UserMapper.getUserByUserName
// type: SQL标签的类型 insert update delete select
private final SqlCommand command;
// 封装了该方法的参数信息、返回类型信息等
private final MethodSignature method;
public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
this.command = new SqlCommand(config, mapperInterface, method);
this.method = new MethodSignature(config, mapperInterface, method);
}
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
// 根据insert、update、delete、select调用不同的方法
switch (command.getType()) {
case INSERT: {
/**
* args是用户 Mapper 所传递的方法参数列表
* 如果方法只包含一个参数并且不包含命名参数, 则返回传递的参数值。
* 如果包含多个参数或包含 @Param 注解修饰的参数,则返回包含名字和对应值的Map对象
*/
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
case UPDATE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
}
case DELETE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
break;
}
case SELECT:
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
result = executeForMap(sqlSession, args);
} else if (method.returnsCursor()) {
result = executeForCursor(sqlSession, args);
} else {
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
}
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for: " + command.getName());
}
if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
throw new BindingException("Mapper method '" + command.getName()
+ " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
}
return result;
}
}
从mapperMethod 中的代码得知,数据的增删改查都是通过SqlSession来实现的。