How to retrieve data from connected table with connected ID - spring

I have tables like this:
User:
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NotEmpty
#Size(min = 3, max = 20)
private String username;
... more fields, not important
Wallet:
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NotEmpty(message = "Please, insert a wallet name")
private String walletName;
private double initialBalance;
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "user_id", nullable = false)
#OnDelete(action = OnDeleteAction.CASCADE)
#JsonIgnore
private User user;
As you can see those two tables are connected, and I can assignee wallet to user.
Now, I created a userProfile HTML page and want to display next stuff: Username, email, initalBalance.
I managed somehow to get principals from logged user:
<h1>Username </h1>
<h1 th:text="${#authentication.getPrincipal().getUsername()}"></h1>
<br>
<h1>Email </h1>
<h1 th:text="${#authentication.getPrincipal().getEmail()}"></h1>
But I have problem to show initialBalance of his wallet.
When I try with walletRepository.findAll() obviously that return initialBalances from all users.
This is how I tried so far:
#GetMapping("/userWallet/{user_id}/balance")
public String getUserWallet(#PathVariable(value = "user_id") Long user_id, Model model, Wallet wallet) {
model.addAttribute("wallet", walletRepository.findByUserIdAndInitialBalance(user_id, wallet.getInitialBalance()));
return "userProfile";
}
And in HTML:
<tr th:each="wallets:${wallet}">
<div class="title">
<h1>Balance </h1>
<h1 th:text="${wallets.initialBalance}"></h1>
</div>
But nothing is printed at screen.
So just to be clear, I want to provide a initialBalance from user wallet.

Related

Can you render jsp/html elements (i.e. a form:form) dynamically in one page?

