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

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

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

תוֹכֶן:

מבוא

התחלתי להכיר טכנולוגיות ומסגרות שהיו חדשות לי על ידי לימוד דוגמאות שונות שבהן נעשה שימוש, כי בדרך כלל אני מבין משהו הכי טוב כשאני רואה אותו בפעולה באמצעות דוגמה של אפליקציה מלאה. בדרך כלל, דוגמאות כאלה הן יישומי CRUD ( C reate, Read , Update , D elete); האינטרנט מלא בדוגמאות כאלה בדרגות שונות של מורכבות. הבעיה היא שבדרך כלל לא מסבירים בפירוט איך, מה ולמה נעשה שם, למה נוספה תלות כזו או אחרת, למה צריך מחלקה כזו או אחרת וכו'. ברוב המקרים, הם לוקחים אפליקציה גמורה לחלוטין, עם קובץ POM סופי, עם גרסאות סופיות של שיעורים, ופשוט רצים על כל אחד מהם, מבלי להתמקד בדברים הקטנים שכנראה נראים ברורים לאדם מנוסה. הסתכלתי על הרבה דוגמאות כאלה ובדרך כלל ברור איך הכל עובד, אבל איך הם הגיעו לזה לא לגמרי ברור. לכן, החלטתי שדוגמה כזו תהיה שימושית, לא מעמדה של מפתח מנוסה, אלא מעמדה של מתחיל שמעולם לא עסק ב-Spring, Hibernate ודברים אחרים.
Знакомство с Maven, Spring, MySQL, Hibernate и первое CRUD приложение (часть 1) - 2
אנסה לתאר בפירוט רב ככל האפשר (עד כמה שהבנתי מאפשרת לי) את כל הדרך שלי ליצירת אפליקציית CRUD, החל ממשהו ברמה הפשוטה ביותר של Hello World. קודם כל, אני עושה את זה בשביל עצמי, כי כשאתה מנסה לתאר, לספר, להסביר משהו, הוא הרבה יותר נספג ומאורגן בראש שלך. אבל אם זה מועיל למישהו ועוזר לו להבין משהו, אני אשמח מאוד. בדוגמה זו, בואו ננסה ליצור יישום CRUD פשוט באמצעות Maven , Tomcat , Spring , Hibernate ו- MySQL . שלבים מקדימים כמו התקנת Maven , MySQL , שימוש בגרסת ה-Ultimate של הרעיון וכו'. אני חושב שאין צורך לתאר בפירוט, לא אמורות להיות בעיות עם זה. ראוי לציין שבדוגמה זו, התצורה תוגדר באמצעות מחלקות Java (הנקראות JavaConfig) ללא שימוש ב-xml.

יצירת פרויקט

