JavaRush /مدونة جافا /Random-AR /مقدمة إلى Maven وSpring وMySQL وHbernate وأول تطبيق CRUD ...
Макс
مستوى

مقدمة إلى Maven وSpring وMySQL وHbernate وأول تطبيق CRUD (الجزء 2)

نشرت في المجموعة
مساء الخير. أود في هذه المقالة أن أشارككم تجربتي الأولى مع أشياء مثل Maven، وSpring، وHibernate، وMySQL، وTomcat أثناء عملية إنشاء تطبيق CRUD بسيط. هذا هو الجزء الثاني من 4. هذه المقالة مخصصة في المقام الأول لأولئك الذين أكملوا بالفعل مستويات 30-40 هنا، ولكنهم لم يغامروا بعد بما هو أبعد من Java النقي وبدأوا للتو (أو على وشك البدء) في دخول العالم المفتوح مع كل هذه التقنيات والأطر وغيرها من الكلمات غير المألوفة. هذا هو الجزء الثاني من المقالة "مقدمة إلى Maven وSpring وMySQL وHbernate وأول تطبيق CRUD." يمكن مشاهدة الجزء الأول باتباع هذا الرابط: مقدمة إلى Maven وSpring وMySQL وHbernate وأول تطبيق CRUD (الجزء الأول)

محتوى:

حسنًا، دعنا ننتقل، فلنحاول الآن استحضار مستودع كامل من الأفلام. في تطبيقنا الصغير والبسيط، بالطبع، يمكنك ببساطة وضع كل المنطق بغباء في وحدة التحكم، ولكن، كما أشرنا بالفعل، من الأفضل أن تتعلم على الفور كيفية القيام بكل شيء بشكل صحيح. لذلك، دعونا إنشاء عدة طبقات. سيكون لدينا DAO مسؤولاً عن العمل مع البيانات، وهي خدمة ، حيث سيكون هناك كل أنواع المنطق الآخر، وسيقوم جهاز التحكم فقط بمعالجة الطلبات واستدعاء طرق الخدمة الضرورية.

كائن الوصول إلى البيانات

كائن الوصول إلى البيانات (DAO) هو نمط التصميم هذا. الهدف هو إنشاء طبقة خاصة تكون مسؤولة وحدها عن الوصول إلى البيانات (العمل مع قاعدة بيانات أو آلية تخزين أخرى). سنقوم في الحزمة daoبإنشاء واجهة FilmDAOتحتوي على طرق مثل الإضافة والحذف وما إلى ذلك. لقد أطلقت عليها اسمًا مختلفًا بعض الشيء، لكنها تتوافق مع عمليات CRUD الأساسية ( C create و Read و U pdate و D elete).

تجدر الإشارة هنا إلى أنه بالإضافة إلى DAO، هناك أيضًا نهج مثل المستودع، ويبدو أنهما متشابهان جدًا، وكلاهما يستخدم للعمل مع البيانات. لم أفهم بعد ما هي ميزات هذه الأساليب وما هو الفرق بينهما. لذلك، قد أكون مخطئًا هنا ويجب أن يسمى هذا مستودعًا، وليس الطاو، أو ربما يكون شيئًا بينهما. لكن في معظم الأمثلة التي رأيتها ودرستها، يُسمى هذا 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 وHbernate وأول تطبيق CRUD (الجزء 2) - 1

المراقب المالي ووجهات النظر

لنعمل الآن على طرق التحكم وملء الصفحات. عند ملء الصفحات، سنحتاج إلى بعض التقنيات. على سبيل المثال، لعرض قائمة الأفلام، نحتاج إلى حلقة، إذا أردنا، على سبيل المثال، تغيير بعض النقوش، اعتمادًا على المعلمات، نحتاج إلى شروط، وما إلى ذلك. يتيح لك تنسيق JSP (صفحات JavaServer) استخدام إدراجات تعليمات برمجية Java التي يمكن من خلالها تنفيذ كل هذا. لكنني لا أرغب في استخدام كود جافا ممزوجًا بكود HTML على الصفحة. سيكون، على أقل تقدير، قبيحًا جدًا. لحسن الحظ، لحل هذه المشكلة، يوجد شيء رائع مثل JSTL (مكتبة العلامات القياسية لصفحات JavaServer) أو مكتبة العلامات القياسية 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)، نمر عبر جميع عناصر القائمة التي تم تمريرها ( filmsList)، لكل عنصر ( film) نقوم بإنشاء صف جديد ونكتب القيمة المقابلة في كل خلية. هناك نقطة واحدة هنا، يبدو أن التسجيل film.idيحتاج إلى أن يُفهم على أنه film.getId()، على سبيل المثال. لا يتم الوصول إلى الحقل مباشرةً، ولكن يتم استدعاء المُحصل. في العمود الأخير ( 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()). لكن الربيع متصل بنا لسبب ما ، لذا دعه يتحكم في هذا الأمر بنفسه. لوضع فصولنا تحت سيطرة Spring، نحتاج إلى الإشارة إلى أنها مكونات. للقيام بذلك، نقوم بتمييزها بتعليقات توضيحية خاصة:
@Repository
public class FilmDAOImpl implements FilmDAO {
@Service
public class FilmServiceImpl implements FilmService {
الشروح @Repositoryو @Service، وكذلك @Controllerمشتقة من @Component. ما هي الميزات والاختلافات المحددة لهذه التعليقات التوضيحية الثلاثة وكيف تختلف عن مكون بسيط يجب قراءتها بشكل منفصل في الوثائق أو الأدلة. في الوقت الحالي، يكفي معرفة أن هذه التعليقات التوضيحية تخبر Spring أن هذه الفئات عبارة عن مستودع وخدمة على التوالي. والآن لم نعد بحاجة إلى إنشاء كائنات ملموسة من هذه الفئات بأنفسنا:
private FilmService filmService = new FilmServiceImpl();
بدلاً من ذلك، يمكنك وضع علامة على الحقل بتعليق توضيحي خاص وسيقوم Spring بتحديد التنفيذ المناسب:
@Autowired
private FilmService filmService;
يخبر التعليق التوضيحي @Autowired(الربط التلقائي) Spring أنه يجب عليه البحث في سياقه واستبدال حبة مناسبة هنا. بشكل مريح للغاية. إذا كنا نستخدم الواجهات سابقًا حتى لا نقلق بشأن التنفيذ المحدد للطرق، فلا داعي الآن للقلق بشأن تنفيذ الواجهة نفسها وحتى معرفة اسمها. الفكرة هي أنه لا يوصى باستخدام الربط التلقائي في الحقل، فمن الأفضل استخدام مُنشئ أو مُضبط. اقرأ المزيد عن هذا في الوثائق. بالنسبة لنا، من حيث المبدأ، هذا ليس مهما، يمكننا أن نترك الأمر بأمان. لكن بما أن الفكرة تطلبها سنحترم أن كل شيء جميل وبدون أي تحذيرات صفراء. في فئة وحدة التحكم، لنقم بإنشاء أداة ضبط ونعلق عليها:
@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 وHbernate وأول تطبيق CRUD (الجزء 4)
تعليقات
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION