Enterprise Java

Image Display and Upload with Thymeleaf, Spring MVC, and MySQL

In modern web development, the ability to seamlessly display images from various sources and allow users to upload images are common requirements. This article explores the dynamic display of images through Spring Boot and Thymeleaf. Thymeleaf is a server-side Java template engine that integrates seamlessly with Spring MVC to provide an efficient way to handle dynamic content rendering.

1. Setting Up the Spring Boot Project

Thymeleaf simplifies the process of rendering dynamic content, including images, within Spring MVC applications. Here’s a guide on how to display images from different sources using Thymeleaf.

1.1 Maven Dependencies

Let us begin by creating a new Spring Boot project in our preferred IDE or by using Spring Initializer to set up the project with the necessary dependencies, including Spring Boot starters for web development, Thymeleaf for template rendering, Spring Data JPA for database access, MySQL Connector for connecting to a MySQL database, Spring Boot Starter Test for testing, and additional Thymeleaf extras.

         <dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-jpa</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-security</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-thymeleaf</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>org.thymeleaf.extras</groupId>
			<artifactId>thymeleaf-extras-springsecurity6</artifactId>
		</dependency>

		<dependency>
			<groupId>com.mysql</groupId>
			<artifactId>mysql-connector-j</artifactId>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-tomcat</artifactId>
			<scope>provided</scope>
		</dependency>
	</dependencies>

Once our project is ready, we can proceed to configure the application properties (src/main/resources/application.properties) like this:

1.2 Application Properties

# Set Thymeleaf template prefix and suffix
spring.thymeleaf.prefix=classpath:/templates/
spring.thymeleaf.suffix=.html
spring.thymeleaf.cache=false

# DataSource Configuration
spring.datasource.url=jdbc:mysql://localhost:3306/displayimagesthymeleaf?useSSL=false
spring.datasource.username=your_username
spring.datasource.password=your_password
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver

# JPA (Java Persistence API) Configuration
spring.jpa.hibernate.ddl-auto=update
spring.jpa.show-sql=true

# Server Port
server.port=8080

2. Fetching Images from the File System

To demonstrate image display from the local file system, let’s start by organizing the static resources.

2.1 Organizing Static Resources

Create a static directory within the src/main/resources directory. This is where static resources, including images, will be stored. For example, if we have an image named example.jpg, place it in the src/main/resources/static/images directory. The project structure should look like this:

Fig 1: Project structure for thymeleaf to display static images
Fig 1: Project structure for thymeleaf to display static images

2.2 Thymeleaf Template for Static Image Display

Next, create a Thymeleaf template that references the static image. Thymeleaf simplifies the integration of dynamic content, including static resources, into HTML templates. We have created an HTML page named imagedisplay.html inside the src/main/resources/templates/ directory.


<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Static Image Display</title>
</head>
<body>
    <h1>Displaying Static Images in Spring Boot</h1>

    <!-- Display static image -->
    <img th:src="@{/images/example.jpg}" alt="Example Image">
</body>
</html>

In the above example, @{/images/example.jpg} references the static image in the images directory. The @{/} syntax is used to specify the root context path.

2.3 Controller for Thymeleaf Display

Next, create a controller class to handle requests and direct them to the Thymeleaf template.

@Controller
public class ImageController {
    
     @GetMapping("/display")
    public String displayImage() {
        return "imagedisplay";
    }
}

This controller maps the /display URL to the Thymeleaf template, allowing users to view the static image.

Run the Spring Boot application and navigate to http://localhost:8080/display (assuming the application is running on port 8080). We should see the Thymeleaf template displaying the static image.

Fig 2: static image display in thymeleaf + spring boot
Fig 2: static image display in thymeleaf + spring boot

2.4 External URL

Fetching images from an external URL involves using the URL of the desired image like this:

    <!-- External URL -->
    <img th:src="@{https://path/to/the/externalURL/image/logo.png}" alt="External Image">

3. Fetching Images from a Database using JPA

We can employ Java Persistence API (JPA) entities and repositories for database-backed image retrieval. Let’s create the SQL statements to generate the necessary tables in our MySQL database:

CREATE TABLE image_entity (
    id BIGINT AUTO_INCREMENT PRIMARY KEY,
    image_name VARCHAR(255),
    image_data LONGBLOB
);

