mercoledì 13 maggio 2009

Gestione dei tipi nel driver JDBC

Immagiamo di avere una tabella con un campo data, uno intero e uno stringa, e di scrivere e usare il seguente semplice programma JDBC:

public class PGTest {
public static void main(String[] args) throws Exception{
Class driverClass = Class.forName("org.postgresql.Driver");
driverClass.newInstance();

Connection connection = DriverManager.getConnection("jdbc:postgresql://localhost/pgdaydb","luca", "fluca");

Statement st = connection.createStatement();
String sql = "SELECT date_field, int_field, string_field FROM mytable";
ResultSet rs = st.executeQuery(sql);

while(rs.next()){
String stringForDate = rs.getString("date_field");
String stringForInt = rs.getString("int_field");
int intForString = rs.getInt("string_field");

System.out.println("Obtained: " + stringForDate + " - " + stringForInt + " - " + intForString);
}

}
}

Quello che succede è che i campi sono ottenuti dal database, ma i loro valori sono scambiati: il tipo data e intero sono inseriti in stringhe e quello stringa in un intero.
Se siete fortunati, questo programma girerà senza nessun problema!
Come mai avviene cio?
La ragione è che il driver JDBC di PostgreSQL gestisce la comunicazione con il backend attraverso stringhe di caratteri, e quindi ogni valore di tupla inviato indietro dal database è restituito come "stringa". A questo punto, quando su una colonna invocate un getInt() il driver effettua (si veda AbstractJdbc2ResultSet):

 public int getInt(int columnIndex) throws SQLException
{
checkResultSet(columnIndex);
if (wasNullFlag)
return 0; // SQL NULL

Encoding encoding = connection.getEncoding();
if (encoding.hasAsciiNumbers()) {
try {
return getFastInt(columnIndex);
} catch (NumberFormatException ex) {
}
}
return toInt( getFixedString(columnIndex) );
}



public static int toInt(String s) throws SQLException
{
if (s != null)
{
try
{
s = s.trim();
return Integer.parseInt(s);
}
catch (NumberFormatException e)
{
try
{
BigDecimal n = new BigDecimal(s);
BigInteger i = n.toBigInteger();

int gt = i.compareTo(INTMAX);
int lt = i.compareTo(INTMIN);

if (gt > 0 || lt < 0)
{
throw new PSQLException(GT.tr("Bad value for type {0} : {1}", new Object[]{"int",s}),
PSQLState.NUMERIC_VALUE_OUT_OF_RANGE);
}
return i.intValue();

}
catch ( NumberFormatException ne )
{
throw new PSQLException(GT.tr("Bad value for type {0} : {1}", new Object[]{"int",s}),
PSQLState.NUMERIC_VALUE_OUT_OF_RANGE);
}
}
}
return 0; // SQL NULL
}


Quindi il driver effettua una semplice conversione a Integer della stringa con il valore della colonna. In sostanza il driver non effettua nessun controllo sul tipo del dato che si sta chiedendo al ResultSet, e tenta di convertirlo da stringa al valore desiderato.
Si noti che comunque il driver e' ingrado di stabilire il tipo di una determinata colonna, e in effetti la generica getObject() viene implementata per mezzo di getInternalObject():

 protected Object internalGetObject(int columnIndex, Field field) throws SQLException
{
switch (getSQLType(columnIndex))
{
case Types.BIT:
// Also Types.BOOLEAN in JDBC3
return getBoolean(columnIndex) ? Boolean.TRUE : Boolean.FALSE;
case Types.TINYINT:
case Types.SMALLINT:
case Types.INTEGER:
return new Integer(getInt(columnIndex));
case Types.BIGINT:
return new Long(getLong(columnIndex));
case Types.NUMERIC:
case Types.DECIMAL:
return getBigDecimal
(columnIndex, (field.getMod() == -1) ? -1 : ((field.getMod() - 4) & 0xffff));
case Types.REAL:
return new Float(getFloat(columnIndex));
case Types.FLOAT:
case Types.DOUBLE:
return new Double(getDouble(columnIndex));
case Types.CHAR:
case Types.VARCHAR:
case Types.LONGVARCHAR:
return getString(columnIndex);
case Types.DATE:
return getDate(columnIndex);
case Types.TIME:
return getTime(columnIndex);
case Types.TIMESTAMP:
return getTimestamp(columnIndex, null);
case Types.BINARY:
case Types.VARBINARY:
case Types.LONGVARBINARY:
return getBytes(columnIndex);
case Types.ARRAY:
return getArray(columnIndex);
case Types.CLOB:
return getClob(columnIndex);
case Types.BLOB:
return getBlob(columnIndex);
...
}
e quindi viene cercato il tipo della colonna per poter fare un cast automatico opportuno.
Nelle specifiche JDBC esiste una tabella che illustra le conversioni che un driver deve supportare fra i vari tipi. Ad esempio la tabella illustra come una data possa anche essere ottenuta come byte[]. Si presti attenzione al fatto che tali conversioni sono le minime indispensabili che un driver deve fornire, quindi il fatto che il driver JDBC di PostgreSQL consenta anche la conversione di esempio da data a stringa non rappresenta affatto una limitazione del driver stesso.

Nessun commento: