工作流程:
public static void main(String[] args) throws IOException {
InputStream inputStream = Resources.getResourceAsStream("mybatis-config.xml");
SqlSessionFactory sqlSessionFactory = new SqlSessionFactoryBuilder().build(inputStream);
SqlSession sqlSession = sqlSessionFactory.openSession();
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
List<User> userList = userMapper.getUserByUsername("admin");
}
1、獲取配置文件mybatis-config.xml,由SqlSessionFactoryBuilder對象,讀取並解析配置文件,返回SqlSessionFactory對象
2、由SqlSessionFactory創建SqlSession 對象,沒有手動設置的話事務默認開啓
3、調用SqlSession中的api,獲取指定UserMapper.class文件
4、由jdk動態代理,獲取到UserMapper.xml文件,內部進行復雜的處理,最後調用jdbc執行SQL語句,封裝結果返回。
具體實現:
1.1、由SqlSessionFactoryBuilder讀取xml配置文件,解析成Configuration對象
//在創建XMLConfigBuilder時,它的構造方法中解析器XPathParser已經讀取了配置文件
public Configuration parse() {
if (parsed) {
throw new BuilderException("Each XMLConfigBuilder can only be used once.");
}
parsed = true;
//parser是XPathParser解析器對象,讀取節點內數據,<configuration>是MyBatis配置文件中的頂層標籤
parseConfiguration(parser.evalNode("/configuration"));
//最後返回的是Configuration 對象
return configuration;
}
//此方法中讀取了各個標籤內容並封裝到Configuration中的屬性中。
private void parseConfiguration(XNode root) {
try {
propertiesElement(root.evalNode("properties"));
Properties settings = settingsAsProperties(root.evalNode("settings"));
loadCustomVfs(settings);
loadCustomLogImpl(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);
}
}
1.2、根據Configuration創建SqlSessionFactory
public SqlSessionFactory build(Configuration config) {
//創建了DefaultSqlSessionFactory對象,傳入Configuration對象。
return new DefaultSqlSessionFactory(config);
}
2.1、根據SqlSessionFactory創建會話DefaultSqlSession對象
// 創建SqlSession會話
public SqlSession openSession() {
return openSessionFromDataSource(configuration.getDefaultExecutorType(), null, false);
}
/**
*
* ExecutorType:執行器的類型
* level:事務級別
* autoCommit:是否自動提交事務
*/
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();
}
}
2.2、事務創建:
// getTransactionFactoryFromEnvironment,根據配置的數據源驅動獲取相對應的事務
private TransactionFactory getTransactionFactoryFromEnvironment(Environment environment){
if (environment == null || environment.getTransactionFactory() == null) {
return new ManagedTransactionFactory();
}
return environment.getTransactionFactory();
}
// 比如配置mysql數據源,在初始化Configuration時,指定了jdbc事務工廠。
public Configuration() {
typeAliasRegistry.registerAlias("JDBC", JdbcTransactionFactory.class);
// ....省略N個配置項
}
// transactionFactory.newTransaction創建事務,存儲連接、數據源、事務級別等信息
public enum TransactionIsolationLevel {
NONE(Connection.TRANSACTION_NONE), // 無事務
READ_COMMITTED(Connection.TRANSACTION_READ_COMMITTED), // 不可重複讀
READ_UNCOMMITTED(Connection.TRANSACTION_READ_UNCOMMITTED), // 讀未提交
REPEATABLE_READ(Connection.TRANSACTION_REPEATABLE_READ), // 可重複讀
SERIALIZABLE(Connection.TRANSACTION_SERIALIZABLE); // 串行化
}
2.3、執行機創建
// 根據事務和執行機類型創建執行機(默認不指定則爲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;
}
// 執行機加入到每個攔截器executor = (Executor) interceptorChain.pluginAll(executor);
// 說明這個執行器需要被各個攔截器攔截處理
private final List<Interceptor> interceptors = new ArrayList<>(); // 所有的攔截器
public Object pluginAll(Object target) {// target執行機
for (Interceptor interceptor : interceptors) {
target = interceptor.plugin(target);
}
return target;
}
2.3.1、執行機區別
a、SimpleExecutor:每執行一次update或select,就開啓一個Statement對象,用完立刻關閉Statement對象
b、ReuseExecutor:執行update或select,以sql作爲key查找Statement對象,存在就使用,不存在就創建,用完後,不關閉Statement對象,而是放置於Map內,供下一次使用。簡言之,就是重複使用Statement對象
c、BatchExecutor:執行update(沒有select,JDBC批處理不支持select),將所有sql都添加到批處理中addBatch(),等待統一執行executeBatch(),它緩存了多個Statement對象,每個Statement對象都是addBatch()完畢後,等待逐一執行executeBatch()批處理。與JDBC批處理相同
作用範圍:SqlSession生命週期範圍內
d、CachingExecutor:默認開啓,同個作用域下的緩存執行機,使用內存的,將數據保存到緩存中,這樣可以有效的解決增刪改查性能
2.3.2、攔截器
// 攔截器可以自定義,但是隻能定義以下幾種類,因爲Configuration只加載這幾類
// 默認的攔截器順序:Executor -> ParameterHandler -> StatementHandler -> ResultSetHandler
// 第一種:源碼對ParameterHandler類的處理,對參數的攔截
public ParameterHandler newParameterHandler(MappedStatement mappedStatement, Object parameterObject, BoundSql boundSql) {
ParameterHandler parameterHandler = mappedStatement.getLang().createParameterHandler(mappedStatement, parameterObject, boundSql);
parameterHandler = (ParameterHandler) interceptorChain.pluginAll(parameterHandler);
return parameterHandler;
}
// 第二種:源碼對ResultSetHandler類的處理,對結果集的攔截,可以對返回的結果進行處理
public ResultSetHandler newResultSetHandler(Executor executor, MappedStatement mappedStatement, RowBounds rowBounds, ParameterHandler parameterHandler,
ResultHandler resultHandler, BoundSql boundSql) {
ResultSetHandler resultSetHandler = new DefaultResultSetHandler(executor, mappedStatement, parameterHandler, resultHandler, boundSql, rowBounds);
resultSetHandler = (ResultSetHandler) interceptorChain.pluginAll(resultSetHandler);
return resultSetHandler;
}
// 第三種:源碼對StatementHandler類的處理,對查詢前的攔截,可以對最終sql作修改
public StatementHandler newStatementHandler(Executor executor, MappedStatement mappedStatement, Object parameterObject, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) {
StatementHandler statementHandler = new RoutingStatementHandler(executor, mappedStatement, parameterObject, rowBounds, resultHandler, boundSql);
statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler);
return statementHandler;
}
自定義實現攔截器
@Intercepts({@Signature(
type = StatementHandler.class,
method = "prepare",
args = {Connection.class, Integer.class})})
public class SqlStatusInterceptor implements Interceptor {// 實現接口方法}
2.4、創建DefaultSqlSession
// 默認爲DefaultSqlSession,頂級接口SqlSession,作用是操作sql命令、獲取mapper和管理事務
new DefaultSqlSession(configuration, executor, autoCommit);
3.1、根據jdk動態代理獲取指定Mapper
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
// 會根據mapperRegistry這個map獲取對應的UserMapper.xml
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
// 而mapperRegistry內部的關鍵代碼
private final Map<Class<?>, MapperProxyFactory<?>> knownMappers = new HashMap<>();
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>) knownMappers.get(type);
// ....
mapperProxyFactory.newInstance(sqlSession);
// ....
}
// mapperProxyFactory.newInstance(sqlSession);
// 此處內部由MapperProxyFactory創建jdk的代理對象(Proxy對象),
protected T newInstance(MapperProxy<T> mapperProxy) {
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy);
}
// 而knownMappers對象則是在加載配置文件的時候動態addMapper進去的
public void addMappers(String packageName, Class<?> superType) {
ResolverUtil<Class<?>> resolverUtil = new ResolverUtil<>();
resolverUtil.find(new ResolverUtil.IsA(superType), packageName);
Set<Class<? extends Class<?>>> mapperSet = resolverUtil.getClasses();
for (Class<?> mapperClass : mapperSet) {
addMapper(mapperClass);
}
}
至此,根據SqlSession,可以由getMapper獲取指定的代理對象。
而代理對象怎麼調用sql命令呢?原理則是根據MappedStatement對象。
4.1、根據代理對象查詢
List<User> userList = userMapper.getUserByUsername("admin");
// 該代理對象先獲取MappedStatement對象,獲取UserMapper.xml信息
// 根據該xml信息去執行相應的sql語句
@Override
public void select(String statement, Object parameter, RowBounds rowBounds, ResultHandler handler) {
// ...
MappedStatement ms = configuration.getMappedStatement(statement);
executor.query(ms, wrapCollection(parameter), rowBounds, handler);
// ...
}
4.2、MappedStatement對象
protected final Map<String, MappedStatement> mappedStatements = new StrictMap<MappedStatement>("Mapped Statements collection")
.conflictMessageProducer((savedValue, targetValue) ->
". please check " + savedValue.getResource() + " and " + targetValue.getResource());
// 該對象的值是在初始化配置文件時加載的
// 過程:XMLConfigBuilder->XMLMapperBuilder->XMLStatementBuilder->MapperBuilderAssistant
// key:全包名,value:
private void parseConfiguration(XNode root) {
// ...
mapperElement(root.evalNode("mappers"));
// ...
}
// 解析UserMapper.xml的mapper元素
private void mapperElement(XNode parent) throws Exception {
// ...
for (XNode child : parent.getChildren()) {
XMLMapperBuilder mapperParser = new XMLMapperBuilder(inputStream, configuration, resource, configuration.getSqlFragments());
mapperParser.parse();
}
// ...
}
// 解析每個節點
public void parseStatementNode() {
// ...
builderAssistant.addMappedStatement(...);
// ...
}
// 增加到MappedStatement對象中
public MappedStatement addMappedStatement(...) {
MappedStatement.Builder statementBuilder = new MappedStatement.Builder(...);
ParameterMap statementParameterMap = getStatementParameterMap(parameterMap, parameterType, id);
if (statementParameterMap != null) {
statementBuilder.parameterMap(statementParameterMap);
}
MappedStatement statement = statementBuilder.build();
// 關鍵代碼
configuration.addMappedStatement(statement);
return statement;
}
4.3、Excutor對象執行查詢
獲取了MappedStatement對象,則由執行機將該對象xml信息轉換成BoundSql(最終SQL語句),繼而執行sql命令
@Override
public <E> List<E> query(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler) throws SQLException {
BoundSql boundSql = ms.getBoundSql(parameter);
return query(ms, parameter, rowBounds, resultHandler, key, boundSql);
}
// query方法內部調用-->queryFromDatabase方法內部調用-->(SimpleExcutor)doQuery方法爲jdbc實現
@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.query(stmt, resultHandler);
} finally {
closeStatement(stmt);
}
}
5、總結(核心代碼總結)
1、獲取配置文件mybatis-config.xml
2、由SqlSessionFactoryBuilder對象,讀取配置文件
3、根據配置文件設置MapperRegistry對象(存放接口代理對象)
4、根據配置文件設置MappedStatement對象(存放接口對應的xml信息)
5、根據配置文件解析TransactionFactory事務對象(如mysql爲JdbcTransactionFactory)
6、根據配置文件解析Excutor對象(默認SimpleExcutor執行機)
7、將Excutor加入攔截器(攔截器順序Executor\ParameterHandler\StatementHandler\ResultSetHandler)
8、返回SqlSessionFactory對象,由SqlSessionFactory創建SqlSession 對象
9、調用SqlSession中的api,獲取指定的接口代理對象(根據MapperRegistry對象獲取)
3、調用SqlSession中的api,獲取指定UserMapper.class文件
10、該代理對象調用查詢方法,由攔截器攔截,獲取指定的接口對應xml信息,組裝BoundSql(SQL語句)對象
11、根據BoundSql對象,獲取SQL語句,由驅動(如mysql爲jdbc)調用sql命令,查詢數據庫。
參考資料:
https://blog.csdn.net/weixin_43184769/article/details/91126687
https://zhuanlan.zhihu.com/p/59844703