We recently upgraded our Grails project from 3.2.6 to 3.3.5, after which we began receiving intermittent errors indicating the the connection had already been closed when using the builtin domain class methods such as the findBy, findAllBy, save methods.
We do not received this error every time these methods are called, but rather they seem to happen after a random number of instances have been called.
We seem to be able to resolve the issue if we wrap the call to these domain methods in a withTransaction closure, but would prefer not to go through the whole application to track down all calls to these domain class methods and wrap them in this closure.
In case it is relevant, we the project is configured with multiple datasources. The domain classes which are causing this error use the default dataSource.
Does anyone know why this has started happening, did something change between Grails 3.2.6 and 3.3.5 which would require us to start making all domain class calls within transaction? If so, is there a way to prevent this need?
Stacktrace:
2024-07-12 11:21:02.198 ERROR — [io-7080-exec-85] o.h.engine.jdbc.spi.SqlExceptionHelper : Connection has already been closed.
org.hibernate.exception.GenericJDBCException: could not prepare statement
at org.hibernate.exception.internal.StandardSQLExceptionConverter.convert(StandardSQLExceptionConverter.java:47)
at org.hibernate.engine.jdbc.spi.SqlExceptionHelper.convert(SqlExceptionHelper.java:111)
at org.hibernate.engine.jdbc.internal.StatementPreparerImpl$StatementPreparationTemplate.prepareStatement(StatementPreparerImpl.java:182)
at org.hibernate.engine.jdbc.internal.StatementPreparerImpl.prepareQueryStatement(StatementPreparerImpl.java:148)
at org.hibernate.loader.Loader.prepareQueryStatement(Loader.java:1934)
at org.hibernate.loader.Loader.executeQueryStatement(Loader.java:1903)
at org.hibernate.loader.Loader.executeQueryStatement(Loader.java:1881)
at org.hibernate.loader.Loader.doQuery(Loader.java:925)
at org.hibernate.loader.Loader.doQueryAndInitializeNonLazyCollections(Loader.java:342)
at org.hibernate.loader.Loader.doList(Loader.java:2622)
at org.hibernate.loader.Loader.doList(Loader.java:2605)
at org.hibernate.loader.Loader.listIgnoreQueryCache(Loader.java:2434)
at org.hibernate.loader.Loader.list(Loader.java:2429)
at org.hibernate.loader.criteria.CriteriaLoader.list(CriteriaLoader.java:109)
at org.hibernate.internal.SessionImpl.list(SessionImpl.java:1787)
at org.hibernate.internal.CriteriaImpl.list(CriteriaImpl.java:363)
at org.grails.orm.hibernate.query.AbstractHibernateQuery.listForCriteria(AbstractHibernateQuery.java:719)
at org.grails.orm.hibernate.query.AbstractHibernateQuery.list(AbstractHibernateQuery.java:709)
at org.grails.datastore.gorm.finders.FindAllByFinder.invokeQuery(FindAllByFinder.java:54)
at org.grails.datastore.gorm.finders.FindAllByFinder$1.doInSession(FindAllByFinder.java:48)
at org.grails.datastore.mapping.core.DatastoreUtils.execute(DatastoreUtils.java:319)
at org.grails.datastore.gorm.finders.AbstractFinder.execute(AbstractFinder.java:42)
at org.grails.datastore.gorm.finders.FindAllByFinder.doInvokeInternal(FindAllByFinder.java:45)
at org.grails.datastore.gorm.finders.DynamicFinder.invoke(DynamicFinder.java:254)
at org.grails.datastore.gorm.finders.DynamicFinder.invoke(DynamicFinder.java:392)
at org.grails.datastore.gorm.finders.FinderMethod$invoke$2.call(Unknown Source)
at org.grails.datastore.gorm.GormStaticApi$_methodMissing_closure2.doCall(GormStaticApi.groovy:181)
at sun.reflect.GeneratedMethodAccessor6163.invoke(Unknown Source)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.codehaus.groovy.reflection.CachedMethod.invoke(CachedMethod.java:98)
at groovy.lang.MetaMethod.doMethodInvoke(MetaMethod.java:325)
at org.codehaus.groovy.runtime.metaclass.ClosureMetaClass.invokeMethod(ClosureMetaClass.java:264)
at groovy.lang.MetaClassImpl.invokeMethod(MetaClassImpl.java:1034)
at groovy.lang.Closure.call(Closure.java:418)
application.yml:
production:
dataSources:
dataSource:
url: "jdbc:mysql://<<URL>>?autoReconnect=true&useSSL=false"
dialect: org.hibernate.dialect.MySQL5InnoDBDialect
driverClassName: "com.mysql.cj.jdbc.Driver"
username: "<USERNAME>"
password: "<PASSWORD>"
pooled: true
properties:
jmxEnabled: true
initialSize: 5
maxActive: 50
minIdle: 5
maxIdle: 25
maxWait: 10000
maxAge: 600000
timeBetweenEvictionRunsMillis: 5000
minEvictableIdleTimeMillis: 60000
validationQuery: 'SELECT 1'
validationQueryTimeout: 3
validationInterval: 15000
testOnBorrow: true
testWhileIdle: true
testOnReturn: false
jdbcInterceptors: ConnectionState;StatementCache(max=200)
removeAbandoned: true
removeAbandonedTimeout: 60
dbProperties:
autoReconnect: true
second:
url: 'jdbc:as400://<URL>;transaction isolation=none'
autocommit: true
pooled: true
username: "<USERNAME>"
password: "<PASSWORD>"
driverClassName: com.ibm.as400.access.AS400JDBCDriver
properties:
jmxEnabled: true
initialSize: 1
maxActive: 50
minIdle: 1
maxIdle: 25
maxWait: 10000
maxAge: 600000
timeBetweenEvictionRunsMillis: 5000
minEvictableIdleTimeMillis: 60000
validationQueryTimeout: 3
validationInterval: 15000
testOnBorrow: true
testWhileIdle: true
testOnReturn: false
jdbcInterceptors: ConnectionState;StatementCache(max=200)
removeAbandoned: true
removeAbandonedTimeout: 60