49

I have a big CLOB (more than 32kB) that I want to read to a String, using StringBuilder. How do I do this in the most efficient way? I can not use the "int length" constructor for StringBuilder since the lenght of my CLOB is longer than a "int" and needs a "long" value.

I am not that confortable with the Java I/O classes, and would like to get some guidance.

Edit - I have tried with this code for clobToString():

private String clobToString(Clob data) {
    StringBuilder sb = new StringBuilder();
    try {
        Reader reader = data.getCharacterStream();
        BufferedReader br = new BufferedReader(reader);

        String line;
        while(null != (line = br.readLine())) {
            sb.append(line);
        }
        br.close();
    } catch (SQLException e) {
        // handle this exception
    } catch (IOException e) {
        // handle this exception
    }
    return sb.toString();
}
Jonas
  • 97,987
  • 90
  • 271
  • 355

12 Answers12

50

Ok I will suppose a general use, first you have to download apache commons, there you will find an utility class named IOUtils which has a method named copy();

Now the solution is: get the input stream of your CLOB object using getAsciiStream() and pass it to the copy() method.

InputStream in = clobObject.getAsciiStream();
StringWriter w = new StringWriter();
IOUtils.copy(in, w);
String clobAsString = w.toString();
Omar Al Kababji
  • 1,698
  • 3
  • 16
  • 30
  • Thanks, that looks nice. But I leavy the question open little bit more, because I would prefer a solution that only uses the standard library. – Jonas Jan 31 '10 at 09:46
  • I already have the Apache Commons library loaded so this is the perfect solution. Thanks! – John Strickler Jun 02 '11 at 14:47
  • 12
    getAsciiStream will give you headaches if you use unicode. (or any characters falling outside of ascii) – TJ Ellis Sep 29 '11 at 12:41
  • 16
    I changed `InputStream` to `Reader` and `clobObject.getAsciiStream()` to `clobObject.getCharacterStream()` to prevent encoding issues. – Dormouse Jun 11 '14 at 07:52
26

What's wrong with:

clob.getSubString(1, (int) clob.length());

?

For example Oracle oracle.sql.CLOB performs getSubString() on internal char[] which defined in oracle.jdbc.driver.T4CConnection and just System.arraycopy() and next wrap to String... You never get faster reading than System.arraycopy().

UPDATE Get driver ojdbc6.jar, decompile CLOB implementation, and study which case could be faster based on the internals knowledge.

gavenkoa
  • 37,355
  • 13
  • 206
  • 248
  • Leaves a lot of newlines characters in the string. – Gervase Sep 16 '14 at 19:58
  • 1
    @Gervase Newlines can be significant in XML. Anyway, you shoud trim useless spaces and newlines before storing it to the DB. – Florian F May 23 '16 at 14:43
  • Some points to clear: What happen if clob.length() is greater than Integer.MAX_VALUE? What's jar contains oracle.sql.CLOB? – Stephan May 26 '16 at 14:33
  • 2
    @Stephan I studied `ojdbc6.jar`. `Integer.MAX_VALUE` is a limit for array length for **JDK Platform 2** and String hold chars in array. So you out of luck for > 2 GiB CLOBs... Try streaming approach because you can't hold that data with pure Java memory model (unless you use some native extension and 64-bit platform with enough system memory). – gavenkoa May 27 '16 at 14:24
  • what's wrong? https://stackoverflow.com/questions/16249238/java-sql-sqlrecoverableexception-while-fetching-clob-data-using-hibernate-orac ... I face the same problem when SQL close connection to CLOB data on production – Marek Bernád Jul 08 '20 at 13:37
  • 1
    @MarekBernád OK. I believe that you have problem because you cross transaction / connection boundaries. It is the problem with cumbersome frameworks that hides resource management. If you in managed EE environment access getter inside `@Transactional` )) *If you bother with efficiency Hibernate is not a good framework*. – gavenkoa Jul 08 '20 at 16:12
19

