Previously we were creating a new Connection every time we needed it. But this is a heavy operation as it needs to make some TCP handshake (we mostly communicate with DBs over network) and allocate resources on DB side. Then we do a small SQL operation that's 100x times faster than the whole set up. And what do we do afterwards? We just close the connection.
To make it more effective we can re-use that Connection - finish the transaction and just store the Connection for the next operations. But this speed comes with complications that we're going to cover in the next steps.
- Add C3P0 as a compile-time dependency
- Create a
ComboPooledDataSource
that takes your originalDataSource
. From now on pass this newDataSource
instead of the original one everywhere. - Debug and research the classes that now are returned by
ComboPooledDataSource
- is this the same H2 Connection that was before? Is that the same H2PreparedStatement
that was before? Does this remind you the design pattern we talked before? - Research and debug: what happens when you invoke
connection.close()
?
- Configure
maxPoolSize
,minPoolSize
. What values would be most appropriate here? How can you correlate them with the thread pool configuration? - Configure
maxStatements
andmaxStatementsPerConnection
. Why do you need these? How do you decide which values to put there? How do these correlate with the number of dynamically generated SQL statements and static SQL statements?
We've been working with embedded DB so far - it doesn't use network and instead JDBC Driver invokes Java classes of the DB Server directly. But remote DBs involves network.
- Research how JDBC Connections work over the network and what happens if the underlying TCP connection breaks (link)
- Experiment: what happens if the DB is forcibly restarted? Would the already established connections be valid after that?
- Think & research: what do network firewalls do with a stalled connection when there is no traffic for some time?
- Play with
testConnectionOnCheckout
,testConnectionOnCheckin
,idleConnectionTestPeriod
- Think & research: which of the above would you choose and why? Does the choice depend on the type of app that you're writing? Would the choice be the same for apps that require low latency? Would it be different for apps that have zero tolerance to errors?
- Think & research: how does idle connection testing work? Wouldn't a pool require separate thread to do some background work?
- Research:
Connection#isValid()
- will that work for all the JDBC Drivers? If not, what are other options?
Tip: some DB Pools actually recognize death specific SQLExceptions (after all you work with pool's Statement
that
can catch exceptions first) and can remove the connection even without testing. C3P0 is one these smart pools. Though
client code would receive exception anyway.
- Think & research: what are
Deny
andReject
policies of the network firewalls? How do they differ and do clients recognize the the connection cannot be established? When do clients find that out in case of each of the policy? - Think & research: what's going to happen when the pool is exhausted but you asked for yet another Connection from it?
- Read about and configure
checkoutTimeout
. Can this value be large like 10min? Can it be small like 100ms? - Think & research: if you borrow the connection from the pool and don't return it back, what does it mean?
- Read about and configure
unreturnedConnectionTimeout
- Find an option in C3P0 to debug which code borrowed and didn't return the connection back?
Tip: issues related to firewalls deny
ing packages are valid not only for databases but for any integration that
collaborates over network. Read about Connection Timeout and Socket Timeout to dive into the topic.
App Servers come with their own DB Pool implementation. Tomcat is not an exception. But to configure it we would need to use JNDI.
JNDI (java naming & directory interface) is a way to place objects and data into directory-like manner and retrieve
them. E.g. a dataSource object could be configured and created by AppServer itself and then placed under
db/dataSource
name. Our code can then get this through JNDI.
Why don't you simply configure DataSource inside the app like we did it before? In some organizations separate people (Operations a.k.a. Ops) prefer to manage things like DB Connections. They can change the settings in runtime and publish the changes to our apps. This is not too common, but it's not too rare either.
- Configure a Tomcat's DataSource via JNDI
- Write a code that gets the DataSource via JNDI. For this you will need to use Spring's FactoryBean approach. Read about it if you don't know it.
- Replace C3P0 DataSource with the Tomcat's one. Deploy the app to Tomcat and make sure everything works.
- Research Tomcat Pool options - could you find all the options that you configured in C3P0? The names are different, but it's very similar.
- Replace your FactoryBean with Spring's
<jee:jndi-lookup>
utility
Try running tests. Now they fail because in tests nothing starts up a JNDI Context. So there is no DataSource configured that can be looked up. So what can we do? There are 2 choices: either hook up some simple JNDI implementation as a lib for testing and create it there or.. get rid of DataSource JNDI :)
The fact that you don't use JNDI doesn't mean you can't use Tomcat's DB Pool - it's in the classpath of Tomcat so
your app has access to those classes. But only if it's deployed and not if you're running tests. To fix this you can
add a dependency org.apache.tomcat:tomcat-jdbc
. This way it's going to be accessible both to tests and deployed app.
But we don't want to duplicate the same classes - they're present in Tomcat itself and also are present in the war file.
Find a way to add the dependency so that the code compiles and the tests pass but extra lib is not included into the
war file.
Yet another best-of-breed book that covers architectural questions is Release it!. It explains many approaches and hazzards related to building distributed systems.