0

So I'm trying to retrieve text and a blob(image) from a database inside a jtable in a nice way. For some reason, whenever I try to retrieve the blob I get this:

java.io.ByteArrayInputStream@1cb63183

in every other column, although I've specified that the blob is on the 8th !

here is a screenshot:

http://s24.postimg.org/t71o3izlh/Screen_Shot_2016_01_28_at_1_26_55_PM.png

enter image description here

Here is my code

sql = "SELECT * FROM Products";  

ResultSet rs = stmt.executeQuery(sql);

  ResultSetMetaData rsmd = rs.getMetaData();
 // getting the columns number
  int columnsNumber = rsmd.getColumnCount();

// setting a vector with columns number
Vector columns = new Vector(columnsNumber);


 // adding column names
for(int i=1; i<=columnsNumber; i++)
columns.add(rsmd.getColumnName(i));

// setting a vector with row data
Vector rowdata = new Vector();
Vector row;
JTable table = new JTable(rowdata, columns);

while(rs.next())
{

 row = new Vector(columnsNumber);

    for(int i=1; i<=columnsNumber; i++)
   {
    // adding the rows in "row vector"
    InputStream binaryStream = rs.getBinaryStream(i);
    row.add(rs.getString(i));
    row.add(rs.getBinaryStream(8));

}
// adding the rows in the database
rowdata.add(row);
}

Could someone explain to me why is this approach not doing the job?

by the way, if I removed this line:

 row.add(rs.getBinaryStream(8));

The problem of getting the java.io.ByteArrayInputSteam will disappear, however, I will only be getting the text representation of the image.

it's actually not a duplicate because I'm trying to add it in a specific column depending on how many rows I have, which are BOTH text and Image. So it's dynamically done and the sql has both the image and the text at the same time unlike the "possible duplicate"

Thanks in advance!

mKorbel
  • 109,525
  • 20
  • 134
  • 319
