I recently encountered an issue where one of my teammates was experiencing an issue where an application datasource would periodically become exhausted. After discussing it a few times, we hypothesized that it was a situation where there was a database connection leak. It was suspected the leak was within boundaries of some application code with a low call rate. This would line up with, why the application would run for an extended period and then all of a sudden max out its database connections.
By inspecting the logs around the times of the database connection pool becoming exhausted it became pretty clear the potential location of the problematic code.
The cause of the issue was not a result of necessarily bad code writing, but more of a case of not knowing how legacy semantics would work within the context of a spring transactional boundary. The semantics of how a call to DataSource.getConnection() behaves within the Spring transaction boundary is probably not entirely evident.
The exact situation was even a little more confounded as the developer was actually calling a legacy API. The legacy API had some interfaces defined to require a database connection to be provided. The API would perform some database activity and then expect the client/caller to perform connection maintenance and clean up.
The developer had a method within their application that was using a Spring annotation to mark it as transactional:
@Transactional(propagation = Propagation.REQUIRED) .
Note: Propagation.REQUIRED indicates that if a transaction is already initiated by a calling method then this method will participate in that transaction. If one is not present, one will be created.
Within that method there was a call to retrieve a new database connection by calling getConnection on a datasource. That call will always return a new database connection despite being within a transaction boundary and will never participate in the spring managed transaction. Therefore if you use such a call you need to perform the standard database cleanup and close the connection after use.
However you could use the DataSourceUtils class from spring. If you use the getConnection on the DataSourceUtils it will check if there is an active connection for the current thread. If there is one it will utilize the active connection. If there isn't one then it will retrieve a new connection. Of course you need to be careful with DataSourceUtils use as well. If it leverages an active connection then no management of the connection is needed. If it ends up creating a new one then that local logic should handle commit, rollback and clean up.
Hopefully you will never need to mix legacy code within a more modern transaction context, but if you do run up against it, it is good to understand how it will behave.
By inspecting the logs around the times of the database connection pool becoming exhausted it became pretty clear the potential location of the problematic code.
The cause of the issue was not a result of necessarily bad code writing, but more of a case of not knowing how legacy semantics would work within the context of a spring transactional boundary. The semantics of how a call to DataSource.getConnection() behaves within the Spring transaction boundary is probably not entirely evident.
The exact situation was even a little more confounded as the developer was actually calling a legacy API. The legacy API had some interfaces defined to require a database connection to be provided. The API would perform some database activity and then expect the client/caller to perform connection maintenance and clean up.
The developer had a method within their application that was using a Spring annotation to mark it as transactional:
@Transactional(propagation = Propagation.REQUIRED) .
Note: Propagation.REQUIRED indicates that if a transaction is already initiated by a calling method then this method will participate in that transaction. If one is not present, one will be created.
Within that method there was a call to retrieve a new database connection by calling getConnection on a datasource. That call will always return a new database connection despite being within a transaction boundary and will never participate in the spring managed transaction. Therefore if you use such a call you need to perform the standard database cleanup and close the connection after use.
However you could use the DataSourceUtils class from spring. If you use the getConnection on the DataSourceUtils it will check if there is an active connection for the current thread. If there is one it will utilize the active connection. If there isn't one then it will retrieve a new connection. Of course you need to be careful with DataSourceUtils use as well. If it leverages an active connection then no management of the connection is needed. If it ends up creating a new one then that local logic should handle commit, rollback and clean up.
Hopefully you will never need to mix legacy code within a more modern transaction context, but if you do run up against it, it is good to understand how it will behave.
Comments
Post a Comment