Home> HowTos> Java-Howtos> Tomcat 5.5 DBCP - Postgres

Tomcat 5.5 DBCP - PostgreSQL

Um BLOBS (Bilder, Dokumente, etc) aus einer Postgres-Datenbank zu lesen, braucht man den LargeObjectManager. Für den braucht man aber wieder die org.postgresql.PGConnection; bei Zugriff über einen Connection Pool kann das zu Problemen führen.

Zugriff auf die Blobs über die LargeObjectAPI
Erlauben vom Zugriff auf die DelegatingConnection in der server.xml
Cast auf die org.postgresql.PGConnection via DelegatingConnection
Anpassungen an Tomcat 5.0 - Vermeidung der ClassCastException

Vorraussetzungen

Wir wollen eine Webseite bauen, auf der Benutzer Bilder hoch laden können. Da wir aber nicht wildfremden Leuten erlauben wollen, Daten in mein Dateisystem zu schreiben, muss eine Möglichkeit her, die Bilder anders ab zu legen.
Da die Seite aus einer Postgres-Datenbank gespeist wird, ist es naheliegend, die Daten in BLOBS (Binary Large OBjectS) in der Datenbank ab zu legen.
Die Datenbank dazu ist schon da, und ein ConnectionPool ist eingerichtet:

server.xml:

(...)								
<Context path="/webApp"
 	docBase="/srv/www/tomcat/base/apps/webapp.war"
        debug="0" privileged="false" reload="true">

<Resource name="jdbc/postgresDB" auth="Container" type="javax.sql.DataSource"
               maxActive="20" maxIdle="10" maxWait="10000"
               username="MyUsername" password="######" driverClassName="org.postgresql.Driver"
               url="jdbc:postgresql://localhost:5432/myDatabase"
               removeAbandoned="true" removeAbandonedTimeout="60" logAbandoned="true"/>

</Context>
(...)

nach oben

Zugriff auf die BLOBS der Datenbank

Fix in die Postgres-Doku geguckt, und eine eigene Klasse geschrieben, die auf die Connection aus dem ConnectionPool zugreift.

MyClass.java:

(...)								
  public void updateImage(byte[] imageData) throws IOException, SQLException, Exception{
  

    DataSource ds = (DataSource)new InitialContext().lookup("java:comp/env/jdbc/postgresDB");
    Connection con = ds.getConnection();
    boolean oldAutoCommit = con.getAutoCommit();
    con.setAutoCommit(false);
    ByteArrayInputStream inStream = new ByteArrayInputStream(imageData);
    LargeObjectManager lobj = ((org.postgresql.PGConnection)con).getLargeObjectAPI();

    //Das Bild selbst erzeugen
    int oid = lobj.create(LargeObjectManager.READ | LargeObjectManager.WRITE);
    LargeObject obj = lobj.open(oid, LargeObjectManager.WRITE);
    byte buf[] = new byte[2048];
    int s, tl = 0;
    while ((s = inStream.read(buf, 0, 2048)) > 0)
    {
      obj.write(buf, 0, s);
      tl += s;
    }
    obj.close();
   
    PreparedStatement ps = con.prepareStatement("UPDATE images SET img_data = ? WHERE img_id = ?");
    ps.setInt(1,oid);
    ps.setBigDecimal(2,getImageId());
    ps.executeUpdate();
    ps.close();
    pcon.commit();
    pcon.setAutoCommit(oldAutoCommit);
    con.close();
  }
(...)
Das sollte es doch tun, oder?
Leider nein - die Zeile
LargeObjectManager lobj = ((org.postgresql.PGConnection)con).getLargeObjectAPI();
wirft eine java.lang.ClassCastException - die Connection ist eine DBCP-Connection, die die Postgres-Connection nur schachtelt. Um an die originale Postgres-Connection zu kommen, müssen wir uns die innerste Connection holen, und auf die Postgres-Connection casten.
Um auf die innere Connection zugreifen zu können, müssen wir das in der server.xml explizit erlauben, per Default ist der Zugriff auf die Basis-Connection verboten.
nach oben

Zugriff auf die Connection erlauben:

Damit auf die inneren Connections zugegriffen werden kann, muss das im Deployment-Descriptor erlaubt werden.
Das passiert über den Parameter accessToUnderlyingConnectionAllowed, der muss auf "true" gesetzt werden.

server.xml:

(...)								
<Context path="/webApp"
 	docBase="/srv/www/tomcat/base/apps/webapp.war"
        debug="0" privileged="false" reload="true">

