作者:郑志杰
mybatis操作数据库的过程
// 第一步:读取mybatis-config.xml配置文件
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
// 第二步:构建SqlSessionFactory(框架初始化)
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().bulid();
// 第三步:打开sqlSession
SqlSession session = sqlSessionFactory.openSession();
// 第四步:获取Mapper接口对象(底层是动态代理)
AccountMapper accountMapper = session.getMapper(AccountMapper.class);
// 第五步:调用Mapper接口对象的方法操作数据库;
Account account = accountMapper.selectByPrimaryKey(1);
通过调用session.getMapper(AccountMapper.class)所得到的AccountMapper 是一个动态代理对象,所以执行
accountMapper.selectByPrimaryKey(1)方法前,都会被invoke()拦截,先执行invoke()中的逻辑。
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
// 要执行的方法所在的类如果是Object,直接调用,不做拦截处理
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
//如果是默认方法,也就是java8中的default方法
} else if (isDefaultMethod(method)) {
// 直接执行default方法
return invokeDefaultMethod(proxy, method, args);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
// 从缓存中获取MapperMethod
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
从methodCache获取对应DAO方法的MapperMethod
MapperMethod的主要功能是执行SQL语句的相关操作,在初始化的时候会实例化两个对象:SqlCommand(Sql命令)和 MethodSignature(方法签名)。
/**
* 根据Mapper接口类型、接口方法、核心配置对象 构造MapperMethod对象
* @param mapperInterface
* @param method
* @param config
*/
public MapperMethod(Class<?> mapperInterface, Method method, Configuration config) {
this.command = new SqlCommand(config, mapperInterface, method);
// 将Mapper接口中的数据库操作方法(如Account selectById(Integer id);)封装成方法签名MethodSignature
this.method = new MethodSignature(config, mapperInterface, method);
}
new SqlCommand()调用SqlCommand类构造方法:
public SqlCommand(Configuration configuration, Class<?> mapperInterface, Method method) {
// 获取Mapper接口中要执行的某个方法的方法名
// 如accountMapper.selectByPrimaryKey(1)
final String methodName = method.getName();
// 获取方法所在的类
final Class<?> declaringClass = method.getDeclaringClass();
// 解析得到Mapper语句对象(对配置文件中的<mapper></mapper>中的sql语句进行封装)
MappedStatement ms = resolveMappedStatement(mapperInterface, methodName, declaringClass,
configuration);
if (ms == null) {
if (method.getAnnotation(Flush.class) != null) {
name = null;
type = SqlCommandType.FLUSH;
} else {
throw new BindingException("Invalid bound statement (not found): "
+ mapperInterface.getName() + "." + methodName);
}
} else {
// 如com.bjpowernode.mapper.AccountMapper.selectByPrimaryKey
name = ms.getId();
// SQL类型:增 删 改 查
type = ms.getSqlCommandType();
if (type == SqlCommandType.UNKNOWN) {
throw new BindingException("Unknown execution method for: " + name);
}
}
}
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;
}
调用mapperMethod.execute(sqlSession, args)
在mapperMethod.execute()方法中,我们可以看到:mybatis定义了5种SQL操作类型:
insert/update/delete/select/flush。其中,select操作类型又可以分为五类,这五类的返回结果都不同,分别对应:
•返回参数为空:executeWithResultHandler();
•查询多条记录:executeForMany(),返回对象为JavaBean
•返参对象为map:executeForMap(), 通过该方法查询数据库,最终的返回结果不是JavaBean,而是Map
•游标查询:executeForCursor();关于什么是游标查询,自行百度哈;
•查询单条记录: sqlSession.selectOne(),通过该查询方法,最终只会返回一条结果;
通过源码追踪我们可以不难发现:当调用mapperMethod.execute() 执行SQL语句的时候,无论是
insert/update/delete/flush,还是select(包括5种不同的select), 本质上时通过sqlSession调用的。在SELECT操作中,虽然调用了MapperMethod中的方法,但本质上仍是通过Sqlsession下的select(), selectList(), selectCursor(), selectMap()等方法实现的。
而SqlSession的内部实现,最终是调用执行器Executor(后面会细说)。这里,我们可以先大概看一下mybatis在执行SQL语句的时候的调用过程: