Java數(shù)據(jù)庫連接池之DBCP淺析_動力節(jié)點Java學(xué)院整理
一. 為何要使用數(shù)據(jù)庫連接池
假設(shè)網(wǎng)站一天有很大的訪問量,數(shù)據(jù)庫服務(wù)器就需要為每次連接創(chuàng)建一次數(shù)據(jù)庫連接,極大的浪費數(shù)據(jù)庫的資源,并且極易造成數(shù)據(jù)庫服務(wù)器內(nèi)存溢出、拓機(jī)。
數(shù)據(jù)庫連接是一種關(guān)鍵的有限的昂貴的資源,這一點在多用戶的網(wǎng)頁應(yīng)用程序中體現(xiàn)的尤為突出.對數(shù)據(jù)庫連接的管理能顯著影響到整個應(yīng)用程序的伸縮性和健壯性,影響到程序的性能指標(biāo).數(shù)據(jù)庫連接池正式針對這個問題提出來的.數(shù)據(jù)庫連接池負(fù)責(zé)分配,管理和釋放數(shù)據(jù)庫連接,它允許應(yīng)用程序重復(fù)使用一個現(xiàn)有的數(shù)據(jù)庫連接,而不是重新建立一個。
數(shù)據(jù)庫連接池在初始化時將創(chuàng)建一定數(shù)量的數(shù)據(jù)庫連接放到連接池中, 這些數(shù)據(jù)庫連接的數(shù)量是由最小數(shù)據(jù)庫連接數(shù)來設(shè)定的.無論這些數(shù)據(jù)庫連接是否被使用,連接池都將一直保證至少擁有這么多的連接數(shù)量.連接池的最大數(shù)據(jù)庫連接數(shù)量限定了這個連接池能占有的最大連接數(shù),當(dāng)應(yīng)用程序向連接池請求的連接數(shù)超過最大連接數(shù)量時,這些請求將被加入到等待隊列中.
數(shù)據(jù)庫連接池的最小連接數(shù)和最大連接數(shù)的設(shè)置要考慮到以下幾個因素:
1, 最小連接數(shù):是連接池一直保持的數(shù)據(jù)庫連接,所以如果應(yīng)用程序?qū)?shù)據(jù)庫連接的使用量不大,將會有大量的數(shù)據(jù)庫連接資源被浪費.
2, 最大連接數(shù):是連接池能申請的最大連接數(shù),如果數(shù)據(jù)庫連接請求超過次數(shù),后面的數(shù)據(jù)庫連接請求將被加入到等待隊列中,這會影響以后的數(shù)據(jù)庫操作
3, 如果最小連接數(shù)與最大連接數(shù)相差很大:那么最先連接請求將會獲利,之后超過最小連接數(shù)量的連接請求等價于建立一個新的數(shù)據(jù)庫連接.不過,這些大于最小連接數(shù)的數(shù)據(jù)庫連接在使用完不會馬上被釋放,他將被 放到連接池中等待重復(fù)使用或是空間超時后被釋放.
二, 數(shù)據(jù)庫連接池的原理及實現(xiàn)
到了這里我們已經(jīng)知道數(shù)據(jù)庫連接池是用來做什么的了, 下面我們就來說數(shù)據(jù)庫連接池是如何來實現(xiàn)的.
1, 建立一個數(shù)據(jù)庫連接池pool, 池中有若干個Connection 對象, 當(dāng)用戶發(fā)來請求需要進(jìn)行數(shù)據(jù)庫交互時則會使用池中第一個Connection對象.
2, 當(dāng)本次連接結(jié)束時, 再將這個Connection對象歸還池中, 這樣就可以保證池中一直有足夠的Connection對象.
public class SimplePoolDemo {
//創(chuàng)建一個連接池
private static LinkedList<Connection> pool = new LinkedList<Connection>();
//初始化10個連接
static{
try {
for (int i = 0; i < 10; i++) {
Connection conn = DBUtils.getConnection();//得到一個連接
pool.add(conn);
}
} catch (Exception e) {
throw new ExceptionInInitializerError("數(shù)據(jù)庫連接失敗,請檢查配置");
}
}
//從池中獲取一個連接
public static Connection getConnectionFromPool(){
return pool.removeFirst();//移除一個連接對象
}
//釋放資源
public static void release(Connection conn){
pool.addLast(conn);
}
}
以上的Demo就是一個簡單的數(shù)據(jù)庫連接池的例子, 先在靜態(tài)代碼塊中初始化10個Connection對象, 當(dāng)本次請求結(jié)束后再將Connection添加進(jìn)池中.
這只是我們自己手動去實現(xiàn)的, 當(dāng)然在實際生產(chǎn)中并不需要我們?nèi)ナ謩尤憯?shù)據(jù)庫連接池. 下面就重點講DBCP和C3P0的實現(xiàn)方式.
三, DBCP連接池
首先我們來看DBCP 的例子, 然后根據(jù)例子來分析:
#連接設(shè)置 driverClassName=com.mysql.jdbc.Driver url=jdbc:mysql://localhost:3306/bjpowernode username=root password=abc #<!-- 初始化連接 --> initialSize=10 #最大連接數(shù)量 maxActive=50 #<!-- 最大空閑連接 --> maxIdle=20 #<!-- 最小空閑連接 --> minIdle=5 #<!-- 超時等待時間以毫秒為單位 60000毫秒/1000等于60秒 --> maxWait=60000 #JDBC驅(qū)動建立連接時附帶的連接屬性屬性的格式必須為這樣:[屬性名=property;] #注意:"user" 與 "password" 兩個屬性會被明確地傳遞,因此這里不需要包含他們。 connectionProperties=useUnicode=true;characterEncoding=utf8 #指定由連接池所創(chuàng)建的連接的自動提交(auto-commit)狀態(tài)。 defaultAutoCommit=true #driver default 指定由連接池所創(chuàng)建的連接的只讀(read-only)狀態(tài)。 #如果沒有設(shè)置該值,則“setReadOnly”方法將不被調(diào)用。(某些驅(qū)動并不支持只讀模式,如:Informix) defaultReadOnly= #driver default 指定由連接池所創(chuàng)建的連接的事務(wù)級別(TransactionIsolation)。 #可用值為下列之一:(詳情可見javadoc。)NONE,READ_UNCOMMITTED, READ_COMMITTED, REPEATABLE_READ, SERIALIZABLE defaultTransactionIsolation=REPEATABLE_READ
DBCP配置文件
DBCPUtils:
public class DBCPUtils {
private static DataSource ds;//定義一個連接池對象
static{
try {
Properties pro = new Properties();
pro.load(DBCPUtils.class.getClassLoader().getResourceAsStream("dbcpconfig.properties"));
ds = BasicDataSourceFactory.createDataSource(pro);//得到一個連接池對象
} catch (Exception e) {
throw new ExceptionInInitializerError("初始化連接錯誤,請檢查配置文件!");
}
}
//從池中獲取一個連接
public static Connection getConnection() throws SQLException{
return ds.getConnection();
}
public static void closeAll(ResultSet rs,Statement stmt,Connection conn){
if(rs!=null){
try {
rs.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(stmt!=null){
try {
stmt.close();
} catch (SQLException e) {
e.printStackTrace();
}
}
if(conn!=null){
try {
conn.close();//關(guān)閉
} catch (SQLException e) {
e.printStackTrace();
}
}
}
}
在這個closeAll方法中, conn.close(); 這個地方會將connection還回到池子中嗎? DataSource 中是如何處理close()方法的呢?
上面的兩個問題就讓我們一起來看看源碼是如何來實現(xiàn)的吧.
這里我們從ds.getConnection();入手, 看看一個數(shù)據(jù)源DataSource是如何創(chuàng)建connection的.
用eclipse導(dǎo)入:commons-dbcp-1.4-src.zip和commons-pool-1.5.6-src.zip則可查看源碼:
BasicDataSource.class:(implements DataSource)
public Connection getConnection() throws SQLException {
return createDataSource().getConnection();
}
接下來看createDataSoruce() 方法:
protected synchronized DataSource createDataSource()
throws SQLException {
if (closed) {
throw new SQLException("Data source is closed");
}
// Return the pool if we have already created it
if (dataSource != null) {
return (dataSource);
}
// create factory which returns raw physical connections
ConnectionFactory driverConnectionFactory = createConnectionFactory();
// create a pool for our connections
createConnectionPool();
// Set up statement pool, if desired
GenericKeyedObjectPoolFactory statementPoolFactory = null;
if (isPoolPreparedStatements()) {
statementPoolFactory = new GenericKeyedObjectPoolFactory(null,
-1, // unlimited maxActive (per key)
GenericKeyedObjectPool.WHEN_EXHAUSTED_FAIL,
0, // maxWait
1, // maxIdle (per key)
maxOpenPreparedStatements);
}
// Set up the poolable connection factory
createPoolableConnectionFactory(driverConnectionFactory, statementPoolFactory, abandonedConfig);
// Create and return the pooling data source to manage the connections
createDataSourceInstance();
try {
for (int i = 0 ; i < initialSize ; i++) {
connectionPool.addObject();
}
} catch (Exception e) {
throw new SQLNestedException("Error preloading the connection pool", e);
}
return dataSource;
}
從源代碼可以看出,createDataSource()方法通過7步,逐步構(gòu)造出一個數(shù)據(jù)源,下面是詳細(xì)的步驟:
1、檢查數(shù)據(jù)源是否關(guān)閉或者是否創(chuàng)建完成,如果關(guān)閉了就拋異常,如果已經(jīng)創(chuàng)建完成就直接返回。
2、調(diào)用createConnectionFactory()創(chuàng)建JDBC連接工廠driverConnectionFactory,這個工廠使用數(shù)據(jù)庫驅(qū)動來創(chuàng)建最底層的JDBC連接
3、調(diào)用createConnectionPool()創(chuàng)建數(shù)據(jù)源使用的連接池,連接池顧名思義就是緩存JDBC連接的地方。
4、如果需要就設(shè)置statement的緩存池,這個一般不需要設(shè)置
5、調(diào)用createPoolableConnectionFactory創(chuàng)建PoolableConnection的工廠,這個工廠使用上述driverConnectionFactory來創(chuàng)建底層JDBC連接,然后包裝出一個PoolableConnection,這個PoolableConnection與連接池設(shè)置了一對多的關(guān)系,也就是說,連接池中存在多個PoolableConnection,每個PoolableConnection都關(guān)聯(lián)同一個連接池,這樣的好處是便于該表PoolableConnection的close方法的行為,具體會在后面詳細(xì)分析。
6、調(diào)用createDataSourceInstance()創(chuàng)建內(nèi)部數(shù)據(jù)源
7、為連接池中添加PoolableConnection
經(jīng)過以上7步,一個數(shù)據(jù)源就形成了,這里明確一點,一個數(shù)據(jù)源本質(zhì)就是連接池+連接+管理策略。下面,將對每一步做詳細(xì)的分析。
JDBC連接工廠driverConnectionFactory的創(chuàng)建過程
protected ConnectionFactory createConnectionFactory() throws SQLException {
// Load the JDBC driver class
Class driverFromCCL = null;
if (driverClassName != null) {
try {
try {
if (driverClassLoader == null) {
Class.forName(driverClassName);
} else {
Class.forName(driverClassName, true, driverClassLoader);
}
} catch (ClassNotFoundException cnfe) {
driverFromCCL = Thread.currentThread(
).getContextClassLoader().loadClass(
driverClassName);
}
} catch (Throwable t) {
String message = "Cannot load JDBC driver class '" +
driverClassName + "'";
logWriter.println(message);
t.printStackTrace(logWriter);
throw new SQLNestedException(message, t);
}
}
// Create a JDBC driver instance
Driver driver = null;
try {
if (driverFromCCL == null) {
driver = DriverManager.getDriver(url);
} else {
// Usage of DriverManager is not possible, as it does not
// respect the ContextClassLoader
driver = (Driver) driverFromCCL.newInstance();
if (!driver.acceptsURL(url)) {
throw new SQLException("No suitable driver", "08001");
}
}
} catch (Throwable t) {
String message = "Cannot create JDBC driver of class '" +
(driverClassName != null ? driverClassName : "") +
"' for connect URL '" + url + "'";
logWriter.println(message);
t.printStackTrace(logWriter);
throw new SQLNestedException(message, t);
}
// Can't test without a validationQuery
if (validationQuery == null) {
setTestOnBorrow(false);
setTestOnReturn(false);
setTestWhileIdle(false);
}
// Set up the driver connection factory we will use
String user = username;
if (user != null) {
connectionProperties.put("user", user);
} else {
log("DBCP DataSource configured without a 'username'");
}
String pwd = password;
if (pwd != null) {
connectionProperties.put("password", pwd);
} else {
log("DBCP DataSource configured without a 'password'");
}
ConnectionFactory driverConnectionFactory = new DriverConnectionFactory(driver, url, connectionProperties);
return driverConnectionFactory;
}
上面一連串代碼干了什么呢?其實就干了兩件事:
1、獲取數(shù)據(jù)庫驅(qū)動
2、使用驅(qū)動以及參數(shù)(url、username、password)構(gòu)造一個工廠。
一旦這個工廠構(gòu)建完畢了,就可以來生成連接,而這個連接的生成其實是驅(qū)動加上配置來完成的.
創(chuàng)建連接池的過程
protected void createConnectionPool() {
// Create an object pool to contain our active connections
GenericObjectPool gop;
if ((abandonedConfig != null) && (abandonedConfig.getRemoveAbandoned())) {
gop = new AbandonedObjectPool(null,abandonedConfig);
}
else {
gop = new GenericObjectPool();
}
gop.setMaxActive(maxActive);
gop.setMaxIdle(maxIdle);
gop.setMinIdle(minIdle);
gop.setMaxWait(maxWait);
gop.setTestOnBorrow(testOnBorrow);
gop.setTestOnReturn(testOnReturn);
gop.setTimeBetweenEvictionRunsMillis(timeBetweenEvictionRunsMillis);
gop.setNumTestsPerEvictionRun(numTestsPerEvictionRun);
gop.setMinEvictableIdleTimeMillis(minEvictableIdleTimeMillis);
gop.setTestWhileIdle(testWhileIdle);
connectionPool = gop;
}
在創(chuàng)建連接池的時候,用到了common-pool里的GenericObjectPool,對于JDBC連接的緩存以及管理其實是交給GenericObjectPool的,DBCP其實只是負(fù)責(zé)創(chuàng)建這樣一種pool然后使用它而已。
創(chuàng)建statement緩存池
一般來說,statement并不是重量級的對象,創(chuàng)建過程消耗的資源并不像JDBC連接那樣重,所以沒必要做緩存池化,這里為了簡便起見,對此不做分析。
創(chuàng)建PoolableConnectionFactory
這一步是一個承上啟下的過程,承上在于利用上面兩部創(chuàng)建的連接工廠和連接池,構(gòu)建PoolableConnectionFactory,啟下則在于為后面的向連接池里添加連接做準(zhǔn)備。
下面先上一張靜態(tài)的類關(guān)系圖:
protected void createPoolableConnectionFactory(ConnectionFactory driverConnectionFactory,
KeyedObjectPoolFactory statementPoolFactory, AbandonedConfig configuration) throws SQLException {
PoolableConnectionFactory connectionFactory = null;
try {
connectionFactory =
new PoolableConnectionFactory(driverConnectionFactory,
connectionPool,
statementPoolFactory,
validationQuery,
validationQueryTimeout,
connectionInitSqls,
defaultReadOnly,
defaultAutoCommit,
defaultTransactionIsolation,
defaultCatalog,
configuration);
validateConnectionFactory(connectionFactory);
} catch (RuntimeException e) {
throw e;
} catch (Exception e) {
throw new SQLNestedException("Cannot create PoolableConnectionFactory (" + e.getMessage() + ")", e);
}
}
可以看見,在創(chuàng)建PoolableConnectionFactory的時候,需要用到前面創(chuàng)建的driverConnectionFactory以及連接池connectionPool,那么那個構(gòu)造函數(shù)到底干了先什么呢?
public PoolableConnectionFactory(
ConnectionFactory connFactory,
ObjectPool pool,
KeyedObjectPoolFactory stmtPoolFactory,
String validationQuery,
int validationQueryTimeout,
Collection connectionInitSqls,
Boolean defaultReadOnly,
boolean defaultAutoCommit,
int defaultTransactionIsolation,
String defaultCatalog,
AbandonedConfig config) {
_connFactory = connFactory;
_pool = pool;
_config = config;
_pool.setFactory(this);
_stmtPoolFactory = stmtPoolFactory;
_validationQuery = validationQuery;
_validationQueryTimeout = validationQueryTimeout;
_connectionInitSqls = connectionInitSqls;
_defaultReadOnly = defaultReadOnly;
_defaultAutoCommit = defaultAutoCommit;
_defaultTransactionIsolation = defaultTransactionIsolation;
_defaultCatalog = defaultCatalog;
}
它在內(nèi)部保存了真正的JDBC 連接的工廠以及連接池,然后,通過一句_pool.setFactory(this); 將它自己設(shè)置給了連接池。這行代碼十分重要,要理解這行代碼,首先需要明白common-pool中的GenericObjectPool添加內(nèi)部元素的一般方法,沒錯,那就是必須要傳入一個工廠Factory。GenericObjectPool添加內(nèi)部元素時會調(diào)用addObject()這個方法,內(nèi)部其實是調(diào)用工廠的makeObejct()方法來創(chuàng)建元素,然后再加入到自己的池中。_pool.setFactory(this)這句代碼其實起到了啟下的作用,沒有它,后面的為連接池添加連接也就不可能完成。
當(dāng)創(chuàng)建完工廠后,會有個validateConnectionFactory(connectionFactory);這個方法的作用僅僅是用來驗證數(shù)據(jù)庫連接可使用,看代碼:
protected static void validateConnectionFactory(PoolableConnectionFactory connectionFactory) throws Exception {
Connection conn = null;
try {
conn = (Connection) connectionFactory.makeObject();
connectionFactory.activateObject(conn);
connectionFactory.validateConnection(conn);
connectionFactory.passivateObject(conn);
}
finally {
connectionFactory.destroyObject(conn);
}
}
先是用makeObject方法來創(chuàng)建一個連接,然后做相關(guān)驗證(就是用一些初始化sql來試著執(zhí)行一下,看看能不能連接到數(shù)據(jù)庫),然后銷毀連接,這里并沒有向連接池添加連接,真正的添加連接在后面,不過,我們可以先通過下面一張時序圖來看看makeObject方法到底做了什么。
下面是一張整體流程的時序圖:
從圖中可以看出,makeObject方法的大致流程:從driverConnectionFactory那里拿到底層連接,初始化驗證,然后創(chuàng)建PoolableConnection,在創(chuàng)建這個PoolableConnection的時候,將PoolableConnection與連接池關(guān)聯(lián)了起來,真正做到了連接池和連接之間的一對多的關(guān)系,這也為改變PoolableConnection的close方法提供了方便。
下面是makeObject方法的源代碼:
public Object makeObject() throws Exception {
Connection conn = _connFactory.createConnection();
if (conn == null) {
throw new IllegalStateException("Connection factory returned null from createConnection");
}
initializeConnection(conn); //初始化,這個過程可有可無
if(null != _stmtPoolFactory) {
KeyedObjectPool stmtpool = _stmtPoolFactory.createPool();
conn = new PoolingConnection(conn,stmtpool);
stmtpool.setFactory((PoolingConnection)conn);
}
//這里是關(guān)鍵
return new PoolableConnection(conn,_pool,_config);
}
其中PoolableConnection的構(gòu)造函數(shù)如下:
public PoolableConnection(Connection conn, ObjectPool pool, AbandonedConfig config) {
super(conn, config);
_pool = pool;
}
內(nèi)部關(guān)聯(lián)了一個連接池,這個連接池的作用體現(xiàn)在PoolableConnection的close方法中:
public synchronized void close() throws SQLException {
if (_closed) {
// already closed
return;
}
boolean isUnderlyingConectionClosed;
try {
isUnderlyingConectionClosed = _conn.isClosed();
} catch (SQLException e) {
try {
_pool.invalidateObject(this); // XXX should be guarded to happen at most once
} catch(IllegalStateException ise) {
// pool is closed, so close the connection
passivate();
getInnermostDelegate().close();
} catch (Exception ie) {
// DO NOTHING the original exception will be rethrown
}
throw (SQLException) new SQLException("Cannot close connection (isClosed check failed)").initCause(e);
}
if (!isUnderlyingConectionClosed) {
// Normal close: underlying connection is still open, so we
// simply need to return this proxy to the pool
try {
_pool.returnObject(this); // XXX should be guarded to happen at most once
} catch(IllegalStateException e) {
// pool is closed, so close the connection
passivate();
getInnermostDelegate().close();
} catch(SQLException e) {
throw e;
} catch(RuntimeException e) {
throw e;
} catch(Exception e) {
throw (SQLException) new SQLException("Cannot close connection (return to pool failed)").initCause(e);
}
} else {
// Abnormal close: underlying connection closed unexpectedly, so we
// must destroy this proxy
try {
_pool.invalidateObject(this); // XXX should be guarded to happen at most once
} catch(IllegalStateException e) {
// pool is closed, so close the connection
passivate();
getInnermostDelegate().close();
} catch (Exception ie) {
// DO NOTHING, "Already closed" exception thrown below
}
throw new SQLException("Already closed.");
}
}
一行_pool.returnObject(this)表明并非真的關(guān)閉了,而是返還給了連接池。
到這里, PoolableConnectionFactory創(chuàng)建好了,它使用driverConnectionFactory來創(chuàng)建底層連接,通過makeObject來創(chuàng)建PoolableConnection,這個PoolableConnection通過與connectionPool關(guān)聯(lián)來達(dá)到改變close方法的作用,當(dāng)PoolableConnectionFactory創(chuàng)建好的時候,它自己已經(jīng)作為一個工廠類被設(shè)置到了connectionPool,后面connectionPool會使用這個工廠來生產(chǎn)PoolableConnection,而生成的所有的PoolableConnection都與connectionPool關(guān)聯(lián)起來了,可以從connectionPool取出,也可以還給connectionPool。接下來,讓我們來看一看到底怎么去初始化connectionPool。
創(chuàng)建數(shù)據(jù)源并初始化連接池
createDataSourceInstance();
try {
for (int i = 0 ; i < initialSize ; i++) {
connectionPool.addObject();
}
} catch (Exception e) {
throw new SQLNestedException("Error preloading the connection pool", e);
}
我們先看 createDataSourceInstance();
protected void createDataSourceInstance() throws SQLException {
PoolingDataSource pds = new PoolingDataSource(connectionPool);
pds.setAccessToUnderlyingConnectionAllowed(isAccessToUnderlyingConnectionAllowed());
pds.setLogWriter(logWriter);
dataSource = pds;
}
其實就是創(chuàng)建一個PoolingDataSource,作為底層真正的數(shù)據(jù)源,這個PoolingDataSource比較簡單,這里不做詳細(xì)介紹
接下來是一個for循環(huán),通過調(diào)用connectionPool.addObject();來為連接池添加數(shù)據(jù)庫連接,下面是一張時序圖:
可以看出,在3.5中創(chuàng)建的PoolableConnectionFactory在這里起作用了,addObject依賴的正是makeObject,而makeObject在上面也介紹過了。
到此為止,數(shù)據(jù)源創(chuàng)建好了,連接池里也有了可以使用的連接,而且每個連接和連接池都做了關(guān)聯(lián),改變了close的行為。這個時候BasicDataSource正是可以工作了,調(diào)用getConnection的時候,實際是調(diào)用底層數(shù)據(jù)源的getConnection,而底層數(shù)據(jù)源其實就是從連接池中獲取的連接。
四.總結(jié)
整個數(shù)據(jù)源最核心的其實就三個東西:
一個是連接池,在這里體現(xiàn)為common-pool中的GenericObjectPool,它負(fù)責(zé)緩存和管理連接,所有的配置策略都是由它管理。
第二個是連接,這里的連接就是PoolableConnection,當(dāng)然它是對底層連接進(jìn)行了封裝。
第三個則是連接池和連接的關(guān)系,在此表現(xiàn)為一對多的互相引用。對數(shù)據(jù)源的構(gòu)建則是對連接池,連接以及連接池與連接的關(guān)系的構(gòu)建,掌握了這些點,就基本能掌握數(shù)據(jù)源的構(gòu)建。
以上就是本文的全部內(nèi)容,希望對大家的學(xué)習(xí)有所幫助,也希望大家多多支持腳本之家。
相關(guān)文章
在SpringBoot中實現(xiàn)一個訂單號生成系統(tǒng)的示例代碼
在Spring Boot中設(shè)計一個訂單號生成系統(tǒng),主要考慮到生成的訂單號需要滿足的幾個要求:唯一性、可擴(kuò)展性、以及可能的業(yè)務(wù)相關(guān)性,本文給大家介紹了幾種常見的解決方案及相應(yīng)的示例代碼,需要的朋友可以參考下2024-02-02
淺析SpringBoot微服務(wù)中異步調(diào)用數(shù)據(jù)提交數(shù)據(jù)庫的問題
這篇文章主要介紹了SpringBoot微服務(wù)中異步調(diào)用數(shù)據(jù)提交數(shù)據(jù)庫的問題,今天本文涉及到的知識點不難,都是很簡單的crud操作,本文結(jié)合實例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下2022-07-07

