MyBatis源码分析:SqlSession
1.SqlSession
源码:
package org.apache.ibatis.session;
import java.io.Closeable;
import java.sql.Connection;
import java.util.List;
import java.util.Map;
import org.apache.ibatis.cursor.Cursor;
import org.apache.ibatis.executor.BatchResult;
/**
* The primary Java interface for working with MyBatis.
* Through this interface you can execute commands, get mappers and manage transactions.
*
* @author Clinton Begin
*/
public interface SqlSession extends Closeable {
<T> T selectOne(String statement);
<T> T selectOne(String statement, Object parameter);
<E> List<E> selectList(String statement);
<E> List<E> selectList(String statement, Object parameter);
<E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds);
<K, V> Map<K, V> selectMap(String statement, String mapKey);
<K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey);
<K, V> Map<K, V> selectMap(String statement, Object parameter, String mapKey, RowBounds rowBounds);
<T> Cursor<T> selectCursor(String statement);
<T> Cursor<T> selectCursor(String statement, Object parameter);
<T> Cursor<T> selectCursor(String statement, Object parameter, RowBounds rowBounds);
void select(String statement, Object parameter, ResultHandler handler);
void select(String statement, ResultHandler handler);
void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler);
int insert(String statement);
int insert(String statement, Object parameter);
int update(String statement);
int update(String statement, Object parameter);
int delete(String statement);
int delete(String statement, Object parameter);
void commit();
void commit(boolean force);
void rollback();
void rollback(boolean force);
List<BatchResult> flushStatements();
@Override
void close();
void clearCache();
Configuration getConfiguration();
<T> T getMapper(Class<T> type);
Connection getConnection();
}
从接口的注释中可以看出,SqlSession接口主要提供如下功能:
- 执行特定SQL:ibatis遗留的使用方式,可通过update,insert,select,delete等方法,带上命名空间和SQL的id来执行配置好的SQL;
- 获取映射器(Mapper):让映射器通过命名空间和方法名称找到对应SQL,发送给数据库执行后返回结果;
- 管理事物:ibatis遗留的使用方式,通过commit,rollback方法提交或回滚事物;
2.实现类
SqlSession接口在mybatis中有2个实现类:分别为DefaultSqlSession和SqlSessionManager。
UML图如下:
3.ibatis遗留的使用方式
涉及接口(省略了重载):
selectOne()
selectList()
selectMap()
selectCursor()
select()
insert()
update()
delete()
commit()
rollback()
我们以
/**
* Retrieve a list of mapped objects from the statement key and parameter,
* within the specified row bounds.
* @param <E> the returned list element type
* @param statement Unique identifier matching the statement to use.
* @param parameter A parameter object to pass to the statement.
* @param rowBounds Bounds to limit object retrieval
* @return List of mapped object
*/
<E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds)
接口为例进行分析,此接口的注释中声明:该接口根据statement key和parameter返回查询结果映射的列表,长度为irowBounds;泛型T
为返回结果的类型;statement参数为命名空间+SQL id;parameter为传递给statemtent的参数;
我们来看一下这个接口在实现类DefaultSqlSession
中的实现:
@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
//1.从配置中读取MappedStatement
MappedStatement ms = configuration.getMappedStatement(statement);
//2.调用executor的query方法进行查询
return executor.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER);
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error querying database. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
这里涉及到了两个Mybatis的组件:MappedStatement和Executor。
selectList
方法中使用了一个wrapCollection
方法,该方法将输入object参数进行封装,逻辑如下:
- 首先判断object的类型,若既不是Collection也不是Array,则直接返回;
- 若object的类型为Collection,且为List,则构造一个StrictMap对象,并设置两个enetity,key分别为"collection"和"list",val均为object,并将map返回;
- 若object的类型为Collection,但不为List,则构造一个StrictMap对象,并设置一个entity,key为“collection”,val为object,,并将map返回;
- 若object的类型为Array,则构造一个StrictMap对象,并设置一个entity,key为“array”,value为object,并将map返回;
wrapCollection
方法:
private Object wrapCollection(final Object object) {
if (object instanceof Collection) {
StrictMap<Object> map = new StrictMap<Object>();
map.put("collection", object);
if (object instanceof List) {
map.put("list", object);
}
return map;
} else if (object != null && object.getClass().isArray()) {
StrictMap<Object> map = new StrictMap<Object>();
map.put("array", object);
return map;
}
return object;
}
selectList
demo:
package mybatis;
import org.apache.ibatis.io.Resources;
import org.apache.ibatis.session.SqlSession;
import org.apache.ibatis.session.SqlSessionFactory;
import org.apache.ibatis.session.SqlSessionFactoryBuilder;
import java.io.IOException;
import java.io.Reader;
public class HelloWorldMyBatis {
private static SqlSessionFactory sqlSessionFactory = null;
private SqlSessionFactory getSqlSessionFactory() {
if (null == sqlSessionFactory) {
final String resource = "mybatis_config.xml";
try {
Reader reader = Resources.getResourceAsReader(resource);
return new SqlSessionFactoryBuilder().build(reader);
} catch (IOException e) {
throw new IllegalStateException("Cannot build SqlSessionFactory", e);
}
} else {
return sqlSessionFactory;
}
}
public static void main(String[] args) {
final SqlSession sqlSession = new HelloWorldMyBatis().getSqlSessionFactory().openSession();
//mapper的命名空间:mybatis.UserMapper
//SQL id:getUser
User user = sqlSession.selectOne("mybatis.UserMapper.getUser", 1L);
System.out.println(user);
sqlSession.close();
}
}
4.通过映射器的方式
涉及接口:
<T> T getMapper(Class<T> type);
此接口在DefaultSqlSession中的实现如下:
@Override
public <T> T getMapper(Class<T> type) {
return configuration.<T>getMapper(type, this);
}
此方法涉及到Mybatis组件:Configuration
demo:
public class HelloWorldMyBatis {
private static SqlSessionFactory sqlSessionFactory = null;
private SqlSessionFactory getSqlSessionFactory() {
if (null == sqlSessionFactory) {
final String resource = "mybatis_config.xml";
try {
Reader reader = Resources.getResourceAsReader(resource);
return new SqlSessionFactoryBuilder().build(reader);
} catch (IOException e) {
throw new IllegalStateException("Cannot build SqlSessionFactory", e);
}
} else {
return sqlSessionFactory;
}
}
public static void main(String[] args) {
final SqlSession sqlSession = new HelloWorldMyBatis().getSqlSessionFactory().openSession();
mapper的命名空间:mybatis.UserMapper
final UserMapper mapper = sqlSession.getMapper(mybatis.UserMapper.class);
//SQL id:getUser
final User user = mapper.getUser(1L);
System.out.println(user);
sqlSession.close();
}
}
5.两种方式对比
《深入浅出Mybatis技术原理与实战》一书强烈建议读者使用映射器的方式来操作数据库,原因如下:
- Mapper是一个接口,可以进一步屏蔽SqlSession对象,提高代码可读性;
- sqlSession.selectOne方法是功能性代码,参数晦涩难懂,不包含业务逻辑,不符合面向对象编程规范,而mapper.getUser方法符合面向对象编程规范,也更符合业务逻辑;
- 使用mapper的方式,IDE可以检查java语法,避免不必要的错误;