前言
对于Mybatis大家应该都很熟悉了,记得刚开始学Java的时候持久层两大框架Mybatis和Hibernate,当时我就比较喜欢Mybatis,Hibernate使用的不多,因为Mybatis的SQL语句比较自由,条理性比较清晰,当然不是说Hibernate不好,而是个人比较喜欢那种文件SQL清晰的风格,本文带大家简单回顾一下Mybatis。
一、演示DEMO
下面快速构建一个mybatis项目,数据库使用的MySQL8.0。
<!-- mybatis使用的是3.3 -->
<dependency>
<groupId>org.mybatis</groupId>
<artifactId>mybatis</artifactId>
<version>3.3.0</version>
</dependency>
<!-- mysql驱动包 -->
<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和false
private 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例子按照这么个逻辑来执行的,当然这是分析的比较简单而且侧重点不同最终出来的流程图。
内容来源:蚂蚁课堂