JavaRush /Java Blog /Random EN /Introduction to Maven, Spring, MySQL, Hibernate and the f...
Макс
Level 41

Introduction to Maven, Spring, MySQL, Hibernate and the first CRUD application (part 2)

Published in the Random EN group
Good afternoon. In this article I would like to share my first encounter with things like Maven, Spring, Hibernate, MySQL and Tomcat in the process of creating a simple CRUD application. This is the second part of 4. The article is intended primarily for those who have already completed 30-40 levels here, but have not yet ventured beyond pure Java and are just beginning (or are about to begin) to enter the open world with all these technologies, frameworks and other unfamiliar words. This is the second part of the article "Introduction to Maven, Spring, MySQL, Hibernate and the first CRUD application." The first part can be seen by following this link: Introduction to Maven, Spring, MySQL, Hibernate and the first CRUD application (part 1)

Content:

Well, let's move on, let's now try to conjure a whole repository of films. In our small and simple application, of course, you can simply stupidly put all the logic right in the controller, but, as already noted, it is better to immediately learn how to do everything correctly. Therefore, let's create several layers. We will have a DAO responsible for working with data, a Service , where there will be all sorts of other logic, and the Controller will only process requests and call the necessary service methods.

Data Access Object

Data Access Object (DAO) is such a design pattern. The point is to create a special layer that will be solely responsible for accessing data (working with a database or other storage mechanism). In the package daowe will create an interface FilmDAOin which there will be methods such as add, delete, etc. I called them a little differently, but they correspond to the basic CRUD operations ( C reate, Read , U pdate, D elete).

It is worth noting here that in addition to DAO, there is also such an approach as Repository, they seem to be very similar, both are used for working with data. I have not yet figured out what features these approaches have and what the difference is between them. Therefore, I may be mistaken here and this should be called a repository, and not a Tao, or maybe it’s something in between. But in most of the examples that I have seen and studied, this is called DAO, so I will probably call it the same. At the same time, perhaps somewhere further in the text I will use the word repository. In any case, if I am wrong somewhere with this, please forgive me.

package testgroup.filmography.dao;

import testgroup.filmography.model.Film;

import java.util.List;

public interface FilmDAO {
    List<Film> allFilms();
    void add(Film film);
    void delete(Film film);
    void edit(Film film);
    Film getById(int id);
}
Now we need its implementation. We won’t connect the database yet, it’s still a little scary. To practice and get used to it, let’s first simulate storage in memory and create a list with several films. To store the list, we will use not List, but Map, to make it convenient to retrieve a specific movie by its id, without going through the entire list. For generation idwe use AtomicInteger . Let's create a class FilmDAOImpl, implement all the methods and fill out the map. Something like that.
package testgroup.filmography.dao;

import testgroup.filmography.model.Film;

import java.util.*;

public class FilmDAOImpl implements FilmDAO {
    private static final AtomicInteger AUTO_ID = new AtomicInteger(0);
    private static Map<Integer, Film> films = new HashMap<>();

    static {
        Film film1 = new Film();
        film1.setId(AUTO_ID.getAndIncrement());
        film1.setTitle("Inception");
        film1.setYear(2010);
        film1.setGenre("sci-fi");
        film1.setWatched(true);
        films.put(film1.getId(), film1);

        // + film2, film3, film4, ...
    }
    @Override
    public List<Film> allFilms() {
        return new ArrayList<>(films.values());
    }

    @Override
    public void add(Film film) {
        film.setId(AUTO_ID.getAndIncrement());
        films.put(film.getId(), film);
    }

    @Override
    public void delete(Film film) {
        films.remove(film.getId());
    }

    @Override
    public void edit(Film film) {
        films.put(film.getId(), film);
    }

    @Override
    public Film getById(int id) {
        return films.get(id);
    }
}

Service

Now let's add a service layer. In principle, in this example it is quite possible to do without it, limiting ourselves to DAO; the application will be very simple and there are no plans for any complex logic in the service. But suddenly in the future you will want to add all sorts of complications and interesting things to the project, so for the sake of completeness, let it be. For now, it will simply call methods from the DAO. serviceLet's create an interface in the package FilmService.
package testgroup.filmography.service;