My answer is just a flavor of the same. But I tested it with serializing a zipped content and it worked. So I can trust this solution unlike the one offered first (that use readLine) because it will ignore line breaks and corrupt the input.

/*********************************************************************************************
 * From CLOB to String
 * @return string representation of clob
 *********************************************************************************************/
private String clobToString(java.sql.Clob data)
{
    final StringBuilder sb = new StringBuilder();

    try
    {
        final Reader         reader = data.getCharacterStream();
        final BufferedReader br     = new BufferedReader(reader);

        int b;
        while(-1 != (b = br.read()))
        {
            sb.append((char)b);
        }

        br.close();
    }
    catch (SQLException e)
    {
        log.error("SQL. Could not convert CLOB to string",e);
        return e.toString();
    }
    catch (IOException e)
    {
        log.error("IO. Could not convert CLOB to string",e);
        return e.toString();
    }

    return sb.toString();
}
Stan Sokolov
  • 1,542
  • 16
  • 17
19

I can not use the "int length" constructor for StringBuilder since the length of my CLOB is longer than a int and needs a long value.

If the CLOB length is greater than fits in an int, the CLOB data won't fit in a String either. You'll have to use a streaming approach to deal with this much XML data.

If the actual length of the CLOB is smaller than Integer.MAX_VALUE, just force the long to int by putting (int) in front of it.

Jonas
  • 97,987
  • 90
  • 271
  • 355
Barend
  • 16,572
  • 1
  • 52
  • 76
4

If you really must use only standard libraries, then you just have to expand on Omar's solution a bit. (Apache's IOUtils is basically just a set of convenience methods which saves on a lot of coding)

You are already able to get the input stream through clobObject.getAsciiStream()

You just have to "manually transfer" the characters to the StringWriter:

InputStream in = clobObject.getAsciiStream();
Reader read = new InputStreamReader(in);
StringWriter write = new StringWriter();

int c = -1;
while ((c = read.read()) != -1)
{
    write.write(c);
}
write.flush();
String s = write.toString();

Bear in mind that

  1. If your clob contains more character than would fit a string, this won't work.
  2. Wrap the InputStreamReader and StringWriter with BufferedReader and BufferedWriter respectively for better performance.
Zeus
  • 5,885
  • 5
  • 44
  • 79
Edwin Lee
  • 3,410
  • 6
  • 25
  • 35
  • That looks similar to the code i provided in my question, are there any key differences between them that I don't see? In example in a performance point of view? – Jonas Jan 31 '10 at 12:41
  • Oops, i missed out on your code fragment! It's somewhat similar, but bear in mind that by just grabbing the BufferedReader.readLine(), you'll miss out on the linebreaks. – Edwin Lee Feb 01 '10 at 00:36
  • 1
    Small Correction Line 2 should be Reader read = new InputStreamReader(in); – Vivek Jun 04 '12 at 07:15
  • 2
    No, no, no. `getAsciiStream()` forces ASCII encoding and corrupts all non-ASCII-characters. What you're doing is getting an `InputStream` (bytes) from a character source, and then immediately turning them back into characters using a random (platform default) encoding on `InputStreamReader`. It's a redundant operation except for the fact that it corrupts non-ASCII data. Just read from the `getCharacterStream()` `Reader` directly and write to the `StringWriter`. – Christoffer Hammarström Sep 20 '12 at 12:02
4

If using Mule, below are the steps.

Follow below steps.

Enable streaming in the connector i.e. progressiveStreaming=2

Typecast DB2 returned CLOB to java.sql.Clob (IBM Supports this type cast)

Convert that to character stream (ASCII stream sometimes may not support some special characters). So you may to use getCharacterStream()

That will return a "reader" object which can be converted to "String" using common-io (IOUtils).

So in short, use groovy component and add below code.

clobTest = (java.sql.Clob)payload.field1 
bodyText = clobTest.getCharacterStream() 
targetString = org.apache.commons.io.IOUtils.toString(bodyText)
payload.PAYLOADHEADERS=targetString return payload

Note: Here I'm assuming "payload.field1" is holding clob data.

