Mybatis学习
Mybatis是什么?
一个ORM框架, Object Relational Mapping,用于实现面向对象语言里不同类型系统的数据之间的转换
官方文档:https://mybatis.org/mybatis-3/
在官方文档中 getting started介绍的示例,快速入门
1.获取一个SqlSessionFactory,两种方式,一种通过XML方式,通常也都是使用这种方式,还有一种是通过非XML的方式
2.通过 SqlSessionFactory 获取一个SqlSession
3.通过执行映射的sql获取到结果集
数据库源 --》sql语句--》操作执行
数据库源:Driver、url、UserName、Password
sql语句:select、Inser、Update、Delete
操作执行:Connection、PrepareStatement、ResultSet
第一步,Mybatis如何解析数据库源:
首先在mybatis-config.xml 文件中定义数据源的配置,在这个文件中指定数据库的driver、URL、userName、password等
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE configuration PUBLIC "-//mybatis.org//DTD Config 3.0//EN" "http://mybatis.org/dtd/mybatis-3-config.dtd"> <configuration> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="${driver}"/> <property name="url" value="${url}"/> <property name="username" value="${username}"/> <property name="password" value="${password}"/> </dataSource> </environment> </environments> <mappers> <mapper resource="org/mybatis/example/BlogMapper.xml"/> </mappers> </configuration>
通过加载读取mybatis-config.xml文件,就可以拿到获取到数据库的对象
String resource = "org/mybatis/example/mybatis-config.xml"; InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
org.mybatis.spring.SqlSessionFactoryBean#buildSqlSessionFactory
源码中的这个方法中 根据Resource 的 configLocation变量,通过XMLConfigBuilder 去解析本地的xml文件得到InputStream流,最终得到 Configuration configuration,
protected SqlSessionFactory buildSqlSessionFactory() throws IOException { Configuration configuration; XMLConfigBuilder xmlConfigBuilder = null; if (this.configuration != null) { configuration = this.configuration; if (configuration.getVariables() == null) { configuration.setVariables(this.configurationProperties); } else if (this.configurationProperties != null) { configuration.getVariables().putAll(this.configurationProperties); } } else if (this.configLocation != null) { xmlConfigBuilder = new XMLConfigBuilder(this.configLocation.getInputStream(), null, this.configurationProperties); configuration = xmlConfigBuilder.getConfiguration(); } else { if (LOGGER.isDebugEnabled()) { LOGGER.debug("Property 'configuration' or 'configLocation' not specified, using default MyBatis Configuration"); } configuration = new Configuration(); if (this.configurationProperties != null) { configuration.setVariables(this.configurationProperties); } }
这个Configuration 中有Environment environment 属性,
package org.apache.ibatis.session;public class Configuration { protected Environment environment; protected boolean safeRowBoundsEnabled; protected boolean safeResultHandlerEnabled; protected boolean mapUnderscoreToCamelCase; protected boolean aggressiveLazyLoading;
这个environment中可以看到有三个属性:
public final class Environment { private final String id; private final TransactionFactory transactionFactory; private final DataSource dataSource;
能看到 有dataSource属性,那么就相当于Java拿到了数据源的配置;
第二步,Mybatis如何解析到sql语句:
在我们的项目中,都会定义mapper的xml文件,比如上面的mybatis-config.xml文件中,加载了一个叫 BlogMapper.xml 的文件
<mappers> <mapper resource="org/mybatis/example/BlogMapper.xml"/> </mappers>
在这个文件里面写具体的sql:
<?xml version="1.0" encoding="UTF-8" ?> <!DOCTYPE mapper PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" "http://mybatis.org/dtd/mybatis-3-mapper.dtd"> <mapper namespace="org.mybatis.example.BlogMapper"> <select id="selectBlog" resultType="Blog"> select * from Blog where id = #{id} </select> </mapper>
那么具体的sql 的XML文件有几种加载方式呢,上面的示例中是通过resource标签,实际是可以通过4种方式加载文件
resource标签的方式:
<!-- Using classpath relative resources --> <mappers> <mapper resource="org/mybatis/builder/AuthorMapper.xml"/> <mapper resource="org/mybatis/builder/BlogMapper.xml"/> <mapper resource="org/mybatis/builder/PostMapper.xml"/> </mappers>
url标签的方式:
<!-- Using url fully qualified paths --> <mappers> <mapper url="file:///var/mappers/AuthorMapper.xml"/> <mapper url="file:///var/mappers/BlogMapper.xml"/> <mapper url="file:///var/mappers/PostMapper.xml"/> </mappers>
class标签的方式:
<!-- Using mapper interface classes --> <mappers> <mapper class="org.mybatis.builder.AuthorMapper"/> <mapper class="org.mybatis.builder.BlogMapper"/> <mapper class="org.mybatis.builder.PostMapper"/> </mappers>
package标签的方式:
<!-- Register all interfaces in a package as mappers --> <mappers> <package name="org.mybatis.builder"/> </mappers>
那么这4种加载方式的优先级是什么样子的呢,我们可以通过源码看出来,在第一步 buildSqlSessionFactory 步骤中,解析xml文件得到DataSource的过程中,是解析了之后设置到 environments 中,其中还有其他的标签的解析,我们可以看到mapper也是在这里解析的:
org.apache.ibatis.builder.xml.XMLConfigBuilder#parseConfiguration
private void parseConfiguration(XNode root) { try { Properties settings = this.settingsAsPropertiess(root.evalNode("settings")); this.propertiesElement(root.evalNode("properties")); this.loadCustomVfs(settings); this.typeAliasesElement(root.evalNode("typeAliases")); this.pluginElement(root.evalNode("plugins")); this.objectFactoryElement(root.evalNode("objectFactory")); this.objectWrapperFactoryElement(root.evalNode("objectWrapperFactory")); this.reflectionFactoryElement(root.evalNode("reflectionFactory")); this.settingsElement(settings); this.environmentsElement(root.evalNode("environments")); this.databaseIdProviderElement(root.evalNode("databaseIdProvider")); this.typeHandlerElement(root.evalNode("typeHandlers")); this.mapperElement(root.evalNode("mappers")); } catch (Exception var3) { throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + var3, var3); } }
那么再具体去看一下这个解析过程就能看出来优先级:
private void mapperElement(XNode parent) throws Exception { if (parent != null) { for (XNode child : parent.getChildren()) { if ("package".equals(child.getName())) { 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) { ErrorContext.instance().resource(resource); 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); 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."); } } } } }
这样就加载了有sql 的xml文件,那么往下谁解析了这个xml文件,就可以拿到这sql语句了
这里的具体的解析的类看下 XMLMapperBuilder 这个类,调用的是parse方法,这里会有对xml文件中的标签的解析
private void configurationElement(XNode context) { try { String namespace = context.getStringAttribute("namespace"); if (namespace == null || namespace.equals("")) { throw new BuilderException("Mapper's namespace cannot be empty"); } builderAssistant.setCurrentNamespace(namespace); cacheRefElement(context.evalNode("cache-ref")); cacheElement(context.evalNode("cache")); parameterMapElement(context.evalNodes("/mapper/parameterMap")); resultMapElements(context.evalNodes("/mapper/resultMap")); sqlElement(context.evalNodes("/mapper/sql")); buildStatementFromContext(context.evalNodes("select|insert|update|delete")); } catch (Exception e) { throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e); } }
解析得到的解析后的这些数据,都通过 org.apache.ibatis.builder.xml.XMLStatementBuilder#parseStatementNode 方法,最终都给到了 MapperBuilderAssistant ,通过这个这个类的 addMappedStatement方法将解析得到的sql语句等都设置到了 mappedStatements 属性中,也就是说后续的交互都是通过 Configuration 对象,前期的这些解析的工作,最终把结果都赋值到了Configuration对象中,使用的时候是通过Configuration中获取就可以
org.apache.ibatis.builder.MapperBuilderAssistant#addMappedStatement()---设置
org.apache.ibatis.session.Configuration#mappedStatements --configuration中的属性
第三步:Mybatis怎么去操作执行的:
拿到了数据库源,也拿到了sql语句,那么怎么去执行呢,拿到了 sqlSessionFactory,下一步拿到session,
SqlSession session = sqlSessionFactory.openSession()
在openSession的方法中,实际调用的方法 openSessionFromDataSource 中,我们看到传入一个 ExecutorType,也是通过configuration中获取的,传入了一个默认的执行器
那么Mybatis中有几种执行器呢,默认的又是哪种呢
通过看configuration的源码可以看到这个默认的执行器是 SIMPLE
protected ExecutorType defaultExecutorType = ExecutorType.SIMPLE;
总共有3种也可以通过枚举值类看出来
public enum ExecutorType { SIMPLE, REUSE, BATCH }
分别对应的三种 BatchExecutor、ReuseExecutor、SimpleExecutor
public Executor newExecutor(Transaction transaction, ExecutorType executorType) { executorType = executorType == null ? defaultExecutorType : executorType; executorType = executorType == null ? ExecutorType.SIMPLE : executorType; Executor executor; if (ExecutorType.BATCH == executorType) { executor = new BatchExecutor(this, transaction); } else if (ExecutorType.REUSE == executorType) { executor = new ReuseExecutor(this, transaction); } else { executor = new SimpleExecutor(this, transaction); } if (cacheEnabled) { executor = new CachingExecutor(executor); } executor = (Executor) interceptorChain.pluginAll(executor); return executor; }
从上面的代码顺便也可以看出来mybatis在这里使用了一级缓存,通过cacheEnableed变量控制是否使用一级缓存,这里的cacheEnableed 默认值是true,所以默认是开启的
protected boolean cacheEnabled = true;
拿到了 Executor就可以去执行了,通过执行selectOne方法去执行:
Object blog = session.selectOne(
"org.mybatis.example.BlogMapper.selectBlog", 101);
selectOne方法底层是调用的selectList
在这个方法中第一步就是从configuration中获取在第二步中设置进去的mappedStatements属性,然后去执行,执行的时候会涉及到缓存的概念,不详细看了
private <E> List<E> queryFromDatabase(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { List<E> list; localCache.putObject(key, EXECUTION_PLACEHOLDER); try { list = doQuery(ms, parameter, rowBounds, resultHandler, boundSql); } finally { localCache.removeObject(key); } localCache.putObject(key, list); if (ms.getStatementType() == StatementType.CALLABLE) { localOutputParameterCache.putObject(key, parameter); } return list; }
执行doQuery方法去查询,有一个prepareStatement方法
@Override public <E> List<E> doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException { Statement stmt = null; try { Configuration configuration = ms.getConfiguration(); StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql); stmt = prepareStatement(handler, ms.getStatementLog()); return handler.<E>query(stmt, resultHandler); } finally { closeStatement(stmt); } }
prepareStatement方法得到了statement,交给StatementHandler处理,具体的handler去执行sql,并交由 ResultSetHandler对结果集处理
@Override public <E> List<E> query(Statement statement, ResultHandler resultHandler) throws SQLException { String sql = boundSql.getSql(); statement.execute(sql); return resultSetHandler.<E>handleResultSets(statement); }
对结果的处理也是通过第二步中对sql文件解析得到的mappedStatements,从这里获取具体的结果对象指定的result的格式
@Override public List<Object> handleResultSets(Statement stmt) throws SQLException { ErrorContext.instance().activity("handling results").object(mappedStatement.getId()); final List<Object> multipleResults = new ArrayList<Object>(); int resultSetCount = 0; ResultSetWrapper rsw = getFirstResultSet(stmt); List<ResultMap> resultMaps = mappedStatement.getResultMaps(); int resultMapCount = resultMaps.size(); validateResultMapsCount(rsw, resultMapCount); while (rsw != null && resultMapCount > resultSetCount) { ResultMap resultMap = resultMaps.get(resultSetCount); handleResultSet(rsw, resultMap, multipleResults, null); rsw = getNextResultSet(stmt); cleanUpAfterHandlingResultSet(); resultSetCount++; } String[] resultSets = mappedStatement.getResulSets(); if (resultSets != null) { while (rsw != null && resultSetCount < resultSets.length) { ResultMapping parentMapping = nextResultMaps.get(resultSets[resultSetCount]); if (parentMapping != null) { String nestedResultMapId = parentMapping.getNestedResultMapId(); ResultMap resultMap = configuration.getResultMap(nestedResultMapId); handleResultSet(rsw, resultMap, null, parentMapping); } rsw = getNextResultSet(stmt); cleanUpAfterHandlingResultSet(); resultSetCount++; } }
ResultSetWrapper中的属性是和sql中解析的标签是对应的:
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>>();
按照这些对应的关系,将数据库的字段对应的类型和java属性的类型对应上,完成最终的结果集处理
到这里mybatis的处理就基本结束了。