I have a Spring Boot 3 application with Thymeleaf in the front end. The page looks like this:
<!DOCTYPE html>
<html xmlns:th="http://www.thymeleaf.org" xmlns="http://www.w3.org/1999/html" th:lang="en" lang="en">
<!-- Head-->
<head th:replace="~{fragments/head :: head}"></head>
<body>
<div th:if="${profileSection == 'Profile info'}">
<div th:replace="~{fragments/user/profile-info :: profileInfo}"></div>
</div>
<div th:if="${profileSection == 'Downloads'}">
<div th:replace="~{fragments/user/downloads :: downloads}"></div>
</div>
</body>
</html>
When a user clicks on the profile link, the above page will be rendered with profileSection == 'Profile info'
so that fragments/user/profile-info :: profileInfo
will be rendered first and the user details like name, email, phone nr, etc will be rendered. There is a sidebar with let's say 2 links. Profile info
& Downloads
.
What I want is this: When a user clicks on the Download
link from the sidebar, fragments/user/downloads :: downloads
should be rendered in the middle of the page without fetching downloads from the backend, instead it should use the current user
object like: user.getDownloads()
to render the download list. In that way, I don't need to do an extra round to the DB to fetch the user again. The user has a @ManyToMany
relationship with the Download entity.
How can I do this? Or, is it even possible?
If a User's downloads contain a lot of data, it can consume a lot of memory unnecessarily, as the user may not access the downloads section at all. There is a better approach to this problem.
To start, make sure the @ManyToMany
relation has FetchType.LAZY
(which is the default setting, so you should be fine if you haven't specified FetchType.EAGER
explicitly). This way, the memory won't be filled up unless the user specifically requests the downloads.
When the user clicks on the Downloads
link in the sidebar, you should have a code like this in the HTML file:
<a th:href="@{/user/downloads}">Downloads</a>
In the Controller class, you can have something like:
@GetMapping("/user/downloads")
public String getDownloads(@AuthenticationPrincipal(expression = "user") User user, Model model) {
user.getDownloads().size(); // Now you have access to downloads
model.addAttribute("profileSection", "Downloads");
model.addAttribute("user", user);
return "user/profile-page";
}
Make sure to import the AuthenticationPrincipal annotation:
org.springframework.security.core.annotation.AuthenticationPrincipal
Do not use this one, it's deprecated:
org.springframework.security.web.bind.annotation.AuthenticationPrincipal
Spring Boot provides the logged-in user in your Controller method, so just add the user object to the model, and inside the fragments/user/downloads :: downloads
fragment, you can do something like below snippet to loop through the user's download list:
<div th:each="download : ${user.downloads}">
...
</div>
Note: Be careful if you are using Lombok. The @Data
/ @Getter
annotations can cause lazy items to load without an explicit call.