How to display byte array from a model in Thymeleaf
I want to display all my products info, but I have problem with showing a product image. I get my products from DB and then I add them to Model
, but don't know why only image don't display. In HTML it looks like this:
<div class="row">
<div class="col-sm-3 my-2 d-flex align-content-stretch flex-wrap" th:each="product : ${products}">
<div class="card">
<img class="card-img-top" th:src="${product.image}">
<div class="card-body">
<h5 class="card-title" th:text="${product.name}">Product name</h5>
<p class="card-text" th:text="${product.description}">Product summary</p>
<p class="card-text" th:text="${product.cost}">Product summary</p>
</div>
</div>
</div>
</div>
In controller I add all the products like this:
@GetMapping("/")
public String getHomePage(Model model) {
model.addAttribute("products", productRepository.findAll());
return "home";
}
And the product model is as shown:
@Entity
@Getter
public class Product extends BaseEntity {
private String name;
private String description;
@OneToOne
private Category category;
private double cost;
@Lob
private byte[] image;
public Product() {
super();
}
public Product(String name, String description, Category category, double cost, byte[] image) {
this();
this.name = name;
this.description = description;
this.category = category;
this.cost = cost;
this.image = image;
}
}
My problem is that I want to display multiple images at one time.
BTW, I know that the findAll
method is not a good choose, but It is only for testing proposes. Later I want to implement pagination, but first how to display, a byte array image?
I'm answering this old question, in the hope of helping someone with the same need.
In order to show image using bytes, you have to create a controller action with the role of showing the image:
@GetMapping("/product/image/{id}")
public void showProductImage(@PathVariable String id
HttpServletResponse response) throws IOException {
response.setContentType("image/jpeg"); // Or whatever format you wanna use
Product product = productRepository.findById(id);
InputStream is = new ByteArrayInputStream(product.getImage());
IOUtils.copy(is, response.getOutputStream());
}
Thus, you can simply show your image with:
<div class="row">
<div class="col-sm-3 my-2 d-flex align-content-stretch flex-wrap" th:each="product : ${products}">
<div class="card">
<img class="card-img-top" th:src="@{'product/image/' + @{product.image}}">
<div class="card-body">
<h5 class="card-title" th:text="${product.name}">Product name</h5>
<p class="card-text" th:text="${product.description}">Product summary</p>
<p class="card-text" th:text="${product.cost}">Product summary</p>
</div>
</div>
</div>
PS: you better use the wrapper Byte in spite of byte for your image attribute. This will let you manage cases of no image (null)
Edit: The last line of showProductImage method is copying InputStream to OutputStream (check IOUtils doc for more details)
Using Spring Boot and having the same issue, I was not convinced by using HttpServletResponse to get the image in a separated request since we have already retrieved the image byte array included in model attributes, I was wondering like: How can we load the image from the byte array itself ?
Found the solution by converting the image byte array to base64 string and pass it to JavaScript to fill the image preview element:
In Java
Add an extra String attribute representing the base64 encoding value of the image:
private String imageBase64;
public String getImageBase64() {
return imageBase64;
}
public void setImageBase64(String imageBase64) {
this.imageBase64 = imageBase64;
}
After getting object from database, add an extra step of base64 conversion from image byte array as follows:
import java.util.Base64;
import org.apache.commons.lang3.ArrayUtils;
if(ArrayUtils.isNotEmpty(product.getImage())) {
String imageBase64 = Base64.getEncoder().encodeToString(product.getImage());
productDTO.setImageBase64(imageBase64);
}
In JSP/HTML
Add an extra hidden field containing the base64 string of the image
<input type="hidden" id="imageBase64" value="${product.imageBase64}" readonly></input>
<img id="imagePreview" src="#" alt="Background image" />
In JavaScript
Set the image content by the base64 value from the hidden field
var imageBase64 = $('#imageBase64').val();
var imagePreview= document.getElementById('imagePreview');
if(imagePreview) {
imagePreview.src = "data:image/png;base64," + imageBase64;
}
Besides, can also store the image as Base64 string and get rid of the custom conversion phase...