<Resource name="jdbc/postgresDB" auth="Container" type="javax.sql.DataSource"
               maxActive="20" maxIdle="10" maxWait="10000"
               username="MyUsername" password="######" driverClassName="org.postgresql.Driver"
               url="jdbc:postgresql://localhost:5432/myDatabase"
               removeAbandoned="true" removeAbandonedTimeout="60" logAbandoned="true"
               accessToUnderlyingConnectionAllowed="true"/>

</Context>
(...)


nach oben

Cast der Connection

Um an die Postgres-Connection zu kommen, brauchen wird die innerste Connection.
Um an diese heran zu kommen, müssen wir die Connection, die wir aus dem ConnectionPool haben, auf eine org.apache.tomcat.dbcp.dbcp.DelegatingConnection casten. Diese bietet uns die Methode getInnermostDelegate() an, mit der wir die Connection bekommen, deren Typ wir auch bei der Definition des ConnectionPools in der server.xml angegeben haben.
Die erforderlichen Klassen zum Kompilieren finden sich in der Bibliothek naming-factory-dbcp.jar im Verzeichnis common/lib/ des Tomcat; sie gehören zum
Jakarta Commons - Projekt.
Also wird die Bibliothek in den Classpath aufgenommen, und die Klasse folgendermaßen modifiziert:

MyClass.java:

(...)	

  private Connection getPostgresConnection(Connection con) throws Exception {
    pgCon = ((org.apache.tomcat.dbcp.dbcp.DelegatingConnection)con).getInnermostDelegate();
    return pgCon;
  }
							
  public void updateImage(byte[] imageData) throws IOException, SQLException, Exception{
  
    DataSource ds = (DataSource)new InitialContext().lookup("java:comp/env/jdbc/postgresDB");
    Connection con = ds.getConnection();
    Connection pgCon = getPostgresConnection(con);
    boolean oldAutoCommit = pgCon.getAutoCommit();
    pgCon.setAutoCommit(false);
    
    ByteArrayInputStream inStream = new ByteArrayInputStream(imageData);
    LargeObjectManager lobj = ((org.postgresql.PGConnection)pgCon).getLargeObjectAPI();

    //Das Bild selbst erzeugen
    int oid = lobj.create(LargeObjectManager.READ | LargeObjectManager.WRITE);
    LargeObject obj = lobj.open(oid, LargeObjectManager.WRITE);
    byte buf[] = new byte[2048];
    int s, tl = 0;
    while ((s = inStream.read(buf, 0, 2048)) > 0)
    {
      obj.write(buf, 0, s);
      tl += s;
    }
    obj.close();
   
    PreparedStatement ps = con.prepareStatement("UPDATE images SET img_data = ? WHERE img_id = ?");
    ps.setInt(1,oid);
    ps.setBigDecimal(2,getImageId());
    ps.executeUpdate();
    ps.close();
    pgCon.commit();
    pgCon.setAutoCommit(oldAutoCommit);
    con.close();
    //Ganz wichtig: NICHT die pgCon schliessen, sonst wird sie nicht in den Pool 
    //zurück gegeben, sondern echt geschlossen!

  }
(...)
Für den Tomcat 5.5 sollte das reichen, wir können endlich auf Blobs zugreifen.
nach oben

Anpassungen für den Tomcat 5.0

Wir wollen die Klasse auch auf dem Tomcat 5.0 benutzen.
Bei der Ausführung wirft aber die Zeile
pgCon = ((org.apache.tomcat.dbcp.dbcp.DelegatingConnection)con).getInnermostDelegate();
wieder eine ClassCastException.
Der Grund: Der Tomcat 5.0 arbeitet mit einer anderen Version der Commons-Klassen; Die Packages heißen anders. Und bleibt nichts anderes übrig, als die Klassen vorher mit instanceof ab zu prüfen.
Die Klassen für die DelegatingConnection des Tomcat 5.0 finden sich in der Datei commons-dbcp-1.2.1 vom Jakarta Commons Projekt.

MyClass.java:

  private Connection getPostgresConnection(Connection con) throws Exception {
    Connection pgCon = null;
    if(con instanceof org.apache.commons.dbcp.DelegatingConnection)
      pgCon = ((org.apache.commons.dbcp.DelegatingConnection)con).getInnermostDelegate();
    else
      pgCon = ((org.apache.tomcat.dbcp.dbcp.DelegatingConnection)con).getInnermostDelegate();
    return pgCon;
  }

nach oben