JavaRush /בלוג Java /Random-HE /היכרות עם Maven, Spring, MySQL, Hibernate ויישום ה-CRUD ה...
Макс
רָמָה

היכרות עם Maven, Spring, MySQL, Hibernate ויישום ה-CRUD הראשון (חלק 2)

פורסם בקבוצה
אחר הצהריים טובים. במאמר זה אני רוצה לשתף את המפגש הראשון שלי עם דברים כמו Maven, Spring, Hibernate, MySQL ו- Tomcat בתהליך של יצירת יישום CRUD פשוט. זהו החלק השני מתוך 4. המאמר מיועד בעיקר לאלו שכבר השלימו כאן 30-40 רמות, אך עדיין לא העזו מעבר לג'אווה הטהורה ורק מתחילים (או עומדים להתחיל) להיכנס לעולם הפתוח עם כל הטכנולוגיות, המסגרות ושאר המילים הלא מוכרות הללו. זהו החלק השני של המאמר "מבוא ל-Maven, Spring, MySQL, Hibernate ויישום ה-CRUD הראשון". ניתן לראות את החלק הראשון בקישור הזה: מבוא ל-Maven, Spring, MySQL, Hibernate ויישום ה-CRUD הראשון (חלק 1)

תוֹכֶן:

ובכן, בואו נמשיך הלאה, בואו ננסה כעת להעלות על הדעת מאגר שלם של סרטים. באפליקציה הקטנה והפשוטה שלנו, כמובן, אפשר פשוט בטיפשות לשים את כל ההיגיון בבקר, אבל, כפי שכבר צוין, עדיף ללמוד מיד איך לעשות הכל נכון. לכן, בואו ניצור כמה שכבות. יהיה לנו DAO שאחראי לעבודה עם נתונים, שירות , שבו יהיו כל מיני היגיון אחר, והבקר יעבד רק בקשות ויתקשר לשיטות השירות הדרושות.

אובייקט גישה לנתונים

אובייקט גישה לנתונים (DAO) הוא דפוס עיצוב כזה. העניין הוא ליצור שכבה מיוחדת שתהיה אחראית בלעדית לגישה לנתונים (עבודה עם מסד נתונים או מנגנון אחסון אחר). בחבילה daoניצור ממשק FilmDAOבו יהיו שיטות כמו הוספה, מחיקה וכו'. קראתי להם קצת אחרת, אבל הם מתאימות לפעולות ה-CRUD הבסיסיות ( C reate, Read , Update , D elete).

ראוי לציין כאן כי בנוסף ל-DAO, יש גם גישה כזו כמו Repository, נראה שהם מאוד דומים, שניהם משמשים לעבודה עם נתונים. עדיין לא הבנתי אילו תכונות יש לגישות האלה ומה ההבדל ביניהן. לכן, ייתכן שאני טועה כאן ויש לקרוא לזה מאגר, ולא טאו, או אולי זה משהו באמצע. אבל ברוב הדוגמאות שראיתי ולמדתי זה נקרא DAO, אז כנראה אקרא לזה אותו דבר. יחד עם זאת, אולי במקום אחר בטקסט אשתמש במילה מאגר. בכל מקרה, אם אני טועה איפשהו עם זה, אנא סלח לי.

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);
}
עכשיו אנחנו צריכים את היישום שלו. עדיין לא נחבר את מסד הנתונים, זה עדיין קצת מפחיד. כדי להתאמן ולהתרגל, בואו נדמות תחילה אחסון בזיכרון וניצור רשימה עם מספר סרטים. כדי לאחסן את הרשימה, נשתמש לא List, אלא Map, כדי שיהיה נוח לאחזר סרט ספציפי לפי id, מבלי לעבור על הרשימה כולה. לדור idאנו משתמשים ב-AtomicInteger . בואו ליצור מחלקה FilmDAOImpl, ליישם את כל השיטות ולמלא את המפה. משהו כזה.
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);
    }
}

שֵׁרוּת

