Mybatis源码分析
Mybatis
Mybatis是一款持久层框架,其作用是简化了jdbc操作,并且有很强的扩展性。
实现思路
因为Mybatis是和数据库打交道,所以其实现的思路可以简单用以下过程概括。
- 确定数据源
通过在mybatis-config.xml中配置的标签中的内容,封装DataSource - 封装sql语句
- 执行sql
其实质还是需要通过jdbc的操作,包括创建Connection对象,(或是从连接池获取Connection),构建PrapareStatement,最后获取ResultSet。
实现技术
实现可以简单概括为,读取mybatis-config.xml和mapper.xml中的内容,动态代理实现mapper接口,通过门面模式简化操作。
源码分析
常用的使用mybatis代码如下
String resource = "mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
// sqlSessionFactory.openSession(false);//关闭自动提交
// sqlSessionFactory.openSession(TransactionIsolationLevel.READ_COMMITTED);//关闭自动提交,同时配置隔离级别
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
sqlSession.commit();
List<User> all = mapper.getByName("test");
all.forEach(System.out::println);
读取资源
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
这一步用一句话概括是,将mybatis-config.xml以及mapper.xml的文件全部读取到mybatis中的Configuartion对象中。
#### Configuartion
Configuartion是整个Mybaits依赖的资源类。
其中包含了很多重要的字段。
public class Configuration {
protected Environment environment;//用于存放数据源信息
protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection");//用于存放mapper信息
......
}
解析datasource
Environment类就是解析对应mybaits-config.xml中的
<environment id="development">
<transactionManager type="JDBC"/>
<dataSource type="POOLED">
<property name="driver" value="com.mysql.jdbc.Driver"/>
<property name="url"
value="jdbc:mysql://127.0.0.1:3306/zg_test?useUnicode=true&characterEncoding=UTF8&zeroDateTimeBehavior=convertToNull&serverTimezone=Asia/Shanghai"/>
<property name="username" value="root"/>
<property name="password" value="123456"/>
</dataSource>
</environment>
具体的代码执行为
解析mapper
解析的mapper信息会被封装为一个MappedStatement对象,然后加入Configuration中的mappedStatements(Map)字段中。这个map的key就是mapper接口对应的方法,value为MappedStatement。
解析mapper的流程如下
具体的代码执行为
获取sqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
这行代码是通过工厂类获取SqlSession。
SqlSession主要包含了两个重要的成员变量,Configuration和Executor。
private Configuration configuration;
private Executor executor;
Configuration就是上文介绍的资源类。这里重点介绍Executor。
我们实际进行的增删改查都是通过这个Executor来进行的。
Executor
首先看看Executor接口的方法定义。
public interface Executor {
ResultHandler NO_RESULT_HANDLER = null;
int update(MappedStatement ms, Object parameter) throws SQLException;
<E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey cacheKey, BoundSql boundSql) throws SQLException;
<E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException;
List<BatchResult> flushStatements() throws SQLException;
void commit(boolean required) throws SQLException;
void rollback(boolean required) throws SQLException;
CacheKey createCacheKey(MappedStatement ms, Object parameterObject, RowBounds rowBounds, BoundSql boundSql);
boolean isCached(MappedStatement ms, CacheKey key);
void clearLocalCache();
void deferLoad(MappedStatement ms, MetaObject resultObject, String property, CacheKey key, Class<?> targetType);
Transaction getTransaction();
void close(boolean forceRollback);
boolean isClosed();
void setExecutorWrapper(Executor executor);
}
接口里,主要包含了事务的一些方法,和缓存方法,还有查询和更新等方法。
其继承关系为
首先,最底层的接口是Executor,有两个实现类:BaseExecutor和CachingExecutor,CachingExecutor用于二级缓存,而BaseExecutor则用于一级缓存及基础的操作。并且由于BaseExecutor是一个抽象类,提供了三个实现:SimpleExecutor,BatchExecutor,ReuseExecutor,而具体使用哪一个Executor则是可以在mybatis-config.xml中进行配置的。配置如下:
Executor的实现类
- SimpleExecutor是最简单的执行器,根据对应的sql直接执行即可,不会做一些额外的操作;
- BatchExecutor执行器,顾名思义,通过批量操作来优化性能。通常需要注意的是批量更新操作,由于内部有缓存的实现,使用完成后记得调用flushStatements来清除缓存。
- ReuseExecutor 可重用的执行器,重用的对象是Statement,也就是说该执行器会缓存同一个sql的Statement,省去Statement的重新创建,优化性能。内部的实现是通过一个HashMap来维护* * * * Statement对象的。由于当前Map只在该session中有效,所以使用完成后记得调用flushStatements来清除Map。
获取动态代理类
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
我们获取到的其实是代理类,MapperProxy
public class MapperProxy<T> implements InvocationHandler, Serializable {
private static final long serialVersionUID = -6424540398559729838L;
private final SqlSession sqlSession;
private final Class<T> mapperInterface;
private final Map<Method, MapperMethod> methodCache;
public MapperProxy(SqlSession sqlSession, Class<T> mapperInterface, Map<Method, MapperMethod> methodCache) {
this.sqlSession = sqlSession;
this.mapperInterface = mapperInterface;
this.methodCache = methodCache;
}
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
if (Object.class.equals(method.getDeclaringClass())) {
try {
return method.invoke(this, args);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
private MapperMethod cachedMapperMethod(Method method) {
MapperMethod mapperMethod = methodCache.get(method);
if (mapperMethod == null) {
mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration());
methodCache.put(method, mapperMethod);
}
return mapperMethod;
}
}
通过MapperProxy代码查看,MapperProxy实现了InvocationHandler接口。
并且在invoke方法中,将Method方法封装为MapperMethod。
于是查看MapperMethod类源码。
public class MapperMethod {
private final SqlCommand command;
private final MethodSignature method;
......
}
MapperMethod类是整个代理机制的核心类,对SqlSession中的操作进行了封装使用。 该类里有两个内部类SqlCommand和MethodSignature。 SqlCommand用来封装CRUD操作,也就是我们在xml中配置的操作的节点。每个节点都会生成一个MappedStatement类。MethodSignature用来封装方法的参数以及返回类型,在execute的方法中我们发现在这里又回到了SqlSession中的接口调用,和我们自己实现UerDao接口的方式中直接用SqlSession对象调用DefaultSqlSession的实现类的方法是一样的,经过一大圈的代理又回到了原地,这就是整个动态代理的实现过程了。
关系映射
执行完sql后,程序会获得ResultSet,最后一步就是将ResultSet封装为我们自己的java类。
debug位置 DefaultResultSetHandler 的getFirstResultSet方法。
主要进行关系ResultSet和我们自己的java类映射的类是ResultSetWrapper
class ResultSetWrapper {
private final ResultSet resultSet;
private final TypeHandlerRegistry typeHandlerRegistry;
private final List<String> columnNames = new ArrayList<String>();
private final List<String> classNames = new ArrayList<String>();
private final List<JdbcType> jdbcTypes = new ArrayList<JdbcType>();
private final Map<String, Map<Class<?>, TypeHandler<?>>> typeHandlerMap = new HashMap<String, Map<Class<?>, TypeHandler<?>>>();
private Map<String, List<String>> mappedColumnNamesMap = new HashMap<String, List<String>>();
private Map<String, List<String>> unMappedColumnNamesMap = new HashMap<String, List<String>>();
public ResultSetWrapper(ResultSet rs, Configuration configuration) throws SQLException {
super();
this.typeHandlerRegistry = configuration.getTypeHandlerRegistry();
this.resultSet = rs;
final ResultSetMetaData metaData = rs.getMetaData();
final int columnCount = metaData.getColumnCount();
for (int i = 1; i <= columnCount; i++) {
columnNames.add(configuration.isUseColumnLabel() ? metaData.getColumnLabel(i) : metaData.getColumnName(i));
jdbcTypes.add(JdbcType.forCode(metaData.getColumnType(i)));
classNames.add(metaData.getColumnClassName(i));
}
}
TypeHandlerRegistry
其中TypeHandlerRegistry就是我们一些常用的typeHandler,包括我们自己定义的用于处理枚举的一些。