לכן, מכיוון שאני חדש, לא נשתמש בארכיטיפים לא ברורים. Spring initializr עדיין נשמע מפחיד מדי. לכן, ניצור את הפרויקט הפשוט ביותר של מייבן. אין לי שם דומיין, אז ב-groupid אני פשוט אכתוב testgroup, וב-artifactid אכתוב את השם, למשל, filmography(זו תהיה רשימה של סרטים). אנחנו יוצרים פרויקט ובוחרים Enable auto-importמתי הרעיון מציע אותו. הודות לכך, בכל פעם שנבצע שינויים כלשהם בקובץ POM (Project Object Model, קובץ זה מתאר את כל המבנה של פרויקט Maven), הכל יוחל באופן אוטומטי על הפרויקט. הספריות יילקחו מהמאגר המקומי שלנו אם כבר יש לנו אותן, או אם נשתמש בכמה תלות חדשות שלא טיפלנו בהן בעבר, Maven פשוט יוריד אותן דרך האינטרנט מהמאגר המרכזי. ל-Maven יש גם פונקציה להורדת מקורות ותיעוד (הורדת מקורות ו/או תיעוד). זה גם מאוד נוח, אם משהו לא ברור עם מחלקה או שיטה כלשהי, אתה יכול ללכת לקוד המקור ולראות איך הכל עובד בפנים. בואו נוסיף כמה פרטים. זה יהיה יישום אינטרנט ואנו נשתמש ב- Tomcat . כדי לפרוס אפליקציה ל-Tomcat, צריך להעביר אותה לשם בצורה של ארכיון מלחמה (Web Application Resource, פורמט מיוחד ליישומי אינטרנט). לשם כך, הוסף את השורה הבאה לקובץ ה-POM כך שהאפליקציה תקומפל לארכיון מלחמה:
<packaging>war</packaging>
ובכן, תצטרך גם ספרייה מיוחדת למקורות אינטרנט, במקרה שלנו יהיו דפי jsp וכמה משאבי אינטרנט. בואו ניצור mainספרייה webapp. זה צריך להיקרא בדיוק כך ולהיות ממוקם בדיוק באותו mainאופן כמו java, resourcesכי זהו מבנה הספריות הסטנדרטי של Maven. לאחר שהתקנו את החבילה פנימה warובכך קבענו שמדובר בפרויקט אינטרנטי, הספרייה webappתסומן אוטומטית כמקורות יישום אינטרנט (תהיה עליה נקודה כחולה) וכל מה שקשור לרשת יבוצע בתיקייה זו. ורגע אחד. כברירת מחדל, Maven משתמש בגרסה 1.5 של השפה, אבל אני רוצה להשתמש, למשל, בגרסה 1.8 - Java 8 (אתה יכול לקחת 10, או 11, אבל עדיין אין תוכניות להשתמש בתכונות כלשהן משם, אז שיהיה 8 ). אפשר לפתור את זה בפשטות רבה, אנחנו כותבים בגוגל משהו כמו "Maven java 8" ורואים מה צריך להוסיף לקובץ ה-POM כדי שמייבן תרכיב את השיעורים שלנו לגרסה הנדרשת. כתוצאה מכך, יש לנו את הדברים הבאים: Знакомство с Maven, Spring, MySQL, Hibernate и первое CRUD приложение (часть 1) - 3

חיבור MVC קפיצי

אתה צריך להתחיל איפשהו. לפי התוכנית, נחבר את מסד הנתונים ונשתמש ב-Hibernate, אבל כל זה נשמע קצת מפחיד מדי לעת עתה. קודם כל צריך לעשות משהו פשוט יותר. אביב MVC, זה כבר יותר טוב, אנחנו מכירים את דפוס ה-MVC כבר הרבה זמן, הוא שימש בחצי מהמשימות הגדולות של הקורס. מכאן נתחיל לרקוד. כדי ליצור אפליקציית אינטרנט עם Spring MVC, אנחנו צריכים גם Servlet-API, כלומר. הדבר הזה שבעזרתו תתקיים האינטראקציה של בקשה-תגובה. בואו ננסה לחבר את זה. אנחנו הולכים לגוגל, מחפשים את התלות הדרושות במאגר Maven ומוסיפים אותם ל- pom.xml. Знакомство с Maven, Spring, MySQL, Hibernate и первое CRUD приложение (часть 1) - 4בסעיף ספריות חיצוניות אתה יכול לראות שלא רק spring-webmvc נטען , אלא גם המון דברים אחרים. הָהֵן. אנחנו לא צריכים לכלול בנוסף תלות עבור ליבת קפיץ , הקשר , שעועית וכו'. שאנחנו צריכים, כל מה שהיינו צריכים נשלף יחד עם spring-webmvc .

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

