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, 2020 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, 2020 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, 2020 at 17:08

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Browse other questions tagged or ask your own question.