Storing and Retrieving Images and Files with JDBC

Naveen AnamaneniUpdated · 6 min read
mediumJava 25javajdbcblobfiles

Databases can store more than numbers and short text. A BLOB (Binary Large Object) column holds binary data such as an image, a PDF or an audio clip. A CLOB (Character Large Object) column holds large text, such as a long document. In JDBC, you write to these columns with a PreparedStatement by giving it a stream, and read them back from the ResultSet as a stream. Using streams means a large file never has to be loaded into memory all at once. In this lesson, you will learn how to store and retrieve an image and a text file, and when it is better to keep files outside the database.

BLOB vs CLOB

PointBLOBCLOB
HoldsRaw bytesCharacters (text)
Used forImages, PDFs, audio, video, zip filesLong documents, XML, JSON, logs
Java stream typeInputStreamReader
Store withsetBinaryStream, setBytes, setBlobsetCharacterStream, setString, setClob
Read withgetBinaryStream, getBytes, getBlobgetCharacterStream, getString, getClob
MySQL type namesBLOB, MEDIUMBLOB, LONGBLOBTEXT, MEDIUMTEXT, LONGTEXT
PostgreSQL type namesBYTEATEXT

Methods

MethodDescription
ps.setBinaryStream(i, inputStream, length)It is used to send binary data from a stream.
ps.setBytes(i, byteArray)It sends binary data already held in memory; fine for small items.
ps.setCharacterStream(i, reader)It sends text from a Reader.
rs.getBinaryStream(column)It returns a stream for reading a BLOB.
rs.getBytes(column)It returns a BLOB as a byte array.
rs.getCharacterStream(column), rs.getString(column)They read a CLOB as a stream or as a String.

Example: Storing and Retrieving a Photo and an Essay

The program first creates two small files so it has something to store: a 2,048-byte file standing in for a photo, and a short text file.

Lobs.java▶ Try it
import java.io.InputStream;
import java.io.Reader;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.Statement;
import java.util.Arrays;
 
public class Lobs {
    public static void main(String[] args) throws Exception {
        // Two small files to store: a fake "photo" and a text document.
        byte[] photoBytes = new byte[2048];
        for (int i = 0; i < photoBytes.length; i++) {
            photoBytes[i] = (byte) (i * 7);
        }
        Path photo = Files.write(Path.of("photo.png"), photoBytes);
        Path essay = Files.writeString(Path.of("essay.txt"),
            "My favorite subject is Java.\nI like collections the most.\n");
 
        try (Connection con =
                 DriverManager.getConnection("jdbc:h2:mem:school")) {
            try (Statement st = con.createStatement()) {
                st.execute("CREATE TABLE document (id INT PRIMARY KEY, "
                    + "student VARCHAR(50), photo BLOB, essay CLOB)");
            }
 
            // Store: stream the files straight into the database.
            String insert = "INSERT INTO document VALUES (?, ?, ?, ?)";
            try (PreparedStatement ps = con.prepareStatement(insert);
                 InputStream in = Files.newInputStream(photo);
                 Reader reader = Files.newBufferedReader(essay)) {
                ps.setInt(1, 1);
                ps.setString(2, "Navika");
                ps.setBinaryStream(3, in, Files.size(photo));
                ps.setCharacterStream(4, reader);
                System.out.println(ps.executeUpdate() + " row stored");
            }
 
            // Retrieve: stream them back out to new files.
            String query = "SELECT photo, essay FROM document WHERE id = ?";
            try (PreparedStatement ps = con.prepareStatement(query)) {
                ps.setInt(1, 1);
                try (ResultSet rs = ps.executeQuery()) {
                    rs.next();
                    Path photoCopy = Path.of("photo-copy.png");
                    try (InputStream in = rs.getBinaryStream("photo")) {
                        Files.copy(in, photoCopy);
                    }
                    String text = rs.getString("essay");
                    System.out.println("Photo bytes back: "
                        + Files.size(photoCopy));
                    System.out.println("Identical to the original? "
                        + Arrays.equals(photoBytes,
                            Files.readAllBytes(photoCopy)));
                    System.out.println("Essay characters: " + text.length());
                    System.out.println("First line: "
                        + text.lines().findFirst().orElse(""));
                    Files.delete(photoCopy);
                }
            }
        } finally {
            Files.delete(photo);
            Files.delete(essay);
        }
    }
}
Output
1 row stored
Photo bytes back: 2048
Identical to the original? true
Essay characters: 58
First line: My favorite subject is Java.

Explanation:

  1. The table has a BLOB column for the photo and a CLOB column for the essay.
  2. Storing: setBinaryStream(3, in, size) hands the driver an InputStream on the photo file; setCharacterStream(4, reader) hands it a Reader on the text file. The driver reads from the streams while executing, so the file's contents flow to the database without being held in a Java array.
  3. Retrieving: getBinaryStream("photo") gives a stream of the stored bytes, and Files.copy writes it to a new file. The essay is small, so getString is the easy way to read it.
  4. The check compares the retrieved file with the original, byte for byte: identical.
  5. The streams are opened in try-with-resources, so the files are closed even if the insert fails.

Should Files Go in the Database?

Store in the database whenStore on disk or in object storage when
Files are small (thumbnails, signatures, short documents)Files are large (video, big images, backups)
The file must be saved or rolled back together with its rowFiles are served directly to browsers or a CDN
You want one backup to contain everythingYou want the database to stay small and fast

The common design for larger files is to keep the file in a file system or an object store such as Amazon S3, and store only its path or key, size and type in the database.

Common Mistakes

Loading a huge file with getBytes. The whole file must fit in memory. Use streams for anything large.

Using a CLOB for binary data, or a BLOB for text. Character conversion corrupts binary data; text in a BLOB loses its character set.

Closing the source stream before executing the statement. The driver reads it during executeUpdate.

SELECT * on a table with large columns. Every query drags the big data along. Select those columns only when needed.

Practice

  1. Easy: Store a real image from your computer and read it back into a second file. Open both to compare.
  2. Harder: Add file_name, content_type and size_bytes columns, fill them when storing, and write a method that lists the stored documents without reading the BLOB.

Quick check: BLOB and CLOB

4 questions
1Which column type stores an image?
2Which method sends a file to a BLOB column as a stream?
3Why prefer streams to getBytes for large files?
4What is the usual design for large files?

Key Takeaways

Was this tutorial helpful?

Lesson 496 of 578