Compare commits

...

7 Commits

Author SHA1 Message Date
shark 49df2c176d
Merge 0736876199 into 88d90f1434 2025-07-21 11:15:15 +08:00
wenshao 88d90f1434 1.2.27-SNAPSHOT
Java CI / Test JDK ${{ matrix.java }}, ${{ matrix.os }} (11, ubuntu-latest) (push) Waiting to run Details
Java CI / Test JDK ${{ matrix.java }}, ${{ matrix.os }} (17, ubuntu-latest) (push) Waiting to run Details
Java CI / Test JDK ${{ matrix.java }}, ${{ matrix.os }} (21, ubuntu-latest) (push) Waiting to run Details
Java CI / Test JDK ${{ matrix.java }}, ${{ matrix.os }} (8, ubuntu-latest) (push) Waiting to run Details
2025-07-21 05:43:34 +08:00
wenshao dac24c674e 1.2.26 release 2025-07-21 05:42:11 +08:00
wenshao 96a47b7369 improved for urlUserPassword changed 2025-07-20 14:15:57 +08:00
wenshao 2e788bde25 userPassword Version 2025-07-19 10:40:20 +08:00
wenshao b5169b4264 remove useUnfairLock 2025-07-19 10:05:15 +08:00
刘强 0736876199 Fix solve the issue in SQL where the < operator is mistakenly recognized as <@ when used jointly with user-defined variables. 2025-05-07 13:48:34 +08:00
13 changed files with 281 additions and 63 deletions

View File

@ -4,7 +4,7 @@
<parent>
<groupId>com.alibaba</groupId>
<artifactId>druid-parent</artifactId>
<version>1.2.26-SNAPSHOT</version>
<version>1.2.27-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>druid</artifactId>

View File

@ -18,7 +18,7 @@ package com.alibaba.druid;
public final class VERSION {
public static final int MajorVersion = 1;
public static final int MinorVersion = 2;
public static final int RevisionVersion = 24;
public static final int RevisionVersion = 27;
public static String getVersionNumber() {
return VERSION.MajorVersion + "." + VERSION.MinorVersion + "." + VERSION.RevisionVersion;

View File

@ -187,20 +187,23 @@ public abstract class DruidAbstractDataSource extends WrapperAdapter implements
protected volatile long cachedPreparedStatementCount;
protected volatile long cachedPreparedStatementDeleteCount;
protected volatile long cachedPreparedStatementMissCount;
protected volatile long userPasswordVersion;
private volatile FilterChainImpl filterChain;
static final AtomicLongFieldUpdater<DruidAbstractDataSource> errorCountUpdater = AtomicLongFieldUpdater.newUpdater(DruidAbstractDataSource.class, "errorCount");
static final AtomicLongFieldUpdater<DruidAbstractDataSource> dupCloseCountUpdater = AtomicLongFieldUpdater.newUpdater(DruidAbstractDataSource.class, "dupCloseCount");
static final AtomicLongFieldUpdater<DruidAbstractDataSource> startTransactionCountUpdater = AtomicLongFieldUpdater.newUpdater(DruidAbstractDataSource.class, "startTransactionCount");
static final AtomicLongFieldUpdater<DruidAbstractDataSource> commitCountUpdater = AtomicLongFieldUpdater.newUpdater(DruidAbstractDataSource.class, "commitCount");
static final AtomicLongFieldUpdater<DruidAbstractDataSource> rollbackCountUpdater = AtomicLongFieldUpdater.newUpdater(DruidAbstractDataSource.class, "rollbackCount");
static final AtomicLongFieldUpdater<DruidAbstractDataSource> cachedPreparedStatementHitCountUpdater = AtomicLongFieldUpdater.newUpdater(DruidAbstractDataSource.class, "cachedPreparedStatementHitCount");
static final AtomicLongFieldUpdater<DruidAbstractDataSource> preparedStatementCountUpdater = AtomicLongFieldUpdater.newUpdater(DruidAbstractDataSource.class, "preparedStatementCount");
static final AtomicLongFieldUpdater<DruidAbstractDataSource> closedPreparedStatementCountUpdater = AtomicLongFieldUpdater.newUpdater(DruidAbstractDataSource.class, "closedPreparedStatementCount");
static final AtomicLongFieldUpdater<DruidAbstractDataSource> cachedPreparedStatementCountUpdater = AtomicLongFieldUpdater.newUpdater(DruidAbstractDataSource.class, "cachedPreparedStatementCount");
static final AtomicLongFieldUpdater<DruidAbstractDataSource> cachedPreparedStatementDeleteCountUpdater = AtomicLongFieldUpdater.newUpdater(DruidAbstractDataSource.class, "cachedPreparedStatementDeleteCount");
static final AtomicLongFieldUpdater<DruidAbstractDataSource> cachedPreparedStatementMissCountUpdater = AtomicLongFieldUpdater.newUpdater(DruidAbstractDataSource.class, "cachedPreparedStatementMissCount");
static final AtomicLongFieldUpdater<DruidAbstractDataSource>
errorCountUpdater = AtomicLongFieldUpdater.newUpdater(DruidAbstractDataSource.class, "errorCount"),
dupCloseCountUpdater = AtomicLongFieldUpdater.newUpdater(DruidAbstractDataSource.class, "dupCloseCount"),
startTransactionCountUpdater = AtomicLongFieldUpdater.newUpdater(DruidAbstractDataSource.class, "startTransactionCount"),
commitCountUpdater = AtomicLongFieldUpdater.newUpdater(DruidAbstractDataSource.class, "commitCount"),
rollbackCountUpdater = AtomicLongFieldUpdater.newUpdater(DruidAbstractDataSource.class, "rollbackCount"),
cachedPreparedStatementHitCountUpdater = AtomicLongFieldUpdater.newUpdater(DruidAbstractDataSource.class, "cachedPreparedStatementHitCount"),
preparedStatementCountUpdater = AtomicLongFieldUpdater.newUpdater(DruidAbstractDataSource.class, "preparedStatementCount"),
closedPreparedStatementCountUpdater = AtomicLongFieldUpdater.newUpdater(DruidAbstractDataSource.class, "closedPreparedStatementCount"),
cachedPreparedStatementCountUpdater = AtomicLongFieldUpdater.newUpdater(DruidAbstractDataSource.class, "cachedPreparedStatementCount"),
cachedPreparedStatementDeleteCountUpdater = AtomicLongFieldUpdater.newUpdater(DruidAbstractDataSource.class, "cachedPreparedStatementDeleteCount"),
cachedPreparedStatementMissCountUpdater = AtomicLongFieldUpdater.newUpdater(DruidAbstractDataSource.class, "cachedPreparedStatementMissCount"),
userPasswordVersionUpdater = AtomicLongFieldUpdater.newUpdater(DruidAbstractDataSource.class, "userPasswordVersion");
protected static final AtomicReferenceFieldUpdater<DruidAbstractDataSource, FilterChainImpl> filterChainUpdater
= AtomicReferenceFieldUpdater.newUpdater(DruidAbstractDataSource.class, FilterChainImpl.class, "filterChain");
@ -256,7 +259,6 @@ public abstract class DruidAbstractDataSource extends WrapperAdapter implements
static final AtomicLongFieldUpdater<DruidAbstractDataSource> destroyCountUpdater = AtomicLongFieldUpdater.newUpdater(DruidAbstractDataSource.class, "destroyCount");
static final AtomicLongFieldUpdater<DruidAbstractDataSource> createStartNanosUpdater = AtomicLongFieldUpdater.newUpdater(DruidAbstractDataSource.class, "createStartNanos");
private Boolean useUnfairLock = true;
private boolean useLocalSessionState = true;
private boolean keepConnectionUnderlyingTransactionIsolation;
@ -382,8 +384,6 @@ public abstract class DruidAbstractDataSource extends WrapperAdapter implements
this.lock = new ReentrantLock(!useUnfairLock);
this.notEmpty = this.lock.newCondition();
this.empty = this.lock.newCondition();
this.useUnfairLock = useUnfairLock;
}
} finally {
lock.unlock();
@ -519,6 +519,14 @@ public abstract class DruidAbstractDataSource extends WrapperAdapter implements
cachedPreparedStatementMissCountUpdater.incrementAndGet(this);
}
protected void incrementUserPasswordVersion() {
userPasswordVersionUpdater.incrementAndGet(this);
}
protected long getUserPasswordVersion() {
return userPasswordVersionUpdater.get(this);
}
public long getCachedPreparedStatementMissCount() {
return cachedPreparedStatementMissCount;
}

