From 16c939106973ea4ce688494bfce250e78e9b323b Mon Sep 17 00:00:00 2001 From: Scott Andrews Date: Fri, 23 Jan 2009 20:33:17 +0000 Subject: [PATCH] SPR-5429 Update Pet Clinic to use HSQL as an embedded database Created DbcpDataSourceFactory factory bean to create and populate a dbcp based connection pool. The factory bean is based on EmbeddedDataSourceFactory from the JavaConfig version of Pet Clinic. The new DbcpDataSourceFactory has been tested with HSQL in embedded and stand-alone modes. --- .../readme.txt | 10 +- .../config/DbcpDataSourceFactory.java | 243 ++++++++++++++++++ .../main/resources/META-INF/hsqldb/initDB.txt | 55 ++++ .../resources/META-INF/hsqldb/populateDB.txt | 53 ++++ .../src/main/resources/jdbc.properties | 37 ++- .../WEB-INF/applicationContext-jdbc.xml | 16 +- 6 files changed, 404 insertions(+), 10 deletions(-) create mode 100644 org.springframework.samples.petclinic/src/main/java/org/springframework/samples/petclinic/config/DbcpDataSourceFactory.java create mode 100644 org.springframework.samples.petclinic/src/main/resources/META-INF/hsqldb/initDB.txt create mode 100644 org.springframework.samples.petclinic/src/main/resources/META-INF/hsqldb/populateDB.txt diff --git a/org.springframework.samples.petclinic/readme.txt b/org.springframework.samples.petclinic/readme.txt index 462894d633..840a0ceefa 100644 --- a/org.springframework.samples.petclinic/readme.txt +++ b/org.springframework.samples.petclinic/readme.txt @@ -7,6 +7,7 @@ @author Rob Harrop @author Costin Leau @author Sam Brannen +@author Scott Andrews -------------------------------------------------------------------------------- @@ -60,8 +61,13 @@ execution directory. Note that you must do this in order to execute the "tests" target, as you need the JUnit task from Ant's optional.jar, which is not included in this sample application. -To execute the web application with its default settings, simply start the -HSQLDB instance in the "db/hsqldb" directory, for example using "server.bat". +By default, an embedded HSQL instance in configured. No other steps are +necessary to get the data source up and running. + +To use HSQL as a remote database, open "src/jdbc.properties", comment out all +properties in the "HSQL Embedded Settings" section, uncomment all properties in +the "HSQL Settings" section. Start the remote HSQLDB instance in the +"db/hsqldb" directory, for example using "server.bat". For MySQL, you'll need to use the corresponding schema and SQL scripts in the "db/mysql" subdirectory. Follow the steps outlined in diff --git a/org.springframework.samples.petclinic/src/main/java/org/springframework/samples/petclinic/config/DbcpDataSourceFactory.java b/org.springframework.samples.petclinic/src/main/java/org/springframework/samples/petclinic/config/DbcpDataSourceFactory.java new file mode 100644 index 0000000000..aaf71dca0b --- /dev/null +++ b/org.springframework.samples.petclinic/src/main/java/org/springframework/samples/petclinic/config/DbcpDataSourceFactory.java @@ -0,0 +1,243 @@ +/* + * Copyright 2002-2008 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package org.springframework.samples.petclinic.config; + +import java.io.BufferedReader; +import java.io.BufferedWriter; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; +import java.io.StringWriter; +import java.sql.Connection; +import java.sql.SQLException; +import java.sql.Statement; + +import javax.sql.DataSource; + +import org.apache.commons.dbcp.BasicDataSource; +import org.springframework.beans.factory.DisposableBean; +import org.springframework.beans.factory.FactoryBean; +import org.springframework.core.io.Resource; + +/** + * A factory that creates a data source fit for use in a system environment. Creates a DBCP simple data source + * from the provided connection properties. + * + * This factory returns a fully-initialized DataSource implementation. When the DataSource is returned, callers are + * guaranteed that the database schema and data will have been loaded by that time. + * + * Is a FactoryBean, for exposing the fully-initialized DataSource as a Spring bean. See {@link #getObject()}. + * + * @author Chris Beams + * @author Scott Andrews + */ +public class DbcpDataSourceFactory implements FactoryBean, DisposableBean { + + // configurable properties + + private String driverClassName; + + private String url; + + private String username; + + private String password; + + private boolean populate; + + private Resource schemaLocation; + + private Resource dataLocation; + + /** + * The object created by this factory. + */ + private BasicDataSource dataSource; + + public void setDriverClassName(String driverClassName) { + this.driverClassName = driverClassName; + } + + /** + * The data source connection URL + */ + public void setUrl(String url) { + this.url = url; + } + + /** + * The data source username + */ + public void setUsername(String username) { + this.username = username; + } + + /** + *The data source password + */ + public void setPassword(String password) { + this.password = password; + } + + /** + * Indicates that the data base should be populated from the schema and data locations + */ + public void setPopulate(boolean populate) { + this.populate = populate; + } + + /** + * Sets the location of the file containing the schema DDL to export to the database. + * @param schemaLocation the location of the database schema DDL + */ + public void setSchemaLocation(Resource schemaLocation) { + this.schemaLocation = schemaLocation; + } + + /** + * Sets the location of the file containing the data to load into the database. + * @param testDataLocation the location of the data file + */ + public void setDataLocation(Resource testDataLocation) { + this.dataLocation = testDataLocation; + } + + // implementing FactoryBean + + // this method is called by Spring to expose the DataSource as a bean + public DataSource getObject() throws Exception { + if (dataSource == null) { + initDataSource(); + } + return dataSource; + } + + public Class getObjectType() { + return DataSource.class; + } + + public boolean isSingleton() { + return true; + } + + // implementing DisposableBean + + public void destroy() throws Exception { + dataSource.close(); + } + + // internal helper methods + + // encapsulates the steps involved in initializing the data source: creating it, and populating it + private void initDataSource() { + // create the database source first + this.dataSource = createDataSource(); + + if (this.populate) { + // now populate the database by loading the schema and data + populateDataSource(); + } + } + + private BasicDataSource createDataSource() { + BasicDataSource dataSource = new BasicDataSource(); + dataSource.setDriverClassName(this.driverClassName); + dataSource.setUrl(this.url); + dataSource.setUsername(this.username); + dataSource.setPassword(this.password); + return dataSource; + } + + private void populateDataSource() { + DatabasePopulator populator = new DatabasePopulator(dataSource); + populator.populate(this.schemaLocation); + populator.populate(this.dataLocation); + } + + /** + * Populates a in memory data source with data. + */ + private class DatabasePopulator { + + private DataSource dataSource; + + /** + * Creates a new database populator. + * @param dataSource the data source that will be populated. + */ + public DatabasePopulator(DataSource dataSource) { + this.dataSource = dataSource; + } + + /** + * Populate the database executing the statements in the provided resource against the database + * @param sqlFile spring resource containing SQL to run against the db + */ + public void populate(Resource sqlFile) { + Connection connection = null; + try { + connection = dataSource.getConnection(); + try { + String sql = parseSqlIn(sqlFile); + executeSql(sql, connection); + } catch (IOException e) { + throw new RuntimeException("I/O exception occurred accessing the database schema file", e); + } catch (SQLException e) { + throw new RuntimeException("SQL exception occurred exporting database schema", e); + } + } catch (SQLException e) { + throw new RuntimeException("SQL exception occurred acquiring connection", e); + } finally { + if (connection != null) { + try { + connection.close(); + } catch (SQLException e) { + } + } + } + } + + // utility method to read a .sql txt input stream + private String parseSqlIn(Resource resource) throws IOException { + InputStream is = null; + try { + is = resource.getInputStream(); + BufferedReader reader = new BufferedReader(new InputStreamReader(is)); + + StringWriter sw = new StringWriter(); + BufferedWriter writer = new BufferedWriter(sw); + + for (int c=reader.read(); c != -1; c=reader.read()) { + writer.write(c); + } + writer.flush(); + return sw.toString(); + + } finally { + if (is != null) { + is.close(); + } + } + } + + // utility method to run the parsed sql + private void executeSql(String sql, Connection connection) throws SQLException { + Statement statement = connection.createStatement(); + statement.execute(sql); + } + } + +} diff --git a/org.springframework.samples.petclinic/src/main/resources/META-INF/hsqldb/initDB.txt b/org.springframework.samples.petclinic/src/main/resources/META-INF/hsqldb/initDB.txt new file mode 100644 index 0000000000..a75bfbbd86 --- /dev/null +++ b/org.springframework.samples.petclinic/src/main/resources/META-INF/hsqldb/initDB.txt @@ -0,0 +1,55 @@ +CREATE TABLE vets ( + id INTEGER NOT NULL IDENTITY PRIMARY KEY, + first_name VARCHAR(30), + last_name VARCHAR(30) +); +CREATE INDEX vets_last_name ON vets(last_name); + +CREATE TABLE specialties ( + id INTEGER NOT NULL IDENTITY PRIMARY KEY, + name VARCHAR(80) +); +CREATE INDEX specialties_name ON specialties(name); + +CREATE TABLE vet_specialties ( + vet_id INTEGER NOT NULL, + specialty_id INTEGER NOT NULL +); +alter table vet_specialties add constraint fk_vet_specialties_vets foreign key (vet_id) references vets(id); +alter table vet_specialties add constraint fk_vet_specialties_specialties foreign key (specialty_id) references specialties(id); + +CREATE TABLE types ( + id INTEGER NOT NULL IDENTITY PRIMARY KEY, + name VARCHAR(80) +); +CREATE INDEX types_name ON types(name); + +CREATE TABLE owners ( + id INTEGER NOT NULL IDENTITY PRIMARY KEY, + first_name VARCHAR(30), + last_name VARCHAR(30), + address VARCHAR(255), + city VARCHAR(80), + telephone VARCHAR(20) +); +CREATE INDEX owners_last_name ON owners(last_name); + +CREATE TABLE pets ( + id INTEGER NOT NULL IDENTITY PRIMARY KEY, + name VARCHAR(30), + birth_date DATE, + type_id INTEGER NOT NULL, + owner_id INTEGER NOT NULL +); +alter table pets add constraint fk_pets_owners foreign key (owner_id) references owners(id); +alter table pets add constraint fk_pets_types foreign key (type_id) references types(id); +CREATE INDEX pets_name ON pets(name); + +CREATE TABLE visits ( + id INTEGER NOT NULL IDENTITY PRIMARY KEY, + pet_id INTEGER NOT NULL, + visit_date DATE, + description VARCHAR(255) +); +alter table visits add constraint fk_visits_pets foreign key (pet_id) references pets(id); +CREATE INDEX visits_pet_id ON visits(pet_id); diff --git a/org.springframework.samples.petclinic/src/main/resources/META-INF/hsqldb/populateDB.txt b/org.springframework.samples.petclinic/src/main/resources/META-INF/hsqldb/populateDB.txt new file mode 100644 index 0000000000..1bf0c4a6ed --- /dev/null +++ b/org.springframework.samples.petclinic/src/main/resources/META-INF/hsqldb/populateDB.txt @@ -0,0 +1,53 @@ +INSERT INTO vets VALUES (1, 'James', 'Carter'); +INSERT INTO vets VALUES (2, 'Helen', 'Leary'); +INSERT INTO vets VALUES (3, 'Linda', 'Douglas'); +INSERT INTO vets VALUES (4, 'Rafael', 'Ortega'); +INSERT INTO vets VALUES (5, 'Henry', 'Stevens'); +INSERT INTO vets VALUES (6, 'Sharon', 'Jenkins'); + +INSERT INTO specialties VALUES (1, 'radiology'); +INSERT INTO specialties VALUES (2, 'surgery'); +INSERT INTO specialties VALUES (3, 'dentistry'); + +INSERT INTO vet_specialties VALUES (2, 1); +INSERT INTO vet_specialties VALUES (3, 2); +INSERT INTO vet_specialties VALUES (3, 3); +INSERT INTO vet_specialties VALUES (4, 2); +INSERT INTO vet_specialties VALUES (5, 1); + +INSERT INTO types VALUES (1, 'cat'); +INSERT INTO types VALUES (2, 'dog'); +INSERT INTO types VALUES (3, 'lizard'); +INSERT INTO types VALUES (4, 'snake'); +INSERT INTO types VALUES (5, 'bird'); +INSERT INTO types VALUES (6, 'hamster'); + +INSERT INTO owners VALUES (1, 'George', 'Franklin', '110 W. Liberty St.', 'Madison', '6085551023'); +INSERT INTO owners VALUES (2, 'Betty', 'Davis', '638 Cardinal Ave.', 'Sun Prairie', '6085551749'); +INSERT INTO owners VALUES (3, 'Eduardo', 'Rodriquez', '2693 Commerce St.', 'McFarland', '6085558763'); +INSERT INTO owners VALUES (4, 'Harold', 'Davis', '563 Friendly St.', 'Windsor', '6085553198'); +INSERT INTO owners VALUES (5, 'Peter', 'McTavish', '2387 S. Fair Way', 'Madison', '6085552765'); +INSERT INTO owners VALUES (6, 'Jean', 'Coleman', '105 N. Lake St.', 'Monona', '6085552654'); +INSERT INTO owners VALUES (7, 'Jeff', 'Black', '1450 Oak Blvd.', 'Monona', '6085555387'); +INSERT INTO owners VALUES (8, 'Maria', 'Escobito', '345 Maple St.', 'Madison', '6085557683'); +INSERT INTO owners VALUES (9, 'David', 'Schroeder', '2749 Blackhawk Trail', 'Madison', '6085559435'); +INSERT INTO owners VALUES (10, 'Carlos', 'Estaban', '2335 Independence La.', 'Waunakee', '6085555487'); + +INSERT INTO pets VALUES (1, 'Leo', '2000-09-07', 1, 1); +INSERT INTO pets VALUES (2, 'Basil', '2002-08-06', 6, 2); +INSERT INTO pets VALUES (3, 'Rosy', '2001-04-17', 2, 3); +INSERT INTO pets VALUES (4, 'Jewel', '2000-03-07', 2, 3); +INSERT INTO pets VALUES (5, 'Iggy', '2000-11-30', 3, 4); +INSERT INTO pets VALUES (6, 'George', '2000-01-20', 4, 5); +INSERT INTO pets VALUES (7, 'Samantha', '1995-09-04', 1, 6); +INSERT INTO pets VALUES (8, 'Max', '1995-09-04', 1, 6); +INSERT INTO pets VALUES (9, 'Lucky', '1999-08-06', 5, 7); +INSERT INTO pets VALUES (10, 'Mulligan', '1997-02-24', 2, 8); +INSERT INTO pets VALUES (11, 'Freddy', '2000-03-09', 5, 9); +INSERT INTO pets VALUES (12, 'Lucky', '2000-06-24', 2, 10); +INSERT INTO pets VALUES (13, 'Sly', '2002-06-08', 1, 10); + +INSERT INTO visits VALUES (1, 7, '1996-03-04', 'rabies shot'); +INSERT INTO visits VALUES (2, 8, '1996-03-04', 'rabies shot'); +INSERT INTO visits VALUES (3, 8, '1996-06-04', 'neutered'); +INSERT INTO visits VALUES (4, 7, '1996-09-04', 'spayed'); diff --git a/org.springframework.samples.petclinic/src/main/resources/jdbc.properties b/org.springframework.samples.petclinic/src/main/resources/jdbc.properties index 4a05a4620e..9c423e51be 100644 --- a/org.springframework.samples.petclinic/src/main/resources/jdbc.properties +++ b/org.springframework.samples.petclinic/src/main/resources/jdbc.properties @@ -12,13 +12,18 @@ hibernate.show_sql=true jpa.showSql=true #------------------------------------------------------------------------------- -# HSQL Settings +# HSQL Embedded Settings jdbc.driverClassName=org.hsqldb.jdbcDriver -jdbc.url=jdbc:hsqldb:hsql://localhost:9001 +jdbc.url=jdbc:hsqldb:mem:petclinic jdbc.username=sa jdbc.password= +# Properties that control the population of schema and data for a new data source +jdbc.populate=true +jdbc.schemaLocation=classpath:/META-INF/hsqldb/initDB.txt +jdbc.dataLocation=classpath:/META-INF/hsqldb/populateDB.txt + # Property that determines which Hibernate dialect to use # (only applied with "applicationContext-hibernate.xml") hibernate.dialect=org.hibernate.dialect.HSQLDialect @@ -29,6 +34,29 @@ jpa.databasePlatform=org.springframework.samples.petclinic.toplink.EssentialsHSQ # Property that determines which database to use with an AbstractJpaVendorAdapter jpa.database=HSQL +#------------------------------------------------------------------------------- +# HSQL Settings + +#jdbc.driverClassName=org.hsqldb.jdbcDriver +#jdbc.url=jdbc:hsqldb:hsql://localhost:9001 +#jdbc.username=sa +#jdbc.password= + +# Properties that control the population of schema and data for a new data source +#jdbc.populate=false +#jdbc.schemaLocation= +#jdbc.dataLocation= + +# Property that determines which Hibernate dialect to use +# (only applied with "applicationContext-hibernate.xml") +#hibernate.dialect=org.hibernate.dialect.HSQLDialect + +# Property that determines which JPA DatabasePlatform to use with TopLink Essentials +#jpa.databasePlatform=org.springframework.samples.petclinic.toplink.EssentialsHSQLPlatformWithNativeSequence + +# Property that determines which database to use with an AbstractJpaVendorAdapter +#jpa.database=HSQL + #------------------------------------------------------------------------------- # MySQL Settings @@ -37,6 +65,11 @@ jpa.database=HSQL #jdbc.username=pc #jdbc.password=pc +# Properties that control the population of schema and data for a new data source +#jdbc.populate=false +#jdbc.schemaLocation= +#jdbc.dataLocation= + # Property that determines which Hibernate dialect to use # (only applied with "applicationContext-hibernate.xml") #hibernate.dialect=org.hibernate.dialect.MySQLDialect diff --git a/org.springframework.samples.petclinic/src/main/webapp/WEB-INF/applicationContext-jdbc.xml b/org.springframework.samples.petclinic/src/main/webapp/WEB-INF/applicationContext-jdbc.xml index 2a50b4905b..69a2b1f5dc 100644 --- a/org.springframework.samples.petclinic/src/main/webapp/WEB-INF/applicationContext-jdbc.xml +++ b/org.springframework.samples.petclinic/src/main/webapp/WEB-INF/applicationContext-jdbc.xml @@ -20,13 +20,17 @@ - +