Ler campo blob no atributo src da tag img com Java EE

Tenho um campo longblob salvo num banco de dados mysql, que se assemelha a algo como “com.mysql.cj.jdbc.Blob@1b7ec72f”. Quero trazer esse campo e representá-lo como imagem dentro de uma tag img, na aplicação java EE, com servlets, JSP e JDBC que estou construindo. Pesquisei bastante mas ainda não entendi como posso fazer tal coisa. Se alguém tiver um exemplo que possa me ajudar, agradeço.

Deixo alguns trechos do código que estou usando:

Método da classe DAO, que pega o valor blob da coluna “image”:

public ArrayList<Destiny> getAllDestinys() {
		String sql = "SELECT * FROM destiny";
		ArrayList<Destiny> arrDestinys = new ArrayList<Destiny>();
		Blob blob = null;
		try {
			PreparedStatement pst = ConnectionBd.getConnection().prepareStatement(sql);
			ResultSet rs = pst.executeQuery();
			
			while (rs.next()) {
				Destiny destiny = new Destiny();
				blob = rs.getBlob("image");
				
				destiny.setIdDestiny(rs.getInt("idDestiny"));
				destiny.setName(rs.getString("nameDestiny"));
				destiny.setImage(blob.getBytes(1, (int) blob.length()));
				destiny.setCity(rs.getString("city"));
				arrDestinys.add(destiny);
			}
			pst.close();
			rs.close();
		
		} catch (SQLException e) {
			System.out.println("Erro: "+e);
		}
		
		return arrDestinys;
	}

Classe ReadDestinyController, que pega o resultado do método anterior e manda para a ver.jsp:

package controllers;

import java.io.IOException;
import javax.servlet.RequestDispatcher;
import javax.servlet.ServletException;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import agency.vacation.good.daos.DestinyDao;


@WebServlet("/ReadDestinyController")
public class ReadDestinyController extends HttpServlet {
	private static final long serialVersionUID = 1L;
	private DestinyDao destinyDao = new DestinyDao();
	
	protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {	
		request.setAttribute("arrDestinys", this.destinyDao.getAllDestinys());
		
		RequestDispatcher rd = request.getRequestDispatcher("ver.jsp");
		rd.forward(request, response);		
	}
}

A ver.jsp com uma tabela simples apenas para visualizar o resultado:

<%@ page language="java" contentType="text/html; charset=UTF-8"
    pageEncoding="UTF-8"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
    
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Insert title here</title>
</head>
<body>
		<table class="table table-bordered">
			<thead>
				<tr>
					<th>ID</th>
					<th>Nome</th>
					<th>Login</th>
					<th>Senha</th>
				</tr>
			</thead>
			<tbody>
				<c:forEach items="${arrDestinys}" var="arrDestiny">
					<tr>
						<td>${arrDestiny.idDestiny}</td>
						<td>${arrDestiny.name}</td>
						<td>${arrDestiny.city}</td>
						<td>${arrDestiny.image}</td>
						<td><img src="${arrDestiny.image}" width="400px"/></td>
					</tr>
				</c:forEach>
			</tbody>
		</table>
</body>
</html>

Reparem que eu até arrisquei colocar a saída do blob na tag img, mas provavelmente da maneira errada. Os outros dados que são números ou textos aparecem normalmente.

Isso é a penas a representação retornada pelo toString() da classe Blob.

Acredito que o mais fácil é você criar um método getImageBase64 para fazer o encoding da sua imagem em uma String em Base 64.

Você criaria esse método na classe Destiny mais ou menos assim:

private String imageBase64;

public String getImageBase64() {
    if (imageBase64 == null) {
        byte[] imagem = getImage();
        imageBase64 = Base64.getEncoder().encodeToString(imagem);
    }
    return imageBase64;
}

Depois no seu ver.jsp, obteria a imagem assim:

<img src="${arrDestiny.imageBase64}" width="400px"/>

Estou testando aqui sr. @staroski . Da forma que você me indicou, a representação String do atributo imageBase64 é uma lista grande de caracteres. Ao colocar isso no src, a imagem aparece quebrada e o console me retorna java.lang.IllegalArgumentException: Request header is too large

Estou com dúvidas na forma que estou cadastrando a imagem longblob no banco, talvez também esteja errada. Deixo a classe CreateDestinyController aqui e o método DAO de cadastro, para teste:

package controllers;

import java.io.IOException;
import javax.servlet.ServletException;
import javax.servlet.annotation.MultipartConfig;
import javax.servlet.annotation.WebServlet;
import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import agency.vacation.good.daos.DestinyDao;
import agency.vacation.good.models.Destiny;
import javax.servlet.http.Part;

@WebServlet("/CreateDestinyController")
@MultipartConfig(maxFileSize = 16177216)
public class CreateDestinyController extends HttpServlet {
	private static final long serialVersionUID = 1L;
	private DestinyDao destinyDao = new DestinyDao();

	protected void doPost(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException {
		Destiny destiny = new Destiny();
		Part part = request.getPart("image");

		destiny.setName(request.getParameter("nameDestiny"));
		destiny.setCity(request.getParameter("city"));
			
		if (part != null) {
			this.destinyDao.createDestiny(destiny, part);
			response.sendRedirect("ReadDestinyController");			
		}else {
			System.out.println("Está nulo!");
		}
	}
}

Método createDestiny, da classe DestinyDao:

	public boolean createDestiny(Destiny destiny, Part part) {
		String sql = "INSERT INTO destiny (`nameDestiny`, image, city) VALUES (?, ?, ?)";
		
		try {
			PreparedStatement pst = ConnectionBd.getConnection().prepareStatement(sql);
			 InputStream is = part.getInputStream();
			
			pst.setString(1, destiny.getName());
			pst.setBlob(2, is);
			pst.setString(3, destiny.getCity());
			pst.execute();
			pst.close();		
		} catch (SQLException e) {
			return false;
		} catch (IOException e) {
			return false;
		}
		return true;
	}

Bem pessoal, pesquisei um pouco mais e consegui resolver meu problema adaptando o método do @staroski. Deixo aqui resumidamente como ficou, caso alguém precise:

  1. Na classe servlet, salvo a requisição da imagem na varável part do tipo “Part” e envio para a classe DAO destinyDao que irá realizar o cadastro no banco de dados:
Part part = request.getPart("image");
this.destinyDao.createDestiny(destiny, part);
  1. No método createDestiny instancio a variável “is” do tipo InputStream com a part passada como parâmetro, para preencher o objeto do statement:
PreparedStatement pst = ConnectionBd.getConnection().prepareStatement(sql);
InputStream is = part.getInputStream();

pst.setBlob(1, is);
  1. Pego a saída blob do banco de dados como o ResultSetrs” e passo seu método getBytes() como parâmetro do método setImageBase64(), mais ou menos assim:
Destiny outputDestiny = new Destiny();
blob = rs.getBlob("image");

outputDestiny.setImage(blob.getBytes(1, (int) blob.length()));
outputDestiny.setImageBase64(outputDestiny.getImage());
  1. O método setImageBase64() ficou assim:
	public void setImageBase64(byte[] imagemByte) {
	    if (imageBase64 == null) {
	        this.imageBase64 = Base64.getEncoder().encodeToString(imagemByte);
	    }
	}
  1. Por fim, para exibir no atributo src da tag , temos:
<img src="data:image/jpeg;base64,${selectedDestiny.imageBase64}" alt="Imagem decodificada">