Mybatis?MappedStatement類核心原理詳解
MappedStatement
MappedStatement 類是 Mybatis 框架的核心類之一,它存儲(chǔ)了一個(gè) sql 對(duì)應(yīng)的所有信息
Mybatis 通過解析 XML 和 mapper 接口上的注解,生成 sql 對(duì)應(yīng)的 MappedStatement 實(shí)例,并放入 SqlSessionTemplate 中 configuration 類屬性中
正真執(zhí)行 mapper 接口中的方法時(shí),會(huì)從 configuration 中找到對(duì)應(yīng)的 mappedStatement,然后進(jìn)行后續(xù)的操作
MyBatis通過MappedStatement描述<select|update|insert|delete>或者@Select、@Update等注解配置的SQL信息。在介紹MappedStatement組件之前,我們先來了解一下MyBatis中SQL Mapper的配置。不同類型的SQL語句需要使用對(duì)應(yīng)的XML標(biāo)簽進(jìn)行配置。這些標(biāo)簽提供了很多屬性,用來控制每條SQL語句的執(zhí)行行為。下面是標(biāo)簽中的所有屬性:
<select
id="getUserById"
parameterType="int"
parameterMap="deprecated"
resultType="hashmap"
resultMap="userResultMap"
flushCache="false"
useCache="true"
timeout="10000"
fetchSize="256"
statementType="PREPARED"
resultSetType="FORWARD ONLY">
public final class MappedStatement { private String id; private Integer fetchSize; private Integer timeout; private StatementType statementType; private ResultSetType resultSetType; private SqlSource sqlSource; private Cache cache; private ParameterMap parameterMap; private List<ResultMap> resultMaps; private boolean flushCacheRequired; private boolean useCache; private boolean resultOrdered; private SqlCommandType sqlCommandType; private KeyGenerator keyGenerator; private String[] keyProperties; private String[] keyColumns; private boolean hasNestedResultMaps; private String databaseId; private Log statementLog; private LanguageDriver lang; private String[] resultSets;
對(duì)應(yīng)字段含義
id:在命名空間中唯一的標(biāo)識(shí)符,可以被用來引用這條配置信息。
parameterType:用于指定這條語句的參數(shù)類的完全限定名或別名。這個(gè)屬性是可選的,MyBatis能夠根據(jù)Mapper接口方法中的參數(shù)類型推斷出傳入語句的類型。
parameterMap:引用通過標(biāo)簽定義的參數(shù)映射,該屬性已經(jīng)廢棄。
resultType:從這條語句中返回的期望類型的類的完全限定名或別名。注意,如果返回結(jié)果是集合類型,則resultType屬性應(yīng)該指定集合中可以包含的類型,而不是集合本身。
resultMap:用于引用通過標(biāo)簽配置的實(shí)體屬性與數(shù)據(jù)庫(kù)字段之間建立的結(jié)果集的映射(注意:resultMap和resultType屬性不能同時(shí)使用)。
flushCache:用于控制是否刷新緩存。如果將其設(shè)置為true,則任何時(shí)候只要語句被調(diào)用,都會(huì)導(dǎo)致本地緩存和二級(jí)緩存被清空,默認(rèn)值為false。
useCache:是否使用二級(jí)緩存。如果將其設(shè)置為true,則會(huì)導(dǎo)致本條語句的結(jié)果被緩存在MyBatis的二級(jí)緩存中,對(duì)應(yīng)標(biāo)簽,該屬性的默認(rèn)值為true。
timeout:驅(qū)動(dòng)程序等待數(shù)據(jù)庫(kù)返回請(qǐng)求結(jié)果的秒數(shù),超時(shí)將會(huì)拋出異常。
fetchSize:用于設(shè)置JDBC中Statement對(duì)象的fetchSize屬性,該屬性用于指定SQL執(zhí)行后返回的最大行數(shù)。
statementType:參數(shù)可選值為STATEMENT、PREPARED或CALLABLE,這會(huì)讓MyBatis分別使用Statement、PreparedStatement或CallableStatement與數(shù)據(jù)庫(kù)交互,默認(rèn)值為PREPARED。
resultSetType:參數(shù)可選值為FORWARD_ONLY、SCROLL_SENSITIVE或SCROLL_INSENSITIVE,用于設(shè)置ResultSet對(duì)象的特征,具體可參考第2章JDBC規(guī)范的相關(guān)內(nèi)容。默認(rèn)未設(shè)置,由JDBC驅(qū)動(dòng)決定。
databaseId:如果配置了databaseIdProvider,MyBatis會(huì)加載所有不帶databaseId或匹配當(dāng)前databaseId的語句。
resultOrdered:這個(gè)設(shè)置僅針對(duì)嵌套結(jié)果select語句適用,如果為true,就是假定嵌套結(jié)果包含在一起或分組在一起,這樣的話,當(dāng)返回一個(gè)主結(jié)果行的時(shí)候,就不會(huì)發(fā)生對(duì)前面結(jié)果集引用的情況。這就使得在獲取嵌套結(jié)果集的時(shí)候不至于導(dǎo)致內(nèi)存不夠用,默認(rèn)值為false。
resultSets:這個(gè)設(shè)置僅對(duì)多結(jié)果集的情況適用,它將列出語句執(zhí)行后返回的結(jié)果集并每個(gè)結(jié)果集給一個(gè)名稱,名稱使用逗號(hào)分隔。
lang:該屬性用于指定LanguageDriver實(shí)現(xiàn),MyBatis中的LanguageDriver用于解析<select|update|insert|delete>標(biāo)簽中的SQL語句,生成SqlSource對(duì)象。
介紹jdbc的幾個(gè)相關(guān)屬性
resultSetType
在創(chuàng)建PreparedStatement時(shí),resultSetType參數(shù)設(shè)置的是TYPE_SCROLL_INSENSITIVE或TYPE_SCROLL_SENSITIVE,
這兩個(gè)參數(shù)的共同特點(diǎn)是允許結(jié)果集(ResultSet)的游標(biāo)可以上下移動(dòng)。而默認(rèn)的TYPE_FORWARD_ONLY參數(shù)只允許結(jié)果集的游標(biāo)向下移動(dòng)。
如果PreparedStatement對(duì)象初始化時(shí)resultSetType參數(shù)設(shè)置為TYPE_FORWARD_ONLY,在從ResultSet(結(jié)果集)中讀取記錄的時(shí),對(duì)于訪問過的記錄就自動(dòng)釋放了內(nèi)存。而設(shè)置為TYPE_SCROLL_INSENSITIVE或TYPE_SCROLL_SENSITIVE時(shí)為了保證能游標(biāo)能向上移動(dòng)到任意位置,已經(jīng)訪問過的所有都保留在內(nèi)存中不能釋放。所以大量數(shù)據(jù)加載的時(shí)候,就OOM了。
statement = conn.prepareStatement(querySql,ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY);
resultSetType是設(shè)置ResultSet對(duì)象的類型標(biāo)示可滾動(dòng),或者是不可滾動(dòng)。取值如下:
默認(rèn)只能向前滾動(dòng)。
fetchSize
默認(rèn)情況下pgjdbc driver會(huì)一次性拉取所有結(jié)果集,也就是在executeQuery的時(shí)候。對(duì)于大數(shù)據(jù)量的查詢來說,非常容易造成OOM。這種場(chǎng)景就需要設(shè)置fetchSize,執(zhí)行query的時(shí)候先返回第一批數(shù)據(jù),之后next完一批數(shù)據(jù)之后再去拉取下一批。
場(chǎng)景與方案
場(chǎng)景:java端從數(shù)據(jù)庫(kù)讀取100W數(shù)據(jù)進(jìn)行后臺(tái)業(yè)務(wù)處理。
常規(guī)實(shí)現(xiàn)1:分頁讀取出來。缺點(diǎn):需要排序后分頁讀取,性能低下。
常規(guī)實(shí)現(xiàn)2:一次性讀取出來。缺點(diǎn):需要很大內(nèi)存,一般計(jì)算機(jī)不行。
非常規(guī)實(shí)現(xiàn):建立長(zhǎng)連接,利用服務(wù)端游標(biāo),一條一條流式返回給java端。
非常規(guī)實(shí)現(xiàn)優(yōu)化:jdbc中有個(gè)重要的參數(shù)fetchSize(它對(duì)業(yè)務(wù)實(shí)現(xiàn)無影響,即不會(huì)限制讀取條數(shù)等),優(yōu)化后可顯著提升性能。
public static void getAll(int fetchSize) { try { long beginTime=System.currentTimeMillis(); Connection connection = DriverManager.getConnection(MYSQL_URL); connection.setAutoCommit(false); //為了設(shè)置fetchSize,必須設(shè)置為false String sql = "select * from test"; PreparedStatement psst = connection.prepareStatement(sql,ResultSet.TYPE_FORWARD_ONLY, ResultSet.CONCUR_READ_ONLY); psst.setFetchSize(fetchSize); ResultSet rs = psst.executeQuery(); int totalCount=0; while (rs.next()) { totalCount++; } rs.close(); psst.close(); connection.close(); long endTime=System.currentTimeMillis(); System.out.println("totalCount:"+totalCount+";fetchSize:"+fetchSize+";耗時(shí):"+(endTime-beginTime)+"ms"); } catch (SQLException e) { e.printStackTrace(); } }
原理分析
1、先在服務(wù)端執(zhí)行查詢后將數(shù)據(jù)緩存在服務(wù)端。(耗時(shí)相對(duì)較長(zhǎng))
2、java端獲取數(shù)據(jù)時(shí),利用服務(wù)端游標(biāo)進(jìn)行指針跳動(dòng),如果fetchSize為1000,則一次性跳動(dòng)1000條,返回給java端緩存起來。(耗時(shí)較短,跳動(dòng)次數(shù)為N/1000)
3、在調(diào)用next函數(shù)時(shí),優(yōu)先從緩存中取數(shù),其次執(zhí)行2過程。(內(nèi)存讀取,耗時(shí)可忽略)
MappedStatement是怎么來的
還是以XML配置方式為例進(jìn)行分析,簡(jiǎn)單說下源碼查找的過程。Mapper對(duì)應(yīng)的SQL語句定義在xml文件中,順著源碼會(huì)發(fā)現(xiàn)完成xml解析工作的是XMLMapperBuilder,其中對(duì)xml中“select|insert|update|delete”類型元素的解析方法為buildStatementFromContext;buildStatementFromContext使用了XMLStatementBuilder類對(duì)statement進(jìn)行解析,并最終創(chuàng)建了MappedStatement。
所以,XMLStatementBuilder#parseStatementNode方法就是我們分析的重點(diǎn)。但是,在此之前需要有一點(diǎn)準(zhǔn)備工作要做。由于MappedStatement最終是由MapperBuilderAssistant構(gòu)建的,它其中存儲(chǔ)了一些Mapper級(jí)別的共享信息并應(yīng)用到MappedStatement中。所以,先來簡(jiǎn)單了解下它的由來:
public class XMLMapperBuilder extends BaseBuilder { private final XPathParser parser; private final MapperBuilderAssistant builderAssistant; //省略部分字段和方法 public XMLMapperBuilder(InputStream inputStream, Configuration configuration, String resource, Map<String, XNode> sqlFragments) { this(new XPathParser(inputStream, true, configuration.getVariables(), new XMLMapperEntityResolver()), configuration, resource, sqlFragments); } private XMLMapperBuilder(XPathParser parser, Configuration configuration, String resource, Map<String, XNode> sqlFragments) { super(configuration); this.builderAssistant = new MapperBuilderAssistant(configuration, resource); this.parser = parser; this.sqlFragments = sqlFragments; this.resource = resource; } //省略部分字段和方法 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); //省略部分代碼 buildStatementFromContext(context.evalNodes("select|insert|update|delete")); } catch (Exception e) { throw new BuilderException("Error parsing Mapper XML. The XML location is '" + resource + "'. Cause: " + e, e); } }
好了,下面正是開始XMLStatementBuilder#parseStatementNode的分析了。為了節(jié)省篇幅,我直接通過代碼注釋的方式進(jìn)行說明了,部分我認(rèn)為不關(guān)鍵或不常用的內(nèi)容沒有多說。
/** * parseStatementNode方法是對(duì)select、insert、update、delete這四類元素進(jìn)行解析,大體分為三個(gè)過程: * 1、解析節(jié)點(diǎn)屬性:如我們最常用的id、resultMap等; * 2、解析節(jié)點(diǎn)內(nèi)的sql語句:首先把sql語句中包含的<include></include>等標(biāo)簽轉(zhuǎn)為實(shí)際的sql語句,然后執(zhí)行靜態(tài)或動(dòng)態(tài)節(jié)點(diǎn)處理; * 3、根據(jù)以上解析到的內(nèi)容,使用builderAssistant創(chuàng)建MappedStatement,并加入Configuration中。 * <p> * 以上過程中最關(guān)鍵的是第二步,它會(huì)根據(jù)實(shí)際使用的標(biāo)簽,把sql片段轉(zhuǎn)為不同的SqlNode,以鏈表方式存儲(chǔ)到SqlSource中。 */ public void parseStatementNode() { //獲取標(biāo)簽的id屬性,如selectById,對(duì)應(yīng)Mapper接口中的方法名稱 String id = context.getStringAttribute("id"); //獲取databaseId屬性,我們一般都沒有寫。 String databaseId = context.getStringAttribute("databaseId"); /** * 這段代碼雖然不起眼,但是一定要進(jìn)去看一下:其內(nèi)部完成了對(duì)id的再次賦值, * 處理的方式是:id=namespace+"."+id,也就是當(dāng)前Mapper的完全限定名+"."+id, * 比如我們之前例子中的com.raysonxin.dao.CompanyDao.selectById * 這也是Mapper接口中不能存在重載方法的根本原因。 * */ if (!databaseIdMatchesCurrent(id, databaseId, this.requiredDatabaseId)) { return; } /** * 下面這塊代碼會(huì)依次獲取fetchSize、timeout、resultMap等屬性, * 需要注意的是,有些屬性雖然我們沒有設(shè)置,但是mybatis會(huì)設(shè)置默認(rèn)值, * 具體可以查看mybatis的官方說明。 */ Integer fetchSize = context.getIntAttribute("fetchSize"); Integer timeout = context.getIntAttribute("timeout"); String parameterMap = context.getStringAttribute("parameterMap"); String parameterType = context.getStringAttribute("parameterType"); Class<?> parameterTypeClass = resolveClass(parameterType); String resultMap = context.getStringAttribute("resultMap"); String resultType = context.getStringAttribute("resultType"); String lang = context.getStringAttribute("lang"); //默認(rèn)值:XMLLanguageDriver LanguageDriver langDriver = getLanguageDriver(lang); Class<?> resultTypeClass = resolveClass(resultType); String resultSetType = context.getStringAttribute("resultSetType"); //默認(rèn)值:PREPARED StatementType statementType = StatementType.valueOf(context.getStringAttribute("statementType", StatementType.PREPARED.toString())); //默認(rèn)值:DEFAULT ResultSetType resultSetTypeEnum = resolveResultSetType(resultSetType); String nodeName = context.getNode().getNodeName(); SqlCommandType sqlCommandType = SqlCommandType.valueOf(nodeName.toUpperCase(Locale.ENGLISH)); boolean isSelect = sqlCommandType == SqlCommandType.SELECT; boolean flushCache = context.getBooleanAttribute("flushCache", !isSelect); boolean useCache = context.getBooleanAttribute("useCache", isSelect); boolean resultOrdered = context.getBooleanAttribute("resultOrdered", false); // Include Fragments before parsing /** * 英文注釋也說了,在sql解析前處理 include 標(biāo)簽,比如說,我們include了BaseColumns, * 它會(huì)把這個(gè)include標(biāo)簽替換為BaseColumns內(nèi)的sql內(nèi)容 * */ XMLIncludeTransformer includeParser = new XMLIncludeTransformer(configuration, builderAssistant); includeParser.applyIncludes(context.getNode()); // Parse selectKey after includes and remove them. //處理selectKey,主要針對(duì)不同的數(shù)據(jù)庫(kù)引擎做處理 processSelectKeyNodes(id, parameterTypeClass, langDriver); // Parse the SQL (pre: <selectKey> and <include> were parsed and removed) /** * 到了關(guān)鍵步驟了:就是通過這句代碼完成了從xml標(biāo)簽到SqlSource的轉(zhuǎn)換, * SqlSource是一個(gè)接口,這里返回的可能是DynamicSqlSource、也可能是RawSqlSource, * 取決于xml標(biāo)簽中是否包含動(dòng)態(tài)元素,比如 <if test=""></if> * */ SqlSource sqlSource = langDriver.createSqlSource(configuration, context, parameterTypeClass); String resultSets = context.getStringAttribute("resultSets"); //下面這些是針對(duì)selectKey、KeyGenerator等進(jìn)行處理,暫時(shí)跳過了。 String keyProperty = context.getStringAttribute("keyProperty"); String keyColumn = context.getStringAttribute("keyColumn"); KeyGenerator keyGenerator; String keyStatementId = id + SelectKeyGenerator.SELECT_KEY_SUFFIX; keyStatementId = builderAssistant.applyCurrentNamespace(keyStatementId, true); if (configuration.hasKeyGenerator(keyStatementId)) { keyGenerator = configuration.getKeyGenerator(keyStatementId); } else { keyGenerator = context.getBooleanAttribute("useGeneratedKeys", configuration.isUseGeneratedKeys() && SqlCommandType.INSERT.equals(sqlCommandType)) ? Jdbc3KeyGenerator.INSTANCE : NoKeyGenerator.INSTANCE; } /** * 節(jié)點(diǎn)及屬性都解析完成了,使用builderAssistant創(chuàng)建MappedStatement, * 并保存到Configuration#mappedStatements。 * */ builderAssistant.addMappedStatement(id, sqlSource, statementType, sqlCommandType, fetchSize, timeout, parameterMap, parameterTypeClass, resultMap, resultTypeClass, resultSetTypeEnum, flushCache, useCache, resultOrdered, keyGenerator, keyProperty, keyColumn, databaseId, langDriver, resultSets); }
我們?cè)趚ml中定義的select等語句就是通過這個(gè)parseStatementNode方法解析為MappedStatement的,整體來看比較容易理解,核心就是SqlSource的創(chuàng)建過程,
SqlSource是什么
SqlSource是整個(gè)MappedStatement的核心,MappedStatement其他一大堆字段都是為了準(zhǔn)確的執(zhí)行它而定義的。SqlSource是個(gè)半成品的sql語句,因?yàn)閷?duì)于其中的動(dòng)態(tài)標(biāo)簽還沒靜態(tài)化,其中的參數(shù)也未賦值。正是如此,才為我們后續(xù)的調(diào)用執(zhí)行提供了基礎(chǔ),接下來重點(diǎn)看看SqlSource的構(gòu)建過程。為了先從整體上了解,我畫了一個(gè)時(shí)序圖來描述SqlSource的解析、創(chuàng)建過程。
sqlsource是mapped statement對(duì)象中的一個(gè)屬性, 是一對(duì)一關(guān)系, 在創(chuàng)建mapped statement對(duì)象時(shí)創(chuàng)建,
sqlsource的主要作用就是創(chuàng)建一個(gè)sql語句
SqlSource主要有四種實(shí)現(xiàn)類, 其主要作用的就是以下三種 :
RawSqlSource : 存儲(chǔ)的是只有“#{}”或者沒有標(biāo)簽的純文本sql信息
DynamicSqlSource : 存儲(chǔ)的是寫有“${}”或者具有動(dòng)態(tài)sql標(biāo)簽的sql信息
StaticSqlSource : 是DynamicSqlSource和RawSqlSource解析為BoundSql的一個(gè)中間環(huán)節(jié)
BoundSql
private final String sql; private final List<ParameterMapping> parameterMappings; private final Object parameterObject;
oundSql類的作用就是生成我們最終執(zhí)行的sql語句, 里面包含一些屬性
- sql : 執(zhí)行的sql語句 (包含 ?)
- parameterMappings : 映射關(guān)系
- parameterObject : 參數(shù)值
DynamicSqlSource
public class DynamicSqlSource implements SqlSource { private final Configuration configuration; private final SqlNode rootSqlNode; public DynamicSqlSource(Configuration configuration, SqlNode rootSqlNode) { this.configuration = configuration; this.rootSqlNode = rootSqlNode; } @Override public BoundSql getBoundSql(Object parameterObject) { DynamicContext context = new DynamicContext(configuration, parameterObject); rootSqlNode.apply(context); SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration); Class<?> parameterType = parameterObject == null ? Object.class : parameterObject.getClass(); SqlSource sqlSource = sqlSourceParser.parse(context.getSql(), parameterType, context.getBindings()); BoundSql boundSql = sqlSource.getBoundSql(parameterObject); context.getBindings().forEach(boundSql::setAdditionalParameter); return boundSql; } }
- field
configuration : 上下文, 里面包含了各種參數(shù), 方便使用
- method
getBoundSql :
構(gòu)建DynamicContext對(duì)象, 里邊包含一個(gè)StringJoiner屬性, 遍歷sqlNode節(jié)點(diǎn)是用來拼接sql
將拼接的sql傳入, 將#{}替換成? , 并創(chuàng)建一個(gè)StaticSqlSource對(duì)象
通過StaticSqlSource對(duì)象獲取BoundSql對(duì)象
RawSqlSource
public class RawSqlSource implements SqlSource { private final SqlSource sqlSource; public RawSqlSource(Configuration configuration, SqlNode rootSqlNode, Class<?> parameterType) { this(configuration, getSql(configuration, rootSqlNode), parameterType); } public RawSqlSource(Configuration configuration, String sql, Class<?> parameterType) { SqlSourceBuilder sqlSourceParser = new SqlSourceBuilder(configuration); Class<?> clazz = parameterType == null ? Object.class : parameterType; sqlSource = sqlSourceParser.parse(sql, clazz, new HashMap<>()); } private static String getSql(Configuration configuration, SqlNode rootSqlNode) { DynamicContext context = new DynamicContext(configuration, null); rootSqlNode.apply(context); return context.getSql(); } @Override public BoundSql getBoundSql(Object parameterObject) { return sqlSource.getBoundSql(parameterObject); } }
- field
sqlSource : 此屬性的類型為StaticSqlSource
- method
getBoundSql : 獲取BoundSql對(duì)象
StaticSqlSource
只包含一個(gè)創(chuàng)建BoundSql對(duì)象的方法
public class StaticSqlSource implements SqlSource { private final String sql; private final List<ParameterMapping> parameterMappings; private final Configuration configuration; public StaticSqlSource(Configuration configuration, String sql) { this(configuration, sql, null); } public StaticSqlSource(Configuration configuration, String sql, List<ParameterMapping> parameterMappings) { this.sql = sql; this.parameterMappings = parameterMappings; this.configuration = configuration; } @Override public BoundSql getBoundSql(Object parameterObject) { return new BoundSql(configuration, sql, parameterMappings, parameterObject); }
到此這篇關(guān)于Mybatis MappedStatement類核心原理詳解的文章就介紹到這了,更多相關(guān)Mybatis MappedStatement內(nèi)容請(qǐng)搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!
相關(guān)文章
SpringBoot整合canal實(shí)現(xiàn)數(shù)據(jù)緩存一致性解決方案
canal主要用途是基于?MySQL?數(shù)據(jù)庫(kù)增量日志解析,提供增量數(shù)據(jù)訂閱和消費(fèi),canal是借助于MySQL主從復(fù)制原理實(shí)現(xiàn),本文將給大家介紹SpringBoot整合canal實(shí)現(xiàn)數(shù)據(jù)緩存一致性解決方案,需要的朋友可以參考下2024-03-03SpringBoot中MockMVC單元測(cè)試的實(shí)現(xiàn)
Mock是一種用于模擬和替換類的對(duì)象的方法,以便在單元測(cè)試中獨(dú)立于外部資源進(jìn)行測(cè)試,本文主要介紹了SpringBoot中MockMVC單元測(cè)試的實(shí)現(xiàn),具有應(yīng)該的參考價(jià)值,感興趣的可以了解一下2024-02-02Java設(shè)計(jì)模式七大原則之里氏替換原則詳解
在面向?qū)ο蟮某绦蛟O(shè)計(jì)中,里氏替換原則(Liskov Substitution principle)是對(duì)子類型的特別定義。本文將為大家詳細(xì)介紹Java設(shè)計(jì)模式七大原則之一的里氏替換原則,需要的可以參考一下2022-02-02Java 隊(duì)列實(shí)現(xiàn)原理及簡(jiǎn)單實(shí)現(xiàn)代碼
這篇文章主要介紹了Java 隊(duì)列實(shí)現(xiàn)原理及簡(jiǎn)單實(shí)現(xiàn)代碼的相關(guān)資料,需要的朋友可以參考下2016-10-10FluentMybatis實(shí)現(xiàn)mybatis動(dòng)態(tài)sql拼裝和fluent api語法
本文主要介紹了FluentMybatis實(shí)現(xiàn)mybatis動(dòng)態(tài)sql拼裝和fluent api語法,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2021-08-08java加載properties文件的六種方法總結(jié)
這篇文章主要介紹了java加載properties文件的六種方法總結(jié)的相關(guān)資料,需要的朋友可以參考下2017-05-05SpringBoot 使用 OpenAPI3 規(guī)范整合 knife4j的詳細(xì)過程
Swagger工具集使用OpenAPI規(guī)范,可以生成、展示和測(cè)試基于OpenAPI規(guī)范的API文檔,并提供了生成客戶端代碼的功能,本文給大家介紹SpringBoot使用OpenAPI3規(guī)范整合knife4j的詳細(xì)過程,感興趣的朋友跟隨小編一起看看吧2023-12-12使用MyBatis攔截器實(shí)現(xiàn)SQL的完整打印
當(dāng)我們使用Mybatis結(jié)合Mybatis-plus進(jìn)行開發(fā)時(shí),為了查看執(zhí)行sql的信息通常我們可以通過屬性配置的方式打印出執(zhí)行的sql語句,但這樣的打印出了sql語句常帶有占位符信息,不利于排錯(cuò),所以本文介紹了構(gòu)建MyBatis攔截器,實(shí)現(xiàn)SQL的完整打印,需要的朋友可以參考下2024-07-07