דוגמה פשוטה. נניח שהוספנו תלות שמשתמשת ב-API כלשהו, ​​ובמקביל היא תעלה איזשהו יישום עבור ה-API הזה. ואז הוספנו עוד תלות שמשתמשת באותו API וגם מושכת חלק מהיישום שלו בשביל זה, אבל הפעם זה שונה. לפיכך, יהיו לנו 2 יישומים שונים של אותו API. ואם אנחנו בעצמנו רוצים להשתמש בכמה שיטות של ה-API הזה איפשהו, אז תתעורר בעיה, כי המערכת לא תדע באיזה יישום להשתמש, היא תבחר באקראי, אולי לא את זו שציפינו. ואם תציין במפורש תלות לאחד מהיישומים, אז תינתן לה עדיפות.

עם זאת, זו לא המלצה כה מחמירה; היא חלה בעיקר על פרויקטים גדולים שבהם נעשה שימוש בספריות רבות ושונות מחברות שונות. לא נעשה זאת כאן, כדי לא לטעון את קובץ POM יותר מדי; לא צפויות בעיות. אבל בכל זאת, עדיין כדאי לזכור זאת.

עוד הערה אחת. מה זאת אומרת providedתלוי javax.servlet-api? היקף הוא היקף התלות, providedכלומר התלות תהיה זמינה בשלב הקומפילציה ובדיקת האפליקציה, אך היא לא תישמר בארכיון. העובדה היא שכדי לפרוס את האפליקציה נשתמש בקונטיינר של servlet, Tomcat, וכבר יש לה ספריות כאלה בפנים, כך שאין צורך להעביר אותן לשם ולהעמיס על הארכיון עומס מיותר. במבט קדימה, מאותה סיבה נסתדר בלי השיטה הרגילה main, כי היא כבר קיימת בתוך Tomcat.

יצירת דפים ובקר