View File

@ -85,6 +85,7 @@ public final class DruidConnectionHolder {
final ReentrantLock lock = new ReentrantLock();
protected String initSchema;
protected Socket socket;
protected final long userPasswordVersion;
volatile FilterChainImpl filterChain;
@ -116,6 +117,7 @@ public final class DruidConnectionHolder {
this.createNanoSpan = connectNanoSpan;
this.variables = variables;
this.globalVariables = globalVariables;
this.userPasswordVersion = dataSource.getUserPasswordVersion();
this.connectTimeMillis = System.currentTimeMillis();
this.lastActiveTimeMillis = connectTimeMillis;
@ -468,4 +470,7 @@ public final class DruidConnectionHolder {
return buf.toString();
}
public long getUserPasswordVersion() {
return userPasswordVersion;
}
}

View File

@ -234,8 +234,8 @@ public class DruidDataSource extends DruidAbstractDataSource
lock.lock();
try {
urlUserPasswordChanged = (url != null && !this.jdbcUrl.equals(url))
|| (username != null && !this.username.equals(username))
|| (password != null && !this.password.equals(password));
|| (username != null && !username.equals(this.username))
|| (password != null && !password.equals(this.password));
String connectUser = username != null ? username : this.username;
if (username != null) {
@ -266,19 +266,22 @@ public class DruidDataSource extends DruidAbstractDataSource
lock.lock();
try {
if (url != null && !this.jdbcUrl.equals(url)) {
this.jdbcUrl = url; // direct set url, ignore init check
LOG.info("jdbcUrl changed");
}
if (urlUserPasswordChanged) {
if (url != null && !url.equals(this.jdbcUrl)) {
this.jdbcUrl = url; // direct set url, ignore init check
LOG.info("jdbcUrl changed");
}
if (username != null && !this.username.equals(username)) {
this.username = username; // direct set, ignore init check
LOG.info("username changed");
}
if (username != null && !username.equals(this.username)) {
this.username = username; // direct set, ignore init check
LOG.info("username changed");
}
if (password != null && !this.password.equals(password)) {
this.password = password; // direct set, ignore init check
LOG.info("password changed");
if (password != null && !password.equals(this.password)) {
this.password = password; // direct set, ignore init check
LOG.info("password changed");
}
incrementUserPasswordVersion();
}
{
@ -305,32 +308,46 @@ public class DruidDataSource extends DruidAbstractDataSource
}
DruidDataSourceUtils.configFromProperties(this, properties);
if (urlUserPasswordChanged) {
for (int i = poolingCount - 1; i >= 0; i--) {
DruidConnectionHolder connection = connections[i];
JdbcUtils.close(connection.conn);
destroyCountUpdater.incrementAndGet(this);
connections[i] = null;
}
poolingCount = 0;
emptySignal();
}
} finally {
lock.unlock();
}
int minIdle = this.minIdle;
for (int i = 0; i < minIdle; ++i) {
// check need fill
lock.lock();
int replaceCount = 0;
// replace older version urlUserPassword Connection
while ((hasOlderVersionUrlUserPasswordConnection())) {
try {
if (activeCount + poolingCount >= minIdle) {
PhysicalConnectionInfo phyConnInfo = createPhysicalConnection();
boolean result = false;
lock.lock();
try {
for (int i = poolingCount - 1; i >= 0; i--) {
if (connections[i].getUserPasswordVersion() < userPasswordVersion) {
connections[i] = new DruidConnectionHolder(DruidDataSource.this, phyConnInfo);
result = true;
replaceCount++;
break;
}
}
} finally {
lock.unlock();
}
if (!result) {
JdbcUtils.close(phyConnInfo.getPhysicalConnection());
LOG.info("replace older version urlUserPassword failed.");
break;
}
} finally {
lock.unlock();
} catch (SQLException e) {
LOG.error("fill init connection error", e);
}
}
if (replaceCount > 0) {
LOG.info("replace older version urlUserPassword Connection : " + replaceCount);
}
while ((isLowWaterLevel())) {
try {
PhysicalConnectionInfo physicalConnection = createPhysicalConnection();
@ -345,6 +362,30 @@ public class DruidDataSource extends DruidAbstractDataSource
}
}
private boolean hasOlderVersionUrlUserPasswordConnection() {
lock.lock();
try {
long userPasswordVersion = this.userPasswordVersion;
for (int i = 0; i < poolingCount; i++) {
if (connections[i].getUserPasswordVersion() < userPasswordVersion) {
return true;
}
}
} finally {
lock.unlock();
}
return false;
}
private boolean isLowWaterLevel() {
lock.lock();
try {
return activeCount + poolingCount < minIdle;
} finally {
lock.unlock();
}
}
public boolean isKillWhenSocketReadTimeout() {
return killWhenSocketReadTimeout;
}
@ -1915,7 +1956,8 @@ public class DruidDataSource extends DruidAbstractDataSource
return;
}
if (phyMaxUseCount > 0 && holder.useCount >= phyMaxUseCount) {
if ((phyMaxUseCount > 0 && holder.useCount >= phyMaxUseCount)
|| holder.userPasswordVersion < getUserPasswordVersion()) {
discardConnection(holder);
return;
}
@ -1975,6 +2017,7 @@ public class DruidDataSource extends DruidAbstractDataSource
}
}
boolean full = false;
lock.lock();
try {
if (holder.active) {
@ -1985,13 +2028,20 @@ public class DruidDataSource extends DruidAbstractDataSource
result = putLast(holder, currentTimeMillis);
recycleCount++;
if (!result) {
full = poolingCount + activeCount >= maxActive;
}
} finally {
lock.unlock();
}
if (!result) {
JdbcUtils.close(holder.conn);
LOG.info("connection recycle failed.");
String msg = "connection recycle failed.";
if (full) {
msg += " pool is full";
}
LOG.info(msg);
}
} catch (Throwable e) {
holder.clearStatementCache();
@ -2442,7 +2492,7 @@ public class DruidDataSource extends DruidAbstractDataSource
}
protected boolean put(PhysicalConnectionInfo physicalConnectionInfo) {
DruidConnectionHolder holder = null;
DruidConnectionHolder holder;
try {
holder = new DruidConnectionHolder(DruidDataSource.this, physicalConnectionInfo);
} catch (SQLException ex) {

View File

@ -1677,10 +1677,6 @@ public class Lexer {
scanChar();
scanChar();
token = Token.LTLT;
} else if (c1 == '@') {
scanChar();
scanChar();
token = Token.LT_MONKEYS_AT;
} else if (c1 == '-' && charAt(pos + 2) == '>') {
scanChar();
scanChar();

View File

@ -0,0 +1,97 @@
package com.alibaba.druid.bvt.pool;
import com.alibaba.druid.pool.DruidDataSource;
import com.alibaba.druid.pool.DruidPooledConnection;
import junit.framework.TestCase;
import org.junit.Assert;
import java.sql.Connection;
import java.util.Properties;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.Executor;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;
/**
* 这个场景测试initialSize > maxActive
*
* @author wenshao [szujobs@hotmail.com]
*/
public class UserPasswordVersionTest extends TestCase {
private DruidDataSource dataSource;
protected void setUp() throws Exception {
dataSource = new DruidDataSource();
dataSource.setUrl("jdbc:mock:xxx");
dataSource.setUsername("u0");
dataSource.setPassword("p0");
dataSource.setTestOnBorrow(false);
dataSource.setMaxActive(30);
dataSource.setMaxWait(30);
dataSource.setInitialSize(1);
dataSource.setMinIdle(3);
dataSource.init();
}
protected void tearDown() throws Exception {
dataSource.close();
}
public void test_maxWait() throws Exception {
ExecutorService executor = Executors.newFixedThreadPool(10);
final CountDownLatch latch0 = new CountDownLatch(1);
executor.submit(
() -> {
try {
DruidPooledConnection[] connections = new DruidPooledConnection[10];
for (int i = 0; i < connections.length; i++) {
connections[i] = dataSource.getConnection();
}
for (int i = 0; i < connections.length; i++) {
connections[i].close();
}
assertEquals(connections.length, dataSource.getPoolingCount());
} catch (Exception e) {
e.printStackTrace();
} finally {
latch0.countDown();
}
}
);
latch0.await();
DruidPooledConnection conn = dataSource.getConnection();
assertEquals(0, conn.getConnectionHolder().getUserPasswordVersion());
final CountDownLatch latch1 = new CountDownLatch(1);
executor.submit(() -> {
try {
Properties properties = new Properties();
properties.put("druid.username", "u1");
properties.put("druid.password", "p1");
dataSource.configFromProperties(properties);
} catch (Exception e) {
e.printStackTrace();
} finally {
latch1.countDown();
}
});
latch1.await();
assertEquals(9, dataSource.getPoolingCount());
assertEquals(1, dataSource.getActiveCount());
conn.close();
assertEquals(9, dataSource.getPoolingCount());
assertEquals(0, dataSource.getActiveCount());
DruidPooledConnection conn1 = dataSource.getConnection();
assertEquals(1, conn1.getConnectionHolder().getUserPasswordVersion());
conn1.close();
assertEquals(9, dataSource.getPoolingCount());
assertEquals(0, dataSource.getActiveCount());
}
}

View File

@ -3,16 +3,23 @@ package com.alibaba.druid.mysql;
import com.alibaba.druid.DbType;
import com.alibaba.druid.sql.SQLUtils;
import com.alibaba.druid.sql.ast.SQLStatement;
import com.alibaba.druid.sql.ast.expr.SQLBinaryOpExpr;
import com.alibaba.druid.sql.ast.expr.SQLBinaryOperator;
import com.alibaba.druid.sql.ast.expr.SQLVariantRefExpr;
import com.alibaba.druid.sql.ast.statement.SQLSetStatement;
import com.alibaba.druid.sql.dialect.mysql.ast.statement.MySqlSetTransactionStatement;
import com.alibaba.druid.sql.dialect.mysql.parser.MySqlStatementParser;
import com.alibaba.druid.sql.dialect.mysql.visitor.MySqlASTVisitor;
import com.alibaba.druid.sql.dialect.mysql.visitor.MySqlASTVisitorAdapter;
import com.alibaba.druid.sql.parser.SQLStatementParser;
import com.alibaba.druid.util.JdbcConstants;
import org.junit.Assert;
import org.junit.Test;
import java.util.List;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.concurrent.atomic.AtomicReference;
import java.util.function.BinaryOperator;
/**
* Created by szf on 2017/11/16.
@ -150,4 +157,54 @@ public class MysqlVarantRefTest {
Assert.assertTrue(resultExpr5.isSession());
}
@Test
public void test7() {
// in this case, < @sid is recognized as < operator and user-defined variables@sid
String sql = "set @sid=1;select * from aaa where id < @sid; ";
SQLStatementParser parser = new MySqlStatementParser(sql);
List<SQLStatement> stmtList = parser.parseStatementList();
SQLSetStatement result0 = (SQLSetStatement) stmtList.get(0);
SQLVariantRefExpr resultExpr0 = (SQLVariantRefExpr) result0.getItems().get(0).getTarget();
Assert.assertEquals("@sid", resultExpr0.getName());
AtomicReference<SQLVariantRefExpr> resultExpr1Ref = new AtomicReference<>();
AtomicInteger variableCnt = new AtomicInteger(0);
AtomicInteger arrayContainerByOperatorCnt = new AtomicInteger(0);
MySqlASTVisitor visitor = new MySqlASTVisitorAdapter() {
@Override
public boolean visit(SQLVariantRefExpr x) {
resultExpr1Ref.set(x);
variableCnt.addAndGet(1);
return super.visit(x);
}
@Override
public boolean visit(SQLBinaryOpExpr x) {
if(SQLBinaryOperator.Array_ContainedBy.equals(x.getOperator())) {
arrayContainerByOperatorCnt.addAndGet(1);
}
return super.visit(x);
}
};
stmtList.get(1).accept(visitor);
Assert.assertEquals(1, variableCnt.get());
Assert.assertEquals(0, arrayContainerByOperatorCnt.get());
Assert.assertEquals("@sid", resultExpr1Ref.get().getName());
// in this case, <@ is recognized as <@, instead of < user-defined variables@sid
String sql2 = "select * from aaa where id <@ sid; ";
SQLStatementParser parser2 = new MySqlStatementParser(sql2);
List<SQLStatement> stmtList2 = parser2.parseStatementList();
variableCnt.set(0);
arrayContainerByOperatorCnt.set(0);
resultExpr1Ref.set(null);
stmtList2.get(0).accept(visitor);
Assert.assertEquals(0, variableCnt.get());
Assert.assertEquals(1, arrayContainerByOperatorCnt.get());
Assert.assertNull(resultExpr1Ref.get());
}
}

View File

@ -34,7 +34,7 @@
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>druid-spring-boot-starter</artifactId>
<version>1.2.26-SNAPSHOT</version>
<version>1.2.27-SNAPSHOT</version>
</dependency>
<!-- Spring and Spring Boot dependencies -->
<dependency>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>com.alibaba</groupId>
<artifactId>druid-parent</artifactId>
<version>1.2.26-SNAPSHOT</version>
<version>1.2.27-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>druid-spring-boot-3-starter</artifactId>

View File

@ -5,7 +5,7 @@
<parent>
<groupId>com.alibaba</groupId>
<artifactId>druid-parent</artifactId>
<version>1.2.26-SNAPSHOT</version>
<version>1.2.27-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<artifactId>druid-spring-boot-starter</artifactId>

View File

@ -4,7 +4,7 @@
<parent>
<groupId>com.alibaba</groupId>
<artifactId>druid-parent</artifactId>
<version>1.2.26-SNAPSHOT</version>
<version>1.2.27-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
<groupId>com.alibaba</groupId>

15
pom.xml
View File

@ -6,7 +6,7 @@
<groupId>com.alibaba</groupId>
<artifactId>druid-parent</artifactId>
<version>1.2.26-SNAPSHOT</version>
<version>1.2.27-SNAPSHOT</version>
<name>${project.artifactId}</name>
<description>A JDBC datasource implementation.</description>
<packaging>pom</packaging>
@ -80,10 +80,6 @@
</developers>
<distributionManagement>
<repository>
<id>central</id>
<url>https://central.sonatype.org/service/local/staging/deploy/maven2/</url>
</repository>
<snapshotRepository>
<id>central</id>
<url>https://central.sonatype.com/repository/maven-snapshots/</url>
@ -176,6 +172,15 @@
</execution>
</executions>
</plugin>
<plugin>
<groupId>org.sonatype.central</groupId>
<artifactId>central-publishing-maven-plugin</artifactId>
<version>0.8.0</version>
<extensions>true</extensions>
<configuration>
<publishingServerId>central</publishingServerId>
</configuration>
</plugin>
</plugins>
<pluginManagement>
<plugins>