עכשיו בואו נוסיף שכבת שירות. באופן עקרוני, בדוגמה זו ניתן בהחלט להסתדר בלעדיה, להגביל את עצמנו ל-DAO; האפליקציה תהיה פשוטה מאוד ואין תוכניות להיגיון מורכב בשירות. אבל פתאום בעתיד תרצו להוסיף כל מיני סיבוכים ודברים מעניינים לפרויקט, אז למען השלמות, תנו לזה להיות. לעת עתה, זה פשוט יקרא לשיטות מה-DAO. serviceבואו ניצור ממשק בחבילה 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);
}
והיישום שלו:
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);
    }
}
מבנה הפרויקט נראה כעת כך:
היכרות עם Maven, Spring, MySQL, Hibernate ויישום ה-CRUD הראשון (חלק 2) - 1

בקר ותצוגות

כעת נעבוד על שיטות הבקר ומילוי הדפים. בעת מילוי הדפים, נצטרך כמה טכניקות. לדוגמה, כדי להציג רשימה של סרטים, אנחנו צריכים לולאה, אם, נניח, אנחנו רוצים לשנות כתובת כלשהי, בהתאם לפרמטרים, אנחנו צריכים תנאים וכו'. פורמט JSP (JavaServer Pages) מאפשר לך להשתמש בתוספות קוד Java שבעזרתן ניתן ליישם את כל זה. אבל אני לא רוצה להשתמש בקוד Java מעורבב עם קוד HTML בדף. זה יהיה, לכל הפחות, מכוער מאוד. למרבה המזל, כדי לפתור בעיה זו יש דבר נפלא כמו JSTL (ספריית התגים הסטנדרטית של JavaServer Pages) או ספריית התגים הסטנדרטית של JSP. זה מאפשר לנו להשתמש בחבורה שלמה של תגים נוספים בדפי ה-JSP שלנו עבור מגוון צרכים. בואו נחבר אותו ל pom.xml:
<dependency>
      <groupId>jstl</groupId>
      <artifactId>jstl</artifactId>
      <version>1.2</version>
</dependency>
עכשיו בואו נסתכל על הבקר. קודם כל, בואו נסיר את יצירת האובייקט משם Film, זה נעשה לצורך בדיקה ואנחנו לא צריכים שום דבר אחר. בואו נוסיף שם שירות ונקרא לשיטות שלו.
public class FilmController {
    private FilmService filmService = new FilmServiceImpl();
ובכן, בהתאם ניצור שיטות לכל מקרה, נוסיף, נמחק וכו'. ראשית שיטה להצגת העמוד הראשי עם רשימת סרטים:
@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;
    }
אין כאן שום דבר חדש. אנחנו מקבלים רשימה של סרטים מהשירות ומוסיפים אותה לדגם. עכשיו בואו נעשה את העמוד הראשי films.jspששיטה זו מחזירה:
<%@ 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>
בואו נסתכל מקרוב על הדף הזה כדי לראות מה זה מה. <%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %> - כאן מחוברת ליבת JSTL הכוללת את התגים הראשיים ליצירת מחזורים, תנאים וכו' .
  • <table>- תג ליצירת טבלה.
  • <tr>- שורת טבלה
  • <th>- כותרת עמודה
  • <td>- תא טבלה
ראשית, אנו יוצרים שורת כותרות בטבלה עם שמות העמודות. לאחר מכן <c:forEach var="film" items="${filmsList}">, בלולאה (שלקחנו מ-JSTL core), אנו עוברים על כל האלמנטים של הרשימה שעברה ( filmsList), עבור כל אלמנט ( film) אנו יוצרים שורה חדשה ונכתוב את הערך המתאים לכל תא. יש כאן נקודה אחת, נראה שצריך film.idלהבין את ההקלטה כ- film.getId(), כלומר. אין גישה ישירה לשדה, אבל ה-getter נקרא. בעמודה האחרונה ( action) אנו יוצרים קישורים למחיקה ועריכה (ניצור כעת את השיטות המתאימות). ובכן, להלן קישור לשיטה להוספת סרט חדש. כך זה נראה: לאחר מכן, בואו נסתכל על השיטה שתחזיר את דף העריכה של סרט ספציפי:
@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;
    }
