myBatis是目前非常流行的ORM框架,它的功能很强大,然而其实现却比较简单、优雅。本文主要讲述Mybatis的工作原理,以及结合一次select查询的实例,查看myBatis源码,探究其实现。
public class MybatisTest {
public static void main(String[] args) throws IOException {
// 获得mybatis中的全局配置文件
InputStream resourceAsStream = Resources.getResourceAsStream("static/mybatis-config.xml");
// 创建SqlSessionFactoryBuilder,并通过build(inputStream)方法解析出配置信息,并保存在SqlSessionfactory中。
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
SqlSessionFactory factory = sqlSessionFactoryBuilder.build(resourceAsStream);
// 通过factory获得sqlSession对象,同时默认打开事务
SqlSession sqlSession = factory.openSession();
// 通过处理类从sqlSession中获得mapper。
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
//通过调用接口中的api,内部最终以statementId为标志,找寻出对应的sql字符串,最终底层调用jdbc执行相应的sql
Long userSize = mapper.getUserSize();
System.out.println(userSize);
}
}
(1)加载配置: 存储在内存中。
(2)SQL解析:
(3) SQL执行:将最终得到的SQL和参数拿到数据库进行执行,得到操作数据库的结果。
(4)结果映射: 将操作数据库的结果按照映射的配置进行转换,可以转换成HashMap、JavaBean或者基本数据类型,并将最终结果返回。
核心部件 | 部件职责 |
---|---|
SqlSession | 作为MyBatis工作的主要顶层API,表示和数据库交互的会话,完成必要数据库增删改查功能 |
Executor | MyBatis执行器,是MyBatis 调度的核心,负责SQL语句的生成和查询缓存的维护 |
StatementHandler | 封装了JDBC Statement操作,负责对JDBC statement 的操作,如设置参数、将Statement结果集转换成List集合。 |
ParameterHandler | 负责对用户传递的参数转换成JDBC Statement 所需要的参数, |
ResultSetHandler | 负责将JDBC返回的ResultSet结果集对象转换成List类型的集合; |
TypeHandler | 负责java数据类型和jdbc数据类型之间的映射和转换 |
MappedStatement | MappedStatement维护了一条 |
SqlSource | 负责根据用户传递的parameterObject,动态地生成SQL语句,将信息封装到BoundSql对象中,并返回 |
BoundSql | 表示动态生成的SQL语句以及相应的参数信息 |
Configuration | MyBatis所有的配置信息都维持在Configuration对象之中。 |
主要分为一下步骤
1、手动读取配置文件;
2、build(InputStream)方法中从Xml文件中解析
中的配置信息
··· 3、获取SqlSession对象
4、通过class对象获得sqlSession中保存的相应mapper
5、通过实例化的mapper对象,就可以使用MapperProxy调用invoke方法执行sql
其中涉及到:
5.1 通过statementId获得对应的查询信息
5.2 通过不同的查询类型(增、删、改、查),执行相应的方法【示例为查询】
5.1 对于不同的返回类型执行相应的方法
5.3 动态生成sql
5.4 将参数中的动态变量与sql脚本进行绑定
5.5 执行sql
5.6 使用resultHandler处理jdbc返回的结果集
一下为代码层面解析
手动读取配置文件
SqlSessionFactoryBuilder sqlSessionFactoryBuilder = new SqlSessionFactoryBuilder();
InputStream resourceAsStream = Resources.getResourceAsStream("static/mybatis-config.xml");
SqlSessionFactory factory = sqlSessionFactoryBuilder.build(resourceAsStream);
build(InputStream)方法中从Xml文件中解析
中的配置信息
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
// ···有省略···
// 获得Xml文件内容信息
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
// 使用parser.parse()方法对xml文件进行解析
return build(parser.parse());
//···有省略···
}
//以下为parper.parse()详细内容
public Configuration parse() {
// ···有省略···
//解析
parseConfiguration(parser.evalNode("/configuration"));
return configuration;
// ···有省略···
}
// 以下为parseConfiguration()详细内容
private void parseConfiguration(XNode root) {
try {
// issue #117 read properties first
propertiesElement(root.evalNode("properties"));
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
loadCustomLogImpl(settings);
typeAliasesElement(root.evalNode("typeAliases"));
pluginElement(root.evalNode("plugins"));
objectFactoryElement(root.evalNode("objectFactory"));
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
reflectorFactoryElement(root.evalNode("reflectorFactory"));
settingsElement(settings);
// read it after objectFactory and objectWrapperFactory issue #631
// 解析数据库配置信息,以及事务配置
environmentsElement(root.evalNode("environments"));
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
typeHandlerElement(root.evalNode("typeHandlers"));
//通过mappers配置信息获得包名,xml文件名,接口名,进一步解析mapper文件、mapper接口文件
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
// 以下为mapperElement()方法详细内容
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
// 读取package信息
if ("package".equals(child.getName())) {
String mapperPackage = child.getStringAttribute("name");
// 通过package信息读取将并解析相应package中的mapper接口,并添加到对象中的MAP,knownMappers中
configuration.addMappers(mapperPackage);
} else {
// 记录相关的mapper信息,并且对于每种情况下的mapper进行解析,并添加到对象中的MAP,knownMappers中
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
if (resource != null && url == null && mapperClass == null) {
ErrorContext.instance().resource(resource);
try(InputStream inputStream = Resources.getResourceAsStream(resource)) {
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse();
}
} else if (resource == null && url != null && mapperClass == null) {
ErrorContext.instance().resource(url);
try(InputStream inputStream = Resources.getUrlAsStream(url)){
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
mapperParser.parse();
}
} else if (resource == null && url == null && mapperClass != null) {
Class<?> mapperInterface = Resources.classForName(mapperClass);
configuration.addMapper(mapperInterface);
} else {
throw new BuilderException("A mapper element may only specify a url, resource or class, but not more than one.");
}
}
}
}
}
获取SqlSession对象
// 通过SqlSessionFactory获取SqlSession对象
SqlSession sqlSession = factory.openSession();
// 以defaultSqlSessionFactory为例,最终调用openSessionFromDataSource()方法
private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) {
Transaction tx = null;
try {
// 获得上一步从配置文件中取出信息
final Environment environment = configuration.getEnvironment();
// 获得事务信息,并配置事务信息
final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment);
tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit);
// 配置MyBatis执行器
final Executor executor = configuration.newExecutor(tx, execType);
return new DefaultSqlSession(configuration, executor, autoCommit);
} catch (Exception e) {
closeTransaction(tx); // may have fetched a connection so lets call close()
throw ExceptionFactory.wrapException("Error opening session. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
通过class对象获得sqlSession中保存的相应mapper
UserMapper mapper = sqlSession.getMapper(UserMapper.class);
//其中最终会调用到SqlSession中,并从configration中进行读取mapper对象
@Override
public <T> T getMapper(Class<T> type) {
return configuration.getMapper(type, this);
}
// 之前解析mybatis-config.xml文件中就将mapper对象存入到knowMappers对象中,通过类名作为key,MapperProxyFactory作为value,最后通过动态代理工厂生成示例
Map<Class<?>, MapperProxyFactory<?>> configuration.mapperRegistry.knownMappers
// 获得MapperProxy对象后,联合configration对象中的sqlsession创建mapper对象
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
通过实例化的mapper对象,就可以使用MapperProxy调用invoke方法执行sql
@Override
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
return method.invoke(this, args);
} else if (method.isDefault()) {
if (privateLookupInMethod == null) {
return invokeDefaultMethodJava8(proxy, method, args);
} else {
return invokeDefaultMethodJava9(proxy, method, args);
}
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
final MapperMethod mapperMethod = cachedMapperMethod(method);
return mapperMethod.execute(sqlSession, args);
}
//对于不同类型的sql脚本执行对应的操作,这里为SELECT操作
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {
case INSERT: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
break;
}
case UPDATE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
break;
}
case DELETE: {
// ···省略···
}
case SELECT:
// 通过返回类型确定Handler,执行相应的方法
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
result = executeForMap(sqlSession, args);
} else if (method.returnsCursor()) {
result = executeForCursor(sqlSession, args);
} else {
// 获得参数列表
Object param = method.convertArgsToSqlCommandParam(args);
// 通过接口名确定sql,并传入参数列表,执行查询
result = sqlSession.selectOne(command.getName(), param);
if (method.returnsOptional()
&& (result == null || !method.getReturnType().equals(result.getClass()))) {
result = Optional.ofNullable(result);
}
}
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for: " + command.getName());
}
if (result == null && method.getReturnType().isPrimitive() && !method.returnsVoid()) {
throw new BindingException("Mapper method '" + command.getName()
+ " attempted to return null from a method with a primitive return type (" + method.getReturnType() + ").");
}
return result;
}
// 执行selectList并传入statementId以及参数列表
@Override
public <T> T selectOne(String statement, Object parameter) {
// 执行sql,返回如果是null,或者多条数据,将会抛出错误
List<T> list = this.selectList(statement, parameter);
if (list.size() == 1) {
return list.get(0);
} else if (list.size() > 1) {
throw new TooManyResultsException("Expected one result (or null) to be returned by selectOne(), but found: " + list.size());
} else {
return null;
}
}
@Override
public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
// 通过statementId获得mapperStatement对象
MappedStatement ms = configuration.getMappedStatement(statement);
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();
}
}
// 通过确定的mapperStatement对象以及参数列表执行query操作
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
// 获得sql语句
BoundSql boundSql = ms.getBoundSql(parameterObject);
// 确定sql,参数列表,返回值等信息
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
// ···省略部分调用堆栈
// query(ms, parameterObject, rowBounds, resultHandler, key, boundSql)最终调用方法
@Override
public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException {
Statement stmt = null;
try {
// 通过configuration获得StatementHandler,负责操作 Statement 对象与数据库进行交流
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql);
// 参数信息set到statement中
stmt = prepareStatement(handler, ms.getStatementLog());
return handler.query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
// stmt = prepareStatement(handler, ms.getStatementLog());最终调用方法。将参数值set到sql中的对应参数列表中
public void setParameters(PreparedStatement ps) {
ErrorContext.instance().activity("setting parameters").object(mappedStatement.getParameterMap().getId());
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
if (parameterMappings != null) {
for (int i = 0; i < parameterMappings.size(); i++) {
ParameterMapping parameterMapping = parameterMappings.get(i);
if (parameterMapping.getMode() != ParameterMode.OUT) {
Object value;
String propertyName = parameterMapping.getProperty();
if (boundSql.hasAdditionalParameter(propertyName)) { // issue #448 ask first for additional params
value = boundSql.getAdditionalParameter(propertyName);
} else if (parameterObject == null) {
value = null;
} else if (typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
value = parameterObject;
} else {
MetaObject metaObject = configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
}
TypeHandler typeHandler = parameterMapping.getTypeHandler();
JdbcType jdbcType = parameterMapping.getJdbcType();
if (value == null && jdbcType == null) {
jdbcType = configuration.getJdbcTypeForNull();
}
try {
typeHandler.setParameter(ps, i + 1, value, jdbcType);
} catch (TypeException | SQLException e) {
throw new TypeException("Could not set parameters for mapping: " + parameterMapping + ". Cause: " + e, e);
}
}
}
}
}
//handler.query(stmt, resultHandler)最终调用方法
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
// 执行已经处理后的PreparedStatement
ps.execute();
// 对执行结果进行处理,解析为resultType对应的数据类型,并返回
return resultSetHandler.handleResultSets(ps);
}
所谓ORM框架,其实都是在JDBC上封装了一层,底层用的都是JDBC的代码。
MyBatis对jdbc的操作数据库的过程进行封装,使开发者只需要关注 SQL 本身,而不需要花费精力去处理例如注册驱动、创建connection、创建statement、手动设置参数、结果集检索等jdbc繁杂的过程代码。
Mybatis通过xml或注解的方式将要执行的各种statement(statement、preparedStatemnt、CallableStatement)配置起来,并通过java对象和statement中的sql进行映射生成最终执行的sql语句,最后由mybatis框架执行sql并将结果映射成java对象并返回。这将我们的java代码与sql脚本解耦,通过Mybaits进行操作,就可以直接映射为实体的对象,减少了代码开发量。
注:
参考博文:
《深入理解mybatis原理》 MyBatis的架构设计以及实例分析
MyBatis原理分析
同时参考部分网上资源