I am new to web-dev, and coding in general, so I apologize if this question doesn't make sense.
Background: I am working on a web application utilizing the Spring Framework. My application allows users to register a user account, post pictures, make comments on pictures, like pictures etc. (think of a mini Pintrest clone). Users and comments (along with some other non-relevant entities) are all saved as in table entities in a MySQL database.
Problem: I would like users be able to edit their comments after they post them. For the best user experience I do not want users to be taken to a separate page with a form to edit their comment. I would like user to be able to edit their comment in a single page/the same page, without rendering a new one. Flow goes something like this: User goes to their comment on a picture, presses an "edit" button/link and the comment is put in a text box (form:form) which is then editable.
So far I have tried conditional rendering with a boolean switch of "editPressed" but I can't seem to work out the logic. Is this the correct approach or should something like JS be used here? (not sure how js handles form:forms) Below are some code snippets of the models and the .jsp where the comment would be edited.
.jsp:
<c:forEach var="eachComment" items="${allCommentsByPhotoId}">
<div class="card-text d-flex gap-2 mb-4">
<img class="user-comment-card" src="https://cdn-icons-png.flaticon.com/512/1053/1053244.png" alt="" />
<div>
<div class="mb-2">
<c:choose>
<c:when test="${ editPressed == false }">
<c:out value="${eachComment.user.getFirstName()} says: ${ eachComment.getComment() }"></c:out>
</c:when>
<c:otherwise>
<span class="username">
<c:out value="${eachComment.user.getFirstName()}"></c:out>:
</span>
<c:out value="${ eachComment.getComment()}"></c:out>
</c:otherwise>
</c:choose>
</div>
<div class="d-flex align-items-center gap-3">
<!-- -->
<c:if test="${ eachComment.user.getId() == currentUser.id }">
<form:form action="/delete/${eachComment.getId()}" method="delete">
<p class="submit">
<input class="btn btn-danger del-cmt-btn" type="submit" value="Delete" />
</p>
</form:form>
<form:form action="/edit/comment/${eachComment.getId()}">
<p class="submit">
<input class="btn btn-secondary edit-cmt-btn" type="submit" value="Edit" />
</p>
</form:form>
</c:if>
</div>
</div>
</div>
</c:forEach>
Comment Model:
#Entity
#Table(name="user_comments_on_photo")
public class Comment {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String comment;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name="user_id")
private User user;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name="photo_id")
private Photo photo;
#ManyToMany(fetch=FetchType.LAZY)
#JoinTable(
name="user_likes_comment",
joinColumns = #JoinColumn(name="comment_id"),
inverseJoinColumns = #JoinColumn(name="user_id")
)
private List<User> usersWhoLikeComment;
#Column(updatable=false)
#DateTimeFormat(pattern="yyyy-MM-dd")
private Date createdAt;
#DateTimeFormat(pattern="yyyy-MM-dd")
private Date updatedAt;
USER MODEL:
#Id
#GeneratedValue(strategy=GenerationType.IDENTITY)
private Long id;
#Size(min=3)
private String firstName;
#Size(min=3)
private String lastName;
#Email
private String email;
#Size(min=8)
private String password;
#Transient
private String passwordConfirmation;
#Column(updatable=false)
private Date createdAt;
private Date updatedAt;
//RELATIONSHIPS
#OneToMany(mappedBy="user", fetch = FetchType.LAZY)
private List<Photo> photos;
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(
name = "user_likes_photo",
joinColumns = #JoinColumn(name="user_id"),
inverseJoinColumns = #JoinColumn(name="photo_id")
)
private List<Photo> likedPhotos;
//NEW ONE-TO-MANY
#OneToMany(mappedBy = "user", fetch = FetchType.LAZY)
private List<Comment> userComments;
#ManyToMany(fetch=FetchType.LAZY)
#JoinTable(
name="user_likes_comment",
joinColumns = #JoinColumn(name="user_id"),
inverseJoinColumns = #JoinColumn(name="comment_id")
)
private List<Comment> commentsUserLikes;
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(
name = "users_roles",
joinColumns = #JoinColumn(name = "user_id"),
inverseJoinColumns = #JoinColumn(name = "role_id"))
private List<Role> roles;
PHOTO MODEL:
#Entity
#Table(name="photos")
public class Photo {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String photoURL;
private String photoFileName;
#NotEmpty
private String photoTitle;
#NotEmpty
private String photoDescription;
//will add tags later
//RELATIONSHIPS
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name="user_id")
private User user;
#ManyToMany(fetch=FetchType.LAZY)
#JoinTable(
name="user_likes_photo",
joinColumns = #JoinColumn(name="photo_id"),
inverseJoinColumns = #JoinColumn(name="user_id")
)
private List<User> usersWhoLikePhoto;
//NEW ONE-TO-MANY
#OneToMany(mappedBy="photo", fetch = FetchType.LAZY)
private List<Comment> comments;
//CREATED AND UPDATED AT
#Column(updatable=false)
#DateTimeFormat(pattern="yyyy-MM-dd")
private Date createdAt;
#DateTimeFormat(pattern="yyyy-MM-dd")
private Date updatedAt;

How to insert the id of the user to the product in Spring boot?