This SQL statement defines a table named image_entity with columns for ID, image_name, and image_data stored as a LONGBLOB.

3.1 Database Entity and Repository

Create a JPA entity to represent the images and a repository interface for database interactions.

3.1.1 Define Entity Class

Create an entity class to represent the image data. This class should have a field of type byte[] to store the image as a BLOB (Binary Large Object).

@Entity
public class ImageEntity {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @Column
    private String imageName;

    @Lob
    @Column
    private byte[] imageData;

    public ImageEntity() {
    }

    public ImageEntity(Long id, String imageName, byte[] imageData) {
        this.id = id;
        this.imageName = imageName;
        this.imageData = imageData;
    }

    public Long getId() {
        return id;
    }

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

    public String getImageName() {
        return imageName;
    }

    public void setImageName(String imageName) {
        this.imageName = imageName;
    }

    public byte[] getImageData() {
        return imageData;
    }

    public void setImageData(byte[] imageData) {
        this.imageData = imageData;
    }

     public String getImageDataBase64() {
        return Base64.encodeBase64String(this.imageData);
    }    

}

3.1.2 Create a Repository Interface

Next, create a repository interface that extends JpaRepository to perform CRUD operations on the ImageEntity.

public interface ImageRepository extends JpaRepository {
}

3.2 Service Layer

Next, create a service class that handles business logic. In this example, it involves saving and retrieving images from the database.

This ImageService class encapsulates the logic related to image manipulation and interaction with the database.

@Service
public class ImageService {

    @Autowired
    private ImageRepository imageRepository;

    public List getAllImages() {
        return imageRepository.findAll();
    }

    public Optional getImageById(Long id) {
        return imageRepository.findById(id);
    }

    public void saveImage(ImageEntity imageEntity, MultipartFile file) {
        try {
            imageEntity.setImageData(file.getBytes());
            imageRepository.save(imageEntity);
        } catch (IOException ex) {
            Logger.getLogger(ImageService.class.getName()).log(Level.SEVERE, null, ex);
        }
        
    }

}

In the above code, the saveImage(ImageEntity imageEntity, MultipartFile file) method saves an image to the database. It takes an ImageEntity object and a MultipartFile representing the image file uploaded by a user through a form. It reads the content of the uploaded file as a byte array using file.getBytes() and sets it as the imageData property of the ImageEntity. Then, it saves the modified ImageEntity to the database using the save() method provided by the injected imageRepository.

3.3 Controller for Image Upload and Display

Next, we create a controller class to handle requests related to image operations. This controller is responsible for displaying a view that shows a list of images (/imagedisplay/view) and handling the addition of new images (/imagedisplay/add) as shown below:

@Controller
public class DatabaseImageController {

    @Autowired
    private ImageService imageService;
    
    @GetMapping(value = "/imagedisplay/view", produces = {MediaType.IMAGE_JPEG_VALUE, MediaType.IMAGE_PNG_VALUE})
    public String view(Model model) {
        model.addAttribute("images", imageService.getAllImages());
        return "view";
    }

    @GetMapping("/imagedisplay/add")
    public ModelAndView addImage() {
        return new ModelAndView("addImage", "image", new ImageEntity());
    }

    @PostMapping(value = "/imagedisplay/add", consumes = MULTIPART_FORM_DATA_VALUE)
    public String addImageProfile(ImageEntity image, @RequestPart("file") MultipartFile file) {
        imageService.saveImage(image, file);
        return "redirect:/imagedisplay/view";
    }
    
}

Here is an explanation of the main part of the code:

  • @GetMapping("/imagedisplay/view") Method:
    • This method handles HTTP GET requests for the /imagedisplay/view endpoint. It produces images in JPEG or PNG format (as specified by the produces attribute). It adds a list of images retrieved from the database using imageService.getAllImages() to the model and returns the view name view. This view is used to display the images.
  • @GetMapping("/imagedisplay/add") Method:
    • This method handles HTTP GET requests for the /imagedisplay/add endpoint. It returns a ModelAndView object with the view name addImage and an instance of ImageEntity bound to the image model attribute. This is used for displaying a form for adding images.
  • @PostMapping("/imagedisplay/add") Method:
    • This method handles HTTP POST requests for the /imagedisplay/add endpoint. It consumes multipart form data (consumes = MULTIPART_FORM_DATA_VALUE). It takes an ImageEntity object and a MultipartFile as parameters, representing the image and the file being uploaded. It saves the image using the imageService.saveImage(image, file) method and then redirects the user to the /imagedisplay/view endpoint.

