Skip to content

Commit

Permalink
Store images in a separate table and fetch them on demand
Browse files Browse the repository at this point in the history
This fixes vaadin/starters#85

It also makes image handling smarter as the images are not loaded as part of an entity, which you rarely want
  • Loading branch information
Artur- committed Oct 6, 2022
1 parent addef0c commit 5e448fd
Show file tree
Hide file tree
Showing 9 changed files with 142 additions and 151 deletions.
29 changes: 29 additions & 0 deletions src/main/java/my/app/data/entity/ImageData.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
package my.app.data.entity;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Lob;

@Entity
public class ImageData extends AbstractEntity {

@Lob
@Column(length = 1000000)
private byte[] data;

public ImageData() {
}

public ImageData(byte[] data) {
this.data = data;
}

public void setData(byte[] data) {
this.data = data;
}

public byte[] getData() {
return data;
}

}
18 changes: 8 additions & 10 deletions src/main/java/my/app/data/entity/SampleBook.java
Original file line number Diff line number Diff line change
@@ -1,18 +1,14 @@
package my.app.data.entity;

import java.time.LocalDate;
import java.util.UUID;

import javax.annotation.Nonnull;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Lob;

@Entity
public class SampleBook extends AbstractEntity {

@Nonnull
@Lob
@Column(length = 1000000)
private byte[] image;
@Nonnull
private String name;
@Nonnull
Expand All @@ -22,12 +18,14 @@ public class SampleBook extends AbstractEntity {
private Integer pages;
@Nonnull
private String isbn;
private UUID imageId;

public byte[] getImage() {
return image;
public UUID getImageId() {
return imageId;
}
public void setImage(byte[] image) {
this.image = image;

public void setImage(UUID imageId) {
this.imageId = imageId;
}
public String getName() {
return name;
Expand Down
23 changes: 12 additions & 11 deletions src/main/java/my/app/data/entity/User.java
Original file line number Diff line number Diff line change
@@ -1,16 +1,18 @@
package my.app.data.entity;

import com.fasterxml.jackson.annotation.JsonIgnore;
import java.util.Set;
import java.util.UUID;

import javax.annotation.Nonnull;
import javax.persistence.Column;
import javax.persistence.ElementCollection;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.FetchType;
import javax.persistence.Lob;
import javax.persistence.Table;

import com.fasterxml.jackson.annotation.JsonIgnore;

import my.app.data.Role;

@Entity
Expand All @@ -28,9 +30,7 @@ public class User extends AbstractEntity {
@Nonnull
private Set<Role> roles;
@Nonnull
@Lob
@Column(length = 1000000)
private byte[] profilePicture;
private UUID profilePictureId;

public String getUsername() {
return username;
Expand All @@ -56,11 +56,12 @@ public Set<Role> getRoles() {
public void setRoles(Set<Role> roles) {
this.roles = roles;
}
public byte[] getProfilePicture() {
return profilePicture;
}
public void setProfilePicture(byte[] profilePicture) {
this.profilePicture = profilePicture;

public UUID getProfilePictureId() {
return profilePictureId;
}

public void setProfilePictureId(UUID profilePictureId) {
this.profilePictureId = profilePictureId;
}
}
11 changes: 11 additions & 0 deletions src/main/java/my/app/data/service/ImageDataRepository.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package my.app.data.service;

import java.util.UUID;

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

import my.app.data.entity.ImageData;

public interface ImageDataRepository extends JpaRepository<ImageData, UUID> {

}
22 changes: 20 additions & 2 deletions src/main/java/my/app/data/service/SampleBookService.java
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
package my.app.data.service;

import java.util.Base64;
import java.util.Optional;
import java.util.UUID;

import javax.transaction.Transactional;

import my.app.data.entity.ImageData;
import my.app.data.entity.SampleBook;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
Expand All @@ -12,17 +17,24 @@
public class SampleBookService {

private final SampleBookRepository repository;
private final ImageDataRepository imageDataRepository;

@Autowired
public SampleBookService(SampleBookRepository repository) {
public SampleBookService(SampleBookRepository repository, ImageDataRepository imageDataRepository) {
this.repository = repository;
this.imageDataRepository = imageDataRepository;
}

public Optional<SampleBook> get(UUID id) {
return repository.findById(id);
}

public SampleBook update(SampleBook entity) {
@Transactional
public SampleBook update(SampleBook entity, ImageData sampleBookImage) {
if (sampleBookImage != null) {
sampleBookImage = imageDataRepository.save(sampleBookImage);
entity.setImage(sampleBookImage.getId());
}
return repository.save(entity);
}

Expand All @@ -38,4 +50,10 @@ public int count() {
return (int) repository.count();
}

@Transactional
public String getImageUrl(SampleBook entity) {
ImageData image = imageDataRepository.getReferenceById(entity.getImageId());
return "data:image;base64," + Base64.getEncoder().encodeToString(image.getData());
}

}
27 changes: 22 additions & 5 deletions src/main/java/my/app/data/service/UserService.java
Original file line number Diff line number Diff line change
@@ -1,28 +1,39 @@
package my.app.data.service;

import java.util.Base64;
import java.util.Optional;
import java.util.UUID;
import my.app.data.entity.User;
import org.springframework.beans.factory.annotation.Autowired;

import javax.transaction.Transactional;

import org.springframework.data.domain.Page;
import org.springframework.data.domain.Pageable;
import org.springframework.stereotype.Service;

import my.app.data.entity.ImageData;
import my.app.data.entity.User;

@Service
public class UserService {

private final UserRepository repository;
private final ImageDataRepository imageDataRepository;

@Autowired
public UserService(UserRepository repository) {
public UserService(UserRepository repository, ImageDataRepository imageDataRepository) {
this.repository = repository;
this.imageDataRepository = imageDataRepository;
}

public Optional<User> get(UUID id) {
return repository.findById(id);
}

public User update(User entity) {
@Transactional
public User update(User entity, ImageData profilePicture) {
if (profilePicture != null) {
profilePicture = imageDataRepository.save(profilePicture);
entity.setProfilePictureId(profilePicture.getId());
}
return repository.save(entity);
}

Expand All @@ -38,4 +49,10 @@ public int count() {
return (int) repository.count();
}

@Transactional
public String getProfilePictureUrl(User user) {
ImageData image = imageDataRepository.getReferenceById(user.getProfilePictureId());
return "data:image;base64," + Base64.getEncoder().encodeToString(image.getData());
}

}
19 changes: 11 additions & 8 deletions src/main/java/my/app/views/MainLayout.java
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package my.app.views;

import java.io.ByteArrayInputStream;
import java.util.Optional;

import com.vaadin.flow.component.applayout.AppLayout;
import com.vaadin.flow.component.applayout.DrawerToggle;
import com.vaadin.flow.component.avatar.Avatar;
Expand All @@ -17,11 +20,11 @@
import com.vaadin.flow.server.StreamResource;
import com.vaadin.flow.server.auth.AccessAnnotationChecker;
import com.vaadin.flow.theme.lumo.LumoUtility;
import java.io.ByteArrayInputStream;
import java.util.Optional;

import my.app.components.appnav.AppNav;
import my.app.components.appnav.AppNavItem;
import my.app.data.entity.User;
import my.app.data.service.UserService;
import my.app.security.AuthenticatedUser;
import my.app.views.adminroleonly.AdminroleonlyView;
import my.app.views.loggedin.LoggedInView;
Expand All @@ -37,12 +40,14 @@ public class MainLayout extends AppLayout {

private H2 viewTitle;

private AuthenticatedUser authenticatedUser;
private AccessAnnotationChecker accessChecker;
private final AuthenticatedUser authenticatedUser;
private final AccessAnnotationChecker accessChecker;
private final UserService userService;

public MainLayout(AuthenticatedUser authenticatedUser, AccessAnnotationChecker accessChecker) {
public MainLayout(AuthenticatedUser authenticatedUser, AccessAnnotationChecker accessChecker, UserService userService) {
this.authenticatedUser = authenticatedUser;
this.accessChecker = accessChecker;
this.userService = userService;

setPrimarySection(Section.DRAWER);
addDrawerContent();
Expand Down Expand Up @@ -110,9 +115,7 @@ private Footer createFooter() {
User user = maybeUser.get();

Avatar avatar = new Avatar(user.getName());
StreamResource resource = new StreamResource("profile-pic",
() -> new ByteArrayInputStream(user.getProfilePicture()));
avatar.setImageResource(resource);
avatar.setImage(userService.getProfilePictureUrl(user));
avatar.setThemeName("xsmall");
avatar.getElement().setAttribute("tabindex", "-1");

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,14 @@
package my.app.views.masterdetailbook;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.util.Base64;
import java.util.Optional;
import java.util.UUID;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;

import com.vaadin.flow.component.UI;
import com.vaadin.flow.component.button.Button;
import com.vaadin.flow.component.button.ButtonVariant;
Expand All @@ -26,16 +35,11 @@
import com.vaadin.flow.server.StreamResource;
import com.vaadin.flow.server.auth.AnonymousAllowed;
import com.vaadin.flow.spring.data.VaadinSpringDataHelpers;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.util.Base64;
import java.util.Optional;
import java.util.UUID;

import my.app.data.entity.ImageData;
import my.app.data.entity.SampleBook;
import my.app.data.service.SampleBookService;
import my.app.views.MainLayout;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.PageRequest;

@PageTitle("Master Detail Book")
@Route(value = "master-detail-book/:sampleBookID?/:action?(edit)", layout = MainLayout.class)
Expand Down Expand Up @@ -63,6 +67,7 @@ public class MasterDetailBookView extends Div implements BeforeEnterObserver {
private SampleBook sampleBook;

private final SampleBookService sampleBookService;
private ImageData sampleBookImage;

@Autowired
public MasterDetailBookView(SampleBookService sampleBookService) {
Expand All @@ -80,8 +85,8 @@ public MasterDetailBookView(SampleBookService sampleBookService) {
// Configure Grid
LitRenderer<SampleBook> imageRenderer = LitRenderer
.<SampleBook>of("<img style='height: 64px' src=${item.image} />").withProperty("image", item -> {
if (item != null && item.getImage() != null) {
return "data:image;base64," + Base64.getEncoder().encodeToString(item.getImage());
if (item != null && item.getImageId() != null) {
return sampleBookService.getImageUrl(item);
} else {
return "";
}
Expand Down Expand Up @@ -129,7 +134,7 @@ public MasterDetailBookView(SampleBookService sampleBookService) {
this.sampleBook = new SampleBook();
}
binder.writeBean(this.sampleBook);
sampleBookService.update(this.sampleBook);
sampleBookService.update(this.sampleBook, this.sampleBookImage);
clearForm();
refreshGrid();
Notification.show("SampleBook details stored.");
Expand Down Expand Up @@ -218,7 +223,7 @@ private void attachImageUpload(Upload upload, Image preview) {
if (this.sampleBook == null) {
this.sampleBook = new SampleBook();
}
this.sampleBook.setImage(uploadBuffer.toByteArray());
this.sampleBookImage = new ImageData(uploadBuffer.toByteArray());
});
preview.setVisible(false);
}
Expand All @@ -236,11 +241,12 @@ private void populateForm(SampleBook value) {
this.sampleBook = value;
binder.readBean(this.sampleBook);
this.imagePreview.setVisible(value != null);
if (value == null || value.getImage() == null) {
sampleBookImage = null;
if (value == null || value.getImageId() == null) {
this.image.clearFileList();
this.imagePreview.setSrc("");
} else {
this.imagePreview.setSrc("data:image;base64," + Base64.getEncoder().encodeToString(value.getImage()));
this.imagePreview.setSrc(sampleBookService.getImageUrl(sampleBook));
}

}
Expand Down
112 changes: 10 additions & 102 deletions src/main/resources/data.sql

Large diffs are not rendered by default.

0 comments on commit 5e448fd

Please sign in to comment.