משהו חדש הופיע כאן - זו הערה @PathVariable. זה מציין שהפרמטר הזה ( int id) מתקבל משורת הכתובת. כדי לציין את מיקומו של פרמטר זה בשורת הכתובת, נעשה שימוש בקונסטרוקציה {id}(אגב, אם שם המשתנה זהה, כמו במקרה זה, אין צורך לציין אותו בסוגריים, אלא פשוט לכתוב אותו @PathVariable int id). אז, בעמוד הראשי יצרנו קישורים לכל סרט המציינים id:
<a href="/edit/${film.id}">edit</a>
לאחר מכן הערך הזה מוקצה לפרמטר השיטה ואז אנחנו משתמשים בו כדי לקבל סרט ספציפי מהמאגר דרך השירות ולהוסיף אותו למודל. זו הייתה השיטה לקבלת דף העריכה, כעת אנו זקוקים לשיטה לעריכה עצמה:
@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;
    }
בשיטה editPageהוספנו תכונה למודל:
modelAndView.addObject("film", filmService.getById(id));
ועכשיו בעזרת ביאור @ModelAttributeאנחנו מקבלים את התכונה הזו ונוכל לשנות אותה. שיטת בקשה POSTכי כאן נעביר את הנתונים. " redirect:/" פירושו שלאחר ביצוע שיטה זו נפנה אל הכתובת " /", כלומר. השיטה תפעל allFilmsונחזור לעמוד הראשי. עכשיו בואו נעשה את הדף עצמו 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>- טופס לאיסוף ושליחת נתונים, המציין מי יעבד אותם ( /edit)
  • <input>- רכיבי ממשק לאינטראקציה עם המשתמש (לחצנים, שדות קלט וכו')
  • <label>- תווית טקסט
לכן, כאשר תלחץ על הכפתור, <input type="submit" value="Edit film">הנתונים מהטופס יישלחו לשרת (נוסף במיוחד שדה בלתי נראה עם הערך idכדי שהשרת יידע איזו רשומה במסד הנתונים צריך להתעדכן). בשיטה editFilmהם יוקצו לשדות התכונה המתאימים film. לאחר מכן נחזור לעמוד הראשי עם רשימה מעודכנת. עמוד העריכה נראה כך: כעת נתחיל להוסיף סרטים חדשים לרשימה. לשם כך תזדקק גם לטופס להזנה ושליחה של נתונים. אתה יכול ליצור טופס בעמוד הראשי או שאתה יכול ליצור דף נפרד, כמו editPage.jsp. אבל, מצד שני, הטופס להוספה יהיה בדיוק כמו לעריכה, כלומר. 4 שדות קלט וכפתור שלח. אז למה ליצור דף חדש אז, בואו נשתמש ב- editPage.jsp. שיטה לקבל את הדף:
@RequestMapping(value = "/add", method = RequestMethod.GET)
    public ModelAndView addPage() {
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.setViewName("editPage");
        return modelAndView;
    }
בשיטה editPageהעברנו בנוסף את התכונה כדי לשנות אותה מאוחר יותר, אבל כאן אנחנו פשוט מקבלים את הדף. והשיטה להוסיף:
@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;
    }
מכיוון שלא העברנו כאן את התכונה, יווצר כאן אובייקט חדש Film. ובכן, בעצם אין כאן שום דבר חדש. ראוי גם לציין ששתי השיטות זמינות ב" /add". זה אפשרי בשל העובדה שהם נענים לסוגים שונים של בקשות. על ידי לחיצה על הקישור בעמוד הראשי אנו מבצעים בקשת GET, אשר מובילה אותנו אל ה- addPage. וכאשר בדף ההוספה אנו לוחצים על הכפתור כדי לשלוח נתונים, מתבצעת בקשת POST, וה- addFilm. כדי להוסיף סרט חדש, החלטנו להשתמש באותו עמוד כמו לעריכה. אבל שם הנתונים נשלחים לכתובת " /edit":
<c:url value="/edit" var="var"/>
<form action="${var}" method="POST">
    <input type="submit" value="Edit film">