That's it!

Regards Naveen

Naveen K Reddy
  • 309
  • 4
  • 11
2

A friendly helper method using apache commons.io

Reader reader = clob.getCharacterStream();
StringWriter writer = new StringWriter();
IOUtils.copy(reader, writer);
String clobContent = writer.toString();
fl0w
  • 2,419
  • 23
  • 31
0
public static String readClob(Clob clob) throws SQLException, IOException {
    StringBuilder sb = new StringBuilder((int) clob.length());
    Reader r = clob.getCharacterStream();
    char[] cbuf = new char[2048];
    int n;
    while ((n = r.read(cbuf, 0, cbuf.length)) != -1) {
        sb.append(cbuf, 0, n);
    }
    return sb.toString();
}

The above approach is also very efficient.

Rohit
  • 333
  • 2
  • 11
0
public static final String tryClob2String(final Object value)
{
    final Clob clobValue = (Clob) value;
    String result = null;

    try
    {
        final long clobLength = clobValue.length();

        if (clobLength < Integer.MIN_VALUE || clobLength > Integer.MAX_VALUE)
        {
            log.debug("CLOB size too big for String!");
        }
        else
        {
            result = clobValue.getSubString(1, (int) clobValue.length());
        }
    }
    catch (SQLException e)
    {
        log.error("tryClob2String ERROR: {}", e);
    }
    finally
    {
        if (clobValue != null)
        {
            try
            {
                clobValue.free();
            }
            catch (SQLException e)
            {
                log.error("CLOB FREE ERROR: {}", e);
            }
        }
    }

    return result;
}
kayz1
  • 6,652
  • 3
  • 48
  • 54
0
private String convertToString(java.sql.Clob data)
{
    final StringBuilder builder= new StringBuilder();

    try
    {
        final Reader         reader = data.getCharacterStream();
        final BufferedReader br     = new BufferedReader(reader);

        int b;
        while(-1 != (b = br.read()))
        {
            builder.append((char)b);
        }

        br.close();
    }
    catch (SQLException e)
    {
        log.error("Within SQLException, Could not convert CLOB to string",e);
        return e.toString();
    }
    catch (IOException e)
    {
        log.error("Within IOException, Could not convert CLOB to string",e);
        return e.toString();
    }
    //enter code here
    return builder.toString();
}
Wesley Coetzee
  • 4,392
  • 2
  • 25
  • 42
Hemant
  • 11
  • 6
  • 2
    Usually it's better to explain a solution instead of just posting some rows of anonymous code. You can read [How do I write a good answer](https://stackoverflow.com/help/how-to-answer), and also [Explaining entirely code-based answers](https://meta.stackexchange.com/questions/114762/explaining-entirely-%E2%80%8C%E2%80%8Bcode-based-answers) – Anh Pham Dec 04 '17 at 10:32
0
 public static String clobToString(final Clob clob) throws SQLException, IOException {
        try (final Reader reader = clob.getCharacterStream()) {
            try (final StringWriter stringWriter = new StringWriter()) {
                IOUtils.copy(reader, stringWriter);
                return stringWriter.toString();
            }
        }
    }
Jonas_Hess
  • 1,373
  • 16
  • 26
-2

CLOB are like Files, you can read parts of it easily like this

// read the first 1024 characters
String str = myClob.getSubString(0, 1024);

and you can overwrite to it like this

// overwrite first 1024 chars with first 1024 chars in str
myClob.setString(0, str,0,1024);

I don't suggest using StringBuilder and fill it until you get an Exception, almost like adding numbers blindly until you get an overflow. Clob is like a text file and the best way to read it is using a buffer, in case you need to process it, otherwise you can stream it into a local file like this

int s = 0;
File f = new File("out.txt");
FileWriter fw new FileWriter(f);

while (s < myClob.length())
{
    fw.write(myClob.getSubString(0, 1024));
    s += 1024;
}

fw.flush();
fw.close();
Khaled.K
  • 5,516
  • 1
  • 30
  • 47