I'm having problem with the Id of the user. I have 2 model, User and Product and there are a FK beetween them (1 User can have many Product). After logged, I want the user can add a new product, but the problem when i added a new product, the column User_ID is null, and i tried lots of way but i can't add the User_id to the Product.
This is my Product
#Entity
#Table(name = "product")
#Data
#NoArgsConstructor
#AllArgsConstructor
#Getter
#Setter
public class Product {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String productName;
private double price;
private String productType;
private String description;
private boolean sold = false;
#ManyToOne
#JoinColumn(name = "userId", insertable = false, updatable = false, referencedColumnName = "id")
private User user;
This is my User
#Entity
#Table(name = "user")
#Data
#AllArgsConstructor
#NoArgsConstructor
public class User implements UserDetails {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#NotBlank(message = "Please enter your email")
#Email(message = "Enter a valid email address")
private String email;
#NotBlank(message = "Please enter your password")
#Length(min = 6, message = "Password must be at least 6 characters")
private String password;
#NotBlank(message = "Please enter your name")
private String name;
private boolean enable = true;
#OneToMany(mappedBy = "user")
private List<Product> products;
I tried to add like this but it doesn't work
#PostMapping("/mystore")
public String addProduct(Product product) {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
User user = (User) authentication.getPrincipal();
System.out.println(user.getId());
product.setUser(user);
productService.save(product);
return "redirect:/mystore";
}
So is there any way to get the ID of the user and add it into the product?
I think the problem is that you are getting the user from authentication.getPrincipal(). you should get the user from the database and the Hinernate Session which is behind the Spring JPA will maintain into the session the User Entity. So, query the user form the DB using the principal, then set the user into product then save the product

Problem with relation #ManyToOne in Spring Boot, Hibernate, JPA

I have classes -> Country and City.
I wanna create works request, that when I call to get all countries, I will get all countries, with cities.
When I call to get all cities, I will get all cities with only countries from Country model.
I wanna add new cities with relation to countries.
My Country model class:
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Id
private Long id;
#NotEmpty(message = "Name of country is mandatory.")
#Column(unique = true)
private String nameOfCountry;
#NotBlank(message = "Name of capital is mandatory.")
private String capital;
#Max(value = 17098242L, message = "Maximum value for population = 10000000000")
private Long surface;
private String countryCode;
private String telephoneCode;
#OneToMany(cascade = CascadeType.PERSIST)
#JoinColumn(name = "country_Id", updatable = false, insertable = false)
private List<CityModelDao> cityModelDao;
My City model class:
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Id
private Long id;
#NotEmpty(message = "Name of country is mandatory.")
#NotNull
#Column(nullable = false, unique = true)
private String nameOfCity;
I know that I don't have here #ManyToOne, but I still do it wrong and now I haven't got more ideas.
My response from get countries:
And this is it what i want.
But when i call to get cities my response is:
Unfortunately I havent got information about country.
In db I have in cities information about fk from country:
Could you help me to do works relation? I ve tried something like:
CityModel:
#ManyToOne()
#JoinColumn(name = "country_Id")
private CountryModelDao countryId;
CountryModel:
#OneToMany(mappedBy = "countryId", orphanRemoval = true, cascade = CascadeType.PERSIST)
private List<CityModelDao> cityModelDao;
But it was wrong. And when I tried with above relation city, I got error.
Could You tell me how to do correct #ManyToOne in this case? What I do wrong?
Thanks
The most simplistic bi-directional OneToMany relationship model should be:
#OneToMany(mappedBy = "countryId")
private List<CityModelDao> cityModelDao;
You set Country as the owner of the relationship Country - City;
You expect an attribute 'countryId' in the CityModelDao;
#ManyToOne
#JoinColumn(name = "country_id")
private CountryModelDao countryId;
You will populate with data based on a join operation that will be executed on the column country_id from the CityModelDao table.
Of course, afterwards, you can enrich the annotations with orphan removal, cascade type etc.
LE:
You are using this via REST and you need to avoid the infinite loop.
Please update the relations to:
#JsonManagedReference
#OneToMany(mappedBy = "countryId")
private List<CityModelDao> cityModelDao;
#JsonBackReference
#ManyToOne
#JoinColumn(name = "country_id")
private CountryModelDao countryId;

How to prevent user from injecting field into form backing bean?

An user upload his comment via this form.
Thymeleaf
<form th:action="#{/comment}" th:id="form" method="post">
<input type="hidden" th:name="productId.id" th:value="${product.id}">
<textarea th:field="${comment.message}" class="comment"
placeholder="Write comment here"></textarea>
<input type="submit" id="submit" value="comment">
</form>
Actual HTML
<form action="/comment" id="form" method="post" class="">
<input type="hidden" name="_csrf" value="f6b3f296-3284-4d2d-a2b2-0a9975f5e071">
<input type="hidden" name="productId.id" value="38">
<textarea class="comment" placeholder="Write comment here" id="message" name="message"></textarea>
<input type="submit" id="submit" value="comment">
</form>
However if user overwrites the actual HTML like this, the product's name will be changed to "ABCD"
<form action="/comment" id="form" method="post" class=""><input type="hidden" name="_csrf" value="f6b3f296-3284-4d2d-a2b2-0a9975f5e071">
<input type="hidden" name="productId" value="38">
<input type="hidden" name="productId.name" value="ABCD">
<textarea class="comment" placeholder="Write comment here" id="message" name="message"></textarea>
<input type="submit" id="submit" value="comment">
</form>
I think what happened here is Spring queried the productId and it became managed Entity, and when the user set the name to be "ABCD", it would be saved.
Here is my solution:
Basically just use #Validated with a bunch of groups and put constraint with appropriate groups (UploadCommentValidation in this case) on every single field, which works but seems really messy especially when it gets big.
Example with upload comment above:
Comment Entity: productId and message must be #Not Null, productId must be #Valid,other fields must be #Null
Product Entity: Id must be #NotNull, other fields must be #Null
Comment entity
public class Comment implements Comparable<Comment> {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Null(groups = {UploadCommentValidation.class})
#NotNull(groups = {DeleteCommentValidation.class, UpdateCommentValidation.class})
private Integer id;
#ManyToOne
#JoinColumn(name = "product_id", referencedColumnName = "id")
#JsonBackReference
#Valid
#NotNull(groups = {UploadCommentValidation.class})
#Null(groups = {DeleteCommentValidation.class, UpdateCommentValidation.class})
private Product productId;
#ManyToOne
#JoinColumn(name = "user_id", referencedColumnName = "id")
#JsonBackReference
#Null(groups = {UploadCommentValidation.class, DeleteCommentValidation.class, UpdateCommentValidation.class})
private User userId;
#Column(name = "message")
#NotBlank(message = "please write a comment", groups = {UploadCommentValidation.class, UpdateCommentValidation.class})
#Null(groups = {DeleteCommentValidation.class})
private String message;
#Column(name = "created_at", insertable = false, columnDefinition = "timestamp with time zone not null")
#Temporal(TemporalType.TIMESTAMP)
#Null(groups = {UploadCommentValidation.class, DeleteCommentValidation.class, UpdateCommentValidation.class})
private Calendar createdAt;
#Column(name = "updated_at", columnDefinition = "timestamp with time zone not null")
#Temporal(TemporalType.TIMESTAMP)
#Null(groups = {UploadCommentValidation.class, DeleteCommentValidation.class, UpdateCommentValidation.class})
private Calendar updatedAt;
#Override
public int compareTo(Comment o) {
return this.getId().compareTo(o.getId());
}
}
Product entity
public class Product implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#NotNull(message = "product id null", groups = {AddOrderValidation.class, UploadCommentValidation.class})
#Null(message = "bad request", groups = {ProductRegisterValidation.class})
private Integer id;
#NotBlank(message = "please fill in product name", groups = {ProductRegisterValidation.class})
#Length(max = 255, message = "too long", groups = {ProductRegisterValidation.class})
#Null(groups = {AddOrderValidation.class, UploadCommentValidation.class})
#Column(name = "name")
private String name;
#Column(name = "price")
#Positive(message = "the price must be non-negative", groups = {ProductRegisterValidation.class})
#NotNull(message = "please fill in price", groups = {ProductRegisterValidation.class})
#Null(groups = {AddOrderValidation.class, UploadCommentValidation.class})
private Integer price;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "category_id", referencedColumnName = "id")
#Valid
#NotNull(message = "please select category name", groups = {ProductRegisterValidation.class})
#Null(groups = {AddOrderValidation.class, UploadCommentValidation.class})
private Category categoryId;
#NotBlank(message = "please fill in description", groups = {ProductRegisterValidation.class})
#Length(max = 10000, message = "too long", groups = {ProductRegisterValidation.class})
#Null(groups = {AddOrderValidation.class, UploadCommentValidation.class})
#Column(name = "description")
private String description;
#OneToMany(mappedBy = "product", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#Null(groups = {ProductRegisterValidation.class, AddOrderValidation.class, UploadCommentValidation.class})
private List<ProductImage> productImages;
#OneToOne(mappedBy = "product", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#Null(groups = {ProductRegisterValidation.class, AddOrderValidation.class, UploadCommentValidation.class})
private Thumbnail thumbnail;
#OneToMany(mappedBy = "productId", fetch = FetchType.LAZY)
#JsonManagedReference
#Null(groups = {ProductRegisterValidation.class, AddOrderValidation.class, UploadCommentValidation.class})
private List<Comment> comments;
#OneToMany(mappedBy = "product", fetch = FetchType.LAZY)
#Null(groups = {ProductRegisterValidation.class, AddOrderValidation.class, UploadCommentValidation.class})
private List<Order> orders;
}
Any ideas how to do it the right way? This seems super messy!
UPDATE 1: This is my rest controller
#PostMapping("/comment")
public ResponseEntity<Map<String, String>> commentResponseEntity(#Validated({UploadCommentValidation.class}) Comment comment, BindingResult result) {
if (result.hasErrors()) {
result.getAllErrors().forEach(System.out::println);
return ResponseEntity.noContent().build();
}
User user = getUser();
comment.setUserId(user);
commentRepository.saveAndFlush(comment);
Map<String, String> response = new HashMap<>();
response.put("comment", comment.getMessage());
response.put("user", user.getName());
response.put("commentId", comment.getId().toString());
return ResponseEntity.ok().body(response);
}
You can do this by registering an #InitBinder method
You can do this at the individual controller level or by registering a #ControllerAdvice to be applied to all, or a subset of all, controllers.
#InitBinder()
public void initBinder(WebDataBinder binder) {
binder.setDisallowedFields(new String[] { "id", "version" });
}

