摘要:的解析和運(yùn)行原理構(gòu)建過(guò)程提供創(chuàng)建的核心接口。在構(gòu)造器初始化時(shí)會(huì)根據(jù)和的方法解析為命令。數(shù)據(jù)庫(kù)會(huì)話(huà)器定義了一個(gè)對(duì)象的適配器,它是一個(gè)接口對(duì)象,構(gòu)造器根據(jù)配置來(lái)適配對(duì)應(yīng)的對(duì)象。它的作用是給實(shí)現(xiàn)類(lèi)對(duì)象的使用提供一個(gè)統(tǒng)一簡(jiǎn)易的使用適配器。
MyBatis的解析和運(yùn)行原理 構(gòu)建SqlSessionFactory過(guò)程
SqlSessionFactory提供創(chuàng)建MyBatis的核心接口SqlSession。MyBatis采用構(gòu)造模式去創(chuàng)建SqlSessionFactory,我們可以通過(guò)SqlSessionFactoryBuilder去構(gòu)建。
第一步,通過(guò)XMLConfigBuilder解析配置的XML文件,讀出配置參數(shù),并將讀取的數(shù)據(jù)存入這個(gè)Configuration類(lèi)中。
第二步,使用Configuration對(duì)象去創(chuàng)建SqlSessionFactory。
SqlSessionFactoryBuilder的源碼:
public class SqlSessionFactoryBuilder { ..... public SqlSessionFactory build(InputStream inputStream, String environment, Properties properties) { try { XMLConfigBuilder parser = new XMLConfigBuilder(inputStream, environment, properties); // XMLConfigBuilder解析配置的XML文件,構(gòu)建Configuration 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. } } } // 使用Configuration對(duì)象去創(chuàng)建SqlSessionFactory public SqlSessionFactory build(Configuration config) { // SqlSessionFactory是一個(gè)接口,為此MyBatis提供了一個(gè)默認(rèn)實(shí)現(xiàn)類(lèi) return new DefaultSqlSessionFactory(config); } }構(gòu)建Configuration
在XMLConfigBuilder中,MyBatis會(huì)讀出所有XML配置的信息,然后將這些信息保存到Configuration類(lèi)的單例中。
它會(huì)做如下初始化:
properties全局參數(shù)
setting設(shè)置
typeAliases別名
typeHandler類(lèi)型處理器
ObjectFactory對(duì)象
plugin插件
environment環(huán)境
DatabaseIdProvider數(shù)據(jù)庫(kù)標(biāo)識(shí)
Mapper映射器
XMLConfigBuilder的源碼:
public class XMLConfigBuilder extends BaseBuilder { ... public Configuration parse() { if (parsed) { throw new BuilderException("Each XMLConfigBuilder can only be used once."); } parsed = true; // 解析配置文件,設(shè)置Configuration parseConfiguration(parser.evalNode("/configuration")); return configuration; } private void parseConfiguration(XNode root) { // 讀出MyBatis配置文件中的configuration下的各個(gè)子標(biāo)簽元素 // 把全部信息保存到Configuration類(lèi)的單例中 try { //issue #117 read properties first propertiesElement(root.evalNode("properties")); Properties settings = settingsAsProperties(root.evalNode("settings")); loadCustomVfs(settings); typeAliasesElement(root.evalNode("typeAliases")); pluginElement(root.evalNode("plugins")); objectFactoryElement(root.evalNode("objectFactory")); objectWrapperFactoryElement(root.evalNode("objectWrapperFactory")); reflectorFactoryElement(root.evalNode("reflectorFactory")); settingsElement(settings); // read it after objectFactory and objectWrapperFactory issue #631 environmentsElement(root.evalNode("environments")); databaseIdProviderElement(root.evalNode("databaseIdProvider")); typeHandlerElement(root.evalNode("typeHandlers")); // 設(shè)置mapper映射器 mapperElement(root.evalNode("mappers")); } catch (Exception e) { throw new BuilderException("Error parsing SQL Mapper Configuration. Cause: " + e, e); } } }映射器的內(nèi)部組成
XMLMapperBuilder負(fù)責(zé)對(duì)配置文件中的Mapper映射器進(jìn)行解析,其中在configurationElement方法中可以看出來(lái),會(huì)分別對(duì)配置文件中的parameterMap、resultMap、sql、select|insert|update|delete元素進(jìn)行解析。
public class XMLMapperBuilder extends BaseBuilder { public void parse() { if (!configuration.isResourceLoaded(resource)) { // 解析配置文件中的mapper映射器 configurationElement(parser.evalNode("/mapper")); configuration.addLoadedResource(resource); bindMapperForNamespace(); } parsePendingResultMaps(); parsePendingCacheRefs(); parsePendingStatements(); } private void configurationElement(XNode context) { try { String namespace = context.getStringAttribute("namespace"); if (namespace == null || namespace.equals("")) { throw new BuilderException("Mapper"s namespace cannot be empty"); } builderAssistant.setCurrentNamespace(namespace); cacheRefElement(context.evalNode("cache-ref")); cacheElement(context.evalNode("cache")); // 解析我們配置的parameterMap元素 parameterMapElement(context.evalNodes("/mapper/parameterMap")); // 解析我們配置的resultMap元素 resultMapElements(context.evalNodes("/mapper/resultMap")); // 解析我們配置的sql元素 sqlElement(context.evalNodes("/mapper/sql")); // 解析我們配置的select、insert、update、delete元素 buildStatementFromContext(context.evalNodes("select|insert|update|delete")); } catch (Exception e) { throw new BuilderException("Error parsing Mapper XML. Cause: " + e, e); } } }
在方法buildStatementFromContext()中,會(huì)根據(jù)配置信息創(chuàng)建一個(gè)MappedStatement對(duì)象。
MappedStatement,它保存映射器的一個(gè)節(jié)點(diǎn)(select|insert|update|delete)。包括許多我們配置的SQL、SQL的id、緩存信息、resultMap、parameterType、resultType、languageDriver等重要配置內(nèi)容。
public final class MappedStatement { private Configuration configuration; private String id; private StatementType statementType; private ResultSetType resultSetType; private SqlSource sqlSource; private Cache cache; private ParameterMap parameterMap; private ListresultMaps; private boolean flushCacheRequired; private boolean useCache; private SqlCommandType sqlCommandType; private KeyGenerator keyGenerator; private String databaseId; private LanguageDriver lang; ...... }
SqlSource,它是提供BoundSql對(duì)象的地方,它是MappedStatement的一個(gè)屬性。
public interface SqlSource { BoundSql getBoundSql(Object parameterObject); }
BoundSql,它是建立SQL和參數(shù)的地方。
public class BoundSql { private final String sql; private final ListSqlSession運(yùn)行過(guò)程parameterMappings; private final Object parameterObject; private final Map additionalParameters; private final MetaObject metaParameters; }
SqlSession是一個(gè)接口,在MyBatis中有一個(gè)默認(rèn)實(shí)現(xiàn)DefaultSqlSession。我們構(gòu)建SqlSessionFactory就可以輕易地拿到SqlSession了。通過(guò)SqlSession,我們拿到Mapper,之后可以做查詢(xún)、插入、更新、刪除的方法。
UserMapper userMapper = sqlSession.getMapper(UserMapper.class);
其實(shí)getMapper()方法拿到的mapper是通過(guò)Java動(dòng)態(tài)代理實(shí)現(xiàn)的。從getMapper()方法逐級(jí)往下看,可以發(fā)現(xiàn)在MapperRegistry類(lèi)的getMapper()方法中會(huì)拿到一個(gè)MapperProxyFactory的對(duì)象,最后是通過(guò)MapperProxyFactory對(duì)象去生成一個(gè)Mapper的。
public class DefaultSqlSession implements SqlSession { ..... @Override public映射器的動(dòng)態(tài)代理T getMapper(Class type) { return configuration. getMapper(type, this); } } public class Configuration { ..... public T getMapper(Class type, SqlSession sqlSession) { return mapperRegistry.getMapper(type, sqlSession); } } public class MapperRegistry { ...... public T getMapper(Class type, SqlSession sqlSession) { final MapperProxyFactory mapperProxyFactory = (MapperProxyFactory ) 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); } } }
Mapper映射是通過(guò)動(dòng)態(tài)代理實(shí)現(xiàn)的,MapperProxyFactory用來(lái)生成動(dòng)態(tài)代理對(duì)象。
public class MapperProxyFactory{ ...... protected T newInstance(MapperProxy mapperProxy) { // 動(dòng)態(tài)代理 return (T) Proxy.newProxyInstance(mapperInterface.getClassLoader(), new Class[] { mapperInterface }, mapperProxy); } public T newInstance(SqlSession sqlSession) { final MapperProxy mapperProxy = new MapperProxy (sqlSession, mapperInterface, methodCache); return newInstance(mapperProxy); } }
在MapperProxyFactory的newInstance方法中可以看到有一個(gè)MapperProxy對(duì)象,MapperProxy實(shí)現(xiàn)InvocationHandler接口(動(dòng)態(tài)代理需要實(shí)現(xiàn)這一接口)的代理方法invoke(), 這invoke()方法實(shí)現(xiàn)對(duì)被代理類(lèi)的方法進(jìn)行攔截。
而在invoke()方法中,MapperMethod對(duì)象會(huì)執(zhí)行Mapper接口的查詢(xún)或其他方法。
public class MapperProxyimplements InvocationHandler, Serializable { @Override public Object invoke(Object proxy, Method method, Object[] args) throws Throwable { try { // 先判斷是否一個(gè)類(lèi),在這里Mapper顯然是一個(gè)接口 if (Object.class.equals(method.getDeclaringClass())) { return method.invoke(this, args); // 判斷是不是接口默認(rèn)實(shí)現(xiàn)方法 } else if (isDefaultMethod(method)) { return invokeDefaultMethod(proxy, method, args); } } catch (Throwable t) { throw ExceptionUtil.unwrapThrowable(t); } // 緩存中取出MapperMethod,不存在的話(huà),則根據(jù)Configuration初始化一個(gè) final MapperMethod mapperMethod = cachedMapperMethod(method); // 執(zhí)行Mapper接口的查詢(xún)或其他方法 return mapperMethod.execute(sqlSession, args); } private MapperMethod cachedMapperMethod(Method method) { MapperMethod mapperMethod = methodCache.get(method); if (mapperMethod == null) { mapperMethod = new MapperMethod(mapperInterface, method, sqlSession.getConfiguration()); methodCache.put(method, mapperMethod); } return mapperMethod; } }
MapperMethod采用命令模式運(yùn)行,并根據(jù)上下文跳轉(zhuǎn)。MapperMethod在構(gòu)造器初始化時(shí)會(huì)根據(jù)Configuration和Mapper的Method方法解析為SqlCommand命令。之后在execute方法,根據(jù)SqlCommand的Type進(jìn)行跳轉(zhuǎn)。然后采用命令模式,SqlSession通過(guò)SqlCommand執(zhí)行插入、更新、查詢(xún)、選擇等方法。
public MapperMethod(Class> mapperInterface, Method method, Configuration config) { // 根據(jù)Configuration和Mapper的Method方法解析為SqlCommand this.command = new SqlCommand(config, mapperInterface, method); this.method = new MethodSignature(config, mapperInterface, method); } public Object execute(SqlSession sqlSession, Object[] args) { Object result; // 根據(jù)Type進(jìn)行跳轉(zhuǎn),通過(guò)sqlSession執(zhí)行相關(guān)的操作 switch (command.getType()) { case INSERT: { 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: 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 { Object param = method.convertArgsToSqlCommandParam(args); result = sqlSession.selectOne(command.getName(), param); } 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; }
看到這里,應(yīng)該大概知道了MyBatis為什么只用Mapper接口便能夠運(yùn)行SQL,因?yàn)橛成淦鞯腦ML文件的命名空間namespace對(duì)應(yīng)的便是這個(gè)接口的全路徑,那么它根據(jù)全路徑和方法名便能夠綁定起來(lái),通過(guò)動(dòng)態(tài)代理技術(shù),讓這個(gè)接口跑起來(lái)。而后采用命令模式,最后還是使用SqlSession接口的方法使得它能夠執(zhí)行查詢(xún),有了這層封裝我們便可以使用這個(gè)接口編程。
不過(guò)還是可以看到,最后插入、更新、刪除、查詢(xún)操作還是會(huì)回到SqlSession中進(jìn)行處理。
我們已經(jīng)知道了映射器其實(shí)就是一個(gè)動(dòng)態(tài)代理對(duì)象,進(jìn)入到了MapperMethod的execute方法。它經(jīng)過(guò)簡(jiǎn)單判斷就是進(jìn)入了SqlSession的刪除、更新、插入、選擇等方法。sqlSession執(zhí)行一個(gè)查詢(xún)操作??梢钥吹绞峭ㄟ^(guò)一個(gè)executor來(lái)執(zhí)行的。
其實(shí)SqlSession中的Executor執(zhí)行器負(fù)責(zé)調(diào)度StatementHandler、ParameterHandler、ResultHandler等來(lái)執(zhí)行相關(guān)的SQL。
StatementHandler:使用數(shù)據(jù)庫(kù)的Statement(PrepareStatement)執(zhí)行操作
ParameterHandler:用于SQL對(duì)參數(shù)的處理
ResultHandler:進(jìn)行最后數(shù)據(jù)集(ResultSet)的封裝返回處理
Sqlsession其實(shí)是一個(gè)接口,它有一個(gè)DefaultSqlSession的默認(rèn)實(shí)現(xiàn)類(lèi)。
public class DefaultSqlSession implements SqlSession { private final Configuration configuration; // Executor執(zhí)行器,負(fù)責(zé)調(diào)度SQL的執(zhí)行 private final Executor executor; ...... @Override publicExecutor執(zhí)行器List selectList(String statement, Object parameter, RowBounds rowBounds) { try { MappedStatement ms = configuration.getMappedStatement(statement); // 通過(guò)executor執(zhí)行查詢(xún)操作 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(); } } }
執(zhí)行器起到了至關(guān)重要的作用,它是一個(gè)真正執(zhí)行Java和數(shù)據(jù)庫(kù)交互的東西。在MyBatis中存在三種執(zhí)行器,我們可以在MyBatis的配置文件中進(jìn)行選擇。
SIMPLE,簡(jiǎn)易執(zhí)行器
REUSE,是一種執(zhí)行器重用預(yù)處理語(yǔ)句
BATCH,執(zhí)行器重用語(yǔ)句和批量更新,她是針對(duì)批量專(zhuān)用的執(zhí)行器
它們都提供了查詢(xún)和更新方法,以及相關(guān)的事務(wù)方法。
Executor是通過(guò)Configuration類(lèi)創(chuàng)建的,MyBatis將根據(jù)配置類(lèi)型去確定你需要?jiǎng)?chuàng)建三種執(zhí)行器中的哪一種。
public class Configuration { ...... 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); } // MyBatis插件,構(gòu)建一層層的動(dòng)態(tài)代理對(duì)象 // 在調(diào)度真實(shí)的方法之前執(zhí)行配置插件的代碼 executor = (Executor) interceptorChain.pluginAll(executor); return executor; } }
顯然MyBatis根據(jù)Configuration來(lái)構(gòu)建StatementHandler,然后使用prepareStatement方法,對(duì)SQL編譯并對(duì)參數(shù)進(jìn)行初始化,resultHandler再組裝查詢(xún)結(jié)果返回給調(diào)用者來(lái)完成一次查詢(xún)。
public class SimpleExecutor extends BaseExecutor { ..... @Override publicStatementHandler數(shù)據(jù)庫(kù)會(huì)話(huà)器List doQuery(MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) throws SQLException { Statement stmt = null; try { Configuration configuration = ms.getConfiguration(); // 根據(jù)Configuration來(lái)構(gòu)建StatementHandler StatementHandler handler = configuration.newStatementHandler(wrapper, ms, parameter, rowBounds, resultHandler, boundSql); // 對(duì)SQL編譯并對(duì)參數(shù)進(jìn)行初始化 stmt = prepareStatement(handler, ms.getStatementLog()); // 組裝查詢(xún)結(jié)果返回給調(diào)用者 return handler. query(stmt, resultHandler); } finally { closeStatement(stmt); } } private Statement prepareStatement(StatementHandler handler, Log statementLog) throws SQLException { Statement stmt; Connection connection = getConnection(statementLog); // 進(jìn)行預(yù)編譯和基礎(chǔ)設(shè)置 stmt = handler.prepare(connection, transaction.getTimeout()); // 設(shè)置參數(shù) handler.parameterize(stmt); return stmt; } }
StatementHandler就是專(zhuān)門(mén)處理數(shù)據(jù)庫(kù)會(huì)話(huà)的。
創(chuàng)建StatementHandler:
public class Configuration { ...... 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); // MyBatis插件,生成一層層的動(dòng)態(tài)代理對(duì)象 statementHandler = (StatementHandler) interceptorChain.pluginAll(statementHandler); return statementHandler; } }
RoutingStatementHandler其實(shí)不是我們真實(shí)的服務(wù)對(duì)象,它是通過(guò)適配模式找到對(duì)應(yīng)的StatementHandler來(lái)執(zhí)行。
StatementHandler分為三種:
SimleStatementHandler
PrepareStatementHandler
CallableStatementHandler
在初始化RoutingStatementHandler對(duì)象的時(shí)候它會(huì)根據(jù)上下文環(huán)境來(lái)決定創(chuàng)建哪個(gè)StatementHandler對(duì)象。
public class RoutingStatementHandler implements StatementHandler { ...... private final StatementHandler delegate; public RoutingStatementHandler(Executor executor, MappedStatement ms, Object parameter, RowBounds rowBounds, ResultHandler resultHandler, BoundSql boundSql) { switch (ms.getStatementType()) { case STATEMENT: delegate = new SimpleStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql); break; case PREPARED: delegate = new PreparedStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql); break; case CALLABLE: delegate = new CallableStatementHandler(executor, ms, parameter, rowBounds, resultHandler, boundSql); break; default: throw new ExecutorException("Unknown statement type: " + ms.getStatementType()); } } }
數(shù)據(jù)庫(kù)會(huì)話(huà)器定義了一個(gè)對(duì)象的適配器delegate,它是一個(gè)StatementHandler接口對(duì)象,構(gòu)造器根據(jù)配置來(lái)適配對(duì)應(yīng)的StatementHandler對(duì)象。它的作用是給實(shí)現(xiàn)類(lèi)對(duì)象的使用提供一個(gè)統(tǒng)一、簡(jiǎn)易的使用適配器。此為對(duì)象的適配模式,可以讓我們使用現(xiàn)有的類(lèi)和方法對(duì)外提供服務(wù),也可以根據(jù)實(shí)際的需求對(duì)外屏蔽一些方法,甚至加入新的服務(wù)。
在執(zhí)行器Executor執(zhí)行查詢(xún)操作的時(shí)候,我們看到PreparedStatementHandler的三個(gè)方法:prepare、parameterize和query。
public abstract class BaseStatementHandler implements StatementHandler { ..... @Override public Statement prepare(Connection connection, Integer transactionTimeout) throws SQLException { ErrorContext.instance().sql(boundSql.getSql()); Statement statement = null; try { // 對(duì)SQL進(jìn)行了預(yù)編譯 statement = instantiateStatement(connection); setStatementTimeout(statement, transactionTimeout); setFetchSize(statement); return statement; } catch (SQLException e) { closeStatement(statement); throw e; } catch (Exception e) { closeStatement(statement); throw new ExecutorException("Error preparing statement. Cause: " + e, e); } } } public class PreparedStatementHandler extends BaseStatementHandler { @Override public void parameterize(Statement statement) throws SQLException { // 設(shè)置參數(shù) parameterHandler.setParameters((PreparedStatement) statement); } @Override protected Statement instantiateStatement(Connection connection) throws SQLException { String sql = boundSql.getSql(); if (mappedStatement.getKeyGenerator() instanceof Jdbc3KeyGenerator) { String[] keyColumnNames = mappedStatement.getKeyColumns(); if (keyColumnNames == null) { return connection.prepareStatement(sql, PreparedStatement.RETURN_GENERATED_KEYS); } else { return connection.prepareStatement(sql, keyColumnNames); } } else if (mappedStatement.getResultSetType() != null) { return connection.prepareStatement(sql, mappedStatement.getResultSetType().getValue(), ResultSet.CONCUR_READ_ONLY); } else { return connection.prepareStatement(sql); } } @Override publicList query(Statement statement, ResultHandler resultHandler) throws SQLException { PreparedStatement ps = (PreparedStatement) statement; // 執(zhí)行SQL ps.execute(); // resultSetHandler封裝結(jié)果返回 return resultSetHandler. handleResultSets(ps); } }
一條查詢(xún)SQL的執(zhí)行過(guò)程,Executor會(huì)先調(diào)用StatementHandler的prepare()方法預(yù)編譯SQL語(yǔ)句,同時(shí)設(shè)置一些基本運(yùn)行的參數(shù)。然后用parameterize()方法啟動(dòng)ParameterHandler設(shè)置參數(shù),完成預(yù)編譯,跟著就是執(zhí)行查詢(xún)。
文章版權(quán)歸作者所有,未經(jīng)允許請(qǐng)勿轉(zhuǎn)載,若此文章存在違規(guī)行為,您可以聯(lián)系管理員刪除。
轉(zhuǎn)載請(qǐng)注明本文地址:http://systransis.cn/yun/69531.html
摘要:從使用到原理學(xué)習(xí)線(xiàn)程池關(guān)于線(xiàn)程池的使用,及原理分析分析角度新穎面向切面編程的基本用法基于注解的實(shí)現(xiàn)在軟件開(kāi)發(fā)中,分散于應(yīng)用中多出的功能被稱(chēng)為橫切關(guān)注點(diǎn)如事務(wù)安全緩存等。 Java 程序媛手把手教你設(shè)計(jì)模式中的撩妹神技 -- 上篇 遇一人白首,擇一城終老,是多么美好的人生境界,她和他歷經(jīng)風(fēng)雨慢慢變老,回首走過(guò)的點(diǎn)點(diǎn)滴滴,依然清楚的記得當(dāng)初愛(ài)情萌芽的模樣…… Java 進(jìn)階面試問(wèn)題列表 -...
摘要:哪吒社區(qū)技能樹(shù)打卡打卡貼函數(shù)式接口簡(jiǎn)介領(lǐng)域優(yōu)質(zhì)創(chuàng)作者哪吒公眾號(hào)作者架構(gòu)師奮斗者掃描主頁(yè)左側(cè)二維碼,加入群聊,一起學(xué)習(xí)一起進(jìn)步歡迎點(diǎn)贊收藏留言前情提要無(wú)意間聽(tīng)到領(lǐng)導(dǎo)們的談話(huà),現(xiàn)在公司的現(xiàn)狀是碼農(nóng)太多,但能獨(dú)立帶隊(duì)的人太少,簡(jiǎn)而言之,不缺干 ? 哪吒社區(qū)Java技能樹(shù)打卡?【打卡貼 day2...
摘要:插件插件接口在中使用插件,我們必須實(shí)現(xiàn)接口。它將直接覆蓋你所攔截對(duì)象原有的方法,因此它是插件的核心方法。插件在對(duì)象中的保存插件的代理和反射設(shè)計(jì)插件用的是責(zé)任鏈模式,的責(zé)任鏈?zhǔn)怯扇ザx的。 插件 1、插件接口 在MyBatis中使用插件,我們必須實(shí)現(xiàn)接口Interceptor。 public interface Interceptor { // 它將直接覆蓋你所攔截對(duì)象原有的方法,因...
摘要:目錄其中每個(gè)章節(jié)知識(shí)點(diǎn)都是相關(guān)連由淺入深的一步步全面分析了技術(shù)原理以及實(shí)戰(zhàn)由于文案較長(zhǎng)想深入學(xué)習(xí)以及對(duì)于該文檔感興趣的朋友們可以加群免費(fèi)獲取。這些場(chǎng)景在大量的編碼中使用,具備較強(qiáng)的實(shí)用價(jià)值,這些內(nèi)容都是通過(guò)實(shí)戰(zhàn)得來(lái)的,供讀者們參考。 前言系統(tǒng)掌握MyBatis編程技巧已經(jīng)成了用Java構(gòu)建移動(dòng)互聯(lián)網(wǎng)網(wǎng)站的必要條件 本文主要講解了Mybatis的應(yīng)用,解析了其原理,從而形成一個(gè)完整的知識(shí)...
閱讀 3332·2023-04-25 16:25
閱讀 3861·2021-11-15 18:01
閱讀 1620·2021-09-10 11:21
閱讀 3026·2021-08-02 16:53
閱讀 3094·2019-08-30 15:55
閱讀 2499·2019-08-29 16:24
閱讀 2112·2019-08-29 13:14
閱讀 1046·2019-08-29 13:00