rullzing
  • 622
  • 2
  • 10
  • 22
  • The byte array is a binary representation of the image, you then need to convert this back to a "image" of some kind – MadProgrammer Jan 28 '16 at 20:14
  • @MadProgrammer it's actually not a duplicate because I'm trying to add it in a specific column depending on how many rows I have. So it's dynamically done and the sql has both the image and the text at the same time unlike the "possible duplicate" – rullzing Jan 28 '16 at 20:41
  • So you have no clue what data is actually in the column and you don't know how to decode it? As a general comment to your requirements, that just sounds crazy. You either need two columns, one for the text and one for image or you need another column to specify the type of data. As a general concept, the column should be representative of the type of data it's holding, for example `image` and `text`, not `data`, which precludes the possibility to ever know what type of data it actually is – MadProgrammer Jan 28 '16 at 20:46
  • @MadProgrammer well, only the 8th column should have image. The rest will have text. However, the SQL query is only the one I have up there. For some reason although I've specified the 8th column for the image, I still can't get it to do so. I even tried to add another "data" so that one will be for image and the other for text, and that didn't work either. – rullzing Jan 28 '16 at 20:57
  • Okay, so, it is a duplicate! You have a `ByteArrayInputStream` which is a binary representation of the blob (or image) from the database, you need to use `ImageIO` to read the `ByteArrayInputStream` and reconstitute it back to a `BufferedImage` which can then be displayed in your `JTable`!!! – MadProgrammer Jan 28 '16 at 21:00
  • Possible duplicate of [Convert BufferedInputStream into image](http://stackoverflow.com/questions/20752432/convert-bufferedinputstream-into-image). This `Blob blob = rs.getBlob(8);` and `BufferedImage img = ImageIO.read(blob.getBinaryStream());` are what you need to read the last column and convert into an image. But, given a `ByteArrayStream`, you can also use it, but it's slighlty more complicated – MadProgrammer Jan 28 '16 at 21:02
  • The problem then becomes how to display the image in the `JTable` which would become a duplicate of [Rendering BufferedImage in JTable cell](http://stackoverflow.com/questions/14793396/rendering-bufferedimage-in-jtable-cell) – MadProgrammer Jan 28 '16 at 21:06
  • @MadProgrammer I tried using what you suggested and now I'm getting another error in every other column. This time it is "`ava.io.ByteArrayInputStream@1034d0b4` and when I changed the row.add to the BufferedImage I got this: `BufferedImage@3b7f825e: type = 5 ColorModel: #pixelBits = 24 numComponents = 3 color space = java.awt.color.ICC_ColorSpace@232b985c transparency = 1 has alpha = false isAlphaPre = false ByteInterleavedRaster: width = 1000 height = 750 #numDataElements 3 dataOff[0] = 2"` – rullzing Jan 28 '16 at 21:09
  • It's not an error. The `TableCellRenderer` is using the objects `toString` method to display it. Did you use `ImageIO.read` to read the binary stream from the database? Did you add the resulting image to the `row` `Vector`? Did you use a custom `TableCellRenderer` to display the `BufferedImage`? – MadProgrammer Jan 28 '16 at 21:13
  • @MadProgrammer how is it not an error if it's adding it to every other column rather than adding it to just 1? I actually did add the resulting image to the row vector, this is how it missed up all the text in it. – rullzing Jan 28 '16 at 21:18
  • 1
    To my mind, an error would have generated an `Exception` of some kind, what you have is a bug – MadProgrammer Jan 28 '16 at 21:48

1 Answers1

1

So, you seem to have a series of compounding problems. Let's start here, where you load the data from the database...

while(rs.next())
    {
        row = new Vector(columnsNumber);

        for(int i=1; i<=columnsNumber; i++)
        {
            // adding the rows in "row vector"
            InputStream binaryStream = rs.getBinaryStream(i);
            row.add(rs.getString(i));
            row.add(rs.getBinaryStream(8));
        }
        // adding the rows in the database
        rowdata.add(row);
    }

So, for each row in the ResultSet you look through the columns, BUT, for each column, you add the String value AND the blob from the last column, so the blob will be added 6 times (based on your screen shot). This is, obviously, not what you want and also the reason why you're getting java.io.ByteArrayInputStream@1cb63183 in every other column.

Instead, you want to loop through columns 1-columnsNumbers - 1, because we don't want the last column, and add the image to the last column, maybe something like...

while(rs.next())
{
    row = new Vector(columnsNumber);

    for(int i=1; i < columnsNumber; i++)
    {
        // adding the rows in "row vector"
        InputStream binaryStream = rs.getBinaryStream(i);
        row.add(rs.getString(i));
    }
    row.add(rs.getBinaryStream(8));
    // adding the rows in the database
    rowdata.add(row);
}

Next problem...

It still prints java.io.ByteArrayInputStream@1cb63183 in the last column!?

This is simply because all you added to the row Vector was the binary stream representing the binary data in the database, JTable has no means by which to render this. You should start by having a look at Concepts: Editors and Renderers and Using Custom Renderers for more details about how you can customise the rendering of these components

First, we need to convert the binary data to an image format which we can use

row.add(ImageIO.read(rs.getBinaryStream(8)));

And use something similar to what is outlined in Rendering BufferedImage in JTable cell

or

row.add(new ImageIcon(ImageIO.read(rs.getBinaryStream(8))));

which should allow the "default" TableCellRenderer to render it

Runnable Example...

Warning: This example is a little long, because I had to build the database and populate it. It uses a standalong H2 Database engine for simplicity, but should be translatable to most other database. The example also uses a column type of blob, this is deliberate, as it improves the performance of the database engine.

The problem with getting images to display in a JTable when using a DefaultTableModel is, DefaultTableModel returns Object.class from the TableModel#getColumnClass method

Even the DefaultTableModel documentation makes note of this...

Warning: DefaultTableModel returns a column class of Object. When DefaultTableModel is used with a TableRowSorter this will result in extensive use of toString, which for non-String data types is expensive. If you use DefaultTableModel with a TableRowSorter you are strongly encouraged to override getColumnClass to return the appropriate type.

I overcame this by customising the DefaultTableModel I returned from TestPane#makeTableModel...

DefaultTableModel model = new DefaultTableModel(new String[]{"Name", "Image"}, 0) {
    @Override
    public Class<?> getColumnClass(int columnIndex) {
        return columnIndex == 1 ? Icon.class : super.getColumnClass(columnIndex);
    }
};

This allowed the JTable to use the correct TableCellRenderer

Fruits

import java.awt.BorderLayout;
import java.awt.EventQueue;
import java.awt.image.BufferedImage;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.sql.Blob;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import javax.imageio.ImageIO;
import javax.swing.Icon;
import javax.swing.ImageIcon;
import javax.swing.JFrame;
import javax.swing.JPanel;
import javax.swing.JScrollPane;
import javax.swing.JTable;
import javax.swing.UIManager;
import javax.swing.UnsupportedLookAndFeelException;
import javax.swing.table.DefaultTableModel;

public class Main {

    public static void main(String[] args) {
        try {
            Class.forName("org.h2.Driver");
            makeDatabase();
            populateDatabase();
            new Main();
        } catch (ClassNotFoundException | SQLException | IOException exp) {
            exp.printStackTrace();
        }
    }

    protected static Connection makeConnection() throws SQLException {
        String path = "jdbc:h2:./TestDatabase";
        return DriverManager.getConnection(path, "sa", "");
    }

    protected static void makeDatabase() throws SQLException {
        String cmd = "create table if not exists fruits ("
                + "key BIGINT IDENTITY, "
                + "name varchar(128), "
                + "image longblob)";
        try (Connection con = makeConnection()) {
            try (PreparedStatement stmt = con.prepareStatement(cmd)) {
                System.out.println("> Make fruits table");
                stmt.executeUpdate();
            }
        }
    }

    protected static void populateDatabase() throws SQLException, IOException {
        removeAlFruits();
        insert("Apple", ImageIO.read(new File("Apple.png")));
        insert("Banana", ImageIO.read(new File("Banana.png")));
        insert("Cherries", ImageIO.read(new File("Cherries.png")));
        insert("Grapes", ImageIO.read(new File("Grapes.png")));
        insert("Orange", ImageIO.read(new File("Orange.png")));
        insert("Pear", ImageIO.read(new File("Pear.png")));
        insert("Pine Apple", ImageIO.read(new File("PineApple.png")));
        insert("Strewberry", ImageIO.read(new File("Strewberry.png")));
        insert("Water Melon", ImageIO.read(new File("WaterMelon.png")));
    }

    protected static void insert(String name, BufferedImage image) throws SQLException, IOException {
        String cmd = "insert into fruits (name, image) values (?, ?)";
        try (Connection con = makeConnection()) {
            try (PreparedStatement stmt = con.prepareStatement(cmd)) {
                try (InputStream is = convertImageToInputStream(image)) {
                    System.out.println("> Insert " + name);
                    stmt.setString(1, name);
                    stmt.setBinaryStream(2, is);
                    int rows = stmt.executeUpdate();
                    System.out.println("> " + rows + " rows updated");
                }
            }
        }
    }

    protected static InputStream convertImageToInputStream(BufferedImage image) throws IOException {
        ByteArrayOutputStream baos = null;
        ByteArrayInputStream bais = null;
        try {
            baos = new ByteArrayOutputStream();
            ImageIO.write(image, "png", baos);
            baos.close();
            bais = new ByteArrayInputStream(baos.toByteArray());
        } finally {
            if (baos != null) {
                try {
                    baos.close();
                } catch (IOException ex) {
                }
            }
        }
        return bais;
    }

    protected static void removeAlFruits() throws SQLException {
        String cmd = "delete from fruits";
        try (Connection con = makeConnection()) {
            try (PreparedStatement stmt = con.prepareStatement(cmd)) {
                System.out.println("> Remove all fruits");
                int rows = stmt.executeUpdate();
                System.out.println("> " + rows + " rows updated");
            }
        }
    }

    public Main() {
        EventQueue.invokeLater(new Runnable() {
            @Override
            public void run() {
                try {
                    try {
                        UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());
                    } catch (ClassNotFoundException | InstantiationException | IllegalAccessException | UnsupportedLookAndFeelException ex) {
                        ex.printStackTrace();
                    }

                    JFrame frame = new JFrame("Testing");
                    frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                    frame.add(new TestPane());
                    frame.pack();
                    frame.setLocationRelativeTo(null);
                    frame.setVisible(true);
                } catch (SQLException | IOException ex) {
                    ex.printStackTrace();
                }
            }
        });
    }

    public class TestPane extends JPanel {

        public TestPane() throws SQLException, IOException {
            setLayout(new BorderLayout());
            DefaultTableModel model = makeTableModel();
            JTable table = new JTable(model);
            table.setRowHeight(100);
            add(new JScrollPane(table));
        }

        protected DefaultTableModel makeTableModel() throws SQLException, IOException {
            DefaultTableModel model = new DefaultTableModel(new String[]{"Name", "Image"}, 0) {
                @Override
                public Class<?> getColumnClass(int columnIndex) {
                    return columnIndex == 1 ? Icon.class : super.getColumnClass(columnIndex);
                }

            };
            String cmd = "select name, image from fruits";
            try (Connection con = makeConnection()) {
                try (PreparedStatement stmt = con.prepareStatement(cmd)) {
                    try (ResultSet rs = stmt.executeQuery()) {
                        while (rs.next()) {
                            String name = rs.getString(1);
                            Blob blob = rs.getBlob(2);
                            ImageIcon icon = null;
                            try (InputStream is = blob.getBinaryStream()) {
                                BufferedImage img = ImageIO.read(is);
                                icon = new ImageIcon(img);
                            }
                            model.addRow(new Object[]{name, icon});
                        }
                    }
                }
            }
            return model;
        }

    }
}

The alternative is to use a custom TableCellRender as demonstrated in Rendering BufferedImage in JTable cell

Community
  • 1
  • 1
MadProgrammer
  • 343,457
  • 22
  • 230
  • 366
  • It's actually not even printing `java.io.ByteArrayInputStream@1cb63183` in the last column. It's just like if reading the string of the image so just like if you open an image with a notepad – rullzing Jan 28 '16 at 22:23
  • What is it printing in the last column? How are the images inserted into the database? – MadProgrammer Jan 28 '16 at 22:25
  • it turns out that the ImageIcon method actually works and I was able to view the image by showMessageDialog. So I need to find a way to put it inside a Jtable. Here is a screenshot of what is it printing : http://s21.postimg.org/6ku22ajjr/Screen_Shot_2016_01_28_at_5_30_39_PM.png. The image is inserted using JFileChooser and FileInputStream – rullzing Jan 28 '16 at 22:30
  • [Rendering BufferedImage in JTable cell](http://stackoverflow.com/questions/14793396/rendering-bufferedimage-in-jtable-cell). In theory, `ImageIcon` should be rendered by the `JTable` out of the box if the `TableModel#getColumnClass` method returns `ImageIcon.class` or `Icon.class` – MadProgrammer Jan 28 '16 at 22:33
  • Isn't this case for displaying images only? how can I specify which one is which? Also, I'm not using DefaultTableModel I'm using vectors – rullzing Jan 28 '16 at 22:42
  • No, not really, you can customise each column as required – MadProgrammer Jan 28 '16 at 22:46
  • Okay, fully runnable example. I used a stand alone h2 database to generate the example (and some custom images), but the concepts should be translatable. – MadProgrammer Jan 28 '16 at 23:55
  • all I needed was the TestPane class. I had to disregard the vector approach because it was taking me no where. Thank you ! – rullzing Jan 29 '16 at 01:42