</form>
אנחנו צריכים לצבוט קצת את הדף כך שהוא יתנהג אחרת לצורך הוספה ועריכה. כדי לפתור בעיה זו, נשתמש בתנאים מאותה ספריית ליבה של JSTL:
<%@ 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>
הָהֵן. אנחנו רק בודקים את השדה film.title. אם הוא ריק, אז זה סרט חדש, עלינו למלא את כל הנתונים עבורו ולהוסיף אותו לרשימה. אם השדה הזה לא ריק, אז זה סרט מהרשימה ואתה רק צריך לשנות אותו. זֶה. אנחנו מקבלים שתי גרסאות של הדף שלנו: ובכן, שיטת הבקר האחרונה להסרת סרט מהרשימה:
@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;
    }
אני חושב שאין צורך להגיב כאן על שום דבר, כל זה כבר נשקל. כבר עשינו קישורים לכתובת זו בעמוד הראשי. ובכן, נראה שהכל מוכן כאן, אתה יכול להפעיל את זה שוב ולראות איך הכל עובד.

מאגר ושירות כרכיבי קפיץ

בוא נעשה עוד תיקון קטן. העובדה היא שעכשיו האחסון והשירות שלנו הם רק מחלקות, וכדי להשתמש בהם אנחנו צריכים ליצור אובייקט מחלקה בעצמנו ( new FilmServiceImpl()). אבל ספרינג מחובר מסיבה כלשהי , אז תן לו לשלוט בעניין הזה בעצמו. כדי לשים את השיעורים שלנו בשליטת אביב, עלינו לציין שהם מרכיבים. לשם כך, אנו מסמנים אותם בהערות מיוחדות:
@Repository
public class FilmDAOImpl implements FilmDAO {
@Service
public class FilmServiceImpl implements FilmService {
הערות @Repositoryו @Service, כמו גם @Controllerנגזרים מ @Component. מהן המאפיינים וההבדלים הספציפיים של שלושת ההערות הללו וכיצד הם שונים מרכיב פשוט יש לקרוא בנפרד בתיעוד או במדריכים. לעת עתה, מספיק לדעת שהביאורים אלה מספרים ל-Spring שהשיעורים הללו הם מאגר ושירות, בהתאמה. ועכשיו אנחנו כבר לא צריכים ליצור בעצמנו אובייקטים קונקרטיים של המעמדות האלה:
private FilmService filmService = new FilmServiceImpl();
במקום זאת, תוכל לסמן את השדה בהערה מיוחדת ואביב יבחר את היישום המתאים:
@Autowired
private FilmService filmService;
ההערה @Autowired(כריכה אוטומטית) אומרת לאביב שעליו לחפור בהקשר שלה ולהחליף כאן שעועית מתאימה. בנוחות רבה. אם בעבר השתמשנו בממשקים כדי לא לדאוג ליישום הספציפי של שיטות, עכשיו אנחנו אפילו לא צריכים לדאוג לגבי היישום של הממשק עצמו ואפילו לדעת את שמו. הרעיון הוא שלא מומלץ להשתמש בכריכה אוטומטית בשדה, עדיף להשתמש בקונסטרוקטור או ב-setter. קרא עוד על כך בתיעוד. לנו, באופן עקרוני, זה לא חשוב, אנחנו יכולים להשאיר את זה בבטחה כך. אבל, מכיוון שהרעיון מבקש את זה, נכבד שהכל יפה וללא אזהרות צהובות. במחלקת הבקר, בואו ניצור מגדיר ונעיר לו:
@Controller
public class FilmController {

    private FilmService filmService;

    @Autowired
    public void setFilmService(FilmService filmService) {
        this.filmService = filmService;
    }
ובאופן דומה אנחנו עושים קבע עבור FilmDAOבכיתה FilmServiceImpl. להמשך... היכרות עם Maven, Spring, MySQL, Hibernate ויישום ה-CRUD הראשון (חלק 1) היכרות עם Maven, Spring, MySQL, Hibernate ויישום ה-CRUD הראשון (חלק 2) היכרות עם Maven, Spring, MySQL, Hibernate וה- יישום ה-CRUD הראשון (חלק 3) מבוא ל-Maven, Spring, MySQL, Hibernate ויישום ה-CRUD הראשון (חלק 4)
הערות
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION