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 SqlSessionTemplate implements SqlSession, DisposableBean {
private final SqlSessionFactory sqlSessionFactory;
private final ExecutorType executorType;
private final SqlSession sqlSessionProxy;
private final PersistenceExceptionTranslator exceptionTranslator;
......
public SqlSessionTemplate(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());
}
......
@Override
public <T> T selectOne(String statement) {
return this.sqlSessionProxy.<T> selectOne(statement);
}
......
private class SqlSessionInterceptor implements InvocationHandler {
@Override
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)) {
// force commit even on non-dirty sessions because some databases require
// 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;
...
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
...
}

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

小结

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

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