MyBatis核心源碼深度剖析SQL語(yǔ)句執(zhí)行過(guò)程
1 SQL語(yǔ)句的執(zhí)行過(guò)程介紹
MyBatis核心執(zhí)行組件:

2 SQL執(zhí)行的入口分析
2.1 為Mapper接口創(chuàng)建代理對(duì)象
// 方式1:
User user = session.selectOne("com.oldlu.dao.UserMapper.findUserById", 101);
// 方式2:
UserMapper mapper = session.getMapper(UserMapper.class);
List<User> userList = mapper.findAll();2.2 執(zhí)行代理邏輯
方式1入口分析:
session是DefaultSqlSession類(lèi)型的,因?yàn)閟qlSessionFactory默認(rèn)生成的SqlSession是
DefaultSqlSession類(lèi)型。
selectOne()會(huì)調(diào)用selectList()。
// DefaultSqlSession類(lèi)
public <E> List<E> selectList(String statement, Object parameter, RowBounds
rowBounds) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
// CURD操作是交給Excetor去處理的
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();
}
}方式2入口分析:
獲取代理對(duì)象:
//DefaultSqlSession類(lèi) ====================>
@Override
public <T> T getMapper(Class<T> type) {
return configuration.getMapper(type, this);
}
// Configuration類(lèi) ====================>
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
return mapperRegistry.getMapper(type, sqlSession);
}
//MapperRegistry ----> apperProxyFactory.newInstance ====================>
public <T> T getMapper(Class<T> type, SqlSession sqlSession) {
//從緩存中獲取該Mapper接口的代理工廠對(duì)象
final MapperProxyFactory<T> mapperProxyFactory = (MapperProxyFactory<T>)
knownMappers.get(type);
//如果該Mapper接口沒(méi)有注冊(cè)過(guò),則拋異常
if (mapperProxyFactory == null) {
throw new BindingException("Type " + type + " is not known to the
MapperRegistry.");
}
try {
//【使用代理工廠創(chuàng)建Mapper接口的代理對(duì)象】
return mapperProxyFactory.newInstance(sqlSession);
} catch (Exception e) {
throw new BindingException("Error getting mapper instance. Cause: " + e,
e);
}
}
//MapperProxyFactory --->此時(shí)生成代理對(duì)象 ====================>
protected T newInstance(MapperProxy<T> mapperProxy) {
//Mybatis底層是調(diào)用JDK的Proxy類(lèi)來(lái)創(chuàng)建代理實(shí)例
return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new
Class[] { mapperInterface }, mapperProxy);
}
public T newInstance(SqlSession sqlSession) {
final MapperProxy<T> mapperProxy = new MapperProxy<>(sqlSession,
mapperInterface, methodCache);
return newInstance(mapperProxy);
}
代理對(duì)象執(zhí)行邏輯:
//MapperProxy ====================>
/**代理對(duì)象執(zhí)行的方法,代理以后,所有Mapper的方法調(diào)用時(shí),都會(huì)調(diào)用這個(gè)invoke方法*/
public Object invoke(Object proxy, Method method, Object[] args) throws
Throwable {
try {
if (Object.class.equals(method.getDeclaringClass())) {
//如果是Object方法,則調(diào)用方法本身
return method.invoke(this, args);
} else {
//調(diào)用接口方法:根據(jù)被調(diào)用接口的Method對(duì)象,從緩存中獲取MapperMethodInvoker對(duì)象
//apper接口中的每一個(gè)方法都對(duì)應(yīng)一個(gè)MapperMethodInvoker對(duì)象,而MapperMethodInvoker
對(duì)象里面的MapperMethod保存著對(duì)應(yīng)的SQL信息和返回類(lèi)型以完成SQL調(diào)用 ...
return cachedInvoker(method).invoke(proxy, method, args, sqlSession);
}
} catch (Throwable t) {
throw ExceptionUtil.unwrapThrowable(t);
}
}
/**
獲取緩存中MapperMethodInvoker,如果沒(méi)有則創(chuàng)建一個(gè),而MapperMethodInvoker內(nèi)部封裝這一
個(gè)MethodHandler
*/
private MapperMethodInvoker cachedInvoker(Method method) throws Throwable {
try {
return methodCache.computeIfAbsent(method, m -> {
if (m.isDefault()) {
//如果調(diào)用接口的是默認(rèn)方法(default方法)
try {
if (privateLookupInMethod == null) {
return new
DefaultMethodInvoker(getMethodHandleJava8(method));
} else {
return new
DefaultMethodInvoker(getMethodHandleJava9(method));
}
} catch (IllegalAccessException | InstantiationException |
InvocationTargetException
| NoSuchMethodException e) {
throw new RuntimeException(e);
}
} else {
//如果調(diào)用的普通方法(非default方法),則創(chuàng)建一個(gè)PlainMethodInvoker并放
入緩存,其中MapperMethod保存對(duì)應(yīng)接口方法的SQL以及入?yún)⒑统鰠⒌臄?shù)據(jù)類(lèi)型等信息
return new PlainMethodInvoker(new MapperMethod(mapperInterface,
method, sqlSession.getConfiguration()));
}
});
} catch (RuntimeException re) {
Throwable cause = re.getCause();
throw cause == null ? re : cause;
}
}
// MapperProxy內(nèi)部類(lèi): PainMethodInvoker ====================>
// 當(dāng)cacheInvoker返回了PalinMethodInvoker實(shí)例之后,緊接著調(diào)用了這個(gè)實(shí)例的
PlainMethodInvoker:invoke方法
@Override
public Object invoke(Object proxy, Method method, Object[] args, SqlSession
sqlSession) throws Throwable {
//Mybatis實(shí)現(xiàn)接口方法的核心: MapperMethod::execute方法:
return mapperMethod.execute(sqlSession, args);
}
// MapperMethod ====================>
public Object execute(SqlSession sqlSession, Object[] args) {
Object result;
switch (command.getType()) {
case INSERT: {
// 將args進(jìn)行解析,如果是多個(gè)參數(shù)則,則根據(jù)@Param注解指定名稱(chēng)將參數(shù)轉(zhuǎn)換為Map,
如果是封裝實(shí)體則不轉(zhuǎn)換
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.insert(command.getName(),
param));
break;
}
case UPDATE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.update(command.getName(),
param));
break;
}
case DELETE: {
Object param = method.convertArgsToSqlCommandParam(args);
result = rowCountResult(sqlSession.delete(command.getName(),
param));
break;
}
case SELECT:
//查詢(xún)操作
if (method.returnsVoid() && method.hasResultHandler()) {
executeWithResultHandler(sqlSession, args);
result = null;
} else if (method.returnsMany()) {
result = executeForMany(sqlSession, args);
} else if (method.returnsMap()) {
result = executeForMap(sqlSession, args);
} else if (method.returnsCursor()) {
result = executeForCursor(sqlSession, args);
} else {
//解析參數(shù),因?yàn)镾qlSession::selectOne方法參數(shù)只能傳入一個(gè),但是我們
Mapper中可能傳入多個(gè)參數(shù),
//有可能是通過(guò)@Param注解指定參數(shù)名,所以這里需要將Mapper接口方法中的多個(gè)參
數(shù)轉(zhuǎn)化為一個(gè)ParamMap,
//也就是說(shuō)如果是傳入的單個(gè)封裝實(shí)體,那么直接返回出來(lái);如果傳入的是多個(gè)參數(shù),
實(shí)際上都轉(zhuǎn)換成了Map
Object param = method.convertArgsToSqlCommandParam(args);
//可以看到動(dòng)態(tài)代理最后還是使用SqlSession操作數(shù)據(jù)庫(kù)的
result = sqlSession.selectOne(command.getName(), param);
if (method.returnsOptional()
&& (result == null ||
!method.getReturnType().equals(result.getClass()))) {
result = Optional.ofNullable(result);
}
}
break;
case FLUSH:
result = sqlSession.flushStatements();
break;
default:
throw new BindingException("Unknown execution method for: " +
command.getName());
}
if (result == null && method.getReturnType().isPrimitive() &&
!method.returnsVoid()) {
throw new BindingException("Mapper method '" + command.getName()
+ " attempted to return null from a method
with a primitive return type (" + method.getReturnType() + ").");
}
return result;
}
// 此時(shí)我們發(fā)現(xiàn): 回到了sqlsession中
private <E> Object executeForMany(SqlSession sqlSession, Object[] args) {
List<E> result;
Object param = method.convertArgsToSqlCommandParam(args);
if (method.hasRowBounds()) {
RowBounds rowBounds = method.extractRowBounds(args);
result = sqlSession.selectList(command.getName(), param, rowBounds);
} else {
result = sqlSession.selectList(command.getName(), param);
}
// ...
return result;
}

