001/* =========================================================== 002 * JFreeChart : a free chart library for the Java(tm) platform 003 * =========================================================== 004 * 005 * (C) Copyright 2000-present, by David Gilbert and Contributors. 006 * 007 * Project Info: http://www.jfree.org/jfreechart/index.html 008 * 009 * This library is free software; you can redistribute it and/or modify it 010 * under the terms of the GNU Lesser General Public License as published by 011 * the Free Software Foundation; either version 2.1 of the License, or 012 * (at your option) any later version. 013 * 014 * This library is distributed in the hope that it will be useful, but 015 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 016 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public 017 * License for more details. 018 * 019 * You should have received a copy of the GNU Lesser General Public 020 * License along with this library; if not, write to the Free Software 021 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 022 * USA. 023 * 024 * [Oracle and Java are registered trademarks of Oracle and/or its affiliates. 025 * Other names may be trademarks of their respective owners.] 026 * 027 * ------------------------ 028 * JDBCCategoryDataset.java 029 * ------------------------ 030 * (C) Copyright 2002-present, by Bryan Scott and Contributors. 031 * 032 * Original Author: Bryan Scott; Andy; 033 * Contributor(s): David Gilbert; 034 * Thomas Morgner; 035 * 036 */ 037 038package org.jfree.data.jdbc; 039 040import java.sql.Connection; 041import java.sql.Date; 042import java.sql.DriverManager; 043import java.sql.ResultSet; 044import java.sql.ResultSetMetaData; 045import java.sql.SQLException; 046import java.sql.Statement; 047import java.sql.Types; 048 049import org.jfree.data.category.CategoryDataset; 050import org.jfree.data.category.DefaultCategoryDataset; 051 052/** 053 * A {@link CategoryDataset} implementation over a database JDBC result set. 054 * The dataset is populated via a call to {@link #executeQuery(String)} with 055 * the string SQL query. The SQL query must return at least two columns. The 056 * first column will be the category name and remaining columns values (each 057 * column represents a series). Subsequent calls to 058 * {@link #executeQuery(String)} will refresh the dataset. 059 * <p> 060 * The database connection is read-only and no write back facility exists. 061 * <p> 062 * NOTE: Many people have found this class too restrictive in general use. 063 * For the greatest flexibility, please consider writing your own code to read 064 * data from a{@code ResultSet} and populate a {@link DefaultCategoryDataset} 065 * directly. 066 */ 067public class JDBCCategoryDataset extends DefaultCategoryDataset { 068 069 /** For serialization. */ 070 static final long serialVersionUID = -3080395327918844965L; 071 072 /** The database connection. */ 073 private transient Connection connection; 074 075 /** 076 * A flag the controls whether or not the table is transposed. The default 077 * is 'true' because this provides the behaviour described in the 078 * documentation. 079 */ 080 private boolean transpose = true; 081 082 083 /** 084 * Creates a new dataset with a database connection. 085 * 086 * @param url the URL of the database connection. 087 * @param driverName the database driver class name. 088 * @param user the database user. 089 * @param passwd the database user's password. 090 * 091 * @throws ClassNotFoundException if the driver cannot be found. 092 * @throws SQLException if there is an error obtaining a connection to the 093 * database. 094 */ 095 public JDBCCategoryDataset(String url, 096 String driverName, 097 String user, 098 String passwd) 099 throws ClassNotFoundException, SQLException { 100 101 Class.forName(driverName); 102 this.connection = DriverManager.getConnection(url, user, passwd); 103 } 104 105 /** 106 * Create a new dataset with the given database connection. 107 * 108 * @param connection the database connection. 109 */ 110 public JDBCCategoryDataset(Connection connection) { 111 if (connection == null) { 112 throw new NullPointerException("A connection must be supplied."); 113 } 114 this.connection = connection; 115 } 116 117 /** 118 * Creates a new dataset with the given database connection, and executes 119 * the supplied query to populate the dataset. 120 * 121 * @param connection the connection. 122 * @param query the query. 123 * 124 * @throws SQLException if there is a problem executing the query. 125 */ 126 public JDBCCategoryDataset(Connection connection, String query) 127 throws SQLException { 128 this(connection); 129 executeQuery(query); 130 } 131 132 /** 133 * Returns a flag that controls whether or not the table values are 134 * transposed when added to the dataset. 135 * 136 * @return A boolean. 137 */ 138 public boolean getTranspose() { 139 return this.transpose; 140 } 141 142 /** 143 * Sets a flag that controls whether or not the table values are transposed 144 * when added to the dataset. 145 * 146 * @param transpose the flag. 147 */ 148 public void setTranspose(boolean transpose) { 149 this.transpose = transpose; 150 } 151 152 /** 153 * Populates the dataset by executing the supplied query against the 154 * existing database connection. If no connection exists then no action 155 * is taken. 156 * <p> 157 * The results from the query are extracted and cached locally, thus 158 * applying an upper limit on how many rows can be retrieved successfully. 159 * 160 * @param query the query. 161 * 162 * @throws SQLException if there is a problem executing the query. 163 */ 164 public void executeQuery(String query) throws SQLException { 165 executeQuery(this.connection, query); 166 } 167 168 /** 169 * Populates the dataset by executing the supplied query against the 170 * existing database connection. If no connection exists then no action 171 * is taken. 172 * <p> 173 * The results from the query are extracted and cached locally, thus 174 * applying an upper limit on how many rows can be retrieved successfully. 175 * 176 * @param con the connection. 177 * @param query the query. 178 * 179 * @throws SQLException if there is a problem executing the query. 180 */ 181 public void executeQuery(Connection con, String query) throws SQLException { 182 183 Statement statement = null; 184 ResultSet resultSet = null; 185 try { 186 statement = con.createStatement(); 187 resultSet = statement.executeQuery(query); 188 ResultSetMetaData metaData = resultSet.getMetaData(); 189 190 int columnCount = metaData.getColumnCount(); 191 192 if (columnCount < 2) { 193 throw new SQLException( 194 "JDBCCategoryDataset.executeQuery() : insufficient columns " 195 + "returned from the database."); 196 } 197 198 // Remove any previous old data 199 int i = getRowCount(); 200 while (--i >= 0) { 201 removeRow(i); 202 } 203 204 while (resultSet.next()) { 205 // first column contains the row key... 206 Comparable rowKey = resultSet.getString(1); 207 for (int column = 2; column <= columnCount; column++) { 208 209 Comparable columnKey = metaData.getColumnName(column); 210 int columnType = metaData.getColumnType(column); 211 212 switch (columnType) { 213 case Types.TINYINT: 214 case Types.SMALLINT: 215 case Types.INTEGER: 216 case Types.BIGINT: 217 case Types.FLOAT: 218 case Types.DOUBLE: 219 case Types.DECIMAL: 220 case Types.NUMERIC: 221 case Types.REAL: { 222 Number value = (Number) resultSet.getObject(column); 223 if (this.transpose) { 224 setValue(value, columnKey, rowKey); 225 } 226 else { 227 setValue(value, rowKey, columnKey); 228 } 229 break; 230 } 231 case Types.DATE: 232 case Types.TIME: 233 case Types.TIMESTAMP: { 234 Date date = (Date) resultSet.getObject(column); 235 Number value = date.getTime(); 236 if (this.transpose) { 237 setValue(value, columnKey, rowKey); 238 } 239 else { 240 setValue(value, rowKey, columnKey); 241 } 242 break; 243 } 244 case Types.CHAR: 245 case Types.VARCHAR: 246 case Types.LONGVARCHAR: { 247 String string 248 = (String) resultSet.getObject(column); 249 try { 250 Number value = Double.valueOf(string); 251 if (this.transpose) { 252 setValue(value, columnKey, rowKey); 253 } 254 else { 255 setValue(value, rowKey, columnKey); 256 } 257 } 258 catch (NumberFormatException e) { 259 // suppress (value defaults to null) 260 } 261 break; 262 } 263 default: 264 // not a value, can't use it (defaults to null) 265 break; 266 } 267 } 268 } 269 270 fireDatasetChanged(); 271 } 272 finally { 273 if (resultSet != null) { 274 try { 275 resultSet.close(); 276 } 277 catch (Exception e) { 278 // report this? 279 } 280 } 281 if (statement != null) { 282 try { 283 statement.close(); 284 } 285 catch (Exception e) { 286 // report this? 287 } 288 } 289 } 290 } 291 292}