Skip to content

Commit 5c22b2d

Browse files
committed
CAY-2954 Do not wrap selecting queries in transactions
1 parent a6bcedd commit 5c22b2d

24 files changed

Lines changed: 417 additions & 36 deletions

RELEASE-NOTES.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ Date:
1313
----------------------------------
1414
Changes/New Features:
1515

16+
CAY-2954 Don't wrap selecting queries in transactions
1617
CAY-2956 Get rid of a dedicated adapter for Oracle 8
1718
CAY-2957 Get rid of adapter for legacy HSQLDB <= 1.8
1819
CAY-2963 Replace TypesHandler / types.xml with hardcoded map

cayenne/src/main/java/org/apache/cayenne/access/DataDomainQueryAction.java

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,6 +54,7 @@
5454
import org.apache.cayenne.reflect.ClassDescriptor;
5555
import org.apache.cayenne.reflect.LifecycleCallbackRegistry;
5656
import org.apache.cayenne.tx.BaseTransaction;
57+
import org.apache.cayenne.tx.ReadOnlyTransaction;
5758
import org.apache.cayenne.tx.Transaction;
5859
import org.apache.cayenne.util.GenericResponse;
5960
import org.apache.cayenne.util.ListResponse;
@@ -483,10 +484,17 @@ private QueryCacheEntryFactory getCacheObjectFactory() {
483484
* Gets response from the underlying DataNodes.
484485
*/
485486
void runQueryInTransaction() {
486-
domain.getTransactionManager().performInTransaction(() -> {
487-
runQuery();
488-
return null;
489-
});
487+
if (metadata.isReadOnly() && BaseTransaction.getThreadTransaction() == null) {
488+
domain.getTransactionManager().performInTransaction(() -> {
489+
runQuery();
490+
return null;
491+
}, descriptor -> new ReadOnlyTransaction());
492+
} else {
493+
domain.getTransactionManager().performInTransaction(() -> {
494+
runQuery();
495+
return null;
496+
});
497+
}
490498
}
491499

492500
private void runQuery() {

cayenne/src/main/java/org/apache/cayenne/access/DataNodeQueryAction.java

Lines changed: 6 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -19,20 +19,19 @@
1919

2020
package org.apache.cayenne.access;
2121

22-
import java.sql.Connection;
23-
import java.sql.SQLException;
24-
import java.util.List;
25-
2622
import org.apache.cayenne.ObjectId;
2723
import org.apache.cayenne.ResultIterator;
2824
import org.apache.cayenne.query.Query;
2925
import org.apache.cayenne.query.SQLAction;
3026

27+
import java.sql.Connection;
28+
import java.util.List;
29+
3130
/**
3231
* A helper that executes a sequence of queries, providing correct mapping of the results
3332
* to the original query. Note that this class is not thread-safe as it stores current
3433
* query execution state.
35-
*
34+
*
3635
* @since 1.2
3736
*/
3837
class DataNodeQueryAction {
@@ -45,8 +44,7 @@ public DataNodeQueryAction(DataNode node, OperationObserver observer) {
4544
this.node = node;
4645
}
4746

48-
public void runQuery(Connection connection, final Query originalQuery)
49-
throws SQLException, Exception {
47+
public void runQuery(Connection connection, Query originalQuery) throws Exception {
5048

5149
// wrap to ensure that the result is mapped back to the original query, even if
5250
// the underlying SQLAction uses query substitute...
@@ -71,7 +69,7 @@ public void nextRows(Query query, List<?> dataRows) {
7169
public void nextRows(Query q, ResultIterator it) {
7270
observer.nextRows(originalQuery, it);
7371
}
74-
72+
7573
@Override
7674
public void nextGeneratedRows(Query query, ResultIterator<?> keys, List<ObjectId> idsToUpdate) {
7775
observer.nextGeneratedRows(originalQuery, keys, idsToUpdate);

cayenne/src/main/java/org/apache/cayenne/access/jdbc/SelectAction.java

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -75,12 +75,15 @@ public SelectAction(Select<?> query, DataNode dataNode) {
7575

7676
@Override
7777
public void performAction(Connection connection, OperationObserver observer) throws Exception {
78+
TranslatedSelect translated = dataNode.getSelectTranslator().translate(query, dataNode.getAdapter(), dataNode.getEntityResolver());
79+
performAction(connection, observer, translated);
80+
}
81+
82+
protected void performAction(Connection connection, OperationObserver observer, TranslatedSelect translated) throws Exception {
7883

7984
long t1 = System.currentTimeMillis();
8085

8186
JdbcEventLogger logger = dataNode.getJdbcEventLogger();
82-
TranslatedSelect translated = dataNode.getSelectTranslator()
83-
.translate(query, dataNode.getAdapter(), dataNode.getEntityResolver());
8487

8588
logger.logQuery(translated.sql(), translated.bindings());
8689

cayenne/src/main/java/org/apache/cayenne/dba/AutoAdapter.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -152,6 +152,7 @@ public boolean supportsGeneratedKeysForBatchInserts() {
152152
return getAdapter().supportsGeneratedKeysForBatchInserts();
153153
}
154154

155+
155156
@Override
156157
public boolean supportsBatchUpdates() {
157158
return getAdapter().supportsBatchUpdates();

cayenne/src/main/java/org/apache/cayenne/dba/postgres/PostgresSelectAction.java

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,9 +19,17 @@
1919
package org.apache.cayenne.dba.postgres;
2020

2121
import org.apache.cayenne.access.DataNode;
22+
import org.apache.cayenne.access.OperationObserver;
23+
import org.apache.cayenne.access.jdbc.ColumnDescriptor;
2224
import org.apache.cayenne.access.jdbc.SelectAction;
25+
import org.apache.cayenne.access.translator.ParameterBinding;
26+
import org.apache.cayenne.access.translator.select.TranslatedSelect;
2327
import org.apache.cayenne.query.Select;
2428

29+
import java.sql.Connection;
30+
import java.sql.SQLException;
31+
import java.sql.Types;
32+
2533
/**
2634
* @since 3.0
2735
*/
@@ -35,4 +43,52 @@ <T> PostgresSelectAction(Select<T> query, DataNode dataNode) {
3543
protected int getInMemoryOffset(int queryOffset) {
3644
return 0;
3745
}
46+
47+
@Override
48+
protected void performAction(Connection connection, OperationObserver observer, TranslatedSelect translated) throws Exception {
49+
50+
if (!connection.getAutoCommit() || !readsLargeObjects(translated)) {
51+
super.performAction(connection, observer, translated);
52+
return;
53+
}
54+
55+
// manual tx management for reading LOBs
56+
connection.setAutoCommit(false);
57+
try {
58+
super.performAction(connection, observer, translated);
59+
connection.commit();
60+
} catch (Exception e) {
61+
try {
62+
connection.rollback();
63+
} catch (SQLException ignored) {
64+
// connection is being returned/closed anyway
65+
}
66+
throw e;
67+
} finally {
68+
try {
69+
connection.setAutoCommit(true);
70+
} catch (SQLException ignored) {
71+
// connection is being returned/closed anyway
72+
}
73+
}
74+
}
75+
76+
private static boolean readsLargeObjects(TranslatedSelect translated) {
77+
for (ColumnDescriptor column : translated.resultColumns()) {
78+
if (isLargeObject(column.getJdbcType())) {
79+
return true;
80+
}
81+
}
82+
// a large object bound as a parameter (e.g. in a qualifier) also needs a transaction
83+
for (ParameterBinding binding : translated.bindings()) {
84+
if (isLargeObject(binding.getJdbcType())) {
85+
return true;
86+
}
87+
}
88+
return false;
89+
}
90+
91+
private static boolean isLargeObject(int jdbcType) {
92+
return jdbcType == Types.BLOB || jdbcType == Types.CLOB || jdbcType == Types.NCLOB;
93+
}
3894
}

cayenne/src/main/java/org/apache/cayenne/query/ObjectIdQuery.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -105,6 +105,11 @@ public ObjEntity getObjEntity() {
105105
public boolean isFetchingDataRows() {
106106
return fetchingDataRows;
107107
}
108+
109+
@Override
110+
public boolean isReadOnly() {
111+
return true;
112+
}
108113
};
109114

110115
this.metadataResolver = resolver;

cayenne/src/main/java/org/apache/cayenne/query/ObjectSelectMetadata.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,14 @@ class ObjectSelectMetadata extends BaseQueryMetadata {
3939

4040
protected Map<String, String> pathSplitAliases;
4141

42+
/**
43+
* @since 5.0
44+
*/
45+
@Override
46+
public boolean isReadOnly() {
47+
return true;
48+
}
49+
4250
@Override
4351
void copyFromInfo(QueryMetadata info) {
4452
super.copyFromInfo(info);

cayenne/src/main/java/org/apache/cayenne/query/QueryMetadata.java

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -264,4 +264,15 @@ default void setResultSetMapping(List<Object> resultSetMapping) {
264264
default Function<?, ?> getResultMapper() {
265265
return null;
266266
}
267+
268+
/**
269+
* Returns true if this is a read-only (selecting) query that does not need to run in its own
270+
* transaction. Defaults to false, so any query not explicitly marked read-only is still wrapped
271+
* in a transaction.
272+
*
273+
* @since 5.0
274+
*/
275+
default boolean isReadOnly() {
276+
return false;
277+
}
267278
}

cayenne/src/main/java/org/apache/cayenne/query/QueryMetadataProxy.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -141,4 +141,9 @@ public int getQueryTimeout() {
141141
public boolean isSuppressingDistinct() {
142142
return mdDelegate.isSuppressingDistinct();
143143
}
144+
145+
@Override
146+
public boolean isReadOnly() {
147+
return mdDelegate.isReadOnly();
148+
}
144149
}

0 commit comments

Comments
 (0)