Can't delete child entity without deleting parent entity, regardless of CascadeTypes?

I'm trying to connect an entity (User) to entities they create which will be Surveys.
I have two repositories, one UserRepository and one SurveyRepository. I can load Surveys according to which User has them and currently they are all mapped by the User_ID, which is a field on the Survey entity.
However, when I try to remove a Survey, this removes my User whenever I define CascadeType.ALL.
But when I don't use that, I get another error "Caused by: java.sql.SQLIntegrityConstraintViolationException:"
I'm gussing this is all related to the password encryption I'm using, but I am not even trying to delete the User entity, I'm just deleting the Survey, which holds a reference, or an ID to the Survey..
I've tried CascadeType.All on both sides, and I've tried not having any CascadeType at all as well.. If I have it on both sides, this deletes the user whenever I tell my surveyRepository.delete(currentSurvey);
And whenever I don't have it on both sides, I get the exception above..
User Entity:
#Entity
#Table(name = "user")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "user_id")
private Long id;
#NotEmpty
#Email
#Column(unique = true)
private String email;
private String password;
#NotBlank
private String username;
#NotBlank
private String firstName;
#NotBlank
private String lastName;
#NotBlank private String role;
#OneToMany(fetch = FetchType.EAGER)
private Set<Survey> surveys = new HashSet<>();
Survey Entity:
#Entity
#Table(name = "survey")
public class Survey {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "survey_id")
private Long id;
private String title, creator, description;
private LocalDate date = LocalDate.now();
#OneToMany(orphanRemoval = true, cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinColumn(name = "survey_id")
#OrderBy("position ASC")
private Set<Question> questions = new HashSet<>();
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "user_id")
private User user;
I'm just not sure how I can tell JPA/Hibernate not to touch the User whenever we delete the Survey.
It doesn't matter if I save the User with Survvey or not does it?
Basically I've tried a lot of options and I figure I'm not quite grasping the issue, and I suspect it's about the annotations on the User side, but I still feel as if I should be able to delete the child entity with no problem at all since I am not touching the parent entity?
This is because of EAGER fetch type in User class for surveys.
You delete survey but because it is existed on surveys set in user yet, it wouldn't be deleted actually.
You need to do like this:
// User class
#OneToMany(cascade=CascadeType.ALL, orphanRemoval=true, mappedBy="user")
private Set<Survey> surveys = new HashSet<>();
//Survey class
#ManyToOne
#JoinColumn(name = "user_id")
private User user;

Resources