import testgroup.filmography.model.Film;

import java.util.List;

public interface FilmService {
    List<Film> allFilms();
    void add(Film film);
    void delete(Film film);
    void edit(Film film);
    Film getById(int id);
}
And its implementation:
package testgroup.filmography.service;

import testgroup.filmography.dao.FilmDAO;
import testgroup.filmography.dao.FilmDAOImpl;
import testgroup.filmography.model.Film;

import java.util.List;

public class FilmServiceImpl implements FilmService {
    private FilmDAO filmDAO = new FilmDAOImpl();

    @Override
    public List<Film> allFilms() {
        return filmDAO.allFilms();
    }

    @Override
    public void add(Film film) {
        filmDAO.add(film);
    }

    @Override
    public void delete(Film film) {
        filmDAO.delete(film);
    }

    @Override
    public void edit(Film film) {
        filmDAO.edit(film);
    }

    @Override
    public Film getById(int id) {
        return filmDAO.getById(id);
    }
}
The project structure now looks like this:
Introduction to Maven, Spring, MySQL, Hibernate and the first CRUD application (part 2) - 1

Controller and Views

Let's now work on the controller methods and filling the pages. When filling out the pages, we will need some techniques. For example, to display a list of movies, we need a loop, if, say, we want to change some inscription, depending on the parameters, we need conditions, etc. The JSP (JavaServer Pages) format allows you to use java code inserts with which all this can be implemented. But I don’t want to use java code mixed with HTML code on the page. It would be, at a minimum, very ugly. Fortunately, to solve this problem there is such a wonderful thing as JSTL (JavaServer Pages Standard Tag Library) or the JSP Standard Tag Library. It allows us to use a whole bunch of additional tags in our JSP pages for a variety of needs. Let's connect it to pom.xml:
<dependency>
      <groupId>jstl</groupId>
      <artifactId>jstl</artifactId>
      <version>1.2</version>
