在JDBC底层没有用连接池、操作数据库需要频繁的创建和关联链接。消耗很大的资源;写原生的JDBC代码在java中,一旦我们要修改sql的话,java需要整体编译,不利于系统维护;返回result结果集也需要硬编码。
所以就出现了,一些优秀的持久层框架,它支持定制化 SQL、存储过程以及高级映射。而Mybatis就是这样的框架,避免了几乎所有的JDBC代码和手动设置参数以及获取结果集,可以使用简单的XML或注解来配置和映射原生类型、接口。
我们也提到了Mybatis有两种实现方式,一种注解、一种XML。注解方式,不适合比较复杂的sql 比如关联查询,不方便(收集)管理sql;而XML配置方式,繁琐、麻烦,但可以写一些复杂的查询sql语句。我们一般常用的也是XML配置的方式,除非项目和数据库交互简单不需要繁琐的sql语句。
我们配置好XML就会去java中编写代码,一般执行过程如下:
Reader reader = Resources.getResourceAsReader("mybatis.cfg.xml");
//或者InputStream inputStream = Resources.getResourceAsStream("mybatis.xml");
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(reader);
SqlSession session = sessionFactory.openSession();
session.selectOne
//session.getMapper
sqlSession.commit();
sqlSession.close();
先加载配置文件,然后创建SqlSessionFactory然后构建sql会话,通过SqlSession来执行sql语句,面对增删改都得手动提交,最后关闭一次会话。
当然了你不喜欢去XML,全用java代码来代替也是可以的
DataSource dataSource = BlogDataSourceFactory.getBlogDataSource();
TransactionFactory transactionFactory = new JdbcTransactionFactory();
Environment environment = new Environment("development", transactionFactory, dataSource); Configuration configuration = new Configuration(environment); configuration.addMapper(BlogMapper.class);
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(configuration);
//后面执行跟前面一样
在我们了解源码之前先明白Mybatis一些接口或者类,对后面源码分析有帮助。
1、Configuration
管理mysql-config.xml全局配置关系类
2、SqlSessionFactory
Session 管理工厂接口
3、Session
SqlSession 是一个面向用户(程序员)的接口,SqlSession 中提 供了很多操作数据库的方法
4、Executor
执行器是一个接口(基本执行器、缓存执行器),SqlSession 内部通过执行器操作数据库
5、MappedStatement
底层封装对象,对操作数据库存储封装,包括 sql 语句、输入输出参数
6、StatementHandler
具体操作数据库相关的 handler 接口
7、ResultSetHandler
具体操作数据库返回结果的 handler 接口
现在我们一句句来看执行过程。首先就是把配置文件加载到内存中,这个就是一个输入流。
InputStream inputStream = Resources.getResourceAsStream("mybatis.xml")
之后就是创建SqlSessionFactory,我们具体看怎么创建出来的
SqlSessionFactory sessionFactory = new SqlSessionFactoryBuilder().build(reader);
会有各种build方式,最终都会调用
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) {
try {
XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties);
return build(parser.parse());
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error building SqlSession.", e);
} finally {
ErrorContext.instance().reset();
try {
inputStream.close();
} catch (IOException e) {
// Intentionally ignore. Prefer previous error.
}
}
}
配置文件加载进来了,接下来就是去解析它。
private void parseConfiguration(XNode root) {
try {
//分步骤解析
//issue #117 read properties first
//1.properties
propertiesElement(root.evalNode("properties"));
//2.类型别名
typeAliasesElement(root.evalNode("typeAliases"));
//3.插件
pluginElement(root.evalNode("plugins"));
//4.对象工厂
objectFactoryElement(root.evalNode("objectFactory"));
//5.对象包装工厂
objectWrapperFactoryElement(root.evalNode("objectWrapperFactory"));
//6.设置
settingsElement(root.evalNode("settings"));
// read it after objectFactory and objectWrapperFactory issue #631
//7.环境
environmentsElement(root.evalNode("environments"));
//8.databaseIdProvider
databaseIdProviderElement(root.evalNode("databaseIdProvider"));
//9.类型处理器
typeHandlerElement(root.evalNode("typeHandlers"));
//10.映射器
mapperElement(root.evalNode("mappers"));
} catch (Exception e) {
throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e);
}
}
我们重点去看怎么解析mapper文件把sql语句存储进来的
private void mapperElement(XNode parent) throws Exception {
if (parent != null) {
for (XNode child : parent.getChildren()) {
if ("package".equals(child.getName())) {
//10.4自动扫描包下所有映射器
String mapperPackage = child.getStringAttribute("name");
configuration.addMappers(mapperPackage);
} else {
String resource = child.getStringAttribute("resource");
String url = child.getStringAttribute("url");
String mapperClass = child.getStringAttribute("class");
if (resource != null && url == null && mapperClass == null) {
//10.1使用类路径
ErrorContext.instance().resource(resource);
InputStream inputStream = Resources.getResourceAsStream(resource);
//映射器比较复杂,调用XMLMapperBuilder
//注意在for循环里每个mapper都重新new一个XMLMapperBuilder,来解析
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url != null && mapperClass == null) {
//10.2使用绝对url路径
ErrorContext.instance().resource(url);
InputStream inputStream = Resources.getUrlAsStream(url);
//映射器比较复杂,调用XMLMapperBuilder
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, url, configuration.getSqlFragments());
mapperParser.parse();
} else if (resource == null && url == null && mapperClass != null) {
//10.3使用java类名
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.");
}
}
}
}
}
我们看到先会去解析mapper文件,mapperParser.parse(),之后会把解析的信息存储到configuration中
private void configurationElement(XNode context) {
try {
//1.配置namespace
String namespace = context.getStringAttribute("namespace");
if (namespace.equals("")) {
throw new BuilderException("Mapper's namespace cannot be empty");
}
builderAssistant.setCurrentNamespace(namespace);
//2.配置cache-ref
cacheRefElement(context.evalNode("cache-ref"));
//3.配置cache
cacheElement(context.evalNode("cache"));
//4.配置parameterMap(已经废弃,老式风格的参数映射)
parameterMapElement(context.evalNodes("/mapper/parameterMap"));
//5.配置resultMap(高级功能)
resultMapElements(context.evalNodes("/mapper/resultMap"));
//6.配置sql(定义可重用的 SQL 代码段)
sqlElement(context.evalNodes("/mapper/sql"));
//7.配置select|insert|update|delete TODO
buildStatementFromContext(context.evalNodes("select|insert|update|delete"));
} catch (Exception e) {
throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e);
}
}
在这里会解析我们熟悉的标签如resultMap、parameterMap等,我们还是来关注sql语句这块。最终会进入XMLStatementBuilder的parseStatementNode中
public void parseStatementNode() {
String id = context.getStringAttribute("id");
String databaseId = context.getStringAttribute("databaseId");
//如果databaseId不匹配,退出
if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) {
return;
}
//暗示驱动程序每次批量返回的结果行数
Integer fetchSize = context.getIntAttribute("fetchSize");
//超时时间
Integer timeout = context.getIntAttribute("timeout");
//引用外部 parameterMap,已废弃
String parameterMap = context.getStringAttribute("parameterMap");
//参数类型
String parameterType = context.getStringAttribute("parameterType");
Class> parameterTypeClass = resolveClass(parameterType);
//引用外部的 resultMap(高级功能)
String resultMap = context.getStringAttribute("resultMap");
//结果类型
String resultType = context.getStringAttribute("resultType");
//脚本语言,mybatis3.2的新功能
String lang = context.getStringAttribute("lang");
//得到语言驱动
LanguageDriver langDriver = getLanguageDriver(lang);
Class> resultTypeClass = resolveClass(resultType);
//结果集类型,FORWARD_ONLY|SCROLL_SENSITIVE|SCROLL_INSENSITIVE 中的一种
String resultSetType = context.getStringAttribute("resultSetType");
//语句类型, STATEMENT|PREPARED|CALLABLE 的一种
StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString()));
ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType);
//获取命令类型(select|insert|update|delete)
String nodeName = context.getNode().getNodeName();
SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH));
boolean isSelect = sqlCommandType == SqlCommandType.SELECT;
boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect);
//是否要缓存select结果
boolean useCache = context.getBooleanAttribute("useCache", isSelect);
//仅针对嵌套结果 select 语句适用:如果为 true,就是假设包含了嵌套结果集或是分组了,这样的话当返回一个主结果行的时候,就不会发生有对前面结果集的引用的情况。
//这就使得在获取嵌套的结果集的时候不至于导致内存不够用。默认值:false。
boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false);
// Include Fragments before parsing
//解析之前先解析SQL片段
XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant);
includeParser.applyIncludes(context.getNode());
// Parse selectKey after includes and remove them.
//解析之前先解析
processSelectKeyNodes(id, parameterTypeClass, langDriver);
// Parse the SQL (pre: and were parsed and removed)
//解析成SqlSource,一般是DynamicSqlSource
SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass);
String resultSets = context.getStringAttribute("resultSets");
//(仅对 insert 有用) 标记一个属性, MyBatis 会通过 getGeneratedKeys 或者通过 insert 语句的 selectKey 子元素设置它的值
String keyProperty = context.getStringAttribute("keyProperty");
//(仅对 insert 有用) 标记一个属性, MyBatis 会通过 getGeneratedKeys 或者通过 insert 语句的 selectKey 子元素设置它的值
String keyColumn = context.getStringAttribute("keyColumn");
KeyGenerator keyGenerator;
String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX;
keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true);
if (configuration.hasKeyGenerator(keyStatementId)) {
keyGenerator = configuration.getKeyGenerator(keyStatementId);
} else {
keyGenerator = context.getBooleanAttribute("useGeneratedKeys",
configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType))
? new Jdbc3KeyGenerator() : new NoKeyGenerator();
}
//又去调助手类
builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType,
fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass,
resultSetTypeEnum, flushCache, useCache, resultOrdered,
keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets);
}
最终这些解析的信息都会存在Configuration类中,我只举出了常用的属性
public class Configuration {
//映射的语句,存在Map里
protected final Map mappedStatements = new StrictMap("Mapped Statements collection");
//缓存,存在Map里
protected final Map caches = new StrictMap("Caches collection");
//结果映射,存在Map里
protected final Map resultMaps = new StrictMap("Result Maps collection");
protected final Map parameterMaps = new StrictMap("Parameter Maps collection");
protected final Map keyGenerators = new StrictMap("Key Generators collection");
}
最后创建DefaultSqlSessionFactory返回给SqlSessionFactory
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
现在去创建sqlSession
public SqlSession openSession() {
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}
在每个sql执行都会默认创建事务,所以执行后都要手动提交,其中还会创建sql语句的执行器Executor。最终创建DefaultSqlSession返回给SqlSession,那么SqlSession也创建完成了。创建的过程会把sql信息封装者configuration和执行器Executor都传给SqlSession。
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);
//生成一个执行器(事务包含在执行器里)
final Executor executor = configuration.newExecutor(tx, execType);
//然后产生一个DefaultSqlSession
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();
}
}
接下来就是去执行sql语句了,来分析查询多条语句
public List selectList(String statement, Object parameter, RowBounds rowBounds) {
try {
//根据statement id找到对应的MappedStatement
MappedStatement ms = configuration.getMappedStatement(statement);
//转而用执行器来查询结果,注意这里传入的ResultHandler是null
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();
}
}
可以看到configuration.getMappedStatement(statement)去configuration里拿相应的语句,后通过执行器执行
public List query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
//得到绑定sql
BoundSql boundSql = ms.getBoundSql(parameter);
//创建缓存Key
CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql);
//查询
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
最终调用的就是我们常用的使用jdbc去执行sql语句查询数据库,mbatis默认是会使用缓存的,所以中间先去查询缓存和最后把结果放到缓存中我都省略了
public List query(Statement statement, ResultHandler resultHandler) throws SQLException {
PreparedStatement ps = (PreparedStatement) statement;
ps.execute();
return resultSetHandler. handleResultSets(ps);
}
提交数据库,执行器最终也是通过jdbc来完成的
public void commit(boolean force) {
try {
//转而用执行器来commit
executor.commit(isCommitOrRollbackRequired(force));
//每次commit之后,dirty标志设为false
dirty = false;
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error committing transaction. Cause: " + e, e);
} finally {
ErrorContext.instance().reset();
}
}
还有一种就是通过接口来执行语句的,在spring整合mybatis去执行语句用的就是通过getMapper来获取代理对象
public T getMapper(Class type) {
//最后会去调用MapperRegistry.getMapper
return configuration.getMapper(type, this);
}
通过MapperProxyFactory来创建对象
public T getMapper(Class type, SqlSession sqlSession) {
final MapperProxyFactory mapperProxyFactory = (MapperProxyFactory) knownMappers.get(type);
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the MapperRegistry.");
}
try {
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e, e);
}
}
protected T newInstance(MapperProxy mapperProxy) {
//用JDK自带的动态代理生成映射器
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
代理对象执行的逻辑都是在MapperProxy里,实现了InvocationHandler接口,那么具体逻辑就是invoke方法里
public Object invoke(Object proxy, Method method, Object[] args) throws Throwable {
//代理以后,所有Mapper的方法调用时,都会调用这个invoke方法
//并不是任何一个方法都需要执行调用代理对象进行执行,如果这个方法是Object中通用的方法(toString、hashCode等)无需执行
if (Object.class.equals(method.getDeclaringClass())) {
try {
return method.invoke(this, args);
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
//这里优化了,去缓存中找MapperMethod
//查找对应方法(xml中的id)所执行的sql语句
final MapperMethod mapperMethod = cachedMapperMethod(method);
//执行
return mapperMethod.execute(sqlSession, args);
}
我们继续看代理对象是怎么去执行sql语句的,先判断语句是什么类型的,insert|update|delete|select,分别调用SqlSession的4大类方法
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
//可以看到执行时就是4种情况,insert|update|delete|select,分别调用SqlSession的4大类方法
if (SqlCommandType.INSERT == command.getType()) {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(), param));
} else if (SqlCommandType.UPDATE == command.getType()) {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(), param));
} else if (SqlCommandType.DELETE == command.getType()) {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(), param));
} else if (SqlCommandType.SELECT == command.getType()) {
if (method.returnsVoid() && method.hasResultHandler()) {
//如果有结果处理器
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
//如果结果有多条记录
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
//如果结果是map
result = executeForMap(sqlSession, args);
} else {
//否则就是一条记录
Object param = method.convertArgsToSqlCommandParam(args);
result = sqlSession.selectOne(command.getName(), param);
}
} else {
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;
}
我们看查询的语句执行,最终调用的还是sqlSession.select,回到了先前的方式了,后面的具体实现就不在讲述。
private void executeWithResultHandler(SqlSession sqlSession, Object[] args) {
MappedStatement ms = sqlSession.getConfiguration().getMappedStatement(command.getName());
if (void.class.equals(ms.getResultMaps().get(0).getType())) {
throw new BindingException("method " + command.getName()
+ " needs either a @ResultMap annotation, a @ResultType annotation,"
+ " or a resultType attribute in XML so a ResultHandler can be used as a parameter.");
}
Object param = method.convertArgsToSqlCommandParam(args);
if (method.hasRowBounds()) {
RowBounds rowBounds = method.extractRowBounds(args);
sqlSession.select(command.getName(), param, rowBounds, method.extractResultHandler(args));
} else {
sqlSession.select(command.getName(), param, method.extractResultHandler(args));
}
}
最后就是关闭会话
public void close() {
try {
//转而用执行器来close
executor.close(isCommitOrRollbackRequired(false));
//每次close之后,dirty标志设为false
dirty = false;
} finally {
ErrorContext.instance().reset();
}
}
总结:
首先SqlSessionFactoryBuilder会去parse解析配置文件,解析后的信息都交给Configuration保存,通过Configuration构建build出SqlSessionFactory,SqlSessionFactory会去开启openSession会话SqlSession,通过SqlSession的方法去查询语句query,内部是通过Executor来执行,而执行器会去创建StatementHandler帮忙执行,如果有结果集还会创建ResultSetHandler帮忙协助。