Using Tomcat JDBC Connection Pool in Standalone Java Application
When using a JDBC connection pool in standalone Java applications that require data access, most of the developers will use either commons-dbcp or c3p0. In this tutorial, we will discuss using the JDBC connection pool in Apache Tomcat web container in standalone Java applications.
- Support for highly concurrent environments and multi core/cpu systems
- Commons-dbcp is single-threaded and slow
- Commons-dbcp is complex (over 60 classes), while tomcat-jdbc core contains only 8 classes
- Support asynchronous connection retrieval
- XA connection support
- The connection pool object exposes an MBean that can be registered for monitoring purposes
- Most of the attributes in common-dbcp were supported, as well as many enhanced attributes
- Support of JDBC interceptors
- Spring Framework 3.1.1
- Hibernate 4.1.3
- Spring Data JPA 1.1.0
- Tomcat JDBC Connection Pool 7.0.27
- H2 database 1.3.167
- Guava 12.0
Dependencies
Listing 1 – Project dependencies
001 002 003 004 005 006 007 008 009 010 011 012 013 014 015 016 017 018 019 020 021 022 023 024 025 026 027 028 029 030 031 032 033 034 035 036 037 038 039 040 041 042 043 044 045 046 047 048 049 050 051 052 053 054 055 056 057 058 059 060 061 062 063 064 065 066 067 068 069 070 071 072 073 074 075 076 077 078 079 080 081 082 083 084 085 086 087 088 089 090 091 092 093 094 095 096 097 098 099 100 | < properties > < maven.test.failure.ignore >true</ maven.test.failure.ignore > < spring.framework.version >3.1.1.RELEASE</ spring.framework.version > < hibernate.version >4.1.3.Final</ hibernate.version > < spring.data.jpa.version >1.1.0.RELEASE</ spring.data.jpa.version > < tomcat.dbcp.version >7.0.27</ tomcat.dbcp.version > < h2.version >1.3.167</ h2.version > < slf4j.version >1.6.4</ slf4j.version > < log4j.version >1.2.16</ log4j.version > < guava.version >12.0</ guava.version > </ properties > < dependencies > <!-- Hibernate --> < dependency > < groupId >org.hibernate</ groupId > < artifactId >hibernate-entitymanager</ artifactId > < version >${hibernate.version}</ version > </ dependency > <!-- Spring Framework --> < dependency > < groupId >org.springframework</ groupId > < artifactId >spring-context-support</ artifactId > < version >${spring.framework.version}</ version > </ dependency > < dependency > < groupId >org.springframework</ groupId > < artifactId >spring-aop</ artifactId > < version >${spring.framework.version}</ version > </ dependency > < dependency > < groupId >org.springframework</ groupId > < artifactId >spring-orm</ artifactId > < version >${spring.framework.version}</ version > </ dependency > <!-- Spring Data JPA --> < dependency > < groupId >org.springframework.data</ groupId > < artifactId >spring-data-jpa</ artifactId > < version >${spring.data.jpa.version}</ version > </ dependency > <!-- Tomcat DBCP --> < dependency > < groupId >org.apache.tomcat</ groupId > < artifactId >tomcat-jdbc</ artifactId > < version >${tomcat.dbcp.version}</ version > </ dependency > <!-- Logging --> < dependency > < groupId >org.slf4j</ groupId > < artifactId >slf4j-api</ artifactId > < version >${slf4j.version}</ version > </ dependency > < dependency > < groupId >org.slf4j</ groupId > < artifactId >jcl-over-slf4j</ artifactId > < version >${slf4j.version}</ version > < scope >runtime</ scope > </ dependency > < dependency > < groupId >org.slf4j</ groupId > < artifactId >slf4j-log4j12</ artifactId > < version >${slf4j.version}</ version > < scope >runtime</ scope > </ dependency > < dependency > < groupId >log4j</ groupId > < artifactId >log4j</ artifactId > < version >${log4j.version}</ version > </ dependency > <!-- Others --> < dependency > < groupId >com.h2database</ groupId > < artifactId >h2</ artifactId > < version >${h2.version}</ version > </ dependency > < dependency > < groupId >com.google.guava</ groupId > < artifactId >guava</ artifactId > < version >${guava.version}</ version > </ dependency > </ dependencies > |
Domain Object Model
Listing 2 – the Contact class
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 | @Entity @Table (name = "contact" ) public class Contact { private Long id; private int version; private String firstName; private String lastName; private Date birthDate; private Set<Hobby> hobbies = new HashSet<Hobby>(); @Id @GeneratedValue (strategy=GenerationType.IDENTITY) @Column (name = "ID" ) public Long getId() { return id; } public void setId(Long id) { this .id = id; } @Version @Column (name = "VERSION" ) public int getVersion() { return version; } public void setVersion( int version) { this .version = version; } @Column (name = "FIRST_NAME" ) public String getFirstName() { return firstName; } public void setFirstName(String firstName) { this .firstName = firstName; } @Column (name = "LAST_NAME" ) public String getLastName() { return lastName; } public void setLastName(String lastName) { this .lastName = lastName; } @Column (name = "BIRTH_DATE" ) @Temporal (TemporalType.DATE) public Date getBirthDate() { return birthDate; } public void setBirthDate(Date birthDate) { this .birthDate = birthDate; } @ManyToMany @JoinTable (name = "contact_hobby_detail" , joinColumns = @JoinColumn (name = "CONTACT_ID" ), inverseJoinColumns = @JoinColumn (name = "HOBBY_ID" )) public Set<hobby> getHobbies() { return this .hobbies; } public void setHobbies(Set<hobby> hobbies) { this .hobbies = hobbies; } public String toString() { return "Contact - Id: " + id + ", First name: " + firstName + ", Last name: " + lastName + ", Birthday: " + birthDate; } } |
Listing 3 – the Hobby class
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 | @Entity @Table (name = "hobby" ) public class Hobby { private String hobbyId; private Set<Contact> contacts = new HashSet<Contact>(); public Hobby() { } public Hobby(String hobbyId) { this .hobbyId = hobbyId; } public Hobby(String hobbyId, Set<Contact> contacts) { this .hobbyId = hobbyId; this .contacts = contacts; } @Id @Column (name = "HOBBY_ID" ) public String getHobbyId() { return this .hobbyId; } public void setHobbyId(String hobbyId) { this .hobbyId = hobbyId; } @ManyToMany @JoinTable (name = "contact_hobby_detail" , joinColumns = @JoinColumn (name = "HOBBY_ID" ), inverseJoinColumns = @JoinColumn (name = "CONTACT_ID" )) public Set<Contact> getContacts() { return this .contacts; } public void setContacts(Set<Contact> contacts) { this .contacts = contacts; } } |
Database Schema
In this tutorial, we will use H2 in-memory database. There are 3 tables:
- CONTACT: the table stores the contact information
- HOBBY: the table stores the listing of hobbies available for the application
- CONTACT_HOBBY_DETAIL:models the many-to-many relationship between Contact and Hobby classes
Listing 4 – Database schema creation script (schema.sql)
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 | DROP TABLE IF EXISTS CONTACT; CREATE TABLE CONTACT ( ID INT NOT NULL AUTO_INCREMENT ,FIRST_NAME VARCHAR (60) NOT NULL ,LAST_NAME VARCHAR (40) NOT NULL ,BIRTH_DATE DATE ,VERSION INT NOT NULL DEFAULT 0 , UNIQUE UQ_CONTACT_1 (FIRST_NAME, LAST_NAME) , PRIMARY KEY (ID) ); CREATE TABLE HOBBY ( HOBBY_ID VARCHAR (20) NOT NULL , PRIMARY KEY (HOBBY_ID) ); CREATE TABLE CONTACT_HOBBY_DETAIL ( CONTACT_ID INT NOT NULL ,HOBBY_ID VARCHAR (20) NOT NULL , PRIMARY KEY (CONTACT_ID, HOBBY_ID) , CONSTRAINT FK_CONTACT_HOBBY_DETAIL_1 FOREIGN KEY (CONTACT_ID) REFERENCES CONTACT (ID) ON DELETE CASCADE , CONSTRAINT FK_CONTACT_HOBBY_DETAIL_2 FOREIGN KEY (HOBBY_ID) REFERENCES HOBBY (HOBBY_ID) ); |
Listing 5 – Testing data population script (test-data.sql)
01 02 03 04 05 06 07 08 09 10 11 12 | insert into contact (first_name, last_name, birth_date) values ( 'Clarence' , 'Ho' , '1980-07-30' ); insert into contact (first_name, last_name, birth_date) values ( 'Scott' , 'Tiger' , '1990-11-02' ); insert into hobby (hobby_id) values ( 'Swimming' ); insert into hobby (hobby_id) values ( 'Jogging' ); insert into hobby (hobby_id) values ( 'Programming' ); insert into hobby (hobby_id) values ( 'Movies' ); insert into hobby (hobby_id) values ( 'Reading' ); insert into contact_hobby_detail(contact_id, hobby_id) values (1, 'Swimming' ); insert into contact_hobby_detail(contact_id, hobby_id) values (1, 'Movies' ); insert into contact_hobby_detail(contact_id, hobby_id) values (2, 'Swimming' ); |
Service Layer
In the service layer, there exist 2 interfaces:
- ContactService: provide services for accessing contact information
- HobbyService: provide services for accessing hobby information
Listing 6 and 7 show the ContactService and HobbyService interfaces respectively.
Listing 6 – the ContactService interface
1 2 3 4 5 6 7 8 9 | public interface ContactService { public List<Contact> findAll(); public Contact findById(Long id); public Contact save(Contact contact); } |
Listing 7 – the HobbyService interface
1 2 3 4 5 | public interface HobbyService { public List<Hobby> findAll(); } |
Spring Configuration
Let’s take a look on the Spring configurations. Listing 8 shows the data source, transaction and JPA configurations.
Listing 8 – Spring JPA configuration (datasource-tx-jpa.xml)
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | <!--Tomcat JDBC connection pool configutation --> < bean id = "dataSource" class = "org.apache.tomcat.jdbc.pool.DataSource" > < property name = "driverClassName" value = "org.h2.Driver" /> < property name = "url" value = "jdbc:h2:mem:testdb" /> < property name = "username" value = "sa" /> < property name = "password" value = "" /> </ bean > <!--Intialize the database schema with test data --> < jdbc:initialize-database data-source = "dataSource" > < jdbc:script location = "classpath:schema.sql" /> < jdbc:script location = "classpath:test-data.sql" /> </ jdbc:initialize-database > < bean id = "transactionManager" class = "org.springframework.orm.jpa.JpaTransactionManager" > < property name = "entityManagerFactory" ref = "emf" /> </ bean > < tx:annotation-driven transaction-manager = "transactionManager" /> < bean id = "emf" class = "org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean" > < property name = "dataSource" ref = "dataSource" /> < property name = "jpaVendorAdapter" > < bean class = "org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter" /> </ property > < property name = "packagesToScan" value = "com.skywidesoft.tomcat.dbcp.tutorial.domain" /> < property name = "jpaProperties" > < props > < prop key = "hibernate.dialect" >org.hibernate.dialect.H2Dialect</ prop > < prop key = "hibernate.max_fetch_depth" >3</ prop > < prop key = "hibernate.jdbc.fetch_size" >50</ prop > < prop key = "hibernate.jdbc.batch_size" >10</ prop > < prop key = "hibernate.show_sql" >true</ prop > </ props > </ property > </ bean > < context:annotation-config /> <!--Spring Data JPA Repository Configuration --> < jpa:repositories base-package = "com.skywidesoft.tomcat.dbcp.tutorial.repository" entity-manager-factory-ref = "emf" transaction-manager-ref = "transactionManager" /> |
Some highlights of the configuration in Listing 8 were listed below:
- For the dataSource bean, the class org.apache.tomcat.jdbc.pool.DataSource was used to provide the JDBC DataSource interface for the underlying connection. You will see that the configuration is basically the same as using commons-dbcp.
- The <jdbc:initialize-database> tag is Spring 3.1’s support for initializing the database with the database schema and testing data
- The <jpa:repositories> tag is to configure the Spring Data JPA’s repository abstraction.
Listing 9 shows the Spring application context configuration.
Listing 9 – Spring application context (app-context.xml)
1 2 3 | < import resource = "classpath:datasource-tx-jpa.xml" /> < context:component-scan base-package = "com.skywidesoft.tomcat.dbcp.tutorial.service.jpa" /> |
Spring Data JPA Repository Abstraction
Listing 10 – The ContactRepository Interface
1 2 3 | public interface ContactRepository extends CrudRepository<Contact, Long>{ } |
Listing 11 – The HobbyRepository Interface
1 2 3 | public interface HobbyRepository extends CrudRepository<Hobby, String>{ } |
Note that the interface simply extends the Spring Data Common’s CrudRepository<T,ID> interface, which already provides common data access operations (e.g. findAll, findOne, save, delete, etc.).
JPA Implementation Classes
Listing 12 – The ContactServiceImpl class
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 | @Service ( "contactService" ) @Repository @Transactional public class ContactServiceImpl implements ContactService { final static Logger logger = LoggerFactory.getLogger(ContactServiceImpl. class ); @Autowired private ContactRepository contactRepository; @Transactional (readOnly= true ) public List<Contact> findAll() { logger.info( "Finding all contacts" ); return Lists.newArrayList(contactRepository.findAll()); } @Transactional (readOnly= true ) public Contact findById(Long id) { return contactRepository.findOne(id); } public Contact save(Contact contact) { return contactRepository.save(contact); } } |
Listing 13 – The HobbyServiceImpl class
01 02 03 04 05 06 07 08 09 10 11 12 13 14 | @Service ( "hobbyService" ) @Repository @Transactional public class HobbyServiceImpl implements HobbyService { @Autowired private HobbyRepository hobbyRepository; @Transactional (readOnly= true ) public List<Hobby> findAll() { return Lists.newArrayList(hobbyRepository.findAll()); } } |
Testing
Listing 14 – The ContactServiceTest class
01 02 03 04 05 06 07 08 09 10 11 12 13 14 15 16 17 18 19 | public class ContactServiceTest { public static void main(String[] args) { GenericXmlApplicationContext ctx = new GenericXmlApplicationContext(); ctx.load( "classpath:app-context.xml" ); ctx.refresh(); ContactService contactService = ctx.getBean( "contactService" , ContactService. class ); List<Contact> contacts = contactService.findAll(); for (Contact contact: contacts) { System.out.println(contact); } } } |
2012-05-25 13:35:43,552 INFO [com.skywidesoft.tomcat.dbcp.tutorial.service.jpa.ContactServiceImpl] -
<Finding all contacts>
2012-05-25 13:35:43,665 DEBUG [org.hibernate.SQL] - <select contact0_.ID as ID0_,
contact0_.BIRTH_DATE as BIRTH2_0_, contact0_.FIRST_NAME as FIRST3_0_,
contact0_.LAST_NAME as LAST4_0_, contact0_.VERSION as VERSION0_ from contact contact0_>
Hibernate: select contact0_.ID as ID0_, contact0_.BIRTH_DATE as BIRTH2_0_,
contact0_.FIRST_NAME as FIRST3_0_, contact0_.LAST_NAME as LAST4_0_, contact0_.VERSION as
VERSION0_ from contact contact0_
Contact - Id: 1, First name: Clarence, Last name: Ho, Birthday: 1980-07-30
Contact - Id: 2, First name: Scott, Last name: Tiger, Birthday: 1990-11-02
Conclusion
Download full Eclipse Maven Project.
Reference: Using Tomcat JDBC Connection Pool in Standalone Java Application from our W4G partner Clarence Ho.
Clarence Ho is lead author of Pro Spring 3 from APress. With Pro Spring 3, you’ll learn Spring basics and core topics, and gain access to the authors’ insights and real–world experiences with remoting, Hibernate, and EJB. Beyond the basics, you’ll learn how to leverage the Spring Framework to build various tiers or parts of an enterprise Java application like transactions, the web and presentations tiers, deployment, and much more. A full sample application allows you to apply many of the technologies and techniques covered in this book and see how they work together.
APress has provided the readers of Java Code Geeks with a discount coupon code. The coupon code is: SPR76 and is valid till July 6, 2012. The code offers a 40% off the eBook only from apress.com.
I don’t see zipped archive , where is it ?
Hi Jose,
I just added the link for the archive. Please check it out. Sorry for the inconvenience.
thanks – this is really clear and concise. Much appreciated!