14

I have PostgreSQL database and there is column 'image' with datatype 'bytea'. I cannot modify columns or database configurations. JPA annotated POJO contains followign mapping

@Column(name="image")
private byte[] image;

The returned data is in following format (this is just a sample)

WF5ClN6RlpLZ0hJTUdNQ1FJWmkwcFVGSUdNQ0lDWUE5TUEvanRFeElwK2x0M2tBQUFBQVNVVk9SSzVDWUlJPQo=

When I write this data to file (.jpeg) photo viewer says "this is corrupted file". I also understand that actual image byte data looks different from above sample. I read some blogs which mentioned that PostgreSQL applies hexadecimal conversion to bytea data. How to restore it to original data with or without JPA ?

Database - PostgresSQL Version 9.5.1

Driver

<dependency>
    <groupId>org.postgresql</groupId>
    <artifactId>postgresql</artifactId>
    <version>9.4-1205-jdbc41</version>
</dependency>
S-Man
  • 22,521
  • 7
  • 40
  • 63
Bhushan
  • 567
  • 2
  • 5
  • 14
  • Your example should work (if you don't use the `@Lob` annotation; see [this related question](http://stackoverflow.com/questions/3677380/proper-hibernate-annotation-for-byte)). How did you get that base64 representation exactly? Are you sure that's not something, that's outputted (and possibly transformed) by a view-related service? (Please try to debug the `@Entity` to see, what's exactly get back to the `image` property & what do you set to that property before saving). – pozs Feb 19 '16 at 13:32
  • Are you sure that this is an image? How do you store it? Also check [this](http://stackoverflow.com/q/17667480/1700321). – Aleksandr M Feb 19 '16 at 13:34
  • @pozs decoded with apache codec Base64 class and javax.xml.bind.DatatypeConverter parseBase64Binary() as well. – Bhushan Feb 19 '16 at 13:39

5 Answers5

12

Try to annotate you entity with @Lob

@Lob
@Column(name="image")
private byte[] image;

If you are using hibernate implementation you can add @Type(type="org.hibernate.type.BinaryType") in column too.

@Lob
@Column(name="image")
@Type(type="org.hibernate.type.BinaryType")
private byte[] image;
josivan
  • 1,963
  • 1
  • 15
  • 26
  • I would say if you are using hibernate you **have to** add the `@Type` otherwise you get `found [bytea (Types#BINARY)], but expecting [oid (Types#BLOB)` error on startup – Klesun Apr 07 '22 at 18:07
7

ImageEntity

package com.example;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Entity
public class ImageEntity {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column(name="image")
    private byte[] image;

    public Long getId() {
        return id;
    }

    public void setId(Long id) {
        this.id = id;
    }

    public byte[] getImage() {
        return image;
    }

    public void setImage(byte[] image) {
        this.image = image;
    }
}

ImageRepository

package com.example;

import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;

@Repository
public interface ImageRepository extends JpaRepository<ImageEntity, Long> {
}

Test

package com.example;

import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;

import javax.annotation.Resource;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

import junit.framework.TestCase;

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = TestApplication.class)
public class ImageDaoTest {

    @Resource
    private ImageRepository imageRepository;

    @Test
    public void testImage() throws IOException {

        // Read an image from disk. Assume test.png exists
        ByteArrayOutputStream out = new ByteArrayOutputStream();

        try (InputStream in = getClass().getResourceAsStream("test.png")) {
            int length;
            byte[] buffer = new byte[1024];
            while ((length = in.read(buffer)) != -1) out.write(buffer, 0, length);
        }

        byte[] image = out.toByteArray();

        // Store image to DB
        ImageEntity imageEntiry = new ImageEntity();
        imageEntiry.setImage(image);
        long imageEntiryId = imageRepository.save(imageEntiry).getId();

        // Retrieve image from DB
        ImageEntity resultImageEntiry = imageRepository.findOne(imageEntiryId);
        byte[] resultImage = resultImageEntiry.getImage();

        // Compare retrieved image with source image by byte to byte comparison
        for (int i = 0; i < resultImage.length; i++) {
            TestCase.assertEquals(image[i], resultImage[i]);
        }

    }

}

It works against Postgres 9.5.0-1 with 9.4.1207.jre7 jdbc driver.

Mike Shauneu
  • 3,201
  • 19
  • 21
5

The returned Data looks as if it is base64 encoded. You have to decode it back to binary data before writing to file.

For further information to decode look here

Community
  • 1
  • 1
Neothorn
  • 329
  • 1
  • 7
  • Yes you are right its 64Base ecoded. Thanx. Added complete code below, may be useful for others. – Bhushan Feb 22 '16 at 08:52
2

I am adding complete code which may be useful for others (skipping try/catch),

String base64EncryptedImage = new String(image);
decoded = org.apache.commons.codec.binary.Base64.decodeBase64(base64EncryptedImage);
ImageOutputStream out = new FileImageOutputStream(new File("D://abc.png"));
out.write(decoded);
out.close();
Bhushan
  • 567
  • 2
  • 5
  • 14
0

insert an image, you would use:

  //Get the Large Object Manager to perform operations with
    LargeObjectManager lobj = ((org.postgresql.PGConnection)conn).getLargeObjectAPI();

    // Create a new large object
    int oid = lobj.create(LargeObjectManager.READ | LargeObjectManager.WRITE);

    // Open the large object for writing
    LargeObject obj = lobj.open(oid, LargeObjectManager.WRITE);

    // Now open the file
    File file = new File("myimage.gif");
    FileInputStream fis = new FileInputStream(file);

    // Copy the data from the file to the large object
    byte buf[] = new byte[2048];
    int s, tl = 0;
    while ((s = fis.read(buf, 0, 2048)) > 0) {
        obj.write(buf, 0, s);
        tl += s;
    }

    // Close the large object
    obj.close();

// Now insert the row into imageslo
PreparedStatement ps = conn.prepareStatement("INSERT INTO imageslo VALUES (?, ?)");
ps.setString(1, file.getName());
ps.setInt(2, oid);
ps.executeUpdate();
ps.close();
fis.close();

// Finally, commit the transaction.
conn.commit();

Retrieving the image from the Large Object:

// All LargeObject API calls must be within a transaction block
conn.setAutoCommit(false);

// Get the Large Object Manager to perform operations with
LargeObjectManager lobj = ((org.postgresql.PGConnection)conn).getLargeObjectAPI();

PreparedStatement ps = conn.prepareStatement("SELECT imgoid FROM imageslo WHERE imgname = ?");
ps.setString(1, "myimage.gif");
ResultSet rs = ps.executeQuery();
while (rs.next()) {
    // Open the large object for reading
    int oid = rs.getInt(1);
    LargeObject obj = lobj.open(oid, LargeObjectManager.READ);

    // Read the data
    byte buf[] = new byte[obj.size()];
    obj.read(buf, 0, obj.size());
    // Do something with the data read here

    // Close the object
    obj.close();
}
rs.close();
ps.close();

// Finally, commit the transaction.
conn.commit();
P S M
  • 1,121
  • 12
  • 29
  • 1
    I am using JPA, I cannot do all this stuff like going back to JDBC. I think this solution still will not work with hex encoded data. – Bhushan Feb 19 '16 at 12:54
  • The `bytea` type and large objects are completely separated in PostgreSQL. – pozs Feb 19 '16 at 13:27