בואו ננסה לבשל משהו פשוט עכשיו. ראשית, בואו ניצור webappספרייה נוספת, למשל pages, בה יישמרו התצוגות שלנו, כלומר. דפי jsp, וצור כמה דפים. נצטרך דף שבו בעתיד תוצג רשימה של סרטים, למשל films.jsp, ואולי נוכל לעשות דף נפרד לעריכה, שיהיה editPage.jsp. לא נמלא אותם בשום דבר רציני לעת עתה; רק לצורך בדיקה, ניצור קישור בעמוד אחד לאחר. עכשיו אנחנו צריכים מחלקה שתעבד בקשות, כלומר. בקר. בואו נוסיף חבילה חדשה controllerוניצור בה מחלקה FilmController(באופן כללי, אין צורך לארוז הכל בחבילות שונות, האפליקציה הזו תהיה מאוד קטנה ופשוטה, אבל בפרויקט רגיל יכולים להיות הרבה בקרים, מחלקות תצורה, דגמים וכו', אז גם אם מתחילים בפרויקטים קטנים, עדיף מיד להתרגל לעשות הכל בצורה מסודרת ומובנית כדי שלא יהיה בלגן). בשיעור זה ניצור שיטות שיחזירו את דעותינו בתגובה לבקשות.
package testgroup.filmography.controller;

import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.servlet.ModelAndView;

@Controller
public class FilmController {

    @RequestMapping(value = "/", method = RequestMethod.GET)
    public ModelAndView allFilms() {
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.setViewName("films");
        return modelAndView;
    }

    @RequestMapping(value = "/edit", method = RequestMethod.GET)
    public ModelAndView editPage() {
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.setViewName("editPage");
        return modelAndView;
    }
}
מה הטעם? לאביב MVC יש דבר שנקרא DispatcherServlet. זה כמו הבקר הראשי, כל הבקשות הנכנסות עוברות דרכו ואז הוא מעביר אותן לבקר ספציפי. ההערה @Controllerרק אומרת ל-Spring MVC שהמחלקה הזו היא בקר (טוב, הגיוני באופן כללי), השולח יבדוק את ההערות @RequestMappingכדי לקרוא לשיטה המתאימה. ההערה @RequestMappingמאפשרת לציין כתובות לשיטות בקר, שבאמצעותן הן יהיו זמינות בלקוח (דפדפן). ניתן להחיל אותו גם על מחלקת הבקר כדי להגדיר, כביכול, את כתובת השורש עבור כל השיטות. allFilms()הפרמטר של השיטה valueמוגדר ל- " /", כך שהוא ייקרא מיד כאשר השילוב http://host:port/ יוזן בדפדפן (כלומר, כברירת מחדל זה http://localhost:8080/ או http ://127.0 .0.1:8080/ ). הפרמטר methodמציין איזה סוג בקשה נתמך (GET, POST, PUT וכו'). מכיוון שכאן אנו מקבלים רק נתונים, נעשה שימוש ב-GET. מאוחר יותר, כשיופיעו שיטות הוספה ועריכה, כבר יהיו בקשות POST. (אגב, במקום ביאור @RequestMappingהמציין שיטה, ניתן להשתמש בהערות וכו' @GetMappingבאופן שווה ערך )). בשיטות שלנו, אנו יוצרים אובייקט וקובעים את שם התצוגה שצריך להחזיר. @PostMapping@GetMapping@RequestMapping(method = RequestMethod.GETModelAndView

תְצוּרָה

בואו נעבור להגדרת התצורה. configבואו ניצור מחלקה בחבילה WebConfig. תהיה לו רק שיטה אחת שתחזיר אובייקט מסוג ViewResolver, זהו הממשק הדרוש כדי למצוא ייצוג לפי שם.
package testgroup.filmography.config;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.ViewResolver;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.view.InternalResourceViewResolver;

@Configuration
@EnableWebMvc
@ComponentScan(basePackages = "testgroup.filmography")
public class WebConfig {

    @Bean
    ViewResolver viewResolver() {
        InternalResourceViewResolver viewResolver = new InternalResourceViewResolver();
        viewResolver.setPrefix("/pages/");
        viewResolver.setSuffix(".jsp");
        return viewResolver;
    }
}
@Configurationאומר ל-Spring שמחלקה זו היא מחלקת תצורה ומכילה את ההגדרות והתלות beanשל הרכיבים. שעועית היא חפצים המנוהלים על ידי אביב. ההערה משמשת להגדרת שעועית @Bean. @EnableWebMvcמאפשר לך לייבא את תצורת Spring MVC מה- WebMvcConfigurationSupport. אפשר גם ליישם, למשל, ממשק WebMvcConfigurerשיש בו שלל שיטות, ולהתאים הכל לפי טעמכם, אבל אנחנו לא צריכים להיכנס לזה עדיין, ההגדרות הסטנדרטיות יספיקו. @ComponentScanאומר לאביב היכן לחפש את הרכיבים שהוא צריך לנהל, כלומר. מחלקות המסומנות בהערה @Componentאו בנגזרות שלה כגון @Controller, @Repository, @Service. הערות אלו מגדירות אוטומטית את שעועית המחלקה. בשיטה viewResolver()אנו יוצרים את היישום שלה וקובעים היכן בדיוק לחפש ייצוגים ב webapp. לכן, כאשר בשיטת הבקר אנו מגדירים את השם " films" התצוגה תימצא כ" /pages/films.jsp" אז, יש לנו מחלקת תצורה, אבל כרגע זה רק סוג של מחלקה נפרדת, זה לא משפיע על האפליקציה שלנו בשום אופן . עלינו לרשום את התצורה הזו בהקשר האביב. בשביל זה אתה צריך שיעור AbstractAnnotationConfigDispatcherServletInitializer. בחבילה, configאנו יוצרים את היורש שלו, נגיד AppInitializer , ומיישמים את השיטות שלו.
package testgroup.filmography.config;

import org.springframework.web.servlet.support.AbstractAnnotationConfigDispatcherServletInitializer;

public class AppInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {
    @Override
    protected Class<?>[] getRootConfigClasses() {
        return new Class[0];
    }

    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class[]{WebConfig.class};
    }

    @Override
    protected String[] getServletMappings() {
        return new String[]{"/"};
    }
}
השיטה האחרונה רושמת כתובות ויש עוד 2 שיטות לרישום מחלקות תצורה. תצורות אינטרנט, שבהן ViewResolverמוגדרים 's וכדומה', ממוקמות ב- getServletConfigClasses(). עדיף לקרוא על כל זה בתיעוד ובמדריכים השונים, אבל במקרה שלנו אין צורך להתעמק בזה עדיין, שלנו, WebConfigעקרונית, אפשר RootClassesלהגדיר בשניהם, אפשר אפילו להגדיר את שניהם בבת אחת, זה עדיין יעבוד . עוד דבר אחד. ייתכנו בעיות בקידוד כאשר, בעת שליחת ערכים עם תווים רוסיים מהטופס, התוצאה תהיה שרבוטים. כדי לפתור בעיה זו, נוסיף מסנן שיעבד בקשות מראש. אנו עוברים למחלקה AppInitializer ומחליפים את השיטה getServletFilters, שבה אנו מציינים את הקידוד הרצוי, היא כמובן צריכה להיות זהה לכל מקום אחר, כמו בדפים ובבסיס הנתונים:
protected Filter[] getServletFilters() {
        CharacterEncodingFilter characterEncodingFilter = new CharacterEncodingFilter();
        characterEncodingFilter.setEncoding("UTF-8");
        characterEncodingFilter.setForceEncoding(true);
        return new Filter[] {characterEncodingFilter};
    }