3 查詢(xún)語(yǔ)句的執(zhí)行過(guò)程分析
3.1 selectOne方法分析
// DefaultSqlSession類(lèi) ===============>
// selectOne
@Override
public <T> T selectOne(String statement, Object parameter) {
// //selectOne()會(huì)調(diào)用selectList()。
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;
}
}
// selectList
public <E> List<E> selectList(String statement, Object parameter, RowBounds
rowBounds) {
try {
MappedStatement ms = configuration.getMappedStatement(statement);
// CURD操作是交給Excetor去處理的
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();
}
}
3.2 sql獲取
// CachingExecutor ===============>
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds
rowBounds, ResultHandler resultHandler) throws SQLException {
// 獲取綁定的sql命令,比如"SELECT * FROM xxx"
BoundSql boundSql = ms.getBoundSql(parameterObject);
CacheKey key = createCacheKey(ms, parameterObject, rowBounds, boundSql);
return query(ms, parameterObject, rowBounds, resultHandler, key, boundSql);
}
@Override
public <E> List<E> query(MappedStatement ms, Object parameterObject, RowBounds
rowBounds, ResultHandler resultHandler, CacheKey key, BoundSql boundSql)
throws SQLException {
Cache cache = ms.getCache();
if (cache != null) {
flushCacheIfRequired(ms);
if (ms.isUseCache() && resultHandler == null) {
ensureNoOutParams(ms, boundSql);
@SuppressWarnings("unchecked")
List<E> list = (List<E>) 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;
}
}
return delegate.query(ms, parameterObject, rowBounds, resultHandler, key,
boundSql);
}
//真正執(zhí)行query操作的是SimplyExecutor代理來(lái)完成的,SimplyExecutor的父類(lèi)BaseExecutor的
query方法中:
// BaseExecutor類(lèi):SimplyExecutor的父類(lèi) =================>
@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++;
//localCache是一級(jí)緩存,如果找不到就調(diào)用queryFromDatabase從數(shù)據(jù)庫(kù)中查找
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();
}
deferredLoads.clear();
if (configuration.getLocalCacheScope() == LocalCacheScope.STATEMENT) {
clearLocalCache();
}
}
return list;
}
//第一次,沒(méi)有緩存,所以會(huì)調(diào)用queryFromDatabase方法來(lái)執(zhí)行查詢(xún)。
private <E> List<E> queryFromDatabase(...) throws SQLException {
List<E> list;
localCache.putObject(key, EXECUTION_PLACEHOLDER);
try {
// 查詢(xún)
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;
}
// SimpleExecutor類(lèi) ============================>
public <E> List<E> doQuery(...) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(....);
// 1:SQL查詢(xún)參數(shù)的設(shè)置
stmt = prepareStatement(handler, ms.getStatementLog());
// StatementHandler封裝了Statement
// 2:SQL查詢(xún)操作和結(jié)果集的封裝
return handler.<E>query(stmt);
} finally {
closeStatement(stmt);
}
}
3.3 參數(shù)設(shè)置
// SimplyExecutor類(lèi) ============================>
// 【1】 參數(shù)設(shè)置: prepareStatement
private Statement prepareStatement(StatementHandler handler, Log statementLog)
throws SQLException {
Statement stmt;
// 通過(guò)getConnection方法來(lái)獲取一個(gè)Connection,
Connection connection = getConnection(statementLog);
// 調(diào)用prepare方法來(lái)獲取一個(gè)Statement
stmt = handler.prepare(connection, transaction.getTimeout());
// 設(shè)置SQL查詢(xún)中的參數(shù)值 ***
handler.parameterize(stmt);
return stmt;
}
// RoutingStatementHandler ============================>
// PreparedStatementHandler ============================>
@Override
public void parameterize(Statement statement) throws SQLException {
parameterHandler.setParameters((PreparedStatement) statement);
}
// DefaultParameterHandler ============================> 此時(shí)參數(shù)設(shè)置成功
@Override
public void setParameters(PreparedStatement ps) {
ErrorContext.instance().activity("setting
parameters").object(mappedStatement.getParameterMap().getId());
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
if (parameterMappings != null) {
for (int i = 0; i < parameterMappings.size(); i++) {
ParameterMapping parameterMapping = parameterMappings.get(i);
if (parameterMapping.getMode() != ParameterMode.OUT) {
Object value;
String propertyName = parameterMapping.getProperty();
if (boundSql.hasAdditionalParameter(propertyName)) {
value = boundSql.getAdditionalParameter(propertyName);
} else if (parameterObject == null) {
value = null;
} else if
(typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
value = parameterObject;
} else {
MetaObject metaObject =
configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
}
TypeHandler typeHandler = parameterMapping.getTypeHandler();
JdbcType jdbcType = parameterMapping.getJdbcType();
if (value == null && jdbcType == null) {
jdbcType = configuration.getJdbcTypeForNull();
}
try {
typeHandler.setParameter(ps, i + 1, value, jdbcType);
} catch (TypeException | SQLException e) {
throw new TypeException("Could not set parameters for
mapping.....");
}
}
}
}
}
3.4 SQL執(zhí)行和結(jié)果集的封裝
// RoutingStatementHandler ============================>
@Override
public <E> List<E> query(Statement statement) throws SQLException {
return delegate.<E>query(statement);
}
// PreparedStatementHandler ============================>
@Override
public <E> List<E> query(Statement statement, ResultHandler resultHandler)
throws SQLException {
// 這里就到了熟悉的PreparedStatement了
PreparedStatement ps = (PreparedStatement) statement;
// 執(zhí)行SQL查詢(xún)操作
ps.execute();
// 結(jié)果交給ResultHandler來(lái)處理
return resultSetHandler.<E> handleResultSets(ps);
}
// DefaultResultSetHandler類(lèi)(封裝返回值,將查詢(xún)結(jié)果封裝成Object對(duì)象)
@Override
public List<Object> handleResultSets(Statement stmt) throws SQLException {
ErrorContext.instance().activity("handling
results").object(mappedStatement.getId());
final List<Object> multipleResults = new ArrayList<Object>();
int resultSetCount = 0;
ResultSetWrapper rsw = getFirstResultSet(stmt);
List<ResultMap> resultMaps = mappedStatement.getResultMaps();
int resultMapCount = resultMaps.size();
validateResultMapsCount(rsw, resultMapCount);
while (rsw != null && resultMapCount > resultSetCount) {
ResultMap resultMap = resultMaps.get(resultSetCount);
handleResultSet(rsw, resultMap, multipleResults, null);
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
String[] resultSets = mappedStatement.getResultSets();
if (resultSets != null) {
while (rsw != null && resultSetCount < resultSets.length) {
ResultMapping parentMapping =
nextResultMaps.get(resultSets[resultSetCount]);
if (parentMapping != null) {
String nestedResultMapId = parentMapping.getNestedResultMapId();
ResultMap resultMap =
configuration.getResultMap(nestedResultMapId);
handleResultSet(rsw, resultMap, null, parentMapping);
}
rsw = getNextResultSet(stmt);
cleanUpAfterHandlingResultSet();
resultSetCount++;
}
}
return collapseSingleResultList(multipleResults);
}

4 更新語(yǔ)句的執(zhí)行過(guò)程分析
- xecutor 的 update 方法分析
- insert、update 和 delete 操作都會(huì)清空一二級(jí)緩存
- doUpdate 方法
- PreparedStatementHandler 的 update 方法
- 默認(rèn)是創(chuàng)建PreparedStatementHandler,然后執(zhí)行prepareStatement方法。
- 執(zhí)行結(jié)果為受影響行數(shù)
- 執(zhí)行更新語(yǔ)句的SQL
4.1 sqlsession增刪改方法分析
// DefaultSqlSession ===============>
@Override
public int insert(...) {
return update(statement, parameter);
}
@Override
public int update(String statement) {
return update(statement, null);
}
@Override
public int delete(...) {
return update(....);
}
// insert 、delete操作是通過(guò)調(diào)用update語(yǔ)句進(jìn)行的相關(guān)邏輯
@Override
public int update(String statement, Object parameter) {
try {
dirty = true;
MappedStatement ms = configuration.getMappedStatement(statement);
// 增刪改 最終底層都是 update
return executor.update(ms, wrapCollection(parameter));
} catch (Exception e) {
throw ExceptionFactory.wrapException("Error updating database. Cause: " +
e, e);
} finally {
ErrorContext.instance().reset();
}
}
4.2 sql獲取
// CachingExecutor ===============>
@Override
public int update(MappedStatement ms, Object parameterObject) throws
SQLException {
// 執(zhí)行增刪改,清除緩存
flushCacheIfRequired(ms);
// 跳轉(zhuǎn)BaseExecutor
return delegate.update(ms, parameterObject);
}
// BaseExecutor ===============>
@Override
public int update(MappedStatement ms, Object parameter) throws SQLException {
ErrorContext.instance().resource(ms.getResource()).activity("executing an
update").object(ms.getId());
if (closed) {
throw new ExecutorException("Executor was closed.");
}
// 清除 LocalCache 一級(jí)緩存
clearLocalCache();
//執(zhí)行 doUpdate
return doUpdate(ms, parameter);
}
// SimpleExecutor ===============>
// doUpdate
@Override
public int doUpdate(MappedStatement ms, Object parameter) throws SQLException {
Statement stmt = null;
try {
Configuration configuration = ms.getConfiguration();
StatementHandler handler = configuration.newStatementHandler(...);
// 【1】.獲取statement,并進(jìn)行參數(shù)映射
stmt = prepareStatement(handler, ms.getStatementLog());
// 【2】.handler.update()方法執(zhí)行具體sql指令
return handler.update(stmt);
} finally {
closeStatement(stmt);
}
}
4.3 參數(shù)設(shè)置
// SimplyExecutor類(lèi) ============================>
//【1】 prepareStatement
private Statement prepareStatement(StatementHandler handler, Log statementLog)
throws SQLException {
Statement stmt;
Connection connection = getConnection(statementLog);
// 使用connection對(duì)象信息創(chuàng)建statement,并將超時(shí)時(shí)間綁定
stmt = handler.prepare(connection, transaction.getTimeout());
// parameterize方法設(shè)置sql執(zhí)行時(shí)候需要的參數(shù)
handler.parameterize(stmt);
return stmt;
}
// RoutingStatementHandler ============================>
// PreparedStatementHandler ============================>
@Override
public void parameterize(Statement statement) throws SQLException {
parameterHandler.setParameters((PreparedStatement) statement);
}
// DefaultParameterHandler ============================> 此時(shí)參數(shù)設(shè)置成功
@Override
public void setParameters(PreparedStatement ps) {
ErrorContext.instance().activity("setting
parameters").object(mappedStatement.getParameterMap().getId());
List<ParameterMapping> parameterMappings = boundSql.getParameterMappings();
if (parameterMappings != null) {
for (int i = 0; i < parameterMappings.size(); i++) {
ParameterMapping parameterMapping = parameterMappings.get(i);
if (parameterMapping.getMode() != ParameterMode.OUT) {
Object value;
String propertyName = parameterMapping.getProperty();
if (boundSql.hasAdditionalParameter(propertyName)) {
value = boundSql.getAdditionalParameter(propertyName);
} else if (parameterObject == null) {
value = null;
} else if
(typeHandlerRegistry.hasTypeHandler(parameterObject.getClass())) {
value = parameterObject;
} else {
MetaObject metaObject =
configuration.newMetaObject(parameterObject);
value = metaObject.getValue(propertyName);
}
TypeHandler typeHandler = parameterMapping.getTypeHandler();
JdbcType jdbcType = parameterMapping.getJdbcType();
if (value == null && jdbcType == null) {
jdbcType = configuration.getJdbcTypeForNull();
}
try {
typeHandler.setParameter(ps, i + 1, value, jdbcType);
} catch (TypeException | SQLException e) {
throw new TypeException("Could not set parameters for
mapping.....");
}
}
}
}
}
4.4 SQL執(zhí)行
// RoutingStatementHandler ============================>
@Override
public int update(Statement statement) throws SQLException {
return delegate.update(statement);
}
// PreparedStatementHandler ============================>
@Override
public int update(Statement statement) throws SQLException {
// 這里就是底層JDBC的PreparedStatement 操作了
PreparedStatement ps = (PreparedStatement) statement;
// 執(zhí)行SQL增刪改操作
ps.execute();
// 獲取影響的行數(shù)
int rows = ps.getUpdateCount();
Object parameterObject = boundSql.getParameterObject();
KeyGenerator keyGenerator = mappedStatement.getKeyGenerator();
keyGenerator.processAfter(executor, mappedStatement, ps, parameterObject);
// 返回影響的行數(shù)
return rows;
}
5 小結(jié)
mybatis執(zhí)行SQL的流程都是:
1.根據(jù)statement字符串從configuration中獲取對(duì)應(yīng)的mappedStatement;
2.根據(jù)獲取的mappedStatement創(chuàng)建相應(yīng)的Statement實(shí)例;
3.根據(jù)傳入的參數(shù)對(duì)statement實(shí)例進(jìn)行參數(shù)設(shè)置;
4.執(zhí)行statement并執(zhí)行后置操作;
到此這篇關(guān)于MyBatis核心源碼深度剖析SQL執(zhí)行過(guò)程的文章就介紹到這了,更多相關(guān)MyBatis核心源碼剖析SQL執(zhí)行過(guò)程內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
Mybatis調(diào)用Oracle存儲(chǔ)過(guò)程的方法圖文詳解
這篇文章主要介紹了Mybatis調(diào)用Oracle存儲(chǔ)過(guò)程的方法介紹,需要的朋友可以參考下2017-09-09
詳解Kotlin 高階函數(shù) 與 Lambda 表達(dá)式
這篇文章主要介紹了詳解Kotlin 高階函數(shù) 與 Lambda 表達(dá)式的相關(guān)資料,需要的朋友可以參考下2017-06-06
基于SpringBoot生成二維碼的幾種實(shí)現(xiàn)方式
本文將基于Spring Boot介紹兩種生成二維碼的實(shí)現(xiàn)方式,一種是基于Google開(kāi)發(fā)工具包,另一種是基于Hutool來(lái)實(shí)現(xiàn),具有一定的參考價(jià)值,感興趣的可以了解一下2022-03-03
基于eclipse.ini內(nèi)存設(shè)置的問(wèn)題詳解
本篇文章是對(duì)eclipse.ini內(nèi)存設(shè)置的問(wèn)題進(jìn)行了詳細(xì)的分析介紹,需要的朋友參考下2013-05-05
Java并發(fā)Futures和Callables類(lèi)實(shí)例詳解
Callable對(duì)象返回Future對(duì)象,該對(duì)象提供監(jiān)視線程執(zhí)行的任務(wù)進(jìn)度的方法, Future對(duì)象可用于檢查Callable的狀態(tài),然后線程完成后從Callable中檢索結(jié)果,這篇文章給大家介紹Java并發(fā)Futures和Callables類(lèi)的相關(guān)知識(shí),感興趣的朋友一起看看吧2024-05-05
Java中的ClassLoader類(lèi)加載器使用詳解
這篇文章主要介紹了Java中的ClassLoader類(lèi)加載器使用詳解,ClassLoader用于將CLASS文件動(dòng)態(tài)加載到JVM中去,是所有類(lèi)加載器的基類(lèi),所有繼承自抽象的ClassLoader的加載器,都會(huì)優(yōu)先判斷是否被父類(lèi)加載器加載過(guò),防止多次加載,需要的朋友可以參考下2023-10-10
SpringBoot3配置Logback日志滾動(dòng)文件的方法
本文介紹了在SpringBoot3中配置Logback日志滾動(dòng)文件的方法,因?yàn)镾pringBoot3內(nèi)置的logback版本是1.4.14,之前使用SpringBoot2.1.5的logback配置發(fā)現(xiàn)有些東西不能生效了,需要的朋友可以參考下2024-08-08
Spring的實(shí)例工廠方法和靜態(tài)工廠方法實(shí)例代碼
這篇文章主要介紹了Spring的實(shí)例工廠方法和靜態(tài)工廠方法實(shí)例代碼,具有一定借鑒價(jià)值,需要的朋友可以參考下2018-01-01
Java從內(nèi)存角度帶你理解數(shù)組名實(shí)質(zhì)是個(gè)地址的論述
這篇文章主要介紹了Java如何從內(nèi)存解析的角度理解“數(shù)組名實(shí)質(zhì)是一個(gè)地址”,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)吧2022-09-09

