Its a bit of crap Api - ends up with you writing a load of boiler plate code.
I reckon the best way to go is wrap the classes and make it so that disposing the connection disposes the other stuff (as you can track what gets made on the way out of the wrapped calls).
As long as you've got an IDE that can generate you delegating methods in a class then it's a trivial job to wrap stuff like this.
I didn't realise all the extra stuff needed disposing but just spotted someone doing it here, however I'm in luck as we are already wrapping the basic classes to turn all the annoying exceptions into RuntimeExceptions and to provide some more high level sql operations.
I made a little class for tracking the different bits of stuff:
public class CleanupList
{
private final ArrayList<AutoCloseable> _disposables;
public CleanupList()
{
_disposables = new ArrayList<>();
}
public void cleanup()
{
for(AutoCloseable closeable : _disposables){
//it sucks that they put an exception on this interface
//if anyone actually throws an exception in a close method then there's something seriously wrong going on
//they should have copied the c# more closely imo as it has nicer syntax aswell
try
{
closeable.close();
}
catch (Exception e)
{
throw new RuntimeException(e);
}
}
_disposables.clear();
}
public <T extends AutoCloseable> T track(T statement)
{
_disposables.add(statement);
return statement;
}
}
And then for example in Wrapped Connection (which is something that wraps a database connection):
public class WrappedConnection implements AutoCloseable
{
private final CleanupList _cleanupList;
private Connection _connection;
public WrappedConnection(Connection connection)
{
_connection = connection;
_cleanupList = new CleanupList();
}
public void close()
{
try
{
_connection.close();
_cleanupList.cleanup();
}
catch (SQLException e)
{
throw new RuntimeException(e);
}
}
public PreparedStatement prepareStatement(String sql)
{
try
{
return trackForDisposal(_connection.prepareStatement(sql));
}
catch (SQLException e)
{
throw new RuntimeException(e);
}
}
private <T extends AutoCloseable> T trackForDisposal(T statement)
{
return _cleanupList.track(statement);
}
.... lots more methods
}
You can then also pass the same list into wrapped versions of PreparedStatement/Result sets (which I've not shown here) etc and use it in a similar way.
I don't know what other people are using but in IDEA you can switch on a warning for auto-closable stuff that isn't in a using (or should I say try-with-resources) block:
try(SomethingThatNeedsClosing somethingThatNeedsClosing = new SomethingThatNeedsClosing()){
//do stuff
}
These using blocks do you a try finally close automatically and can only be used with things of the AutoClosable interface type
I dont know why this warning isn't switched on by default in IDEA but there you go.