Skip to content

Commit 1b5d95c

Browse files
committed
HHH-19593 ResourceRegistryStandardImpl triggers identity hashcode on Statements and ResultSets
This reverts commit ef2207f17d328d00ead2c372a6d311fe1b26a432.
1 parent 1ed27d5 commit 1b5d95c

File tree

5 files changed

+777
-41
lines changed

5 files changed

+777
-41
lines changed

hibernate-core/src/main/java/org/hibernate/resource/jdbc/internal/ResourceRegistryStandardImpl.java

Lines changed: 16 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,7 @@
1111
import java.sql.SQLException;
1212
import java.sql.Statement;
1313
import java.util.ArrayList;
14-
import java.util.HashMap;
1514

16-
import org.hibernate.HibernateException;
1715
import org.hibernate.JDBCException;
1816
import org.hibernate.internal.CoreLogging;
1917
import org.hibernate.internal.CoreMessageLogger;
@@ -40,17 +38,9 @@ public final class ResourceRegistryStandardImpl implements ResourceRegistry {
4038
private static final CoreMessageLogger log = CoreLogging.messageLogger( ResourceRegistryStandardImpl.class );
4139
private static final boolean IS_TRACE_ENABLED = log.isTraceEnabled();
4240

43-
// Dummy value to associate with an Object in the backing Map when we use it as a set:
44-
private static final Object PRESENT = new Object();
45-
46-
//Used instead of Collections.EMPTY_SET to avoid polymorphic calls on xref;
47-
//Also, uses an HashMap as it were an HashSet, as technically we just need the Set semantics
48-
//but in this case the overhead of HashSet is not negligible.
49-
private static final HashMap<ResultSet,Object> EMPTY = new HashMap<>( 1, 0.2f );
50-
5141
private final JdbcEventHandler jdbcEventHandler;
5242

53-
private final HashMap<Statement, HashMap<ResultSet,Object>> xref = new HashMap<>();
43+
private final ResultsetsTrackingContainer xref = new ResultsetsTrackingContainer();
5444

5545
private ExtendedState ext;
5646
private Statement lastQuery;
@@ -65,18 +55,15 @@ public ResourceRegistryStandardImpl(JdbcEventHandler jdbcEventHandler) {
6555

6656
@Override
6757
public boolean hasRegisteredResources() {
68-
return hasRegistered( xref )
58+
return xref.hasRegisteredResources()
6959
|| ext != null && ext.hasRegisteredResources();
7060
}
7161

7262
@Override
7363
public void register(Statement statement, boolean cancelable) {
7464
if ( IS_TRACE_ENABLED ) log.tracef( "Registering statement [%s]", statement );
7565

76-
HashMap<ResultSet,Object> previousValue = xref.putIfAbsent( statement, EMPTY );
77-
if ( previousValue != null ) {
78-
throw new HibernateException( "JDBC Statement already registered" );
79-
}
66+
xref.registerExpectingNew( statement );
8067

8168
if ( cancelable ) {
8269
lastQuery = statement;
@@ -87,7 +74,7 @@ public void register(Statement statement, boolean cancelable) {
8774
public void release(Statement statement) {
8875
if ( IS_TRACE_ENABLED ) log.tracev( "Releasing statement [{0}]", statement );
8976

90-
final HashMap<ResultSet,Object> resultSets = xref.remove( statement );
77+
final ResultSetsSet resultSets = xref.remove( statement );
9178
if ( resultSets != null ) {
9279
closeAll( resultSets );
9380
}
@@ -117,12 +104,12 @@ public void release(ResultSet resultSet, Statement statement) {
117104
}
118105
}
119106
if ( statement != null ) {
120-
final HashMap<ResultSet,Object> resultSets = xref.get( statement );
107+
final ResultSetsSet resultSets = xref.getForResultSetRemoval( statement );
121108
if ( resultSets == null ) {
122109
log.unregisteredStatement();
123110
}
124111
else {
125-
resultSets.remove( resultSet );
112+
resultSets.removeResultSet( resultSet );
126113
if ( resultSets.isEmpty() ) {
127114
try {
128115
if ( statement.isClosed() ) {
@@ -143,15 +130,14 @@ public void release(ResultSet resultSet, Statement statement) {
143130
close( resultSet );
144131
}
145132

146-
private static void closeAll(final HashMap<ResultSet,Object> resultSets) {
133+
private static void closeAll(final ResultSetsSet resultSets) {
147134
if ( resultSets == null ) {
148135
return;
149136
}
150-
resultSets.forEach( (resultSet, o) -> close( resultSet ) );
151-
resultSets.clear();
137+
resultSets.forEachResultSet( ResourceRegistryStandardImpl::close );
152138
}
153139

154-
private static void releaseXref(final Statement s, final HashMap<ResultSet, Object> r) {
140+
private static void releaseXref(final Statement s, final ResultSetsSet r) {
155141
closeAll( r );
156142
close( s );
157143
}
@@ -219,19 +205,7 @@ public void register(ResultSet resultSet, Statement statement) {
219205
}
220206
}
221207
if ( statement != null ) {
222-
HashMap<ResultSet,Object> resultSets = xref.get( statement );
223-
224-
// Keep this at DEBUG level, rather than warn. Numerous connection pool implementations can return a
225-
// proxy/wrapper around the JDBC Statement, causing excessive logging here. See HHH-8210.
226-
if ( resultSets == null ) {
227-
log.debug( "ResultSet statement was not registered (on register)" );
228-
}
229-
230-
if ( resultSets == null || resultSets == EMPTY ) {
231-
resultSets = new HashMap<>();
232-
xref.put( statement, resultSets );
233-
}
234-
resultSets.put( resultSet, PRESENT );
208+
xref.storeAssociatedResultset( statement, resultSet );
235209
}
236210
else {
237211
getExtendedStateForWrite().storeUnassociatedResultset( resultSet );
@@ -328,7 +302,7 @@ public void releaseResources() {
328302
}
329303
}
330304

331-
private static boolean hasRegistered(final HashMap resource) {
305+
private static boolean hasRegistered(final ResultSetsSet resource) {
332306
return resource != null && !resource.isEmpty();
333307
}
334308

@@ -344,7 +318,7 @@ private static boolean hasRegistered(final ArrayList resource) {
344318
*/
345319
private static class ExtendedState {
346320
//All fields lazily initialized as well:
347-
private HashMap<ResultSet,Object> unassociatedResultSets;
321+
private ResultSetsSet unassociatedResultSets;
348322
private ArrayList<Blob> blobs;
349323
private ArrayList<Clob> clobs;
350324
private ArrayList<NClob> nclobs;
@@ -357,17 +331,17 @@ public boolean hasRegisteredResources() {
357331
}
358332

359333
public void releaseUnassociatedResult(final ResultSet resultSet) {
360-
final Object removed = unassociatedResultSets == null ? null : unassociatedResultSets.remove( resultSet );
334+
final Object removed = unassociatedResultSets == null ? null : unassociatedResultSets.removeResultSet( resultSet );
361335
if ( removed == null ) {
362336
log.unregisteredResultSetWithoutStatement();
363337
}
364338
}
365339

366340
public void storeUnassociatedResultset(ResultSet resultSet) {
367341
if ( unassociatedResultSets == null ) {
368-
this.unassociatedResultSets = new HashMap<>();
342+
this.unassociatedResultSets = new ResultSetsSet();
369343
}
370-
unassociatedResultSets.put( resultSet, PRESENT );
344+
unassociatedResultSets.storeResultSet( resultSet );
371345
}
372346

373347
public void registerBlob(final Blob blob) {
@@ -394,6 +368,7 @@ public void registerNClob(final NClob nclob) {
394368

395369
public void releaseResources() {
396370
closeAll( unassociatedResultSets );
371+
unassociatedResultSets.clear();
397372

398373
if ( blobs != null ) {
399374
blobs.forEach( blob -> {
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
/*
2+
* SPDX-License-Identifier: Apache-2.0
3+
* Copyright Red Hat Inc. and Hibernate Authors
4+
*/
5+
package org.hibernate.resource.jdbc.internal;
6+
7+
import java.sql.ResultSet;
8+
import java.util.HashMap;
9+
import java.util.Iterator;
10+
import java.util.Map;
11+
import java.util.function.Consumer;
12+
13+
/**
14+
* Similar goal as in {@link ResultsetsTrackingContainer}: make sure to
15+
* be very efficient when handling a single {@link ResultSet}
16+
* as it's a very common case, and try to avoid needing the hashcodes.
17+
* Yet we want to allow scaling to multiple instances as well.
18+
*/
19+
final class ResultSetsSet {
20+
21+
//Implementation notes:
22+
// # if first is null, then the Map in field 'more' is guaranteed to be empty
23+
// # The 'more' Map is lazily initialized, but when emptied it's not guaranteed to be made null
24+
25+
private ResultSet first;
26+
//This could have been a set, but we intentionally use a Map instead to avoid the wrapping done in
27+
//HashSet.
28+
private HashMap<ResultSet,ResultSet> more;
29+
30+
void forEachResultSet(final Consumer<ResultSet> action) {
31+
if ( first != null ) {
32+
action.accept( first );
33+
if ( more != null ) {
34+
more.keySet().forEach( action );
35+
}
36+
}
37+
}
38+
39+
void storeResultSet(final ResultSet resultSet) {
40+
if ( first == null ) {
41+
//no need for further checks as we guarantee "more" to be empty in this case
42+
first = resultSet;
43+
}
44+
else if ( first == resultSet ) {
45+
//no-op for this special case
46+
}
47+
else {
48+
if ( more == null ) {
49+
more = new HashMap<>();
50+
}
51+
more.put( resultSet, resultSet );
52+
}
53+
}
54+
55+
boolean isEmpty() {
56+
return first == null;
57+
}
58+
59+
ResultSet removeResultSet(final ResultSet resultSet) {
60+
if ( first == resultSet ) {
61+
ResultSet v = first;
62+
first = null;
63+
scaleDown();
64+
return v;
65+
}
66+
else if ( more != null ) {
67+
return more.remove( resultSet );
68+
}
69+
else {
70+
return null;
71+
}
72+
}
73+
74+
//When slot "first" is made available, make sure to move an entry from "more" into that field.
75+
//Any entry will do, so we take the first one if there's any.
76+
private void scaleDown() {
77+
if ( more != null && !more.isEmpty() ) {
78+
Iterator<Map.Entry<ResultSet, ResultSet>> iterator = more.entrySet().iterator();
79+
Map.Entry<ResultSet, ResultSet> entry = iterator.next();
80+
final ResultSet resultSet = entry.getKey();
81+
iterator.remove();
82+
first = resultSet;
83+
}
84+
}
85+
86+
void clear() {
87+
first = null;
88+
if ( more != null ) {
89+
more.clear();
90+
this.more = null;
91+
}
92+
}
93+
94+
}

0 commit comments

Comments
 (0)