ובכן, נראה שהכל מוגדר, אתה יכול לנסות להפעיל אותו ולראות מה קורה. הפעלה -> הפעלה -> ערוך תצורות -> הוסף תצורה חדשה -> שרת Tomcat -> מקומי הבא, עליך לבחור חפץ לפריסה. הרעיון עצמו ייתן רמז אזהרה: אין חפצים מסומנים לפריסה . לחץ על כפתור התיקון ובחר ...: המלחמה התפוצצה . או שאתה יכול ללכת לפריסה -> הוסף -> חפץ -> ...: מלחמה התפוצצה . Знакомство с Maven, Spring, MySQL, Hibernate и первое CRUD приложение (часть 1) - 5ואתה גם צריך ללכת ל- Deployment ולהגדיר את שדה ההקשר של Applecation (זה יהיה חלק מכתובת ה-URL שבה היישום יהיה זמין בדפדפן) ל- " /". Знакомство с Maven, Spring, MySQL, Hibernate и первое CRUD приложение (часть 1) - 6אז האפליקציה שלנו תהיה זמינה מיידית בכתובת http://localhost:8080/ (אבל אתה יכול גם לציין משהו שם, למשל " /filmography", ואז רק תצטרך להוסיף את זה לכל הכתובות, כלומר למשל לא יהיה לא " http://localhost:8080/edit" , אבל זה יהיה "http://localhost:8080/filmography/edit" ). לחץ על הפעל והמתן עד שהוא יתחיל. הנה מה שקיבלתי: Знакомство с Maven, Spring, MySQL, Hibernate и первое CRUD приложение (часть 1) - 7הכל נראה בסדר, אבל יש אזהרה אחת. העובדה היא שהדפים שלנו כעת נגישים לציבור וניתן לגשת אליהם ישירות על ידי כתיבת הנתיב בשורת הכתובת. אנו נכנסים http://localhost:8080/pages/films.jsp ועכשיו קיבלנו את הדף שלנו ללא ידיעת הבקר. איכשהו זה לא מאוד נכון, אז ניצור webappספרייה מיוחדת WEB-INF. מה שיש בפנים יוסתר מהציבור וניתן לגשת אליו רק דרך בקר. אנו ממקמים את הספרייה עם התצוגות שלנו ( pages) ב- WEB-INF, ובהתאם ViewResolverלכך מוסיפים אותה לקידומת:
viewResolver.setPrefix("/WEB-INF/pages/");
כעת אנו מקבלים את הדף שלנו בכתובת http://localhost:8080 , אבל אם ננסה ישירות אל http://localhost:8080/WEB-INF/pages/films.jsp נקבל שגיאה 404. ובכן, נהדר, יש לנו את יישום האינטרנט הפשוט ביותר, Hello World כמו שאומרים. מבנה הפרויקט נראה כרגע כך:
Знакомство с Maven, Spring, MySQL, Hibernate и первое CRUD приложение (часть 1) - 8

