Mybatis执行流程分析
文章目录
前言
对于Mybatis大家应该都很熟悉了,记得刚开始学Java的时候持久层两大框架Mybatis和Hibernate,当时我就比较喜欢Mybatis,Hibernate使用的不多,因为Mybatis的SQL语句比较自由,条理性比较清晰,当然不是说Hibernate不好,而是个人比较喜欢那种文件SQL清晰的风格,本文带大家简单回顾一下Mybatis。
一、演示DEMO
下面快速构建一个mybatis项目,数据库使用的MySQL8.0。
<dependency> <groupId>org.mybatis</groupId> <artifactId>mybatis</artifactId> <version>3.3.0</version></dependency><dependency> <groupId>mysql</groupId> <artifactId>mysql-connector-java</artifactId> <version>8.0.16</version></dependency>
创建一个user表来做项目的例子,并且创建对应的实体类、Mapper接口和Mapper.xml,创建类就不写了。
CREATE TABLE `test001`.`user` ( `id` bigint(0) NOT NULL, `name` varchar(32) CHARACTER SET utf8 COLLATE utf8_general_ci NULL DEFAULT NULL, `age` int(0) NULL DEFAULT NULL, PRIMARY KEY (`id`) USING BTREE)
首先是Mybatis的配置文件
<configuration> <environments default="development"> <environment id="development"> <transactionManager type="JDBC"/> <dataSource type="POOLED"> <property name="driver" value="com.mysql.cj.jdbc.Driver"/> <property name="url" value="jdbc:mysql://82.157.xx.xx:3306/test001?characterEncoding=utf8&useSSL=false&serverTimezone=UTC&rewriteBatchedStatements=true"/> <property name="username" value="root"/> <property name="password" value=""/> </dataSource> </environment> </environments> <mappers> <mapper resource="mappers/UserMapper.xml"/> </mappers></configuration>
写个Demo类测试一下:
public static void main(String[] args) throws IOException { Reader reader = Resources.getResourceAsReader("mybatis_config.xml"); SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(reader); SqlSession sqlSession = sqlSessionFactory.openSession(); UserMapper mapper = sqlSession.getMapper(UserMapper.class); UserEntity user = mapper.getUser(2); System.out.println(user);}
OK简易版本的演示环境搭建完成
流程分析
看上面的示例一个简单的mybatis需要这么几步操作:
- 读取mybatis的配置文件。
- 获取SqlSession工厂。
- 获取SqlSession。
- 设置操作的Mapper文件。
- 开始需要的Mapper操作。
读取mybatis的配置文件
读取mybatis的配置文件是比较简单的,就是一个IO流操作。
public static Reader getResourceAsReader(String resource) throws IOException { InputStreamReader reader; if (charset == null) { reader = new InputStreamReader(getResourceAsStream(resource)); } else { reader = new InputStreamReader(getResourceAsStream(resource), charset); } return reader; }
获取SqlSession工厂
SqlSessionFactoryBuilder这是用来创建SqlSessionFactory的,这是使用的建造者模式。
直接看build方法:
public SqlSessionFactory build(Reader reader, String environment, Properties properties) { SqlSessionFactory var5; try { //获取解析XML文件 XMLConfigBuilder parser = new XMLConfigBuilder(reader, environment, properties); //获取SqlSession工厂 var5 = this.build(parser.parse()); } catch (Exception var14) { throw ExceptionFactory.wrapException("Error building SqlSession.", var14); } finally { ErrorContext.instance().reset(); try { reader.close(); } catch (IOException var13) { } } return var5; }
XML解析的parse()方法最终的操作结果是把XML中读取的信息实例化,转换为Java对象Configuration类来存放,因为在config.xml配置文件中有mapper.xml设置,所以mapper.xml的信息也已经加载到了Configuration中。
private void parseConfiguration(XNode root) { try { this.propertiesElement(root.evalNode("properties")); 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(root.evalNode("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); } }
最后通过配置类构造了一个默认的SqlSession工厂类DefaultSqlSessionFactory,并把配置信息保存在了工厂中。
public SqlSessionFactory build(Configuration config) { return new DefaultSqlSessionFactory(config);}
获取SqlSession
openSession最终走了下面的方法
//ExecutorType有三种SIMPLE,REUSE,BATCH,分别对应这三种不同的执行器,我们没有配置默认SIMPLE。后两个参数没有设置默认null和falseprivate SqlSession openSessionFromDataSource(ExecutorType execType, TransactionIsolationLevel level, boolean autoCommit) { Transaction tx = null; //事务 DefaultSqlSession var8; try { //获取配置环境 Environment environment = this.configuration.getEnvironment(); //事务工厂并获取事务 TransactionFactory transactionFactory = this.getTransactionFactoryFromEnvironment(environment); tx = transactionFactory.newTransaction(environment.getDataSource(), level, autoCommit); //获取执行器 Executor executor = this.configuration.newExecutor(tx, execType); //由执行器构建SqlSession var8 = new DefaultSqlSession(this.configuration, executor, autoCommit); } catch (Exception var12) { this.closeTransaction(tx); throw ExceptionFactory.wrapException("Error opening session. Cause: " + var12, var12); } finally { ErrorContext.instance().reset(); } return var8;}
设置操作的Mapper文件
正常情况下接口是不能实例化的除非有子类,这里的Mapper文件实例化了,这说明它产生了子类或代理类,这里采用的代理模式。看源码中最终走到了这个方法MapperProxyFactory,对于Proxy单词大家应该很熟悉,代理的意思,这是Mapper代理工厂创建一个代理类。
public <T> T getMapper(Class<T> type, SqlSession sqlSession) { MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory)this.knownMappers.get(type); if (mapperProxyFactory == null) { throw new BindingException("Type " + type + " is not known to the MapperRegistry."); } else { try { return mapperProxyFactory.newInstance(sqlSession); } catch (Exception var5) { throw new BindingException("Error getting mapper instance. Cause: " + var5, var5); } } }
看Mapper的代理,对于InvocationHandler应该很熟悉了吧,这是典型的JDK动态代理。
还记得JDK动态代理的时候怎么输出代理类吧,在Test Main()方法开始之前加一个JDK动态代理类输出设置。
System.getProperties().put(“sun.misc.ProxyGenerator.saveGeneratedFiles”, “true”);
看一下生成的代理类,因为UserMapper有了子类,所以我们可以实例化对象了。
public final class $Proxy0 extends Proxy implements UserMapper { private static Method m1; private static Method m3; private static Method m2; private static Method m0; public $Proxy0(InvocationHandler var1) throws { super(var1); } public final UserEntity getUser(int var1) throws { try { return (UserEntity)super.h.invoke(this, m3, new Object[]{var1}); } catch (RuntimeException | Error var3) { throw var3; } catch (Throwable var4) { throw new UndeclaredThrowableException(var4); } }...删除省略了部分代码}
Mapper操作
上面知道了生成Mapper使用的是代理设计模式,接下来就好说了,我们可以去invoke()方法去打断点拦截方法的执行。
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 var5) {throw ExceptionUtil.unwrapThrowable(var5); } } else { MapperMethod mapperMethod = this.cachedMapperMethod(method); return mapperMethod.execute(this.sqlSession, args); } }
方法的具体执行,看是不是很熟悉了,这与我们之前用的JDBC是不是很相似。
public Object execute(SqlSession sqlSession, Object[] args) { Object param; Object result; if (SqlCommandType.INSERT == this.command.getType()) { param = this.method.convertArgsToSqlCommandParam(args); result = this.rowCountResult(sqlSession.insert(this.command.getName(), param)); } else if (SqlCommandType.UPDATE == this.command.getType()) { param = this.method.convertArgsToSqlCommandParam(args); result = this.rowCountResult(sqlSession.update(this.command.getName(), param)); } else if (SqlCommandType.DELETE == this.command.getType()) { param = this.method.convertArgsToSqlCommandParam(args); result = this.rowCountResult(sqlSession.delete(this.command.getName(), param)); } else if (SqlCommandType.SELECT == this.command.getType()) { if (this.method.returnsVoid() && this.method.hasResultHandler()) {this.executeWithResultHandler(sqlSession, args);result = null; } else if (this.method.returnsMany()) {result = this.executeForMany(sqlSession, args); } else if (this.method.returnsMap()) {result = this.executeForMap(sqlSession, args); } else {param = this.method.convertArgsToSqlCommandParam(args);result = sqlSession.selectOne(this.command.getName(), param); } } else { if (SqlCommandType.FLUSH != this.command.getType()) {throw new BindingException("Unknown execution method for: " + this.command.getName()); } result = sqlSession.flushStatements(); } if (result == null && this.method.getReturnType().isPrimitive() && !this.method.returnsVoid()) { throw new BindingException("Mapper method '" + this.command.getName() + " attempted to return null from a method with a primitive return type (" + this.method.getReturnType() + ")."); } else { return result; } }
示例代码是SELECT类型的,SELECT ONE操作,看其最终还是查询的列表返回的第一个,如果有多个这个异常大家应该很熟悉了。
public <T> T selectOne(String statement, Object parameter) { 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; } }
后面就不继续往里面看了,了解JDBC等操作的应该都清楚,里面就开始获取SQL语句使用?占位符替换的方式执行SQL语句了。最后将返回的内容转载成想要的类。
总结
最终我们写的demo例子按照这么个逻辑来执行的,当然这是分析的比较简单而且侧重点不同最终出来的流程图。
内容来源:蚂蚁课堂