概要
我们都知道,mybatis框架是一个半自动的ORM框架,可以简化我们对于数据库连接的管理,以及可以对于所有的查询配置的统一维护。是对于传统的JDBC连接操作的二次封装。该篇内容暂时不讨论Mybatis当中的缓存,以及Spring整合Mybatis的事物等相关的信息内容,后续文章会去根据源码分析解读。
传统的JDBC获取数据
public static void main(String[] args) throws ClassNotFoundException, SQLException {
Connection collection = null;
Statement statement = null;
ResultSet resultSet = null;
Class.forName("com.mysql.jdbc.Driver");
try {
//获取数据库连接
collection = DriverManager.getConnection("jdbc:mysql://localhost:3306/student?useSSL=false&allowMultiQueries=true&useUnicode=true&characterEncoding=UTF-8&serverTimezone=GMT%2b8",
"root", "root");
statement = collection.createStatement();
resultSet = statement.executeQuery("select * from t_stu");
while (resultSet.next()) {
System.out.println(resultSet.getString("name"));
}
} finally {
//关闭数据库的资源
if (statement != null) {
statement.close();
}
if (resultSet != null) {
resultSet.close();
}
if (collection != null) {
collection.close();
}
}
}
这样其实我们都需要手动的获取连接数据,关闭流数据。于是我们发现了很多问题如,例如:
- 数据库的连接需要业务方去维护,而且mysql的连接每次使用都需要新建
- sql代码和我们的业务代码耦合到一块,当一个sql很复杂的时候很难维护
- sql当中的参数维护需要人为去管理,当一个逻辑很复杂时会出现很多的参数,造成难以维护的情况
- sql查询后返回的结果需要我们手动去关联,而且没有统一的管理的入口
为了解决上面的一系列问题,聪明的程序员对通用的代码逻辑进行了抽象与封装,于是ORM框架在万众举目当中出现了,mybatis就是其中的一个
理解Mybatis的框架的执行流程
1、配置Mybatis框架的配置xml文件,配置在maven项目的resource目录下
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE configuration
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-config.dtd">
<!--configuration核心配置文件-->
<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://xxx?useSSL=false&serverTimezone=CTT&useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true"/>
<property name="username" value="root"/>
<property name="password" value="root"/>
</dataSource>
</environment>
</environments>
<mappers>
<mapper resource="mybatis-mapper.xml"/>
</mappers>
</configuration>
2、配置对应的Mapper文件
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"https://mybatis.org/dtd/mybatis-3-mapper.dtd">
<!--namespace绑定了一个对应的Dao/Mapper接口-->
<mapper namespace="com.demo.MoveTaskQueryMapper">
<!-- select查询-->
<select id="getList" resultType="com.demo.MoveTask">
SELECT * FROM `out_move_task` where move_task_id = #{id}
</select>
</mapper>
配置xml对的
3、编写对应的dao文件及映射的java对象文件
package com.demo;
import java.util.List;
public interface MoveTaskQueryMapper {
List<MoveTask> getList(String id);
}
@ApiModel
public class MoveTask implements Serializable {
}
4、通过代码获取到指定类的mapper,并调用mapper当中的编写的方法
public static void main(String[] args) throws Exception{
String resource = "./mybatis-config.xml";
InputStream inputStream = Resources.getResourceAsStream(resource);
//加载配置文件,生成SqlSessionFactory。
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
//对于所有的数据库的连接操作都是基于SqlSession进行的操作,每次openSession都会生成一个新的SqlSession
SqlSession sqlSession = sqlSessionFactory.openSession();
Object o = sqlSession.selectOne("com.demo.MoveTaskQueryMapper.getList", "1000077949124349952");
//模拟sqlSession级别缓存数据。
Object o1 = sqlSession.selectOne("com.demo.MoveTaskQueryMapper.getList", "1000077949124349952");
}
-
SqlSessionFactoryBuilder().build()方法,创建SqlSessionFactory对象并初始化配置信息
public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) { try { XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties); //创建默认的SqlSessionFactory实现类,DefaultSqlSessionFactory。 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. } } } public SqlSessionFactory build(Configuration config) { return new DefaultSqlSessionFactory(config); }
-
获取一个SqlSession,所有的数据库操作都是基于SqlSession进行的处理,对于Spring整合的Mybatis也是一样,只是Spring对于Mybatis的SqlSessionFactory对象进行了进一步的封装,后续会讲到。
public class DefaultSqlSessionFactory implements SqlSessionFactory { //DefaultSqlSessionFactory为默认的实现类对象。 @Override public SqlSession openSession() { return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false); } //每次openSession时都会创建默认的DefaultSqlSession。DefaultSqlSession又会将 //解析出来的配置文件属性对象设置为自己的属性。 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); 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(); } } }
-
当我们执行
sqlSession.selectOne("com.demo.MoveTaskQueryMapper.getList")
方法时,最终就会执行到DefaultSqlSession.selectList方法当中。因为是以方法名称的全路径作为唯一标识,所以我们在定义为Mapper文件时或Dao文件时,不要方法重载,不然系统解析就会出现问题。public class DefaultSqlSession implements SqlSession { @Override public <E> List<E> selectList(String statement, Object parameter, RowBounds rowBounds) { try { //1.根据方法的全路径名称获取MappedStatement,这个数据就是我们当初配置的mappers标签内容下的数据 MappedStatement ms = configuration.getMappedStatement(statement); //2.通过Executor发起执行逻辑 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.query(ms, wrapCollection(parameter), rowBounds, Executor.NO_RESULT_HANDLER)。其中最复杂的逻辑是Executor对象的创建。Executor作为SqlSession的属性对象,在每次进行创建时进行属性赋值。
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 { //默认的初始化的SimpleExecutor executor = new SimpleExecutor(this, transaction); } //该值为是否二级别缓存,默认是ture if (cacheEnabled) { executor = new CachingExecutor(executor); } //所有的Mybatis的插件的设置,其实就是递归层层的对SimpleExecutor或者CachingExecutor //进行代理。如果存在插件,则会使用代理后的对象进行执行 executor = (Executor) interceptorChain.pluginAll(executor); return executor; }
-
现在我们基于最简单的SimpleExecutor,默认所有的Executor都是SimpleExecutor的代理。SimpleExecutor继承自BaseExecutor。
public abstract class BaseExecutor implements Executor { //查询方法 public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException { //查询Mapper配置文件当中的sql语句信息 BoundSql boundSql = ms.getBoundSql(parameter); //创建一级缓存的查询key,sqlSession级别缓存 CacheKey key = createCacheKey(ms, parameter, rowBounds, boundSql); //调用下面的查询方法 return query(ms, parameter, rowBounds, resultHandler, key, boundSql); } @Override public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql) throws SQLException { ErrorContext.instance().resource(ms.getResource()) .activity("executing a query").object(ms.getId()); if (closed) { throw new ExecutorException("Executor was closed."); } if (queryStack == 0 && ms.isFlushCacheRequired()) { clearLocalCache(); } List<E> list; try { queryStack++; //一级缓存查询,当我们使用同一个sqlSession多次重复查询时,就会出现查询缓存命中的情况 list = resultHandler == null ? (List<E>) localCache.getObject(key) : null; if (list != null) { handleLocallyCachedOutputParameters(ms, key, parameter, boundSql); } else { //从数据库当中查询我们需要的数据信息 list = queryFromDatabase(ms, parameter, rowBounds, resultHandler, key, boundSql); } } finally { queryStack--; } if (queryStack == 0) { for (DeferredLoad deferredLoad : deferredLoads) { deferredLoad.load(); } // issue #601 deferredLoads.clear(); if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) { // issue #482 clearLocalCache(); } } return list; } //数据库的查询操作 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; } } //因为我们所有的Executor都是基于SimpleExecutor的代理,所有在没有重写方法的前提下,最终的查询都是通过doQuery方法进行 public class SimpleExecutor extends BaseExecutor { @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); //这哭是不是很熟悉,通过传统的Statement进行的查询。 stmt = prepareStatement(handler, ms.getStatementLog()); return handler.query(stmt, resultHandler); } finally { closeStatement(stmt); } } }
代码debug到这里,整个的Mybatis的框架的执行流程就比较清晰了。
对于SqlSessionFactory的创建系统中基本上只会创建一次,对于系统当中SqlSessionFactory接口实例的创建,我们手动创建时使用的SqlSessionFactoryBuilder() .build方法默认创建的是DefaultSqlSessionFactory。如果是多数据库配置的情况下,可以实例化多个SqlSessionFactory,不同的SqlSessionFactory设置不同的配置属性,就可以达到同时配置多个数据源的效果
Spring框架整合Mybatis
DefaultSqlSessionFactory默认实现,一般我们只有一个数据库配置时使用的默认的SqlSessionFactory实现。对于现在我们使用传统的spring的xml配置形式进行SqlSessionFactory的对象创建。spring为了整合mybatis,开发了包mybatis-spring包,当我们在传统spring项目当中进行数据库操作时都是基于这个Bean对象进行的处理。
<bean id="sqlSession" class="org.mybatis.spring.SqlSessionTemplate" scope="prototype">
<constructor-arg index="0" ref="sqlSessionFactory"/>
</bean>
<bean id="sqlSessionFactory" class="org.mybatis.spring.SqlSessionFactoryBean">
<property name="configLocation" value="classpath:SqlMapConfig.xml"/>
<property name="dataSource" ref="dataSource"/>
<property name="typeAliasesPackage" value="com.xxx.dao"/>
<property name="mapperLocations"
value="classpath*:com/xxx/dao/mapper/**/*.xml"/>
</bean>
其中SqlSessionTemplate是对SqlSessionFactory的代理封装。
Spring Boot框架整合Mybatis
用的springboot自动装配的实现:MybatisAutoConfiguration ,该类装配了我们需要的SqlSessionTemplate Bean 对象,所有的mybatis的操作都是基于这个bean对象进行的操作。
@Configuration
@ConditionalOnClass({SqlSessionFactory.class, SqlSessionFactoryBean.class})
@ConditionalOnSingleCandidate(DataSource.class)
@EnableConfigurationProperties({MybatisProperties.class})
@AutoConfigureAfter({DataSourceAutoConfiguration.class, MybatisLanguageDriverAutoConfiguration.class})
public class MybatisAutoConfiguration implements InitializingBean {
@Bean
@ConditionalOnMissingBean
public SqlSessionFactory sqlSessionFactory(DataSource dataSource) throws Exception {
//spring自动装配的spring-mybatis模块当中的类,该类持有SqlSessionFactory属性
SqlSessionFactoryBean factory = new SqlSessionFactoryBean();
//此处省略非关键代码...
applySqlSessionFactoryBeanCustomizers(factory);
return factory.getObject();
}
//这个就是mybatis注入到spring当中最重要的类,SqlSessionFactory也只是它的一个属性
@Bean
@ConditionalOnMissingBean
public SqlSessionTemplate sqlSessionTemplate(SqlSessionFactory sqlSessionFactory) {
ExecutorType executorType = this.properties.getExecutorType();
if (executorType != null) {
return new SqlSessionTemplate(sqlSessionFactory, executorType);
} else {
return new SqlSessionTemplate(sqlSessionFactory);
}
}
}
对于SqlSessionFactoryBean 类,它继承了spring的InitializingBean接口。
public class SqlSessionFactoryBean implements FactoryBean<SqlSessionFactory>, InitializingBean, ApplicationListener<ContextRefreshedEvent> {
private SqlSessionFactory sqlSessionFactory;
//这个方法是不是很熟悉,spring对象实例化后设置对象属性
public void afterPropertiesSet() throws Exception {
Assert.notNull(this.dataSource, "Property 'dataSource' is required");
Assert.notNull(this.sqlSessionFactoryBuilder, "Property 'sqlSessionFactoryBuilder' is required");
Assert.state(this.configuration == null && this.configLocation == null || this.configuration == null || this.configLocation == null, "Property 'configuration' and 'configLocation' can not specified with together");
//关键点在这,构建真正的SqlSessionFactory对象。
this.sqlSessionFactory = this.buildSqlSessionFactory();
}
//初始化sqlSessionFactory 对象属性
protected SqlSessionFactory buildSqlSessionFactory() throws Exception {
//此处省略非关键代码...
return this.sqlSessionFactoryBuilder.build(targetConfiguration);
}
//最终调用的是,我们可以发现其实springBoot在自动装配时其实使用的也是默认的DefaultSqlSessionFactory
public SqlSessionFactory build(Configuration config) {
return new DefaultSqlSessionFactory(config);
}
}
像现在的框架,其实就是在原有的框架的基础上又套了一层,将原有设计的模块的关键对象当中自己新创建的对象的属性进行赋值处理,所以说当代软件框架没有问题是通过加一层中间层解决不了问题的,如果有那么再加一层。像基础的mybatis框架,在与传统的spring项目整合时就引入了当我们在获取一个sqlSession,会通过默认的DefaultSqlSessionFactory.openSessionFromDataSource获取
SqlSession sqlSession=sqlSessionFactory.openSession();
public SqlSession openSession() {
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}
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);
//new一个默认的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();
}
}
- 通过sqlSession获取我们调用的xxxMapper或者xxxDao的代理对象,sqlSession.getMapper(xxx.class),根据代理对象最终来查询我们的sql;
//1 通过DefaultSqlSession获取配置信息,获取已注册的MapperProxy
public <T> T getMapper(Class<T> type) {
return configuration.<T>getMapper(type, this);
}
//2 通过MapperProxyFactory获取mapperProxy
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) 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);
}
}
//3.通过反射获取最终可执行的MapperProxy
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<T>(sqlSession, mapperInterface, methodCache);
return newInstance(mapperProxy);
}
protected T newInstance(MapperProxy<T> mapperProxy) {
//通过JDK动态代理生成反射对象
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
小结
通过对源码的分析,我们大概了解到myabatis是如何解析配置文件并通过最终的sql代理执行的,上面是基于Mapper的唯一标识进行的查询,也可以通过Mapper的代理对象进行查询,其实最终的底层逻辑也是一样的。