I would like to ask you to point me some good direction with writing clean code with Spring framework and Java in general.
I am creating a simple app, which has two main features: searching for beers and being able to comment on those beers.
So on the page user is presented with search form:
<div class="container">
<br/>
<div class="row justify-content-center">
<div class="col-12 col-md-10 col-lg-8">
<form class="card card-sm" method="get" th:action="@{/search}" th:object="${beerFormObject}">
<div class="card-body row no-gutters align-items-center">
<div class="col-auto">
<i class="fas fa-search h4 text-body"></i>
</div>
<!--end of col-->
<div class="col">
<input class="form-control form-control-lg form-control-borderless" placeholder="Search beer"
th:field="*{beerName}" type="search">
</div>
<!--end of col-->
<div class="col-auto">
<button class="btn btn-lg btn-success" type="submit">Search</button>
</div>
<!--end of col-->
</div>
</form>
</div>
<!--end of col-->
</div>
</div>
BeerFormObject is simple dto
public class BeerFormObject {
@NotEmpty
private String beerName;
and controller which is responsible for searching
@Controller
public class SearchController {
@Autowired
BeerService beerService;
@GetMapping("/search")
public ModelAndView searchBeer(@ModelAttribute("beerFormObject") @Valid BeerFormObject beerFormObject,
@ModelAttribute("reviewFormObject") ReviewFormObject reviewFormObject,
BindingResult result) {
if (result.hasErrors()) {
return new ModelAndView("/index");
}
Beer beer = beerService.findByName(beerFormObject.getBeerName());
ModelAndView modelAndView = new ModelAndView("/beer-search-result");
modelAndView.addObject("beer", beer);
return modelAndView;
}
}
If everything is ok, it will show following view (beer-search-result.html):
<div class="container" th:if="${beer}">
<br/>
<div class="card mb-3">
<div class="row">
<div class="col-md-4" style="margin: auto; display: block;">
<img class="card-img img-responsive mx-auto d-block"
style=" display: block; max-width:200px; max-height:250px; width: auto; height: auto;"
th:src="${beer.getImageUrl()}"/>
</div>
<div class="col-md-8" style="margin: auto; display: block;">
<div class="card-block px-2">
<h4 th:text="${beer.getName()}">Name</h4>
<p th:text="${beer.getDescription()}">Description</p>
<p th:text="${beer.getFirstBrewed()}">First Brewed</p>
<p th:text="${beer.getAbv()}">Abv</p>
<p th:text="${beer.getIbu()}">Ibu</p>
</div>
</div>
</div>
</div>
<!-- user comment form -->
<div sec:authorize="isAuthenticated()" th:block>
<div class="row" id="post-review-box">
<div class="col-md-12">
<form method="post" th:action="@{/review}" th:object="${reviewFormObject}">
<input id="beer-id" th:field="*{beerName}" th:value="${beer.getName()}" type="hidden">
<textarea class="form-control animated" cols="50" id="new-review" name="comment"
placeholder="Enter your review here..." rows="5" th:field="*{reviewText}"></textarea>
<div class="text-right">
<button class="btn btn-success btn-lg" type="submit">Save</button>
</div>
</form>
</div>
</div>
</div>
<span sec:authorize="isAnonymous()"> Please <a th:href="@{/login}">Login</a> to post review</span>
<!--Beer comments-->
<div class="container">
<h2 class="text-center">Comments</h2>
<div th:if="${beer.getReviews()}">
<div class="card" th:each="review : ${beer.getReviews()}">
<div class="card-body">
<div class="row">
<div>
<!-- Add rating system-->
<p>
<a class="float-left" href="#">
<strong th:text="${review.getCreated()}">Created Date</strong>
by
<strong th:text="${review.getAuthor().getFirstName()}">Name</strong>
</a>
</p>
<div class="clearfix"></div>
<p th:text="${review.getText()}">Comment Content</p>
</div>
</div>
</div>
</div>
</div>
<p th:unless="${beer.getReviews()}">This product does not have any comments</p>
</div>
</div>
<div th:unless="${beer}">
<p>Nothing here</p>
</div>
This view has three separate views, one of them is Beer description, one is comment form and last one is displaying all comments. As you can see I have another form, which this time is post, because it is creating new entry in database. And the controller for posting review looks like that
@Controller
public class ReviewController {
@Autowired
BeerService beerService;
@Autowired
UserService userService;
@PostMapping("/review")
public ModelAndView postReview(@ModelAttribute("reviewFormObject") @Valid ReviewFormObject reviewFormObject, BindingResult result){
Beer beer = beerService.findByName(reviewFormObject.getBeerName());
beerService.addReview(beer, reviewFormObject.getReviewText());
ModelAndView modelAndView = new ModelAndView("redirect:/search?beerName="+beer.getName());
modelAndView.addObject("beer", beer);
return modelAndView;
}
}
Here are the things I would like to improve:
In
ReviewController
I am doing query to database to find beer with given name, but when I am posting new comment I am already displaying this beer, so I am wodering how could I avoid doing another query.In
SearchController
as a parameter of method I am passing two different form objects which one of them is needed and second one is not. This is probably because I am usingReviewFormObject
in the same fragment, right? What I would like to do is not have this second parameter. But I am not sure how to achieve this.
I am adding some images to visualize better what I am talking about. Cheers.
reviewFormObject
is a parameter insearchBeer
if you don't need it. \$\endgroup\$reviewFormObject
parameter I amgetting Caused by: java.lang.IllegalStateException: Neither BindingResult nor plain target object for bean name 'reviewFormObject' available as request attribute
. If you want to check those classes github.com/pkwiecienJ8/beer-app please look into this repo. Thanks! \$\endgroup\$