דֶגֶם

יש לנו כבר תצוגות ובקר, אבל ב-MVC יש גם אות 3, אז כדי להשלים את התמונה נוסיף גם דגם. בחבילה, modelבואו ניצור מחלקה Film, למשל, עם השדות הבאים: int id, String title(כותרת), int year(שנת יציאה), String genre(ז'אנר) ו- boolean watched(כלומר, האם כבר צפית בסרט הזה או לא).
package testgroup.filmography.model;

public class Film {
    private int id;
    private String title;
    private int year;
    private String genre;
    private boolean watched;
// + Getters and setters
}
שום דבר מיוחד, רק כיתה רגילה, שדות פרטיים, מגדירים וקובעים. אובייקטים של מחלקות כאלה נקראים גם POJO(Plain Old Java Object), ובכן, כלומר. "אובייקט ג'אווה פשוט". כעת ננסה ליצור אובייקט כזה ולהציג אותו בעמוד. לעת עתה, לא נדאג יותר מדי כיצד ליצור אותו ולאתחל אותו. כדי לנסות את זה, בואו פשוט ניצור את זה בצורה טיפשית ישירות בבקר, למשל, כך:
public class FilmController {
    private static Film film;

    static {
        film = new Film();
        film.setTitle("Inception");
        film.setYear(2010);
        film.setGenre("sci-fi");
        film.setWatched(true);
    }
והוסיפו את האובייקט הזה לשלנו ModelAndViewבאמצעות השיטה addObject:
@RequestMapping(method = RequestMethod.GET)
    public ModelAndView allFilms() {
        ModelAndView modelAndView = new ModelAndView();
        modelAndView.setViewName("films");
        modelAndView.addObject("film", film);
        return modelAndView;
    }
כעת אנו יכולים להציג את האובייקט הזה בדף שלנו. במקום films.jspHello World נכתוב ${film}והאובייקט המתאים לשם התכונה " film" יוחלף כאן. בואו ננסה להפעיל אותו ולראות מה קרה (לצורך פלט ברור של האובייקט, המחלקה Filmהוגדרה מחדש toString()):
Знакомство с Maven, Spring, MySQL, Hibernate и первое CRUD приложение (часть 1) - 9

דגם-View-Controller

בשלב זה, נראה שכבר יש לנו אפליקציית Spring MVC מלאה. לפני שתמשיך הלאה, יהיה טוב להסתכל שוב על הכל ולהבין איך הכל עובד. באינטרנט אתה יכול למצוא הרבה תמונות ודיאגרמות על זה, אני אוהב את זה:
Знакомство с Maven, Spring, MySQL, Hibernate и первое CRUD приложение (часть 1) - 10
כאשר אנו כותבים בקשה בשורת הדפדפן, היא מתקבלת Dispatcher Servlet, ואז הוא מוצא בקר מתאים לעבד את הבקשה באמצעותו HandlerMapping(זהו ממשק לבחירת בקר, בודק למי מהבקרים הזמינים יש שיטה שמקבלת כתובת כזו) , קורא לשיטה מתאימה ומחזיר Controllerמידע על התצוגה, ואז השולח מוצא את התצוגה הרצויה לפי השם באמצעות ViewResolver'a, ולאחר מכן נתוני הדגם מועברים לתצוגה זו ונקבל את הדף שלנו כפלט. משהו כזה. להמשך... היכרות עם 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