一、重温JDBC
Java Database Connectivity,简称JDBC。是Java语言中用来规范客户端程序如何来访问数据库的应用程序接口,提供了诸如查询和更新数据库中数据的方法。 随着Java ORM框架的发展,已经很少有机会再在生产系统中写JDBC的代码来访问数据库了,但是基本流程我们还是要熟悉。下面以一个简单的查询为例,温故一下JDBC。
public static void main(String[] args) throws Exception {
Connection conn = getConnection();
String sql = "select * from user where 1=1 and id = ?";
PreparedStatement stmt = conn.prepareStatement(sql);
stmt.setString(1, "501440165655347200");
ResultSet rs = stmt.executeQuery();
while(rs.next()){
String username = rs.getString("username");
System.out.print("姓名: " + username);
}
}
从上面的代码来看,一次简单的数据库查询操作,可以分为几个步骤。
-
创建Connection连接
-
传入参数化查询SQL语句构建预编译对象PreparedStatement
-
设置参数
-
执行SQL
-
从结果集中获取数据
那么,咱们的主角Mybatis是怎样完成这一过程的呢?不着急,咱们一个一个来看。
二、sqlSession
在上一章节的内容中,我们已经看到了在Service层通过@Autowired注入的userMapper是个代理类,在执行方法的时候实际上调用的是代理类的invoke通知方法。
public class MapperProxy<T> implements InvocationHandler{
public Object invoke(Object proxy, Method method, Object[] args)
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
}
1 、创建MapperMethod对象
MapperMethod对象里面就两个属性,SqlCommand和MethodSignature。
SqlCommand包含了执行方法的名称和方法的类型,比如 UNKNOWN,INSERT,UPDATE,DELETE,SELECT,FLUSH
。 MethodSignature可以简单理解为方法的签名信息。里面包含:返回值类型、是否void、是否为集合类型、是否为Cursor等,主要还获取到了方法参数上的@Param注解的名称,方便下一步获取参数值。 比如,如果方法上加了@Param的参数: UsergetUserById(@Param(value="id")Stringid,@Param(value="password")Stringpassword);
,参数会被解析成 {0=id,1=password}
。
2、执行
判断方法的SQL类型和返回值类型 ,调用相应的方法。以方法 UsergetUserById(Stringid,Stringpassword)
为例,会调用到selectOne()方法。
public class MapperMethod {
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {
case INSERT: {}
case UPDATE: {}
case DELETE: {}
case SELECT:
if (method.returnsVoid() && method.hasResultHandler()) {
//无返回值
} else if (method.returnsMany()) {
//返回集合类型
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
//返回Map类型
result = executeForMap(sqlSession, args);
} else if (method.returnsCursor()) {
//返回Cursor
result = executeForCursor(sqlSession, args);
} else {
//将参数args转换为SQL命令的参数
//默认会添加一个《param+参数索引》的参数名
//{password=123456, id=501441819331002368, param1=501441819331002368, param2=123456}
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
}
...
return result;
}
}
可以看到,sqlSession.selectOne就可以获取到数据库中的值并完成转换工作。这里的sqlSession就是 SqlSessionTemplate
实例的对象,所以它会调用到
public class SqlSessionTemplate{
public <T> T selectOne(String statement, Object parameter) {
return this.sqlSessionProxy.<T> selectOne(statement, parameter);
}
}
sqlSessionProxy也是个代理对象。关于它的创建咱们上节课也很认真的分析了,总之它实际会调用到 SqlSessionInterceptor.invoke()
。
3、创建sqlSession对象
sqlSession我们熟悉呀,它作为MyBatis工作的主要顶层API,表示和数据库交互的会话,完成必要数据库增删改查功能。关于它的创建、执行、提交和资源清理都是在SqlSessionInterceptor的通知方法中完成的。
private class SqlSessionInterceptor implements InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//创建SqlSession对象
SqlSession sqlSession = getSqlSession(
SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType,
SqlSessionTemplate.this.exceptionTranslator);
try {
//调用sqlSession实际方法
Object result = method.invoke(sqlSession, args);
return result;
} catch (Throwable t) {
....
} finally {
if (sqlSession != null) {
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
}
}
}
}
上面的重点就是创建了SqlSession并执行它的方法,它是一个DefaultSqlSession实例的对象,里面主要有一个通过configuration创建的执行器,在这里它是SimpleExecutor。
那么,invoke方法实际调用的就是 DefaultSqlSession.selectOne()
。
三、获取BoundSql对象
DefaultSqlSession中的 selectOne()
方法最终也会调用到 selectList()
方法。它先从数据大管家configuration中根据请求方法的全名称拿到对应的MappedStatement对象,然后调用执行器的查询方法。
1、获取MappedStatement对象
//statement是调用方法的全名称,parameter为参数的Map
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
//在mapper.xml中每一个SQL节点都会封装为MappedStatement对象
//在configuration中就可以通过请求方法的全名称获取对应的MappedStatement对象
Ma