" While using the YourKit performance monitoring tool I found two hotspots in file hibernate-core/src/main/java/org/hibernate/resource/jdbc/internal/ResourceRegistryStandardImpl.java function register(Statement statement, boolean cancelable) lines 67-70 at the first and last lines (67 and 70) inside Object.hashCode():
\r\n\r\n {code} if \r\n\t\tif ( xref.containsKey( statement ) ) { throw \r\n\t\t\tthrow new HibernateException( \ "JDBC Statement already registered \ " ); \r\n\t\t } xref \r\n\t\txref .put( statement, null ); \r\n {code}
There \r\n\r\nThere are two problems here:
a \r\n\r\na ) on the no-exception-thrown code path both the containsKey( statement ) and .put( statement, null ) hash the Statement object -- hashing it twice is wasteful
b \r\n\r\nb ) the Statement object (which in our particular case is a com.zaxxer.hikari.pool.HikariProxyPreparedStatement wrapping a com.microsoft.sqlserver.jdbc.SQLServerPreparedStatement) is large and has a lot of fields, making is slow to hash. Worse, some of which the fields seem to be to do with current status rather than identity, which could potentially cause cache misses in the xref hash. If possible, it would be much more efficient to override the default behavior of .hashCode() and .equals() for implementations of Statement to only take into account fields that describe identity, rather than current status. In particular, if an implementation of Statement already has a functional unique identifier, both .hashCode() and .equals() should depend just on that. In our particular case, statement.connection. traceId traceID and statement.delegate. traceId traceID look like they might be unique identifiers. I understand that handling this correctly might require work from the HikariCP connection pool and/or the Microsoft JDBC driver code as well as or instead of from Hibernate.
Issue \r\n\r\nIssue a) can be fixed by changing the code lines above to:
\r\n\r\n {code} Set \r\n\t\tSet <ResultSet> already = xref.put( statement, Collections.EMPTY_SET ); if \r\n\t\tif ( already != null ) { xref \r\n\t\t\txref .put( statement, already ); throw \r\n\t\t\tthrow new HibernateException( \ "JDBC Statement already registered \ " ); \r\n\t\t } \r\n {code}
thus \r\n\r\nthus avoiding hashing twice on the no-exception-thrown code path, and also changing hibernate-core/src/main/java/org/hibernate/resource/jdbc/internal/ResourceRegistryStandardImpl.java function release(Statement statement) lines 81-92 from:
\r\n\r\n {code} \r\n\t\t // Keep this at DEBUG level, rather than warn. Numerous connection pool implementations can return a \r\n\t\t // proxy/wrapper around the JDBC Statement, causing excessive logging here. See HHH-8210. if \r\n\t\tif ( log.isDebugEnabled() && !xref.containsKey( statement ) ) { log \r\n\t\t\tlog .unregisteredStatement(); \r\n\t\t } else \r\n\t\telse { final \r\n\t\t\tfinal Set<ResultSet> resultSets = xref.get( statement ); if \r\n\t\t\tif ( resultSets != null ) { closeAll \r\n\t\t\t\tcloseAll ( resultSets ); \r\n\t\t\t } xref \r\n\t\t\txref .remove( statement ); \r\n\t\t } \r\n {code}
to \r\n\r\nto :
\r\n\r\n {code} final \r\n\t\tfinal Set<ResultSet> resultSets = xref.get( statement ); if \r\n\t\tif ( resultSets != null ) { closeAll \r\n\t\t\tcloseAll ( resultSets ); \r\n\t\t } else if ( log.isDebugEnabled() ) { \r\n\t\t\t // Keep this at DEBUG level, rather than warn. Numerous connection pool implementations can return a \r\n\t\t\t // proxy/wrapper around the JDBC Statement, causing excessive logging here. See HHH-8210. log \r\n\t\t\tlog .unregisteredStatement(); \r\n\t\t } xref \r\n\t\txref .remove( statement ); \r\n {code}
\r\n\r\n and changing function register(ResultSet resultSet, Statement statement) lines 203-213 from:
\r\n\r\n {code} if \r\n\t\tif ( statement != null ) { \r\n\t\t\t // Keep this at DEBUG level, rather than warn. Numerous connection pool implementations can return a \r\n\t\t\t // proxy/wrapper around the JDBC Statement, causing excessive logging here. See HHH-8210. if \r\n\t\t\tif ( log.isDebugEnabled() && !xref.containsKey( statement ) ) { log \r\n\t\t\t\tlog .debug( \ "ResultSet statement was not registered (on register) \ " ); \r\n\t\t\t } Set \r\n\t\t\tSet <ResultSet> resultSets = xref.get( statement ); if \r\n\t\t\tif ( resultSets == null ) { resultSets \r\n \t\t\t\tresultSets = new HashSet<ResultSet>(); xref \r\n\t\t\t\txref .put( statement, resultSets ); \r\n\t\t\t } \r\n {code}
to \r\n\r\nto :
\r\n\r\n {code} if \r\n\t\tif ( statement != null ) { Set \r\n\t\t\tSet <ResultSet> resultSets = xref.get( statement ); if \r\n\t\t\tif ( resultSets == null ) { \r\n\t\t\t\t // Keep this at DEBUG level, rather than warn. Numerous connection pool implementations can return a \r\n\t\t\t\t // proxy/wrapper around the JDBC Statement, causing excessive logging here. See HHH-8210. if \r\n\t\t\t\tif ( log.isDebugEnabled() ) { log \r\n\t\t\t\t\tlog .debug( \ "ResultSet statement was not registered (on register) \ " ); \r\n\t\t\t\t } resultSets \r\n\t\t\t\tresultSets = Collections.EMPTY_SET; \r\n\t\t\t } if \r\n\t\t\tif ( resultSets == Collections.EMPTY_SET ) { resultSets \r\n\t\t\t\tresultSets = new HashSet<ResultSet>(); xref \r\n\t\t\t\txref .put( statement, resultSets ); \r\n\t\t\t } \r\n {code} \r\n" |
|