</dependency>
Now let's take a look at the controller. First of all, let’s remove the creation of the object from there Film, this was done for testing and we don’t need anything else. Let's add a service there and call its methods.
public class FilmController {
    private FilmService filmService = new FilmServiceImpl();
Well, accordingly we will create methods for each case, add, delete, etc. First a method to display the main page with a list of movies:
@RequestMapping(method = RequestMethod.GET)
    public ModelAndView allFilms() {
        List<Film> films = filmService.allFilms();
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.setViewName("films");
        modelAndView.addObject("filmsList", films);
        return modelAndView;
    }
There's nothing new here. We get a list of movies from the service and add it to the model. Now let's make the main page films.jspthat this method returns:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
<title>FILMS</title>
</head>
<body>

<h2>Films</h2>
<table>
    <tr>
        <th>id</th>
        <th>title</th>
        <th>year</th>
        <th>genre</th>
        <th>watched</th>
        <th>action</th>
    </tr>
    <c:forEach var="film" items="${filmsList}">
        <tr>
            <td>${film.id}</td>
            <td>${film.title}</td>
            <td>${film.year}</td>
            <td>${film.genre}</td>
            <td>${film.watched}</td>
            <td>
                <a href="/edit/${film.id}">edit</a>
                <a href="/delete/${film.id}">delete</a>
            </td>
        </tr>
    </c:forEach>
</table>

<h2>Add</h2>
<c:url value="/add" var="add"/>
<a href="${add}">Add new film</a>
</body>
</html>
Let's take a closer look at this page to see what's what. <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> - here the JSTL core is connected, which includes the main tags for creating cycles, conditions, etc.
  • <table>— tag for creating a table.
  • <tr>— table row
  • <th>- column header
  • <td>— table cell
First, we make a table header row with the names of the columns. Then <c:forEach var="film" items="${filmsList}">, in a loop (which we took from JSTL core), we go through all the elements of the passed list ( filmsList), for each element ( film) we create a new row and write the corresponding value into each cell. There is one point here, the recording seems to film.idneed to be understood as film.getId(), i.e. The field is not accessed directly, but the getter is called. In the last column ( action) we make links for deleting and editing (we will create the corresponding methods now). Well, below is a link to the method for adding a new movie. Here's what it looks like: Next, let's take a look at the method that will return the edit page for a specific movie:
@RequestMapping(value = "/edit/{id}", method = RequestMethod.GET)
    public ModelAndView editPage(@PathVariable("id") int id) {
        Film film = filmService.getById(id);
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.setViewName("editPage");
        modelAndView.addObject("film", film);
        return modelAndView;
    }
Something new has appeared here - this is an annotation @PathVariable. It indicates that this parameter ( int id) is obtained from the address bar. To indicate the location of this parameter in the address bar, the construction is used {id}(by the way, if the variable name is the same, as in this case, then you don’t have to indicate it in parentheses, but just write it @PathVariable int id). So, on the main page we have made links for each movie indicating id:
<a href="/edit/${film.id}">edit</a>
Then this value is assigned to the method parameter and then we use it to get a specific movie from the repository through the service and add it to the model. This was the method for getting the editing page, now we need a method for editing itself:
@RequestMapping(value = "/edit", method = RequestMethod.POST)
    public ModelAndView editFilm(@ModelAttribute("film") Film film) {
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.setViewName("redirect:/");
        filmService.edit(film);
        return modelAndView;
    }
In the method editPagewe added an attribute to the model:
modelAndView.addObject("film", filmService.getById(id));
And now with the help of annotation @ModelAttributewe get this attribute and we can change it. Request method POSTbecause here we will pass the data. " redirect:/" means that after executing this method we will be redirected to the address " /", i.e. the method will run allFilmsand we will return to the main page. Now let's make the page itself editPage.jsp:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
    <title>Edit</title>
</head>
<body>
<c:url value="/edit" var="var"/>
<form action="${var}" method="POST">
    <input type="hidden" name="id" value="${film.id}">
    <label for="title">Title</label>
    <input type="text" name="title" id="title">
    <label for="year">Year</label>
    <input type="text" name="year" id="year">
    <label for="genre">Genre</label>
    <input type="text" name="genre" id="genre">
    <label for="watched">Watched</label>
    <input type="text" name="watched" id="watched">
    <input type="submit" value="Edit film">
</form>
</body>
</html>
  • <form>— a form for collecting and sending data, indicating who will process it ( /edit)
  • <input>— interface elements for user interaction (buttons, input fields, etc.)
  • <label>- text label
So, when you click the button, <input type="submit" value="Edit film">the data from the form will be sent to the server (an invisible field with the value has been specially added idso that the server knows which record in the database needs to be updated). In the method editFilmthey will be assigned to the corresponding attribute fields film. We will then return to the main page with an updated list. The editing page looks like this: Now let's start adding new films to the list. To do this, you will also need a form for entering and submitting data. You can make a form on the main page or you can make a separate page, like editPage.jsp. But, on the other hand, the form for adding will be exactly the same as for editing, i.e. 4 input fields and a submit button. So why create a new page then, let’s use editPage.jsp. Method to get the page:
@RequestMapping(value = "/add", method = RequestMethod.GET)
    public ModelAndView addPage() {
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.setViewName("editPage");
        return modelAndView;
    }
In the method, editPagewe additionally passed the attribute in order to change it later, but here we simply receive the page. And the method to add:
@RequestMapping(value = "/add", method = RequestMethod.POST)
    public ModelAndView addFilm(@ModelAttribute("film") Film film) {
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.setViewName("redirect:/");
        filmService.add(film);
        return modelAndView;
    }
Since we did not pass the attribute here, a new object will be created here Film. Well, there’s basically nothing new here. It is also worth noting that we have both methods available at " /add". This is possible due to the fact that they respond to different types of requests. By following the link on the main page we make a GET request, which leads us to the addPage. And when on the adding page we click the button to send data, a POST request is made, and the addFilm. To add a new movie, we decided to use the same page as for editing. But there the data is sent to the address " /edit":
<c:url value="/edit" var="var"/>
<form action="${var}" method="POST">
    <input type="submit" value="Edit film">
</form>
We need to tweak the page a bit so that it behaves differently for adding and editing. To solve this issue, we will use the conditions from the same JSTL core library:
<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<html>
<head>
    <c:if test="${empty film.title}">
        <title>Add</title>
    </c:if>
    <c:if test="${!empty film.title}">
        <title>Edit</title>
    </c:if>
</head>
<body>
<c:if test="${empty film.title}">
    <c:url value="/add" var="var"/>
</c:if>
<c:if test="${!empty film.title}">
    <c:url value="/edit" var="var"/>
</c:if>
<form action="${var}" method="POST">
    <c:if test="${!empty film.title}">
        <input type="hidden" name="id" value="${film.id}">
    </c:if>
    <label for="title">Title</label>
    <input type="text" name="title" id="title">
    <label for="year">Year</label>
    <input type="text" name="year" id="year">
    <label for="genre">Genre</label>
    <input type="text" name="genre" id="genre">
    <label for="watched">Watched</label>
    <input type="text" name="watched" id="watched">
    <c:if test="${empty film.title}">
        <input type="submit" value="Add new film">
    </c:if>
    <c:if test="${!empty film.title}">
        <input type="submit" value="Edit film">
    </c:if>
</form>
</body>
</html>
Those. we are just checking the field film.title. If it is empty, then it is a new movie, we must fill in all the data for it and add it to the list. If this field is not empty, then it is a movie from the list and you just need to change it. That. we get two versions of our page: Well, the last controller method for removing a movie from the list:
@RequestMapping(value="/delete/{id}", method = RequestMethod.GET)
    public ModelAndView deleteFilm(@PathVariable("id") int id) {
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.setViewName("redirect:/");
        Film film = filmService.getById(id);
        filmService.delete(film);
        return modelAndView;
    }
I think there is no need to comment on anything here, all this has already been considered. We have already made links to this address on the main page. Well, everything seems to be ready here, you can run it again and see how everything works.

Repository and Service as Spring Components

Let's make one more small correction. The fact is that now our storage and service are just classes, and in order to use them we have to create a class object ourselves ( new FilmServiceImpl()). But we have Spring connected for a reason , so let him control this matter himself. To put our classes under Spring's control, we need to indicate that they are components. To do this, we mark them with special annotations:
@Repository
public class FilmDAOImpl implements FilmDAO {
@Service
public class FilmServiceImpl implements FilmService {
Annotations @Repositoryand @Service, as well as @Controllerare derived from @Component. What are the specific features and differences of these three annotations and how they differ from a simple component should be read separately in the documentation or guides. For now, it is enough to know that these annotations tell Spring that these classes are a repository and a service, respectively. And now we no longer need to create concrete objects of these classes ourselves:
private FilmService filmService = new FilmServiceImpl();
Instead, you can mark the field with a special annotation and Spring will select the appropriate implementation:
@Autowired
private FilmService filmService;
The annotation @Autowired(auto-binding) tells Spring that it should dig into its context and substitute a suitable bean here. Very comfortably. If before we used interfaces so as not to worry about the specific implementation of methods, now we don’t even need to worry about the implementation of the interface itself and even know its name. The idea is that it is not recommended to use auto-binding on a field; it is better to use a constructor or setter. Read more about this in the documentation. For us, in principle, this is not important, we can safely leave it like that. But, since the idea is asking for it, we will respect that everything is beautiful and without any yellow warnings. In the controller class, let's create a setter and annotate it:
@Controller
public class FilmController {

    private FilmService filmService;

    @Autowired
    public void setFilmService(FilmService filmService) {
        this.filmService = filmService;
    }
And similarly we make a setter for FilmDAOin the class FilmServiceImpl. To be continued... Introducing Maven, Spring, MySQL, Hibernate and the first CRUD application (part 1) Introducing Maven, Spring, MySQL, Hibernate and the first CRUD application (part 2) Introducing Maven, Spring, MySQL, Hibernate and the first CRUD application (part 3) Introduction to Maven, Spring, MySQL, Hibernate and the first CRUD application (part 4)
Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION