官方文档
示例1:com.dzq.MybatisSessionTest根据前期准备jdbc阶段进行解读
- 配置
- 拼接sql语句
- 加载数据源,获取Connection
- 获取Statement
- 执行获取结果
- 结果转换
- 关闭
- 异常处理
最基本的功能,根据此示例读源码
public static void main(String[] args) throws IOException { // 配置文件 String resource = "mybatis-config.xml"; // 解析配置文件 InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); // 获取session SqlSession sqlSession = sqlSessionFactory.openSession(); // 根据state找到sql语句并传入参数执行 String flowKey = sqlSession.selectOne("com.dzq.mapper.TransactionMapper.getFlowKey", 10); System.out.println(flowKey); }示例 2: com.dzq.MybatisMapperTest
如何生成动态代理Mapper,动态代理都做了什么:如何根据动态代理找到对应的sql
public static void main(String[] args) throws IOException { // 配置文件 String resource = "mybatis-config.xml"; // 解析配置文件 InputStream inputStream = Resources.getResourceAsStream(resource); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream); // 获取session SqlSession sqlSession = sqlSessionFactory.openSession(); // 得到mapper代理 TransactionMapper transactionMapper = sqlSession.getMapper(TransactionMapper.class); // 执行代理方法,并转换结果 String flowKey = transactionMapper.getFlowKey(10); System.out.println(flowKey); }
配置文件mybatis-config.xml和对应的TransactionMapper.xml
1. 配置
配置选型在不清楚mybatis整体设计的时候,对于配置的每个属性和参数太多解读其实没有太多意义,最少咱们通过上面jdbc的预习已经知道一些配置参数,但是目前阶段可以了解
- mybatis使用了什么样的配置模式?是properties还是xml,还是支持数据库这种支持。
- 在解析配置是如何加载的?设计到什么样的设计模式?
mybatis选择的配置模式是使用的xml文件,xml可以很好的使用标签对应类,也可以很好的描述类之间的关系和依赖。
加载String resource = "mybatis-config.xml"; // 在classpath下查找文件,得到输入源:很多文件的解析参数都是InputStream,可以来自于网络:(应用层可以是分布式文件系统,也可以是http/https等等),也可以来源于本地 InputStream inputStream = Resources.getResourceAsStream(resource); // 拿到数据源,就可以拿到数据,然后进行解析,解析完后生成SqlSessionFactory工厂,SqlSession是直接执行sql的类 // 通过配置生成对应的工厂,因为已经知道“零件”,可以通过零件创建对应的“产品” SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);加载过程
-
创建专门的解析类XMLConfigBuilder继承baseBuilder
// inputStream 有input就能得到所有的配置信息,但是还是专门创建一个类负责解析 // parser存储所有解析后结果configuration和是否已经解析过,专门负责解析xml的工作还是会由XPathParser负责 XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties); // 构造方法 public XMLConfigBuilder(InputStream inputStream, String environment, Properties props) { // 第一个参数XPathParser,使用的xpath解析器 this(new XPathParser(inputStream, true, props, new XMLMapperEntityResolver()), environment, props); }
-
解析
public Configuration parse() { if (parsed) { throw new BuilderException("Each XMLConfigBuilder can only be used once."); } parsed = true; // 解析根标签configuration parseConfiguration(parser.evalNode("/configuration")); // 返回configuration,用于构建SqlSessionFactory:SqlSessionFactory只有一个属性就是它 return configuration; }
-
/configuration标签解析
// 对各个节点进行解析 private void parseConfiguration(XNode root) { try { propertiesElement(root.evalNode("properties")); Properties settings = settingsAsProperties(root.evalNode("settings")); loadCustomVfs(settings); typeAliasesElement(root.evalNode("typeAliases")); pluginElement(root.evalNode("plugins")); objectFactoryElement(root.evalNode("objectFactory")); objectWrapperFactoryElement(root.evalNode("objectWrapperFactory")); reflectorFactoryElement(root.evalNode("reflectorFactory")); settingsElement(settings); // 重点讲解解析环境 environmentsElement(root.evalNode("environments")); databaseIdProviderElement(root.evalNode("databaseIdProvider")); typeHandlerElement(root.evalNode("typeHandlers")); mapperElement(root.evalNode("mappers")); } catch (Exception e) { throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e); } }
-
介绍XNode类设计
XNode是节点的封装,里面引用jdk中的node节点,对node节点所有功能进行封装,是node的适配器,已经把node节点的一些值放入到Xnode,比如attributes。
-
为什么不把所有的值都放入到Xnode?这样就不用在有它的引用了
node的属性比较多,最主要涉及到子节点的获取,子节点可能很多,这个地方用了懒加载。
private final Node node; private final String name; private final String body; private final Properties attributes; private final Properties variables; private final XPathParser xpathParser; public XNode(XPathParser xpathParser, Node node, Properties variables) { this.xpathParser = xpathParser; this.node = node; this.name = node.getNodeName(); this.variables = variables; this.attributes = parseAttributes(node); this.body = parseBody(node); }
-
-
environments标签解析
private void environmentsElement(XNode context) throws Exception { if (context != null) { if (environment == null) { environment = context.getStringAttribute("default"); } // 查询所有的子标签environment for (XNode child : context.getChildren()) { String id = child.getStringAttribute("id"); if (isSpecifiedEnvironment(id)) { // 事务解析 TransactionFactory txFactory = transactionManagerElement(child.evalNode("transactionManager")); // 数据源解析,重点讲解 DataSourceFactory dsFactory = dataSourceElement(child.evalNode("dataSource")); DataSource dataSource = dsFactory.getDataSource(); // 赋值 Environment.Builder environmentBuilder = new Environment.Builder(id) .transactionFactory(txFactory) .dataSource(dataSource); // 赋值 configuration.setEnvironment(environmentBuilder.build()); } } } }
解析过程核心类关系图
-
DataSourceFactory解析
private DataSourceFactory dataSourceElement(XNode context) throws Exception { if (context != null) { String type = context.getStringAttribute("type"); // 子标签:
// 子标签都是属性,所以直接转换为Properties Properties props = context.getChildrenAsProperties(); // 根据类型返回从Map对象拿到class文件然后创建 DataSourceFactory factory = (DataSourceFactory) resolveClass(type).newInstance(); // 像SqlSessionFactory工厂一样,也是需要“零件”,才能生产“产品” // 这里的材料为了适配所有的标签工厂模式,其他比如TransactionFactory,所以用了Properties // 通过创建metaObject反射出DataSourceFactory factory.setProperties(props); return factory; } throw new BuilderException("Environment declaration requires a DataSourceFactory."); } -
创建DefaultSqlSessionFactory
new DefaultSqlSessionFactory(config);// DefaultSqlSessionFactory属性只有一个config
mybatis是要写全部sql语句的,只是变量是占位符的,所有的sql都在配置文件中,所以在解析配置文件时就已经解析完成。再看解析文件。
// 解析mappers标签,原配置文件,引用了一个mapper的xml文件,再次跳转到解析xml文件 mapperElement(root.evalNode("mappers"));
解析mapper.xml文件
// org.apache.ibatis.builder.xml.XMLMapperBuilder public void parse() { if (!configuration.isResourceLoaded(resource)) { // 解析mapper标签,比较关键 // 直接退转到解析标签org.apache.ibatis.builder.xml.XMLStatementBuilder#parseStatementNode,解析的属性非常多但比较简单,只知道位置就可以 // 最终封装在config.mappedStatements中,key:id,value:MappedStatement具体参数可以直接看此类 configurationElement(parser.evalNode("/mapper")); configuration.addLoadedResource(resource); // 解析Mapper.java接口 // 最终封装在config.knownMappers中,key:type(com.dzq.mapper.TransactionMapper,Class类型),value:MapperProxyFactory bindMapperForNamespace(); } parsePendingResultMaps(); parsePendingCacheRefs(); parsePendingStatements(); }3. 加载数据源,获取Connection
// 执行类型,来源于config,事务级别,是否自动提交 private SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) { Transaction tx = null; try { // 得到环境 final Environment environment = configuration.getEnvironment(); // 事务工厂 final TransactionFactory transactionFactory = getTransactionFactoryFromEnvironment(environment); // 放入dataSource,dataSource是可以得到Connection tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit); // 执行器,里面有tx,而且是根据configuration调用,那么能够拿到其他配置 final Executor executor = configuration.newExecutor(tx, execType); // 创建session ,配置,执行器,是否自动执行 // config哪里都有 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(); } }4. 获取Statement
执行的开始,在获取到SqlSession之后,就可以进行执行,SqlSession是执行的入口,先看看此接口
// 查询T selectOne(String statement); List selectList(String statement); // 修改 int update(String statement); // 插入 int insert(String statement); // 删除 int delete(String statement);
先查看selectList源码,试想一下过程:根据statement得到sql语句返回值和参数,根据executor执行selectList语句,得到结果,根据返回值封装返回。
关键的2个类MappedStatement,Executor
// SqlSession类主要属性和方法 // 主要属性,configuration,executor,autoCommit是构造时创建的,dirty,cursorList是执行是的中间状态 // 配置的引用,能够拿到所有配置 private final Configuration configuration; // 执行器,真实执行的类 private final Executor executor; // 是否自动提交 private final boolean autoCommit; // 有脏数据 private boolean dirty; // 存储所有游标信息,当调用publicCursor selectCursor(String statement),会把所有的返回值存储 private List > cursorList; @Override public List selectList(String statement, Object parameter, RowBounds rowBounds) { try { // 从config中得到MappedStatement,通过config解析得来,MappedStatement是解析配置的时候赋值的 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(); } }
// Executor关键属性和方法 // 封装执行器,自带的适配器模式 protected Executor delegate; // 事务,里面有dataSource protected Transaction transaction; // 配置 protected Configuration configuration; // 查询,ms:mapper属性,parameter:参数,rowBounds:分页,ResultHandler:结果处理 publicList query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { // 根据参数拼接组装sql,简简单单的一个sql也是需要封装的,看看如何封装的 // BoundSql关键属性,sql:sql语句,带通配符, parameterMappings:参数,id,类型,parameterObject:参数值 BoundSql boundSql = ms.getBoundSql(parameter); CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql); return query(ms, parameter, rowBounds, resultHandler, key, boundSql); }
@Override publicList query(MappedStatement ms, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { Cache cache = ms.getCache(); // cache为空,直接看delegate的query方法,还是一种适配模式delegate也是一种Executor if (cache != null) { flushCacheIfRequired(ms); if (ms.isUseCache() && resultHandler == null) { ensureNoOutParams(ms, boundSql); @SuppressWarnings("unchecked") List list = (List ) tcm.getObject(cache, key); if (list == null) { list = delegate. query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); tcm.putObject(cache, key, list); // issue #578 and #116 } return list; } } // delegate真正的executor执行 return delegate. query(ms, parameterObject, rowBounds, resultHandler, key, boundSql); }
// SimpleExecutor @Override publicList doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException { Statement stmt = null; try { // 得到config Configuration configuration = ms.getConfiguration(); // 得到statementHandler,有config创建statementHandler,同样也是适配器模式 StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql); //生成jdbc的statement stmt = prepareStatement(handler, ms.getStatementLog()); // statement 执行query // statementHandler查询,处理stmt return handler. query(stmt, resultHandler); } finally { // 关闭stmt closeStatement(stmt); } }
org.apache.ibatis.executor.statement.baseStatementHandler#prepare模板方法
public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException { ErrorContext.instance().sql(boundSql.getSql()); Statement statement = null; try { // 生成statement,核心二种:PreparedStatementHandler,SimpleStatementHandler,instantiateStatement抽象方法 statement = instantiateStatement(connection); // 设置超时时间 setStatementTimeout(statement, transactionTimeout); // 设置每次获取个数 setFetchSize(statement); return statement; } catch (SQLException e) { closeStatement(statement); throw e; } catch (Exception e) { closeStatement(statement); throw new ExecutorException("Error preparing statement. Cause: " + e, e); } }
- 执行获取结果handler.query(stmt, resultHandler);
// org.apache.ibatis.executor.statement.PreparedStatementHandler查询结果 public
List query(Statement statement, ResultHandler resultHandler) throws SQLException { PreparedStatement ps = (PreparedStatement) statement; // 执行,下一步对结果进行处理 ps.execute(); // resultSetHandler 是PreparedStatementHandler其中一个属性 return resultSetHandler. handleResultSets(ps); }
ResultSetHandler类创建,是通过创建StatementHandler时创建的resultSetHandler的属性
configuration.newResultSetHandler(executor, mappedStatement, rowBounds, parameterHandler, resultHandler, boundSql);
org.apache.ibatis.executor.resultset.DefaultResultSetHandler#handleResultSets
@Override public List
处理handleResultSet结果
org.apache.ibatis.executor.resultset.DefaultResultSetHandler#handleResultSet
private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, ListmultipleResults, ResultMapping parentMapping) throws SQLException { try { if (parentMapping != null) { handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping); } else { if (resultHandler == null) {// 默认为空 // 得到默认的接口处理器 DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory); // 处理结果 handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null); // 取出结果放入到multipleResults结果集中 multipleResults.add(defaultResultHandler.getResultList()); } else { handleRowValues(rsw, resultMap, resultHandler, rowBounds, null); } } } finally { // issue #228 (close resultsets) closeResultSet(rsw.getResultSet()); } }
2021-10-25 增加对非原始类进行赋值内容
// 得到实例 Object rowValue = createResultObject(rsw, resultMap, lazyLoader, columnPrefix); // 继续创建createResultObject // Object resultObject = createResultObject(rsw, resultMap, constructorArgTypes, constructorArgs, columnPrefix); // objectFactory.create(resultType);通过objectFactory根据反射创建实例 // 如果存在非默认构造器 // createByConstructorSignature(rsw, resultType, constructorArgTypes, constructorArgs); // final Constructor> defaultConstructor = findDefaultConstructor(constructors); // 继续调用createUsingConstructor(rsw, resultType, constructorArgTypes, constructorArgs, defaultConstructor); // 得到实体之后,判断不是基本数据类型,进入判断语句再次赋值 if (rowValue != null && !hasTypeHandlerForResultObject(rsw, resultMap.getType())) { // 创建元数据对象, final metaObject metaObject = configuration.newmetaObject(rowValue); // 创建metaObject boolean foundValues = this.useConstructorMappings; if (shouldApplyAutomaticMappings(resultMap, false)) { foundValues = applyAutomaticMappings(rsw, resultMap, metaObject, columnPrefix) || foundValues; } // 对实例赋值 foundValues = applyPropertyMappings(rsw, resultMap, metaObject, lazyLoader, columnPrefix) || foundValues; foundValues = lazyLoader.size() > 0 || foundValues; rowValue = foundValues || configuration.isReturnInstanceForEmptyRow() ? rowValue : null; }7. 关闭
Statement关闭:可以看到生成是从Executor生成的,statement的处理都在其他类中处理,所以也在此类中关闭
// SimpleExecutor @Override publicList 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. query(stmt, resultHandler); } finally { // 关闭 closeStatement(stmt); } }
ResultSet关闭:ResultSet是从org.apache.ibatis.executor.resultset.DefaultResultSetHandler#handleResultSets得到封装到ResultSetWrapper,取值是在handleResultSet,然后就取下一个ResultSet了,则在handleResultSet中close
// 已经封装到ResultSetWrapper中 private void handleResultSet(ResultSetWrapper rsw, ResultMap resultMap, ListmultipleResults, ResultMapping parentMapping) throws SQLException { try { if (parentMapping != null) { handleRowValues(rsw, resultMap, null, RowBounds.DEFAULT, parentMapping); } else { if (resultHandler == null) { DefaultResultHandler defaultResultHandler = new DefaultResultHandler(objectFactory); handleRowValues(rsw, resultMap, defaultResultHandler, rowBounds, null); multipleResults.add(defaultResultHandler.getResultList()); } else { handleRowValues(rsw, resultMap, resultHandler, rowBounds, null); } } } finally { // 取值完后关闭 closeResultSet(rsw.getResultSet()); } }
Connection关闭:是在创建SqlSession时,再创建executor时提前创建Transaction,所以间接由SqlSession保管,由SqlSession关闭。
Statement和ResultSet是每次执行的时候产生的,每次都需要关闭,Connection是链接,所以不在使用的时候可以关闭。
8. 异常处理加载文件:需要处理异常IOException:IO异常是mybatis的提前准备的异常,还没真正到链接数据库的 *** 作。文件异常处理不了。需要在编写时考虑。
开始执行:不需要处理异常,直接进行执行,执行时查错。需重点分析。JDBC *** 作关键异常SQLException,是需要捕获的异常,如何处理的?
开始执行时有2步进程,1:前期准备工作,mybatis自定义异常,2:涉及到JDBC,SQLException
前期准备的异常都是RuntimeException,不需要捕获,出错时处理,比如
@Override publicT selectOne(String statement, Object parameter) { // Popular vote was to return null on 0 results and throw exception on too many. List 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; } }
涉及到JDBC的异常全部没有处理,而是在最外层捕获了异常
// 开始涉及到jdbc的异常,全部抛出 @Override publicList query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { BoundSql boundSql = ms.getBoundSql(parameter); CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql); return query(ms, parameter, rowBounds, resultHandler, key, boundSql); }
@Override publicList selectList(String statement, Object parameter, RowBounds rowBounds) { try { 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(); } }
对于空指针的校验,尽量封装到方法中,比如上面代码
MappedStatement ms = configuration.getMappedStatement(statement); // 是从配置中拿到MappedStatement没有校验为空,如果为空则NullPointerException,那校验在什么地方呢?查看configuration.getMappedStatement(statement);
想到另外一个问题数据结构的一定要合理
调用代码
configuration.getMappedStatement(statement); 进入 this.getMappedStatement(id, true); 进入 //从configuration属性mappedStatements拿到值,还没校验异常 //protected final MapmappedStatements = new StrictMap ("Mapped Statements collection"); return mappedStatements.get(id); //StrictMap类获取,重写了get方法,在最底层抛出异常,而不是在业务层 public V get(Object key) { V value = super.get(key); if (value == null) { // 如果为空抛出异常,而且异常信息提示也很精准。name是StrictMap属性,为map命名。 throw new IllegalArgumentException(name + " does not contain value for " + key); } if (value instanceof Ambiguity) { throw new IllegalArgumentException(((Ambiguity) value).getSubject() + " is ambiguous in " + name + " (try using the full name including the namespace, or rename one of the entries)"); } return value; }
自己写的例子
public class Student { private String name; // 学生有多地址 private List addressList; public List getAddressList() { if (addressList == null || addressList.size() == 0) { throw new RuntimeException(name + " 没有填写家庭地址"); } return addressList; } public static void main(String[] args) { Student student = new Student(); // 不影响业务代码美观 List addressList = student.getAddressList(); //TODO } }
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)