Skip to Main Content

Java Database Connectivity (JDBC)

Announcement

For appeals, questions and feedback about Oracle Forums, please email oracle-forums-moderators_us@oracle.com. Technical questions should be asked in the appropriate category. Thank you!

BLOB support in standard postgresql jdbc driver - OutOfMemoryError

843854Aug 15 2002 — edited Aug 18 2002
Hi all,

I have a problem with PostgreSQL and BLOBs.

Small BLOBs work, but with BLOBs of a certain size my code throws a java.lang.OutOfMemoryError.
The file I tried has about 2-3 MB.


Environment:
- PostgreSQL 7.2 on i686-pc-linux-gnu, compiled by GCC 2.96
- Mandrake 8.2
- 192 MB RAM
- blob column created as datatype "bytea" (maybe that is the problem)
- code posted below (BlobPostgresTest.java)
- console output posted below

- java command to run program:
    java -verbose:gc -Xmx128m -classpath .:pgjdbc2.jar BlobPostgresTest
- java version as follows from "java -version" command:
    java version "1.4.0_01"
    Java(TM) 2 Runtime Environment, Standard Edition (build 1.4.0_01-b03)
    Java HotSpot(TM) Client VM (build 1.4.0_01-b03, mixed mode)
do you have an idea how to solve this problem ?


Console output (including GC activities):
=========================================

java -verbose:gc -Xmx128m -classpath .:pgjdbc2.jar BlobPostgresTest

BLOB/PostgreSQL Demo started
[GC 511K->230K(1984K), 0.0163708 secs]
preparing statement
setting binary stream
[GC 402K->273K(1984K), 0.0092325 secs]
[Full GC 273K->273K(1984K), 0.0771294 secs]
[GC 4351K->4058K(4908K), 0.0046329 secs]
[Full GC 4058K->3770K(4908K), 0.0831070 secs]
[GC 4922K->4922K(6860K), 0.0009556 secs]
[Full GC 4922K->4346K(6860K), 0.0890570 secs]
[GC 6650K->6650K(7820K), 0.0011110 secs]
[Full GC 6650K->5496K(7820K), 0.1420860 secs]
[GC 10104K->10104K(14480K), 0.0012827 secs]
[Full GC 10104K->7800K(14480K), 0.1236744 secs]
[GC 17016K->17016K(24084K), 0.0015421 secs]
[Full GC 17016K->12408K(24084K), 0.1688843 secs]
[GC 30840K->30840K(43224K), 0.0020800 secs]
[Full GC 30840K->21624K(43224K), 0.2547274 secs]
[GC 76920K->74616K(81500K), 0.0041685 secs]
[Full GC 74616K->49272K(81500K), 0.5688448 secs]
[GC 67704K->67704K(88332K), 0.0033407 secs]
[Full GC 67704K->58488K(88332K), 0.2558231 secs]
executing update
[GC 95352K->95352K(104844K), 0.0932741 secs]
[Full GC 95352K->40056K(104844K), 0.9644251 secs]
[GC 69245K->69245K(104844K), 0.0036631 secs]
[Full GC 69245K->69245K(104844K), 0.0814962 secs]
[Full GC 69245K->66324K(129728K), 1.1439123 secs]
Exception in thread "main" java.lang.OutOfMemoryError



BlobPostgresTest.java
=====================
import java.sql.*;
import java.io.*;

/*

drop table blobdemo;

drop sequence blobdemo_id_seq;

create table blobdemo(
  id serial not null primary key,
  name varchar(50),
  content bytea);

*/

public final class BlobPostgresTest {
    private final static String NAME = "TEST";
    //private final static String FILE_NAME = "/tmp/blob/2mb.xxx";
    private final static String FILE_NAME = "BlobPostgresTest.java";