3.4 Displaying Images Fetched from Database using Thymeleaf Template

Next, create a Thymeleaf template to display images fetched from the database dynamically. Create a file named addImage.html to upload images to the database and a file named view.html within the src/main/resources/templates/ directory.

<!-- addImage.html -->
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
        <title>Image Upload Form</title>
        <style>
           
        </style>
    </head>
    <body>
        <div class="center-container">
            <form th:action="@{/imagedisplay/add}" method="post" enctype="multipart/form-data" class="subform">
                <p>Image Upload Form</p>

                <p><label for="file" class="label">Select image:</label>
                    <input type="file" name="file" id="profile-image" accept="image/png, image/jpeg" required/>
                </p>

                <p><label for="imageName">Image Name:</label>
                    <input type="text" id="imageName" name="imageName" required><br>
                </p>

                <p>
                    <input type="submit" value="Upload">
                </p>
            </form>
        </div>
    </body>
</html>
Fig 2: Image Upload form
Fig 2: Image Upload form
<!-- view.html -->
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org">
    <head>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
        <title>Image List</title>          
    </head>
    <body>
        <div th:each="image : ${images}" class="center-container ">   
            <img  th:src="@{'data:image/jpeg;base64,'+${image.getImageDataBase64()}}" alt="images from database" width="50%" height="50%"/>

            <p class="" th:text="Figure + ' '+ ${image.id} + ':' + ${image.imageName}"></p> 

        </div>
    </body>
</html>

In the above Thymeleaf template, the th:src attribute now uses the Base64-encoded string returned by getImageDataBase64 to display the image.

Fig 3: View page showing all the Images fetched from the database
Fig 3: View page showing all the Images fetched from the database

4. Securing Access to Images to Ensure Unauthorized Entry

To prevent unauthorized access to images using Spring Security, we can enhance our Spring Boot application by securing the endpoints that serve the images. Below are the modifications and additions to our existing code.

4.1 Add Spring Security Dependency

Ensure that you have the Spring Security dependency in your pom.xml:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

4.2 Create or Update SecurityConfig Class

Create a SecurityConfig class to configure Spring Security in the application:

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
            http.authorizeRequests()
                .requestMatchers("/").permitAll()
                .requestMatchers("/display/**").permitAll()
                .requestMatchers("/images/**").permitAll() // Permit access to static images
                .requestMatchers("/imagedisplay/view").authenticated() // Require authentication for image view
                .anyRequest().authenticated();
            
            return http.build();
    }

}

In this example, the pattern /images/** is used to permit access to static images, assuming our static images are served from a directory named images under the static folder.

The .requestMatchers("/images/**").permitAll() method allows access to all URLs starting with /images/ without authentication. This is used for serving the static images.

We secured the /imagedisplay/view endpoint to require authentication.

The .requestMatchers("/imagedisplay/view").authenticated() requires authentication for the /imagedisplay/view URL, therefore users must be authenticated to access this endpoint.

5. Conclusion

This article has provided us with the full source code examples to implement static image display and dynamic image display from a MySQL database using Thymeleaf, Spring Boot, and JPA.

Displaying static images from the resources directory in a Spring Boot project is a straightforward process. By organizing static resources in the static directory and leveraging Thymeleaf for template rendering, we can easily integrate static images into our web applications.

Furthermore, the combination of Thymeleaf templates and Spring Boot’s backend framework has allowed us to effortlessly render dynamic images retrieved from the MySQL database, utilizing JPA for efficient data access and management.

6. Download the Source Code

This was an example of using thymeleaf to display images.

Download
You can download the full source code of this example here: Image Display and Upload with Thymeleaf, Spring MVC, and MySQL

Omozegie Aziegbe

Omos holds a Master degree in Information Engineering with Network Management from the Robert Gordon University, Aberdeen. Omos is currently a freelance web/application developer who is currently focused on developing Java enterprise applications with the Jakarta EE framework.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Inline Feedbacks
View all comments
Back to top button