MyBatis学习笔记

每个基于 MyBatis 的应用都是以一个 SqlSessionFactory 的实例为中心的。SqlSessionFactory 的实例可以通过 SqlSessionFactoryBuilder 获得。而 SqlSessionFactoryBuilder 则可以从 XML 配置文件或一个预先定制的 Configuration 的实例构建出 SqlSessionFactory 的实例。

我们就可以通过 SqlSessionFactory 获得 SqlSession 的实例。SqlSession 完全包含了面向数据库执行 SQL 命令所需的所有方法。

可以通过 SqlSession 实例来直接执行已映射的 SQL 语句

1
2
3
4
5
6
SqlSession session = sqlSessionFactory.openSession();
try {
Blog blog = (Blog) session.selectOne("org.mybatis.example.BlogMapper.selectBlog", 101);
} finally {
session.close();
}

新版本更推荐使用对于给定语句能够合理描述参数和返回值的接口(Mapper)

1
2
3
4
5
6
7
SqlSession session = sqlSessionFactory.openSession();
try {
BlogMapper mapper = session.getMapper(BlogMapper.class);
Blog blog = mapper.selectBlog(101);
} finally {
session.close();
}

源码解读

第一种写法

目前公司采用这种写法,在DAO层调用SqlSessionTemplate的方法。好处是可以将一些简单的逻辑写在dao实现类,比如设置创建、更新信息,对sql结果简单的处理。

1
sqlSessionTemplate.selectOne("com.jh.dao.BlogDao.selectBlog", id);

SqlSessionTemplate 是 MyBatis-Spring 的核心。 这个类负责管理 MyBatis 的 SqlSession, 调用 MyBatis 的 SQL 方法, 翻译异常。 SqlSessionTemplate 是线程安全的, 可以被多个 DAO 所共享使用。
当调用 SQL 方法时, 包含从映射器 getMapper()方法返回的方法, SqlSessionTemplate 将会保证使用的 SqlSession 是和当前 Spring 的事务相关的。此外,它管理 session 的生命 周期,包含必要的关闭,提交或回滚操作。

SqlSessionTemplate中的属性SqlSession采用了动态代理的方式,在执行sqlSessionProxy的方法时,会被SqlSessionInterceptor拦截到。在该拦截器的invoke方法中,会从Spring事务管理器获取sqlsession,管理sqlsession的生命周期。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
public class implements SqlSession, DisposableBean {
private final SqlSessionFactory sqlSessionFactory;
private final ExecutorType executorType;
private final SqlSession sqlSessionProxy;
private final PersistenceExceptionTranslator exceptionTranslator;
......
public (SqlSessionFactory sqlSessionFactory, ExecutorType executorType,
PersistenceExceptionTranslator exceptionTranslator) {
notNull(sqlSessionFactory, "Property 'sqlSessionFactory' is required");
notNull(executorType, "Property 'executorType' is required");
this.sqlSessionFactory = sqlSessionFactory;
this.executorType = executorType;
this.exceptionTranslator = exceptionTranslator;
this.sqlSessionProxy = (SqlSession) newProxyInstance(
SqlSessionFactory.class.getClassLoader(),
new Class[] { SqlSession.class },
new SqlSessionInterceptor());
}
......
public <T> T selectOne(String statement) {
return this.sqlSessionProxy.<T> selectOne(statement);
}
......
private class SqlSessionInterceptor implements InvocationHandler {
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
SqlSession sqlSession = getSqlSession(
SqlSessionTemplate.this.sqlSessionFactory,
SqlSessionTemplate.this.executorType,
SqlSessionTemplate.this.exceptionTranslator);
try {
Object result = method.invoke(sqlSession, args);
if (!isSqlSessionTransactional(sqlSession, SqlSessionTemplate.this.sqlSessionFactory)) {
// a commit/rollback before calling close()
sqlSession.commit(true);
}
return result;
} catch (Throwable t) {
Throwable unwrapped = unwrapThrowable(t);
if (SqlSessionTemplate.this.exceptionTranslator != null && unwrapped instanceof PersistenceException) {
// release the connection to avoid a deadlock if the translator is no loaded. See issue #22
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
sqlSession = null;
Throwable translated = SqlSessionTemplate.this.exceptionTranslator.translateExceptionIfPossible((PersistenceException) unwrapped);
if (translated != null) {
unwrapped = translated;
}
}
throw unwrapped;
} finally {
if (sqlSession != null) {
closeSqlSession(sqlSession, SqlSessionTemplate.this.sqlSessionFactory);
}
}
}
}

继续通过断点跟踪,实际调用的还是SqlSession的一个实现类DefaultSqlSession,其中属性executor默认为CachingExecutor,Executor负责执行数据库操作。

1
2
3
4
5
6
7
8
9
10
public class DefaultSqlSession implements SqlSession {
private Configuration configuration;
private Executor executor;
private boolean autoCommit;
private boolean dirty;
private List<Cursor<?>> cursorList;
...
}

CachingExecutor先尝试从内存中获取数据,若没有的话通过代理调用其它Executor实现类的query方法,这里应是mybatis二级缓存,实际没有用过不做研究了。

1
2
3
4
5
public class CachingExecutor implements Executor {
private Executor delegate;
private TransactionalCacheManager tcm = new TransactionalCacheManager();
...
}

BaseExecutor有三个子类,分别对应于SqlSessionTemplate中ExecutorType的3个枚举类型。在公司项目中,指定了ExecutorType为REUSE,根据sql缓存了jdbc的statement

1
2
3
public enum ExecutorType {
SIMPLE, REUSE, BATCH
}

BaseExecutor有个属性PerpetualCache是mybatis一级缓存,本质是一个HashMap,Key根据StatementId(com.jh.dao.BlogDao.selectBlog)、RowBounds分页的参数(limit&offset)、sql语句(select * from blog where id = ?)、查询条件(1)这几个条件生成。

1
2
3
public int update(Blog blog) {
return sqlSessionTemplate.update("com.jh.dao.BlogDao.update", blog);
}

update操作(包括update(),delete(),insert())在执行到BaseExecutor.update()时清空一级缓存,再执行数据库查询操作。

第二种写法

1
2
3
4
5
6
7
@Mapper
public interface CityMapper {
@Select("select * from city where state = #{state}")
City findByState(@Param("state") String state);
}

用到了动态代理,Mapper接口实际调用MapperProxy.invoke,底层同样调用SqlSession的方法

1
2
3
4
5
6
7
8
9
10
11
12
public class MapperProxy<T> implements InvocationHandler, Serializable {
private final SqlSession sqlSession;
private final Class<T> mapperInterface;
private final Map<Method, MapperMethod> methodCache;
...
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
...
}

之后的流程与第一种写法一样。

小结

本文只是简单记录一下mybatis大体流程,之后如有机会将深入研究具体细节。

mybatis源码中用到的动态代理和一些设计模式,值得进一步学习。