diff --git a/pom.xml b/pom.xml
index 10897d2899..af1192f383 100644
--- a/pom.xml
+++ b/pom.xml
@@ -398,7 +398,7 @@
net.sourceforge.jtds
jtds
- 1.2.4
+ 1.2.7
provided
@@ -516,5 +516,19 @@
1.5
test
+
+
+ commons-dbutils
+ commons-dbutils
+ 1.5
+ test
+
+
+
+ org.nutz
+ nutz
+ 1.b.46
+ test
+
diff --git a/src/main/java/com/alibaba/druid/filter/logging/LogFilter.java b/src/main/java/com/alibaba/druid/filter/logging/LogFilter.java
index f695039ea4..4a9717a620 100644
--- a/src/main/java/com/alibaba/druid/filter/logging/LogFilter.java
+++ b/src/main/java/com/alibaba/druid/filter/logging/LogFilter.java
@@ -17,6 +17,7 @@ package com.alibaba.druid.filter.logging;
import java.sql.ResultSetMetaData;
import java.sql.SQLException;
+import java.sql.Savepoint;
import java.sql.Types;
import com.alibaba.druid.filter.FilterChain;
@@ -290,6 +291,30 @@ public abstract class LogFilter extends FilterEventAdapter implements LogFilterM
connectionLog("{conn-" + connection.getId() + "} connected");
}
}
+
+ @Override
+ public Savepoint connection_setSavepoint(FilterChain chain, ConnectionProxy connection) throws SQLException {
+
+ Savepoint savepoint = chain.connection_setSavepoint(connection);
+
+ if (isConnectionLogEnabled()) {
+ connectionLog("{conn " + connection.getId() + "} setSavepoint-" + savepoint.getSavepointId());
+ }
+
+ return savepoint;
+ }
+
+ @Override
+ public Savepoint connection_setSavepoint(FilterChain chain, ConnectionProxy connection, String name)
+ throws SQLException {
+ Savepoint savepoint = chain.connection_setSavepoint(connection, name);
+
+ if (isConnectionLogEnabled()) {
+ connectionLog("{conn " + connection.getId() + "} setSavepoint-" + name);
+ }
+
+ return savepoint;
+ }
@Override
public void connection_rollback(FilterChain chain, ConnectionProxy connection) throws SQLException {
@@ -299,6 +324,15 @@ public abstract class LogFilter extends FilterEventAdapter implements LogFilterM
connectionLog("{conn " + connection.getId() + "} rollback");
}
}
+
+ @Override
+ public void connection_rollback(FilterChain chain, ConnectionProxy connection, Savepoint savePoint) throws SQLException {
+ super.connection_rollback(chain, connection, savePoint);
+
+ if (connectionRollbackAfterLogEnable && isConnectionLogEnabled()) {
+ connectionLog("{conn " + connection.getId() + "} rollback -> " + savePoint.getSavepointId());
+ }
+ }
@Override
public void connection_commit(FilterChain chain, ConnectionProxy connection) throws SQLException {
diff --git a/src/main/java/com/alibaba/druid/mock/MockConnection.java b/src/main/java/com/alibaba/druid/mock/MockConnection.java
index 51d4d8e82a..7b0ebc1407 100644
--- a/src/main/java/com/alibaba/druid/mock/MockConnection.java
+++ b/src/main/java/com/alibaba/druid/mock/MockConnection.java
@@ -30,6 +30,8 @@ import java.sql.SQLXML;
import java.sql.Savepoint;
import java.sql.Statement;
import java.sql.Struct;
+import java.util.ArrayList;
+import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.Executor;
@@ -40,14 +42,16 @@ public class MockConnection extends ConnectionBase implements Connection {
// private final static Log LOG = LogFactory.getLog(MockConnection.class);
- private boolean closed = false;
+ private boolean closed = false;
- private MockDriver driver;
+ private MockDriver driver;
+ private int savepointIdSeed = 0;
+ private List savepoints = new ArrayList();
- private long id;
+ private long id;
- private final long createdTimeMillis = System.currentTimeMillis();
- private long lastActiveTimeMillis = System.currentTimeMillis();
+ private final long createdTimeMillis = System.currentTimeMillis();
+ private long lastActiveTimeMillis = System.currentTimeMillis();
public MockConnection(){
this(null, null, null);
@@ -63,6 +67,10 @@ public class MockConnection extends ConnectionBase implements Connection {
}
}
+ public List getSavepoints() {
+ return savepoints;
+ }
+
public long getLastActiveTimeMillis() {
return lastActiveTimeMillis;
}
@@ -168,6 +176,7 @@ public class MockConnection extends ConnectionBase implements Connection {
throw new MockConnectionClosedException();
}
+ this.savepoints.clear();
}
@Override
@@ -248,7 +257,12 @@ public class MockConnection extends ConnectionBase implements Connection {
if (closed) {
throw new MockConnectionClosedException();
}
- return null;
+
+ MockSavepoint savepoint = new MockSavepoint();
+ savepoint.setSavepointId(this.savepointIdSeed++);
+ this.savepoints.add(savepoint);
+
+ return savepoint;
}
@Override
@@ -256,7 +270,13 @@ public class MockConnection extends ConnectionBase implements Connection {
if (closed) {
throw new MockConnectionClosedException();
}
- return null;
+
+ MockSavepoint savepoint = new MockSavepoint();
+ savepoint.setSavepointId(this.savepointIdSeed++);
+ savepoint.setSavepointName(name);
+ this.savepoints.add(savepoint);
+
+ return savepoint;
}
@Override
@@ -264,6 +284,15 @@ public class MockConnection extends ConnectionBase implements Connection {
if (closed) {
throw new MockConnectionClosedException();
}
+
+
+ int index = this.savepoints.indexOf(savepoint);
+ if (index == -1) {
+ throw new SQLException("savepoint not contained");
+ }
+ for (int i = savepoints.size() - 1; i >= index; --i) {
+ savepoints.remove(i);
+ }
}
@Override
@@ -271,6 +300,16 @@ public class MockConnection extends ConnectionBase implements Connection {
if (closed) {
throw new MockConnectionClosedException();
}
+
+ if (savepoint == null) {
+ throw new SQLException("argument is null");
+ }
+
+ int index = this.savepoints.indexOf(savepoint);
+ if (index == -1) {
+ throw new SQLException("savepoint not contained");
+ }
+ savepoints.remove(savepoint);
}
@Override
diff --git a/src/main/java/com/alibaba/druid/mock/MockSavepoint.java b/src/main/java/com/alibaba/druid/mock/MockSavepoint.java
new file mode 100644
index 0000000000..d36c8786d7
--- /dev/null
+++ b/src/main/java/com/alibaba/druid/mock/MockSavepoint.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 1999-2011 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package com.alibaba.druid.mock;
+
+import java.sql.Savepoint;
+
+public class MockSavepoint implements Savepoint {
+
+ private int savepointId;
+ private String savepointName;
+
+ public int getSavepointId() {
+ return savepointId;
+ }
+
+ public void setSavepointId(int savepointId) {
+ this.savepointId = savepointId;
+ }
+
+ public String getSavepointName() {
+ return savepointName;
+ }
+
+ public void setSavepointName(String savepointName) {
+ this.savepointName = savepointName;
+ }
+
+}
diff --git a/src/main/java/com/alibaba/druid/pool/DruidPooledConnection.java b/src/main/java/com/alibaba/druid/pool/DruidPooledConnection.java
index 4921b79875..37a39161de 100644
--- a/src/main/java/com/alibaba/druid/pool/DruidPooledConnection.java
+++ b/src/main/java/com/alibaba/druid/pool/DruidPooledConnection.java
@@ -736,7 +736,7 @@ public class DruidPooledConnection implements javax.sql.PooledConnection, Connec
} catch (SQLException ex) {
handleException(ex);
} finally {
- handleEndTransaction(dataSource);
+ handleEndTransaction(dataSource, null);
}
}
@@ -762,7 +762,7 @@ public class DruidPooledConnection implements javax.sql.PooledConnection, Connec
} catch (SQLException ex) {
handleException(ex);
} finally {
- handleEndTransaction(dataSource);
+ handleEndTransaction(dataSource, null);
}
}
@@ -787,12 +787,12 @@ public class DruidPooledConnection implements javax.sql.PooledConnection, Connec
} catch (SQLException ex) {
handleException(ex);
} finally {
- handleEndTransaction(dataSource);
+ handleEndTransaction(dataSource, savepoint);
}
}
- private void handleEndTransaction(DruidAbstractDataSource dataSource) {
- if (transactionInfo != null) {
+ private void handleEndTransaction(DruidAbstractDataSource dataSource, Savepoint savepoint) {
+ if (transactionInfo != null && savepoint == null) {
transactionInfo.setEndTimeMillis();
long transactionMillis = transactionInfo.getEndTimeMillis() - transactionInfo.getStartTimeMillis();
diff --git a/src/main/java/com/alibaba/druid/sql/dialect/sqlserver/ast/stmt/SQLServerInsertStatement.java b/src/main/java/com/alibaba/druid/sql/dialect/sqlserver/ast/stmt/SQLServerInsertStatement.java
index 85329a6ce6..460455d4cb 100644
--- a/src/main/java/com/alibaba/druid/sql/dialect/sqlserver/ast/stmt/SQLServerInsertStatement.java
+++ b/src/main/java/com/alibaba/druid/sql/dialect/sqlserver/ast/stmt/SQLServerInsertStatement.java
@@ -1,3 +1,18 @@
+/*
+ * Copyright 1999-2011 Alibaba Group Holding Ltd.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
package com.alibaba.druid.sql.dialect.sqlserver.ast.stmt;
import java.util.ArrayList;
diff --git a/src/main/java/com/alibaba/druid/util/TransactionInfo.java b/src/main/java/com/alibaba/druid/util/TransactionInfo.java
index 7ba5dc7dc0..c7e4ab6ea4 100644
--- a/src/main/java/com/alibaba/druid/util/TransactionInfo.java
+++ b/src/main/java/com/alibaba/druid/util/TransactionInfo.java
@@ -21,7 +21,7 @@ import java.util.List;
public class TransactionInfo {
private final long id;
- private final List sqlList = new ArrayList();
+ private final List sqlList = new ArrayList(4);
private final long startTimeMillis;
private long endTimeMillis;
diff --git a/src/test/java/com/alibaba/druid/bvt/pool/DruidPooledConnectionTest.java b/src/test/java/com/alibaba/druid/bvt/pool/DruidPooledConnectionTest.java
index 57bb8f6b59..39dd1cfa3a 100644
--- a/src/test/java/com/alibaba/druid/bvt/pool/DruidPooledConnectionTest.java
+++ b/src/test/java/com/alibaba/druid/bvt/pool/DruidPooledConnectionTest.java
@@ -1,6 +1,7 @@
package com.alibaba.druid.bvt.pool;
import java.sql.Connection;
+import java.sql.Savepoint;
import junit.framework.Assert;
import junit.framework.TestCase;
@@ -26,7 +27,9 @@ public class DruidPooledConnectionTest extends TestCase {
public void test_rollback() throws Exception {
Connection conn = dataSource.getConnection();
conn.rollback();
- conn.rollback(null);
+
+ Savepoint savepoint = conn.setSavepoint("xx");
+ conn.rollback(savepoint);
conn.close();
}
diff --git a/src/test/java/com/alibaba/druid/bvt/pool/SavepointTest.java b/src/test/java/com/alibaba/druid/bvt/pool/SavepointTest.java
new file mode 100644
index 0000000000..abdb6cf754
--- /dev/null
+++ b/src/test/java/com/alibaba/druid/bvt/pool/SavepointTest.java
@@ -0,0 +1,72 @@
+package com.alibaba.druid.bvt.pool;
+
+import java.sql.Connection;
+import java.sql.Savepoint;
+import java.sql.Statement;
+
+import junit.framework.Assert;
+import junit.framework.TestCase;
+
+import com.alibaba.druid.mock.MockConnection;
+import com.alibaba.druid.pool.DruidDataSource;
+
+public class SavepointTest extends TestCase {
+
+ private DruidDataSource dataSource;
+
+ protected void setUp() throws Exception {
+ dataSource = new DruidDataSource();
+ dataSource.setUrl("jdbc:mock:xxx");
+ dataSource.setTestOnBorrow(false);
+ dataSource.setInitialSize(1);
+
+ }
+
+ protected void tearDown() throws Exception {
+ dataSource.close();
+ }
+
+ public void test_multi_savepoint() throws Exception {
+ Connection conn = dataSource.getConnection();
+ MockConnection physicalConn = conn.unwrap(MockConnection.class);
+
+ Assert.assertEquals(true, conn.getAutoCommit());
+ Assert.assertEquals(true, physicalConn.getAutoCommit());
+
+ conn.setAutoCommit(false);
+
+ Assert.assertEquals(false, conn.getAutoCommit());
+ Assert.assertEquals(false, physicalConn.getAutoCommit());
+
+ Savepoint[] savepoints = new Savepoint[100];
+ for (int i = 0; i < savepoints.length; ++i) {
+ Statement stmt = conn.createStatement();
+ stmt.execute("insert t (" + i + ")");
+ stmt.close();
+ savepoints[i] = conn.setSavepoint();
+
+ Assert.assertEquals(i + 1, physicalConn.getSavepoints().size());
+ for (int j = 0; j <= i; ++j) {
+ Assert.assertTrue(physicalConn.getSavepoints().contains(savepoints[j]));
+ }
+ }
+
+ // rollback single
+ conn.rollback(savepoints[99]);
+ Assert.assertEquals(99, physicalConn.getSavepoints().size());
+
+ // release single
+ conn.releaseSavepoint(savepoints[97]);
+ Assert.assertEquals(98, physicalConn.getSavepoints().size());
+
+ // rollback multi
+ conn.rollback(savepoints[90]);
+ Assert.assertEquals(90, physicalConn.getSavepoints().size());
+
+ // rollback all
+ conn.rollback();
+ Assert.assertEquals(0, physicalConn.getSavepoints().size());
+
+ conn.close();
+ }
+}
diff --git a/src/test/java/com/alibaba/druid/bvt/pool/basic/ConnectionTest3.java b/src/test/java/com/alibaba/druid/bvt/pool/basic/ConnectionTest3.java
index e82425097a..ed3bf705b8 100644
--- a/src/test/java/com/alibaba/druid/bvt/pool/basic/ConnectionTest3.java
+++ b/src/test/java/com/alibaba/druid/bvt/pool/basic/ConnectionTest3.java
@@ -17,6 +17,7 @@ package com.alibaba.druid.bvt.pool.basic;
import java.sql.Connection;
import java.sql.ResultSet;
+import java.sql.SQLException;
import java.util.Properties;
import junit.framework.Assert;
@@ -90,8 +91,24 @@ public class ConnectionTest3 extends TestCase {
conn.setSavepoint();
conn.setSavepoint("savepoint");
conn.rollback();
- conn.rollback(null);
- conn.releaseSavepoint(null);
+ {
+ Exception error = null;
+ try {
+ conn.rollback(null);
+ } catch (SQLException e) {
+ error = e;
+ }
+ Assert.assertNotNull(error);
+ }
+ {
+ Exception error = null;
+ try {
+ conn.releaseSavepoint(null);
+ } catch (SQLException e) {
+ error = e;
+ }
+ Assert.assertNotNull(error);
+ }
conn.createBlob();
conn.createClob();
conn.createNClob();
diff --git a/src/test/java/com/alibaba/druid/bvt/proxy/BasicTypeTest.java b/src/test/java/com/alibaba/druid/bvt/proxy/BasicTypeTest.java
index 4e984bff80..15cd0324d0 100644
--- a/src/test/java/com/alibaba/druid/bvt/proxy/BasicTypeTest.java
+++ b/src/test/java/com/alibaba/druid/bvt/proxy/BasicTypeTest.java
@@ -191,7 +191,7 @@ public class BasicTypeTest extends TestCase {
rs.updateRow();
JdbcUtils.close(rs);
- conn.rollback(point);
+// conn.rollback(point);
conn.setAutoCommit(true);
rs = stmt.executeQuery("SELECT F1, F2, F3, F4, F5, F6, F7, F8, F9, F10, F11, F12, F13, F14, F15, F16, F17 FROM T_BASIC_TYPE");
@@ -212,7 +212,7 @@ public class BasicTypeTest extends TestCase {
rs.getTimestamp("F11");
rs.getTimestamp("F11", Calendar.getInstance());
rs.getBytes("F12");
- rs.getBlob("F12").free();
+ rs.getBlob("F12");
rs.getString("F13");
rs.getAsciiStream("F13");
rs.getCharacterStream("F14");
diff --git a/src/test/java/com/alibaba/druid/test/NutzTransactionTest.java b/src/test/java/com/alibaba/druid/test/NutzTransactionTest.java
new file mode 100644
index 0000000000..d399b6ccda
--- /dev/null
+++ b/src/test/java/com/alibaba/druid/test/NutzTransactionTest.java
@@ -0,0 +1,75 @@
+package com.alibaba.druid.test;
+
+import javax.sql.DataSource;
+
+import junit.framework.Assert;
+import junit.framework.TestCase;
+
+import org.nutz.dao.Chain;
+import org.nutz.dao.Dao;
+import org.nutz.dao.impl.NutDao;
+import org.nutz.trans.Atom;
+import org.nutz.trans.Trans;
+
+import com.alibaba.druid.pool.DruidDataSource;
+
+public class NutzTransactionTest extends TestCase {
+
+ private DataSource dataSource;
+
+ protected void setUp() throws Exception {
+ DruidDataSource dataSource = new DruidDataSource();
+ dataSource.setUrl("jdbc:jtds:sqlserver://192.168.1.105/petstore");
+ dataSource.setUsername("sa");
+ dataSource.setPassword("hello");
+ dataSource.setFilters("log4j");
+
+// BasicDataSource dataSource = new BasicDataSource();
+// dataSource.setDriverClassName("net.sourceforge.jtds.jdbc.Driver");
+// dataSource.setUrl("jdbc:jtds:sqlserver://192.168.1.105/petstore");
+// dataSource.setUsername("sa");
+// dataSource.setPassword("hello");
+
+ this.dataSource = dataSource;
+ }
+
+ public void test_trans() throws Exception {
+ Dao dao = new NutDao(dataSource);
+
+ dao.clear("test");
+ // doTran1(dao);
+ doTran2(dao);
+ }
+
+ void doTran1(final Dao dao) {
+ try {
+ Trans.exec(new Atom() {
+
+ @Override
+ public void run() {
+ dao.insert("[test]", Chain.make("name", "1"));
+ throw new RuntimeException();
+ }
+ });
+ } catch (Exception e) {
+ }
+ Assert.assertEquals(0, dao.count("[test]"));
+ }
+
+ void doTran2(final Dao dao) {
+ try {
+ Trans.exec(new Atom() {
+
+ @Override
+ public void run() {
+ dao.insert("[test]", Chain.make("name", "1"));
+ dao.insert("[test]", Chain.make("name", "111111111111111111111111111111"));
+ }
+ });
+ } catch (Exception e) {
+ // e.printStackTrace();
+ }
+ Assert.assertEquals(0, dao.count("[test]"));
+ }
+
+}