    public final static void main(String[] args) throws Throwable {
	Connection con = null;
	Statement statement = null;
	PreparedStatement insertStatement = null;
	ResultSet rs = null;
	File file = null;
	FileInputStream fis = null;
	BufferedInputStream bis = null;

	try {
	    System.out.println("BLOB/PostgreSQL Demo started");

	    Class.forName("org.postgresql.Driver");

	    con = DriverManager.getConnection
		("jdbc:postgresql://localhost/template1",
		 "postgres", "");
	    con.setAutoCommit(true);

	    statement = con.createStatement();

	    // cleanup
	    statement.executeUpdate("delete from blobdemo");

	    // file
	    file = new File(FILE_NAME);
	    fis = new FileInputStream(file);
	    bis = new BufferedInputStream(fis);

	    // insert one record
	    System.out.println("preparing statement");
	    insertStatement = con.prepareStatement
		("insert into blobdemo ( name, content ) values ( ?, ? )");

	    insertStatement.setString(1, NAME);

	    System.out.println("setting binary stream");
	    insertStatement.setBinaryStream(2, bis, (int)file.length());

	    System.out.println("executing update");
	    insertStatement.executeUpdate();

	    // retrieve
	    rs = statement.executeQuery
		("select id, name, content from blobdemo");

	    while(rs.next()) {
		System.out.println("id=" + rs.getObject(1));
		System.out.println("name=" + rs.getObject(2));

		byte[] bytes = rs.getBytes(3);
		String content = new String(bytes);

		//System.out.println(content);
		System.out.println("retrieved " + bytes.length +  " bytes");
	    }
	} finally {

	    if(rs != null) rs.close();
	    if(statement != null) statement.close();
	    if(insertStatement != null) insertStatement.close();
	    if(con != null) con.close();

	    if(fis != null) fis.close();
	    if(bis != null) bis.close();
	}

	System.out.println("BLOB/PostgreSQL Demo complete");
    }
}

Comments

BEDE

Ouch! Do you realize that, provided you do write that trigger, an insert into that table will last very long? Better create a table mails2send that will have a foreign key to P_IT_ISSUES and have that trigger insert into mails2send  the value of the PK for that row in P_IT_ISSUES. And have a job scan mails2send  and send the emails. Otherwise, your transaction processing will be very slow, most likely inacceptable.

For sending e-mails from PL/SQL use utl_smtp or utl_mail.

Vinipanda

I know, but need to do this temporarily. Will use utl_smtp later on but for now this needs to be done.

Any idea how the trigger can be modified to loop?

Vinipanda

The trigger mentioned is when an issue is raised, it would go to person with approver='Approver 1', he is first approver. When he logs into application and sets p_it_issues.is_approved='Y', then email would shoot to p_it_people.Approver='Approver 2' . He would again log in, set p_it_issues.is_approved='Y' and email would shoot to p_it_people.Approver='Approver 3' . There could be more than one person with p_it_people.Approver='Approver 1' or 2 or 3. So all would need to approve it and set Y .

Billy Verreynne

A trigger like this sends e-mails BEFORE the transaction is committed - which means that e-mails can be send via UTL_SMTP, and the transaction rolled back. Thus users can receive e-mails for transactions never committed.

Simplistic solution:

Create a stored proc that can be called to send e-mail for a row PK parameter, e.g. SendMail( mailID ).

In the trigger call DBMS_JOB.submit() to execute the SendMail() proc for the relevant row.

Only when the transaction is committed, will the jobs scheduled queued for execution.

Note that when using APEX_MAIL from a trigger, workspace security ID needs to be set - and you need to ensure the e-mails are queued without being committed for sending.

Mike Kutz

My Understanding

  • APEX_MAIL uses UTL_MAIL under the hood.
    • APEX_MAIL just makes it easier to send multiple attachments
    • AFAIK - APEX_MAIL now supports email Templates.
  • APEX_MAIL queues the email
    • ROLLBACK will undo the queuing
  • You need a running DBMS_SCHEDULER job to process the queue
    • DBA_SCHEDULER_JOBS where job_name='ORACLE_APEX_MAIL_QUEUE' and owner='APEX_nnnnnn'
  • Using APEX_MAIL.push_queue in a Trigger (or 2nd Process) is really bad.
    • Oracle can "rollback and try again"
      • You can get multiple identical emails sent
    • APEX can "rollback and try again"
      • You can get multiple identical emails sent
    • Rapidly sending multiple identical emails can get you Black Listed faster than you can hit the SEND button.

From my understanding, APEX_MAIL already does 90% of what Billy and BEDE are recommending.

The other 10% is about "how you interact with it".

My recommendation to APEX developers

  • put your "send email" code in a dedicated package
  • Call the "Send Email" code as part of a 2nd Process, not within a trigger.
  • If you need a non-APEX apps to use the same "Send Email" code, force that application to use an API (Package)
    • REVOKE insert, update, delete ON table_in_question FROM application_users_and_roles
    • Use a Table API (TAPI) or Transaction API (XAPI) [a package]
    • The TAPI/XAPI should be responsible for setting up the APEX Session if necessary. (see APEX_MAIL documentation for HOWTO setup APEX Session)

In short

  • Keep your DML code separate from your "Send Email" code.
  • Only send emails on committed data. (use a queue so that your can "send email later after commit")

My $0.02

MK

PS - this is mostly for OP, not Billy/BEDE

Billy Verreynne

Mike, it uses UTL_SMTP - UTL_MAIL is far too primitive.

1 - 6
Locked Post
New comments cannot be posted to this locked post.

Post Details

Locked on Sep 15 2002
Added on Aug 15 2002
1 comment
188 views