3
\$\begingroup\$

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:

  1. 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.

  2. 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 using ReviewFormObject 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.

enter image description here enter image description here enter image description here

\$\endgroup\$
3
  • 2
    \$\begingroup\$ In order to understand how to avoid the query to the DB, I need to see the code of Beer and BeerService. \$\endgroup\$
    – shanif
    May 12 '20 at 8:26
  • 1
    \$\begingroup\$ I don't understand why reviewFormObject is a parameter in searchBeer if you don't need it. \$\endgroup\$
    – shanif
    May 12 '20 at 8:35
  • \$\begingroup\$ @shanif after removing reviewFormObject parameter I am getting 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\$
    – Suule
    May 12 '20 at 17:08

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Browse other questions tagged or ask your own question.