JDBC Lab
JDBC Lab
Using JDBC
In this lab you will create an application to access the World database using the Java
Database Connectivity (JDBC). We will use MySQL, but the same code will work with
other databases.
Required Software
1. Java SE 1.6.0 SDK. Java version 1.5 will also work.
2. A JDBC connector for your database. For MySQL, this is mysql-connector-java-bin-
5.1.7-bin.jar. The JDBC JAR file belongs in a subdirectory of your software library, e.g.
C:\lib\mysql or /opt/lib/mysql (Linux). Don't bury it in C:\Program Files.
3. A database query tool to check your database schema and verify results.
4. Software development tool, such as Eclipse, NetBeans, or a text editor.
At K.U. download these from: http://se.cpe.ku.ac.th/download/mysql
Goals
1. Write a CityBrowser application to find a city in the World database and display information.
2. Create a graphical UI for CityBrowser.
3. Separate the database operations from the rest of the code by designing a Data Access Object
(DAO).
4. Use a Properties file to separate database properties from our Java code (like user name and
password).
country city
Code ID
Capital CountryCode
Code2 District
Continent Name
GNP Population
LifeExpectancy
LocalName
Name
Population countrylanguage
Region
SurfaceArea CountryCode
Language
isOfficial
Percentage
Java Database Access Lab Page 2
1. Configure a Project
In your favorite IDE, create a new Java project named World.
inside the "src" tree, create a "world" package. This will be the base package for our app.
add the MySQL JDBC connector to the project as an "External JAR" or library.
Database
execute
The URLs for Derby and HSQLDB are for embedded mode.
3. You will notice that the JDBC methods throw SQLException. Add a throws declaration to
main: (In a real project, we would catch the exception and report it.)
public static void main(String [] args) throws SQLException {
Java Database Access Lab Page 5
Discussion
What does the output tell you?
We asked DriverManager for a java.sql.Connection, but we got an object of type
com.mysql.jdbc.JDBC4Connection.
Why?
Exercise
1. Add the above code to your application and run it.
2. Modify this code to ask the user for a city name instead of "Bangkok". A city name may contain
spaces, so you shown read an entire input line as city name. Also trim excess space from the
input.
Example:
City name to find? Los Angeles
Matching cities:
Los Angeles, Bobo, CHL pop. 158,215
Los Angeles, California, USA pop. 3,694,820
City name to find? Pattaya
Sorry, Pattaya not found.
City name to find?
Java Database Access Lab Page 8
com.mysql.jdbc
1. A ResultSet lets us iteratively access each row of a query result, without knowing how the
results are stored. What design pattern is this?
2. In the Adapter Pattern, an Adaptor is used to convert from one interface (the interface used by
existing software) to another interface (the required interface). How is adapter used here?
3. An Abstract Factory is an abstract class (or interface) that defines methods for creating a family
of related products. A concrete implementation of the factory creates the actual products. Which
JDBC class is an Abstract Factory?
What methods create the products?
Java Database Access Lab Page 9
ResultSet also has methods to test or change the current position in the results.
Read the Java API doc for ResultSet and write the method names in this table:
Exercise
1. Modify JdbcDemo so that it prints a "city not found" message when the user's city isn't found in
the database.
Hint: the first( ) method returns true and moves the ResultSet cursor to the first item.
You should also change the "while" loop to a "do - while" loop, because if you call first() and
then call next()it will skip the first record!
Example:
City name to find? Pattaya
Sorry, no city named Pattaya
City name to find?
Java Database Access Lab Page 10
The Statement class has many useful methods for getting information about results of database
operations. For example, when we INSERT a new City the database assigns the City an ID. What
is the ID? We can use Statement methods to get it.
Statement statement = connection.createStatement( );
statement.executeUpdate(
"INSERT INTO city VALUES('Bangsaen','Chonburi','THA',25000)",
Statement.RETURN_GENERATED_KEYS);
ResultSet rs = statment.getGeneratedKeys( );
if ( rs.first() ) {
int id = rs.getInt( 1 );
//TODO: assign id to the city object
System.out.println("Created City with id "+id);
}
A PreparedStatement can also return generated keys, and PreparedStatement is faster and safer
for most tasks.
Java Database Access Lab Page 11
Exercise
1. (Discussion) Explain what happened.
2. Be the hacker. What should you input to display all the cities in China?
Hint: the countrycode for China is CHN. Can you inject it?
3. Can you use SQL injection to delete data from the City table?
Discussion
This is called SQL Injection and it is a common form of hacking. The group Lulz Security hacked
into the Sony Pictures customer database (twice!) and stole data for millions of customers.
They published the data on the Internet and posted this message:
"SonyPictures.com was owned
(hacked) by a very simple SQL
injection, one of the most
primitive and common
vulnerabilities, as we should all
know by now.
From a single injection, we
accessed EVERYTHING.
Why do you put such faith in a
company that allows itself to
become open to these simple
attacks?"
Reference:
http://www.bbc.co.uk/news/business-13636704
Java Database Access Lab Page 12
8. Using a PreparedStatement
A prepared statement is an SQL command that is pre-compiled rather than interpreting the SQL
during execution. A prepared statement can contain placeholders (?) where you insert values
before executing the statement. Use Connection to create a Prepared Statement.
For example, to find all cities having a given countrycode:
PreparedStatement pstmt = connection.prepareStatement(
"SELECT * FROM city WHERE countrycode=?" );
// insert a value for countrycode into PreparedStatement
pstmt.setString( 1, "THA" ); // replace the 1st ? with string "THA"
ResultSet rs = pstmt.executeQuery( );
We specify the SQL SELECT query when we create the prepared statement. The ? in the query is
a placeholder where you can insert a value later. Do not put quotes around ?, even if the value will
be a string. The PreparedStatement will take care of that.
The first ? in a prepared statement is parameter 1, the second ? is parameter 2, etc.
To add a new city to the database, with name, district, and population, we would need 3
placeholders:
PreparedStatement pstmt = connection.prepareStatement(
"INSERT INTO city(name,district,population) VALUES(?,?,?)" );
// insert values into prepared statement
pstmt.setString( 1, "Santa Village" );
pstmt.setString( 2, "North Pole" );
pstmt.setInt( 3, 101 ); // populaton of 100 Elves + Santa Claus
int count = pstmt.executeUpdate( );
Since population is an integer value, we use setInt( ) to set the value of placeholder #3.
The executeUpdate() method is used to perform INSERT, UPDATE, and DELETE
commands. It returns the number of rows affected.
PreparedStatement has some advantages over a Statement:
1. It is executed more efficiently than a plain Statement, since the SQL is pre-compiled.
2. It avoids hacking via SQL Injection since the SQL is compiled before inserting user data.
Exercise
1. Modify JdbcDemo to use a PreparedStatement instead of Statement.
2. Try SQL injection again. When asked for a city name, input: Pattaya' OR 'a'='a
What happens?
Java Database Access Lab Page 13
9. Design a CityBrowser
Now that you know how JDBC works, we will design an application to search for cities and display
information. The application should have a graphical user interface, and be reconfigurable. For
example, we should be able to change the database location or user password without changing our
code.
Exercise
1. Identify kinds of things and responsibilities in the CityBrowser. Write them here.
Object Design
The domain layer contains objects and logic related to our application. City and Country classes
belong here.
The persistence layer contains data access objects (DAO) that provide services for accessing
persistent data. A DAO converts data to/from objects (for our application) and external form.
DAO provide 4 basic services referred to as CRUD:
Create save new object data to the database.
Retrieve find object data in the database and recreate objects. There may be several
methods for this service to enable different forms of object lookup.
Update update data for an object already saved to the database
Delete delete object data from the database
We will design one DAO for each kind of object to save. For example:
CityDao - create, retrieve, update, delete City objects
CountryDao - create, retrieve, update, delete Country objects
The persistence service is provided by JDBC. We will add our own connection manager class to
handle creation of a singleton Connection object. This hides details so we can easily change it. An
application usually needs only one connection to the database.
Java Database Access Lab Page 15
11. Implementation
The CityBrowser will use these classes.
City - a plain old Java object (POJO) with default constructor and
get/set methods for all attributes. The countrycode doesn't belong in
the City class -- we will change that to be a Country reference later.
The id attribute is an Integer (not int) so we know which Cities
haven't been saved in the database. Unsaved cities have a null id.
The database assigns an id when a City is saved.
CityDao - a data access object for City objects. Since this class depends
on the others, write this last.
Exercises
1. Create a package named world.domain
2. Implement the City class in the world.domain package with attributes as shown.
3. Use Eclipse's code generation feature to write a default constructor and get/set methods for all
properties:
Source -> Generate Constructor from Superclass... to generate constructor
Source -> Generate Getters and Setters... for get/set methods
4. Write a toString() method to return a useful description of the City.
Java Database Access Lab Page 16
Questions
1. Why is the ConnectionManager constructor private?
2. What is the purpose of the try/catch block? Since getConnection() throws SQLException, why
do we need try - catch??? Why does catch throw a new SQLException?
Java Database Access Lab Page 17
CityDao
find( id: Integer ): City
findByName( String ): City[*]
delete( city: City )
save( city: City ): boolean
What about "update"? There isn't an update method in this CityDao diagram.
You can write a separate update method or use the save method for both "create" and "update".
A DAO can contain many "retrieve" or "find" operations, depending on what is required by the
application. A general findBy( fieldname, value ) is very common.
Exercises
1. Implement the findByName method of CityDao now.
package world.dao;
//TODO add imports and class comment
/**
* Copy current ResultSet row into a City object.
* @param rs ResultSet with a current row
* @return a City object containing resultSet data for one row
*/
private City resultToCity( ResultSet rs ) {
City city = new City();
try {
city.setName( rs.getString("name") );
city.setDistrict( rs.getString("district") );
city.setPopulation( rs.getInt("population") );
city.setCountrycode( rs.getString("countrycode") );
city.setId( rs.getInt("id") );
} catch (SQLException sqle) {
throw new RuntimeException( sqle );
}
return city;
}
2. Copy your JdbcDemo to a new class named CityBrowser (or just modify JdbcDemo).
Modify the code to use the CityDao to find cities. Here is an example:
Scanner console = new Scanner(System.in);
CityDao dao = new CityDao( );
do {
System.out.print("Name of city to find: ");
String name = console.nextLine().trim();
List<City> cities = dao.findByName( name );
if ( cities.size() > 0 ) {
System.out.println("Matches for "+name);
for( City c: cities) {
System.out.printf("%-20s %-20s %-20s pop %,d\n",
c.getName(), c.getDistrict(), c.getCountry(),
c.getPopulation() );
}
}
else System.out.println("No matches found for "+name);
} while (name.length() > 0);
Note: The MySQL "Connector/J Reference" section 4.1 lists all the properties for MySQL drivers.
You can use Properties to store configuration information for your application, too.
Exercise
Use the System properties to display info about the operating system. In BlueJ you can do this
using the Codepad. In Eclipse you can use a Scrapbook instead of writing a class.
// Example using java.util.Properties
Properties prop = System.getProperties( );
System.out.println( "Your OS is " + prop.getProperty("os.name") );
// display all the properties
prop.list( System.out );
Our application will call getProperties() to get the properties object. For convenience, you may
also provide a getProperty(key) method to get the value of a single property.
To read properties from a text file use the method properties.load(InputStream).
Opening files in Java is a problem because the "current directory" may not be what we expect. Or,
the configuration file may be bundled inside a JAR file. A common solution to the problem of
opening input files is this:
Open the file by name as a FileInputStream
(this will work if the file location is relative to the "current" directory
or is an absolute path)
If that fails then
Use the ClassLoader to open the file as a Resource
ClassLoader has a useful method named loadResourceAsStream( String name ) that searches
for resources anywhere on the CLASSPATH, which can include files packed in a JAR file. It
returns an InputStream object.
Exercises
static {
loadProperties( propertyFilename );
}
/** Load properties from a file or resource by name.
* @param filename file or resource name
* @return true if successful
*/
private static void loadProperties(String filename) {
InputStream instream = null;
// first open as a file relative to "current" directory
try {
instream = new FileInputStream( filename );
} catch ( Exception e ) { /* try again */ }
if (instream == null) {
ClassLoader loader = PropertyManager.class.getClassLoader();
instream = loader.getResourceAsStream(filename);
}
if (instream == null) return;
// remove old property values first
properties.clear();
try {
properties.load(instream);
} catch (IOException e) {
System.out.println("Error reading properties file "+filename);
}
// close input stream to free resources
try {
instream.close();
} catch (IOException ioe) { /* shouldn't happen */ }
}
Note: The MySQL Connector/J Reference, section 4.1, lists all the property keys that the MySQL
driver accepts. The user, password, and jdbc.drivers keys are standard and are
recognized by most JDBC drivers.
Its easy to write a TableModel by extending AbstractTableModel. Your TableModel need only
implement a few methods that are specific to your data. These are the number of rows and columns,
column names, and the cell values in the JTable.
Here's a TableModel that provides data from a List of City objects. (Some code omitted for
brevity).
public CityTableModel( ) {
cities = new java.util.ArrayList<City>(); // to avoid NullPointer
}
We need a way to update the city data in the CityTableModel after performing a query, and to
force JTable to update its view of the table. Invoke the fireTableChanged method (inherited from
AbstractTableModel) to force JTable to update the view.
/** Set the list of cities to display in table.
* @param cities list of City data to display. Must not be null.
*/
public void setCities( List<City> cities ) {
assert cities != null;
this.cities = cities;
fireTableDataChanged();
}
This use of TableModel is one situation where our code would be simpler if the view had direct
access to the ResultSet instead of City objects. That's because a TableModel matches the structure
of a database table. Using ResultSet metadata you can write a TableModel that can display data for
any database table! (I have a worksheet for this.)
Java Database Access Lab Page 25
Exercises
Write the save(City city) method using the pseudocode. Your code should handle SQLException.
2. Set values of the PreparedStatement placeholders (?) using values from the City object.
pstmt.setString( 1, city.getName() );
//TODO set the other parameters using data from city
3. Execute the PreparedStatement and save the result count. If successful, the count will be 1.
int count = pstmt.executeUpdate( );
4. Assign the id value to the City object you just saved. This is how our application knows which
City objects have been saved and which are "transient". How can we discover the id value? The
database generates the id itself.
PreparedStatement has a getGeneratedKeys() method that returns auto-generated values, like
the city id. It returns a ResultSet. In this case, we know the ResultSet has only one generated
value, so we can get the field using index number (1).
if ( count > 0 ) {
ResultSet rs = pstmt.getGeneratedKeys( );
rs.first(); // move to first row of ResultSet
int id = rs.getInt( 1 ); // or use column name "GENERATED_KEY"
city.setId( id );
rs.close( );
5. Test your code. Write some code to create a city object and save it in the database.
After saving a city, does the city object have an id assigned?
Java Database Access Lab Page 26
Assignment
Save or Update?
1. We should only save (INSERT) a City object in the database if the City hasn't been saved
already. Modify the save(City) method to check that the id is null before saving the data as a new
city.
If the city id is not null, then UPDATE the data for existing city (use SQL UPDATE instead of
INSERT). In this example,we use a separate update(City) method for this:
public void save(City city) {
if (city.getId() != null) update( city );
2. Write a delete(City) method that deletes a city from the database, using the id. Its an error
to call delete(city) with a city object that has not been saved (id == null). After deleting the
city object from database, set the city object's id to null.
3. Modify the CityDao so that it always returns the same object for a given City, no matter how you
find the city.
Here is one solution (but not perfect). In CityDao keep a Map of id → city for all the cities that the
CityDao has retrieved (findByName) or saved. This Map is a "cache" of persistent city objects.
Methods that return City objects (find, findByXXX, query) should check the cache before creating
a city object. Similarly, save(City) should add a persisted city to the cache, and delete(City)
should remove a city from the cache.
class CityDao {
private static Map<Integer,City> cache = new HashMap<Integer,City>( );
In the case of creating City objects from a ResultSet, we can modify the resultToCity() method to
check the cache:
private City resultToCity( ResultSet rs ) {
Integer id = rs.getInt("id");
City city = null;
// if object is already in cache, then use id
if ( cache.containsKey(id) ) city = cache.get(id);
// otherwise use a new object
else city = new City();
// otherwise, create a new city from ResultSet
... original code goes here
if ( ! cache.containsKey(id) ) cache.put(id, city);
return city;
}
O-R Mapping frameworks do this for you. Coding it yourself will help you appreciate the issues
involved.
Java Database Access Lab Page 27
Keeping a cache creates a new performance issue: how to free objects from the cache when we are
done using them? If not removed from the cache, unused objects will never be garbage collected.
O-R frameworks have a method named attach( obj ) or merge( obj ) to add an object to the cache,
and detach( obj ) remove an object from the cache without deleting object from the database.
References
Sun Java Tutorial has a trail for JDBC.