With ConnectionReleaseMode.AFTER_TRANSACTION (the default), Hibernate does not release the JDBC connection when resolving a lazily loaded collection, if this happens outside of an active transaction.
First, I will describe, what happens if Hibernate executes a query outside of a transaction. Then I will describe how lazy collection loading behaves differently.
When the method list of a query (QueryImpl.java) is getting called, Hibernate delegates to SessionImpl.list(). After having loaded the list, SessionImpl.list() calls SessionImpl.afterOperation(), which calls jdbcContext.afterNontransactionalQuery() (as there is no active transaction). This leads to ConnectionManager.afterTransaction() which releases the JDBC connection. This is exactly what I expect to happen.
Now to the lazily loaded collection:
Hibernate.initialize(collection) and person.getPets().size() both end up in SessionImpl.initializeCollection(). This call gets dispatched down to Loader.doQueryAndInitializeNonLazyCollections() and Loader.doQuery(). The important fact is, that ConnectionManager.afterTransaction() never gets called (like in the query-example above).
The result is, that the JDBC connection is not released.
IMHO resolving of a lazily loaded collection should behave like executing a query. SessionImpl.initializeCollection() should call SessionImpl.afterOperation() in the same way as SessionImpl.list() is doing this.
I will attach a small demo application which shows the difference (concerning ConnectionManager) between queries and collections.