13. [Cours]: نمایانسازی یک پایگاه داده در وب با Spring MVC
کلیدواژهها: معماری چندلایه، اسپرینگ، تزریق وابستگی، سرویس وب / jSON، کلاینت / سرور
13.1. Support
![]() | ![]() |
پروژههای این فصل را میتوانید در پوشه [support / chap-13] بیابید. اسکریپت SQL [dbintrospringdata.sql] به شما امکان میدهد پایگاه داده MySQL مورد نیاز برای تست را ایجاد کنید.
13.2. نقش Spring MVC در یک برنامه وب
بیایید نقش Spring MVC را در توسعه یک برنامه وب در نظر بگیریم. در بیشتر موارد، این برنامه بر روی یک معماری چندلایه مانند زیر ساخته میشود:
![]() |
- لایه [Web] لایهای است که با کاربر وباپلیکیشن تعامل دارد. کاربر از طریق صفحات وبی که در یک مرورگر نمایش داده میشوند، با وباپلیکیشن تعامل میکند. Spring MVC در این لایه قرار دارد، و تنها در این لایه؛
- لایه [métier] قواعد کسبوکار برنامه را پیادهسازی میکند، مانند محاسبه حقوق یا فاکتور. این لایه دادهها را از کاربر از طریق لایه [Web] و از لایه SGBD از طریق لایه [DAO] استفاده میکند؛
- لایه [DAO] (ابجکتهای دسترسی به داده)، لایه [ORM] (نقشهبردار شیء-رابطهای) و درایور JDBC دسترسی به دادهها را از لایه SGBD مدیریت میکنند. لایه [ORM] بهعنوان پلی میان اشیایی که توسط لایه [DAO] مدیریت میشوند و سطر و ستونهای جدولها در یک پایگاه داده رابطهای عمل میکند. مقررات JPA (Java Persistence API) اجازه میدهد تا از ORM در حال استفاده انتزاع شود، به شرط آنکه آن را پیادهسازی کند. در اینجا نیز همینطور خواهد بود و از این پس ما به لایه ORM به عنوان لایه JPA اشاره خواهیم کرد؛
- ادغام لایهها توسط فریمورک Spring انجام میشود؛
13.3. مدل توسعه Spring MVC
اسپرینگ MVC الگوی معماری موسوم به MVC (مدل–نما–کنترلکننده) را به شرح زیر پیادهسازی میکند:
![]() |
پردازش یک درخواست مشتری به شرح زیر انجام میشود:
- درخواست – URLهای درخواستی به شکل http://machine:port/contexte/Action/param1/param2/....?p1=v1&p2=v2&... هستند. [Front Controller] از یک فایل پیکربندی یا anotationهای جاوا برای «مسیردهی» درخواست به کنترلکننده صحیح و اکشن صحیح در آن کنترلکننده استفاده میکند. برای این کار، از فیلد [Action] در URL استفاده میکند. باقیمانده URL و [/param1/param2/...] شامل پارامترهای اختیاری است که به اکشن ارسال خواهند شد. C در MVC در این مورد، رشته [Front Controller, Contrôleur, Action] است. اگر هیچ کنترولری نتواند اقدام درخواستی را مدیریت کند، سرور وب پاسخ خواهد داد که URL درخواستی یافت نشد.
- پردازش
- عمل انتخابشده میتواند از پارامترهای parami که توسط [Front Controller] به آن ارسال شدهاند، استفاده کند. این پارامترها ممکن است از چندین منبع منشأ بگیرند:
- مسیر [/param1/param2/...] از URL،
- پارامترهای [p1=v1&p2=v2] از URL,
- پارامترهای ارسالشده توسط مرورگر در درخواست آن؛
- هنگام پردازش درخواست کاربر، ممکن است اقدام به لایههای [métier] و [2b] نیاز داشته باشد. پس از پردازش درخواست مشتری، ممکن است پاسخهای مختلفی ایجاد شود. یک مثال کلاسیک این است:
- یک صفحهٔ خطا اگر درخواست نتوانست بهدرستی پردازش شود
- در غیر این صورت، یک صفحه تأیید
- اقدام دستور نمایش یک نمای مشخص را صادر میکند [3]. این نما دادههایی را که به عنوان مدل نما شناخته میشوند، نمایش میدهد. این همان M در MVC است. اقدام این مدل M [2c] را ایجاد کرده و دستور نمایش یک نما V [3] را صادر میکند؛
- پاسخ – نمای انتخابشده V از مدل M ساختهشده توسط اقدام برای راهاندازی بخشهای پویا از پاسخی که باید به کلاینت ارسال کند استفاده میکند، و سپس این پاسخ را ارسال میکند.
برای یک سرویس وب / jSON، معماری قبلی کمی تغییر یافته است:
![]() |
- در [4a]، قالب—که یک کلاس جاوا است—توسط یک کتابخانه jSON به یک رشته jSON تبدیل میشود؛
- در [4b]، این رشته jSON به مرورگر ارسال میشود؛
اکنون، بیایید رابطه بین معماری وب MVC و معماری لایهلایه را روشن کنیم. بسته به نحوه تعریف مدل، این دو مفهوم ممکن است مرتبط باشند یا نباشند. بیایید یک برنامه وب تکلایه Spring، MVC را در نظر بگیریم:
![]() |
اگر لایه [Web] را با استفاده از Spring MVC پیادهسازی کنیم، در واقع یک معماری وب MVC خواهیم داشت، اما یک معماری چندلایه نخواهیم داشت. در اینجا لایه [web] همه چیز را مدیریت میکند: ارائه، منطق کسبوکار و دسترسی به دادهها. این اکشنها هستند که این کار را انجام میدهند.
اکنون، بیایید یک معماری وب چندلایه را در نظر بگیریم:
![]() |
لایه [Web] را میتوان بدون فریمورک و بدون پیروی از مدل MVC پیادهسازی کرد. بنابراین ما در واقع یک معماری چندلایه داریم، اما لایه وب مدل MVC را پیادهسازی نمیکند.
برای مثال، در محیط .NET، لایه [Web]میتواند با استفاده از ASP.NET و MVC پیادهسازی شود که منجر به یک معماری لایهای با یک لایه [Web] از نوع MVC میشود. پس از انجام این کار، این لایه ASP.NET MVC میتواند با یک لایه استاندارد ASP.NET (WebForms) جایگزین شود در حالی که بقیه حفظ میشوند (لایه کسبوکار، DAO، ORM) دقیقاً همانطور که هست. سپس ما یک معماری لایهای داریم که در آن لایه [Web] دیگر از نوع MVC نیست.
در MVC بیان کردیم که مدل M همان مدل نما V است، c.a.d – مجموعهای از دادههایی که توسط نما V نمایش داده میشوند. تعریف دیگری از مدل M برای MVC ارائه شده است:
![]() |
بسیاری از نویسندگان معتقدند که آنچه در سمت راست لایه [Web] قرار دارد، مدل M از MVC را تشکیل میدهد. برای جلوگیری از ابهام، میتوان به موارد زیر اشاره کرد:
- مدل دامنه وقتی به کل بخش سمت راست لایه [Web] اشاره میشود
- مدل نماهنگامی که به دادههای نمایشدادهشده توسط یک نما V اشاره میشود
از این پس، اصطلاح «مدل M» منحصراً به مدل یک نما V اشاره خواهد داشت.
13.4. یک پروژه وب / jSON با استفاده از Spring MVC
وبسایت [http://spring.io/guides] آموزشهای شروع کار را برای کمک به شما در کاوش اکوسیستم Spring ارائه میدهد. ما یکی از این آموزشها را دنبال خواهیم کرد تا پیکربندی Maven مورد نیاز برای یک پروژه Spring MVC را کشف کنیم.
13.4.1. پروژهٔ نمایشی
![]() |
- در [1]، یکی از راهنماهای Spring را وارد میکنیم؛
![]() |
- در [2]، مثال [Rest Service] را انتخاب میکنیم؛
- در [3]، پروژه Maven را انتخاب میکنیم؛
- در [4]، نسخه نهایی راهنما را انتخاب میکنیم؛
- در [5] تأیید میکنیم؛
- در [6]، پروژه واردشده؛
خدمات وبی که از طریق استاندارد URL قابل دسترسی هستند و متن jSON را بازمیگردانند، اغلب به عنوان خدمات REST (انتقال وضعیت REpresentational) شناخته میشوند. یک سرویس زمانی RESTful نامیده میشود که از قوانین خاصی پیروی کند.
حال بیایید پروژهٔ واردشده را بررسی کنیم، با پیکربندی Maven آن شروع میکنیم.
13.4.2. پیکربندی Maven
فایل [pom.xml] به شرح زیر است:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.springframework</groupId>
<artifactId>gs-rest-service</artifactId>
<version>0.1.0</version>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.2.2.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
<properties>
<start-class>hello.Application</start-class>
</properties>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<repositories>
<repository>
<id>spring-releases</id>
<url>https://repo.spring.io/libs-release</url>
</repository>
</repositories>
<pluginRepositories>
<pluginRepository>
<id>spring-releases</id>
<url>https://repo.spring.io/libs-release</url>
</pluginRepository>
</pluginRepositories>
</project>
- خطوط ۶–۸: ویژگیهای پروژه Maven. یک برچسب [<packaging>] که نوع فایلی را که توسط ساخت Maven تولید میشود مشخص میکند، وجود ندارد. در غیاب آن، نوع [jar] استفاده میشود. بنابراین این برنامه یک برنامهٔ اجرایی کنسولی است و نه یک برنامهٔ وب، که در این صورت بستهبندی آن [war] خواهد بود؛
- خطوط ۱۰–۱۴: پروژه Maven یک پروژه والد به نام [spring-boot-starter-parent] دارد. این پروژه بیشتر وابستگیهای پروژه را تعریف میکند. این وابستگیها ممکن است کافی باشند، که در این صورت هیچ وابستگی دیگری اضافه نمیشود، یا ممکن است کافی نباشند، که در این صورت وابستگیهای missing اضافه میشوند؛
- خطوط 17–20: آرتفکت [spring-boot-starter-web] شامل کتابخانههای مورد نیاز برای یک پروژه Spring (MVC) از نوع سرویس وب است که در آن هیچ ویوی (view) تولید نمیشود. این آرتیفکت شامل تعداد بسیار زیادی کتابخانه، از جمله کتابخانههای مربوط به یک سرور Tomcat تعبیهشده است. این اپلیکیشن بر روی همین سرور اجرا خواهد شد؛
کتابخانههای گنجاندهشده در این پیکربندی بسیار زیاد هستند:
![]() | ![]() |
در بالا سه آرشیو مربوط به سرور Tomcat آمده است.
13.4.3. معماری یک سرویس Spring [web / jSON]
برای یک سرویس وب / jSON، Spring MVC مدل MVC را به شرح زیر پیادهسازی میکند:
![]() |
- در [4a]، مدل — که یک کلاس جاوا است — توسط کتابخانهای jSON به رشته jSON تبدیل میشود؛
- در [4b]، این رشته jSON به مرورگر ارسال میشود؛
13.4.4. C
![]() |
برنامه واردشده دارای کنترلر زیر است:
package hello;
import java.util.concurrent.atomic.AtomicLong;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
@RestController
public class GreetingController {
private static final String template = "Hello, %s!";
private final AtomicLong counter = new AtomicLong();
@RequestMapping("/greeting")
public Greeting greeting(@RequestParam(value = "name", defaultValue = "World") String name) {
return new Greeting(counter.incrementAndGet(), String.format(template, name));
}
}
- خط ۹: تگ [@RestController] کلاس [GreetingController] را بهعنوان یک کنترلر Spring مشخص میکند، یعنی متدهای آن برای رسیدگی به URL ثبت شدهاند. ما anotation مشابه [@Controller] را دیدهایم. نوع بازگشتی متدهای این کنترلر [String] بود که نام view قابل نمایش بود. در اینجا، متفاوت است. متدهای یک کنترلر از نوع [@RestController] اشیایی را بازمیگردانند که برای ارسال به مرورگر سریالیسازی میشوند. نوع سریالیسازی انجامشده به پیکربندی Spring برای MVC بستگی دارد. در اینجا، آنها به صورت jSON سریالیزه خواهند شد. وجود یک کتابخانه jSON در میان وابستگیهای پروژه باعث میشود که Spring Boot به طور خودکار پروژه را به این شکل پیکربندی کند؛
- خط ۱۴: آناوتیشن [@RequestMapping]، URL را که توسط متد مدیریت میشود، مشخص میکند، در این مورد URL [/greeting]؛
- خط ۱۵: ما قبلاً توضیحات متا [@RequestParam] را توضیح دادهایم. نتیجهای که توسط این متد بازگردانده میشود، یک شیء از نوع [Greeting] است.
- خط ۱۲: یک عدد صحیح بلند از نوع اتمی. این بدان معناست که از دسترسی همزمان پشتیبانی میکند. چندین نخ ممکن است بخواهند همزمان مقدار متغیر [counter] را افزایش دهند. این موضوع بهدرستی مدیریت خواهد شد. یک نخ تنها پس از آنکه نخ در حال تغییر آن، عملیات خود را تکمیل کرد، میتواند مقدار شمارشگر را بخواند.
13.4.5. مدل M
مدل M تولید شده توسط روش قبلی، شیء زیر [Greeting] است:
![]() |
package hello;
public class Greeting {
private final long id;
private final String content;
public Greeting(long id, String content) {
this.id = id;
this.content = content;
}
public long getId() {
return id;
}
public String getContent() {
return content;
}
}
تبدیل jSON این شیء، رشتهٔ کاراکتری {"id":n,"content":"text"} را ایجاد خواهد کرد. در نهایت، رشتهای که توسط متد کنترلکننده تولید میشود، به شکل زیر خواهد بود:
یا
13.4.6. اجرا
![]() |
کلاس [Application.java] کلاس اجرایی پروژه است. کد آن به شرح زیر است:
package hello;
import org.springframework.boot.autoconfigure.EnableAutoConfiguration;
import org.springframework.boot.SpringApplication;
import org.springframework.context.annotation.ComponentScan;
@ComponentScan
@EnableAutoConfiguration
public class Application {
public static void main(String[] args) {
SpringApplication.run(Application.class, args);
}
}
ما قبلاً این کد را در مثال قبلی دیدهایم و توضیح دادهایم.
13.4.7. اجرای پروژه
بیایید پروژه را اجرا کنیم:
![]() |
خروجیهای کنسول زیر تولید میشوند:
- خط ۱۳: سرور Tomcat روی پورت ۸۰۸۰ شروع به کار میکند (خط ۱۲);
- خط ۱۷: سرولت [DispatcherServlet] موجود است؛
- خط ۲۰: متد [GreetingController.greeting] کشف شده است؛
برای آزمایش برنامه وب، ما URL و [http://localhost:8080/greeting] را درخواست میکنیم:
![]() | ![]() |
ما در واقع رشتهٔ مورد انتظار jSON را دریافت میکنیم. ممکن است جالب باشد که به سربرگهای HTTP ارسالشده توسط سرور نگاهی بیندازیم. برای انجام این کار، از افزونه کروم به نام [Advanced Rest Client] استفاده خواهیم کرد (Chrome / Ctrl-T / منوی [Applications] / [Advanced Rest Client] – به پیوستها، بند 22.5 مراجعه کنید):
![]() |
- در [1]، URL درخواستشده؛
- در [2]، از روش GET استفاده میشود؛
- در [3]، پاسخ jSON است؛
- در [4]، سرور اعلام کرد که در حال ارسال پاسخی با فرمت jSON است؛
- در [5]، همان URL درخواست میشود، اما این بار با POST؛
- در [7]، اطلاعات به شکل [urlencoded] به سرور ارسال میشود؛
- در [6]، پارامتر 'name' به همراه مقدار آن؛
- در [8]، مرورگر به سرور اطلاع میدهد که در حال ارسال اطلاعات [urlencoded] برای آن است؛
- در [9]، پاسخ سرور jSON;
13.4.8. ایجاد یک آرشیو قابل اجرا
اکنون یک آرشیو قابل اجرا ایجاد خواهیم کرد:
![]() |
![]() |
- در [1]: ما یک هدف Maven را اجرا میکنیم؛
- در [2]: دو هدف وجود دارد: [clean] برای حذف پوشه [target] از پروژه Maven، و [package] برای بازتولید آن؛
- در [3]: پوشهٔ تولیدشدهٔ [target] در این پوشه ایجاد خواهد شد؛
- در [4]: هدف تولید میشود؛
در لاگهای نمایشدادهشده در کنسول، مهم است که پلاگین [spring-boot-maven-plugin] را ببینید. این پلاگینی است که آرشیو قابل اجرا را تولید میکند.
با استفاده از کنسول، به پوشهٔ تولیدشده بروید:
D:\Temp\wksSTS\gs-rest-service\target>dir
...
11/06/2014 15:30 <DIR> classes
11/06/2014 15:30 <DIR> generated-sources
11/06/2014 15:30 11 073 572 gs-rest-service-0.1.0.jar
11/06/2014 15:30 3 690 gs-rest-service-0.1.0.jar.original
11/06/2014 15:30 <DIR> maven-archiver
11/06/2014 15:30 <DIR> maven-status
...
- خط ۵: آرشیو تولید شده؛
این آرشیو به شرح زیر اجرا میشود:
D:\Temp\wksSTS\gs-rest-service-complete\target>java -jar gs-rest-service-0.1.0.jar
. ____ _ __ _ _
/\\ / ___'_ __ _ _(_)_ __ __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
\\/ ___)| |_)| | | | | || (_| | ) ) ) )
' |____| .__|_| |_|_| |_\__, | / / / /
=========|_|==============|___/=/_/_/_/
:: Spring Boot :: (v1.1.0.RELEASE)
2014-06-11 15:32:47.088 INFO 4972 --- [ main] hello.Application
: Starting Application on Gportpers3 with PID 4972 (D:\Temp\wk
sSTS\gs-rest-service-complete\target\gs-rest-service-0.1.0.jar started by ST in
D:\Temp\wksSTS\gs-rest-service-complete\target)
...
اکنون که برنامه وب راهاندازی شده است، میتوانید با استفاده از یک مرورگر به آن دسترسی پیدا کنید:
![]() |
13.4.9. راهاندازی برنامه روی سرور Tomcat
همانند پروژه قبلی، فایل [pom.xml] را به شرح زیر اصلاح میکنیم:
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>org.springframework</groupId>
<artifactId>gs-rest-service</artifactId>
<version>0.1.0</version>
<packaging>war</packaging>
...
</project>
- خط ۹: باید مشخص کنیم که قصد داریم یک آرشیو WAR (وب ARchive) ایجاد کنیم؛
برنامهٔ وب نیز باید پیکربندی شود. در صورت عدم وجود فایل [web.xml]، این کار با استفاده از کلاسی که از [SpringBootServletInitializer] ارث میبرد، انجام میشود:
![]() |
کلاس [ApplicationInitializer] به شرح زیر است:
package hello;
import org.springframework.boot.builder.SpringApplicationBuilder;
import org.springframework.boot.context.web.SpringBootServletInitializer;
public class ApplicationInitializer extends SpringBootServletInitializer {
@Override
protected SpringApplicationBuilder configure(SpringApplicationBuilder application) {
return application.sources(Application.class);
}
}
- خط ۶: کلاس [ApplicationInitializer] از کلاس [SpringBootServletInitializer] ارث میبرد؛
- خط ۹: متد [configure] بازتعریف شده است (خط ۸);
- خط ۱۰: کلاسی را که پروژه را پیکربندی میکند، مشخص کنید؛
برای اجرای پروژه، مراحل زیر را دنبال کنید:
![]() |
- در [1-2]، پروژه را روی یکی از سرورهای ثبتشده در IDE در Eclipse اجرا کنید؛
پس از انجام این کار، میتوانید URL [http://localhost:8080/gs-rest-service/greeting/?name=Mitchell] را در یک مرورگر درخواست کنید:
![]() |
13.4.10. نتیجهگیری
ما نوعی پروژه Spring را معرفی کردهایم که در آن اپلیکیشن وب یک جریان را به مرورگر ارسال میکند. اکنون یک برنامه وب / jSON توسعه خواهیم داد تا پایگاه داده [dbintrospringdata]، که در آموزش [Introduction à Spring Data] پوشش داده شده است، را در وب در دسترس قرار دهیم.
13.5. در دسترس قرار دادن پایگاه داده [dbintrospringdata] در وب
13.5.1. معماری سرویس وب / jSON
ما معماری زیر را پیادهسازی خواهیم کرد:
![]() |
لایههای [DAO] و [JPA] توسط برنامهای که در آموزش [Introduction à Spring Data] نوشته شده است، پیادهسازی شدهاند.
13.5.2. نصب پایگاه داده
![]() |
اسکریپت SQL [dbintrospringdata.sql] پایگاه داده MySQL مورد نیاز برای آزمایش را ایجاد میکند.
13.5.3. پروژه اکلیپس برای سرویس وب / jSON
پروژه اکلیپس برای سرویس وب / jSON به شرح زیر است:
![]() |
این یک پروژه Maven است که فایل [pom.xml] آن به شرح زیر است:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>istia.st.webjson</groupId>
<artifactId>intro-server-webjson01</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>intro-server-webjson01</name>
<description>démo spring mvc</description>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.2.7.RELEASE</version>
</parent>
<dependencies>
<dependency>
<groupId>istia.st.springdata</groupId>
<artifactId>intro-spring-data-01</artifactId>
<version>0.0.1-SNAPSHOT</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter</artifactId>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.18.1</version>
</plugin>
</plugins>
</build>
</project>
- خطوط ۱۱–۱۵: پروژهٔ والد Maven که قبلاً برای لایهٔ [DAO] استفاده شده است؛
- خطوط ۱۸–۲۲: وابستگی به لایه [DAO]؛
- خطوط ۲۳–۲۶: وابستگی به آرتیفکت [spring-boot-starter-web]. این آرتیفکت شامل تمام وابستگیهای لازم برای ایجاد یک سرویس وب / jSON است. همچنین شامل کتابخانههای غیرضروری نیز میباشد. بنابراین پیکربندی دقیقتری ضروری است. با این حال، این پیکربندی برای شروع کار مفید است؛
- خطوط ۲۸–۳۰: وابستگی به آرتیفکت [spring-boot-starter] مدیریت anotationهای Spring Boot را امکانپذیر میسازد؛
وابستگیهای معرفیشده توسط این پیکربندی به شرح زیر است:
![]() |
- در [1]، میتوانیم ببینیم که اکلیپس وابستگی به آرشیو پروژه [intro-spring-data-01] را تشخیص داده است؛
وابستگیهای فهرستشده در بالا برای هر دو لایه [DAO] و [web] مشترک هستند.
13.5.3.1. پیکربندی لایه [web]
لایه [web] توسط فایلی به نام [AppConfig] پیکربندی میشود:
![]() |
کلاس [WebConfig] لایه [web] را پیکربندی میکند:
package spring.webjson.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.context.embedded.EmbeddedServletContainerFactory;
import org.springframework.boot.context.embedded.ServletRegistrationBean;
import org.springframework.boot.context.embedded.tomcat.TomcatEmbeddedServletContainerFactory;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.Bean;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.servlet.DispatcherServlet;
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter;
import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider;
@EnableWebMvc
public class WebConfig extends WebMvcConfigurerAdapter {
// -------------------------------- [web] پیکربندی لایه
@Autowired
private ApplicationContext context;
@Bean
public DispatcherServlet dispatcherServlet() {
DispatcherServlet servlet = new DispatcherServlet((WebApplicationContext) context);
return servlet;
}
@Bean
public ServletRegistrationBean servletRegistrationBean(DispatcherServlet dispatcherServlet) {
return new ServletRegistrationBean(dispatcherServlet, "/*");
}
@Bean
public EmbeddedServletContainerFactory embeddedServletContainerFactory() {
return new TomcatEmbeddedServletContainerFactory("", 8080);
}
// فیلترها jSON
@Bean(name = "jsonMapper")
public ObjectMapper jsonMapper() {
return new ObjectMapper();
}
@Bean(name = "jsonMapperCategorieWithProduits")
public ObjectMapper jsonMapperCategorieWithProduits() {
// نقشهکش jSON
ObjectMapper mapper = new ObjectMapper();
// فیلترها
mapper.setFilters(
new SimpleFilterProvider().addFilter("jsonFilterCategorie", SimpleBeanPropertyFilter.serializeAllExcept())
.addFilter("jsonFilterProduit", SimpleBeanPropertyFilter.serializeAllExcept("categorie")));
// نتیجه
return mapper;
}
@Bean(name = "jsonMapperProduitWithCategorie")
public ObjectMapper jsonMapperProduitWithCategorie() {
// نقشهریز jSON
ObjectMapper mapper = new ObjectMapper();
// فیلترها
mapper.setFilters(
new SimpleFilterProvider().addFilter("jsonFilterProduit", SimpleBeanPropertyFilter.serializeAllExcept())
.addFilter("jsonFilterCategorie", SimpleBeanPropertyFilter.serializeAllExcept("produits")));
// نتیجه
return mapper;
}
@Bean(name = "jsonMapperCategorieWithoutProduits")
public ObjectMapper jsonMapperCategorieWithoutProduits() {
// نقشهکش jSON
ObjectMapper mapper = new ObjectMapper();
// فیلترها
mapper.setFilters(new SimpleFilterProvider().addFilter("jsonFilterCategorie",
SimpleBeanPropertyFilter.serializeAllExcept("produits")));
// نتیجه
return mapper;
}
@Bean(name = "jsonMapperProduitWithoutCategorie")
public ObjectMapper jsonMapperProduitWithoutCategorie() {
// نقشهکش jSON
ObjectMapper mapper = new ObjectMapper();
// فیلترها
mapper.setFilters(new SimpleFilterProvider().addFilter("jsonFilterProduit",
SimpleBeanPropertyFilter.serializeAllExcept("categorie")));
// نتیجه
return mapper;
}
}
- خط ۱۸: آناوتیشن [@EnableWebMvc] پیکربندیهای خودکار را برای فریمورک Spring MVC راهاندازی میکند؛
- خط ۱۹: کلاس [WebConfig] از کلاس Spring با نام [WebMvcConfigurerAdapter] ارث میبرد تا برخی از Beans را (در خطوط ۲۶–۴۰) مجدداً تعریف کند؛
- خطوط ۲۲–۲۳: تزریق کانکست اسپرینگ؛
- خطوط ۲۵–۲۹: تعریف سرولتی از فریمورک اسپرینگ با شناسه MVC، که درخواستهای HTTP را به کنترلر و متد صحیح مسیریابی میکند. [DispatcherServlet] یک کلاس اسپرینگ است؛
- خطوط ۳۱–۳۴: این مشخص میکند که این سرولت تمام درخواستهای URL را مدیریت میکند؛
- خطوط ۳۶–۳۹: حضور این بین، سرور Tomcat موجود در آرشیوهای پروژه را راهاندازی میکند. این سرور روی پورت ۸۰۸۰ به درخواستها گوش فرا میدهد؛
- خطوط ۴۲–۹۱: بیَنهایی که برای مدیریت فیلترهای jSON استفاده خواهند شد؛
- خطوط ۴۲–۴۵: یک مپر jSON بدون فیلتر؛
- خطوط ۴۷–۵۷: مپر jSON که امکان نمایش یک دستهبندی به همراه محصولاتش را فراهم میکند. توجه داشته باشید که هنگام درخواست یک دستهبندی به همراه محصولاتش، باید هر دو فیلتر jSON برای کلاس [Categorie] و فیلتر مربوط به کلاس [Produit] را پیکربندی کنید. این امر همیشه صادق است. هنگام سریالیسازی یا غیرسریالیسازی یک کلاس در jSON، باید فیلتر jSON را برای آن کلاس و فیلترهای مربوط به تمام وابستگیهایی که باید در آن گنجانده شوند، پیکربندی کنید؛
- خطوط ۵۹–۶۹: مپر jSON، که امکان نمایش یک محصول همراه با دستهبندی آن را فراهم میکند؛
- خطوط ۷۱–۸۰: مپر jSON، که امکان نمایش یک دستهبندی بدون محصولات آن را فراهم میکند؛
- خطوط ۸۲–۹۱: مبدل jSON، که امکان نمایش یک محصول بدون دستهبندی آن را فراهم میکند؛
کلاس [AppConfig] کل برنامه را پیکربندی میکند، یعنی لایههای [web] و [DAO]:
package spring.webjson.config;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Import;
import spring.data.config.DaoConfig;
@ComponentScan(basePackages = { "spring.webjson" })
@Import({ DaoConfig.class, WebConfig.class})
public class AppConfig {
}
- خط ۹: بینهای لایه [DAO] و لایه [web] وارد میشوند؛
- خط ۸: بستههایی را مشخص میکند که در آنها میتوان سایر بیینهای Spring را یافت؛
توجه داشته باشید که ما از anotation [@EnableAutoConfiguration] در هیچ کجا استفاده نکردهایم. ما ترجیح دادیم پیکربندی را خودمان کنترل کنیم.
13.5.4. مدل کاربردی
![]() |
کلاس [ApplicationModel] به شرح زیر است:
package spring.webjson.models;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import spring.data.dao.IDao;
import spring.data.entities.Categorie;
import spring.data.entities.Produit;
@Component
public class ApplicationModel implements IDao {
//لایه [DAO]
@Autowired
private IDao dao;
@Override
public void addProduits(List<Produit> produits) {
dao.addProduits(produits);
}
@Override
public void deleteAllProduits() {
dao.deleteAllProduits();
}
@Override
public void updateProduits(List<Produit> produits) {
dao.updateProduits(produits);
}
@Override
public List<Produit> getAllProduits() {
return dao.getAllProduits();
}
@Override
public void addCategories(List<Categorie> categories) {
dao.addCategories(categories);
}
@Override
public void deleteAllCategories() {
dao.deleteAllCategories();
}
@Override
public void updateCategories(List<Categorie> categories) {
dao.updateCategories(categories);
}
@Override
public List<Categorie> getAllCategories() {
return dao.getAllCategories();
}
@Override
public Produit getProduitByIdWithCategorie(Long idProduit) {
return dao.getProduitByIdWithCategorie(idProduit);
}
@Override
public Produit getProduitByNameWithCategorie(String nom) {
return dao.getProduitByNameWithCategorie(nom);
}
@Override
public Categorie getCategorieByIdWithProduits(Long idCategorie) {
return dao.getCategorieByIdWithProduits(idCategorie);
}
@Override
public Categorie getCategorieByNameWithProduits(String nom) {
return dao.getCategorieByNameWithProduits(nom);
}
@Override
public Produit getProduitByIdWithoutCategorie(Long idProduit) {
return dao.getProduitByIdWithoutCategorie(idProduit);
}
@Override
public Categorie getCategorieByIdWithoutProduits(Long idCategorie) {
return dao.getCategorieByIdWithoutProduits(idCategorie);
}
@Override
public Produit getProduitByNameWithoutCategorie(String nom) {
return dao.getProduitByNameWithoutCategorie(nom);
}
@Override
public Categorie getCategorieByNameWithoutProduits(String nom) {
return dao.getCategorieByNameWithoutProduits(nom);
}
}
- خط ۱۲: این کلاس یک سنگلتون Spring است؛
- خط ۱۳: که رابط [IDao] لایه [DAO] را پیادهسازی میکند؛
- خطوط ۱۶–۱۷: تزریق یک مرجع به لایه [DAO]؛
- خطوط ۱۹–۹۹: پیادهسازی رابط [IDao];
معماری لایه وب به شرح زیر تکامل مییابد:
![]() |
- در [2b]، متدهای کنترلکنندهها با نمونهی واحد [ApplicationModel] ارتباط برقرار میکنند؛
این استراتژی هنگام مدیریت کش انعطافپذیری فراهم میکند. کلاس [ApplicationModel] را میتوان برای ذخیره اطلاعاتی که از لایه [DAO] یا دادههای پیکربندی بهدست آمده است، استفاده کرد. این میتواند زمانی مفید باشد که شما کنترل لایه [DAO] را ندارید. این استراتژی کش ممکن است در طول زمان تکامل یابد. هرگونه تغییر تأثیری بر کد کنترلکنندهها نخواهد داشت.
13.5.5. کنترلکننده
![]() |
![]() |
در اینجا تنها یک کنترلر داریم، کلاس [MyController].
13.5.5.1. متدهای URL
مثالهای URL که توسط این کنترلر ارائه شدهاند به شرح زیر است:
| محصولات را به پایگاه داده اضافه میکند. این موارد ارسال میشوند. پاسخ، رشته jSON است که شامل فهرست محصولات اضافه شده همراه با کلیدهای اصلی آنها میباشد. |
| حذف تمام محصولات از پایگاه داده. |
| محصولات را در پایگاه داده بهروزرسانی میکند. این موارد ارسال میشوند. پاسخ، رشته jSON از فهرست محصولات بهروزرسانیشده است. |
| رشته jSON را برای همه محصولات بازیابی میکند. |
| دستهبندیها را به پایگاه داده اضافه میکند. این عملیات ارسال میشود. پاسخ رشتهای به شکل jSON است که شامل فهرست دستهبندیهای اضافه شده همراه با کلیدهای اصلی آنها میباشد. اگر دستهبندیها شامل محصولاتی باشند، آنها نیز به پایگاه داده اضافه میشوند. |
| تمام دستهبندیها را از پایگاه داده، به همراه تمام محصولات موجود در آنها، حذف میکند. پس از این، پایگاه داده خالی خواهد بود. |
| دستهبندیها را در پایگاه داده بهروزرسانی میکند. این عملیات ارسال میشود. پاسخ، فهرستی از دستهبندیهای بهروزرسانیشده است. اگر دستهبندیها شامل محصولاتی باشند، آنها نیز در پایگاه داده بهروزرسانی میشوند. رشتهی jSON را برای دستهبندیهای اصلاحشده بازمیگرداند؛ |
| رشته jSON را از همه دستهبندیها بازیابی میکند. |
| رشته jSON را برای محصولی که با شناسه آن شناسایی شده است، به همراه دستهبندی آن بازیابی میکند. |
| رشته jSON را برای محصولی که با شناسه آن شناسایی شده است، به استثنای دستهبندی آن، بازیابی میکند. |
| رشته jSON را برای محصولی که با نامش شناسایی شده است، همراه با دستهبندی آن، بازیابی میکند. |
| رشته jSON را برای محصولی که با نامش شناسایی شده است، بدون در نظر گرفتن دستهبندی آن، بازیابی میکند. |
| رشته jSON را برای دستهای که با شناسه آن مشخص شده است، به همراه محصولاتش بازیابی میکند. |
| رشته jSON را برای یک دستهبندی مشخصشده با نام، به همراه محصولات آن، بازیابی میکند. |
| بازیابی رشته jSON از یک دستهبندی مشخصشده با نام آن، به استثنای محصولات آن. |
| رشته jSON را برای دستهای که با شناسه آن مشخص شده است، بدون احتساب محصولاتش، بازیابی میکند. |
متدهای نمایشدادهشده URL متناظر با متدهای رابط [IDao] در لایه [DAO] هستند. روشهای سرویس وب /jSON همگی بر اساس یک قالب یکسان هستند. ما چند مورد از آنها را بررسی خواهیم کرد.
13.5.5.2. اسکلت کنترلکننده
اسکلت کنترلکننده به شرح زیر است:
package spring.webjson.service;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.io.CharStreams;
import spring.data.dao.DaoException;
import spring.data.entities.Categorie;
import spring.data.entities.Produit;
import spring.webjson.models.ApplicationModel;
import spring.webjson.models.Response;
@Controller
public class MyController {
//وابستگیهای Spring
@Autowired
private ApplicationModel application;
// فیلترها jSON
@Autowired
@Qualifier("jsonMapper")
private ObjectMapper jsonMapper;
@Autowired
@Qualifier("jsonMapperCategorieWithProduits")
private ObjectMapper jsonMapperCategorieWithProduits;
@Autowired
@Qualifier("jsonMapperProduitWithCategorie")
private ObjectMapper jsonMapperProduitWithCategorie;
@Autowired
@Qualifier("jsonMapperCategorieWithoutProduits")
private ObjectMapper jsonMapperCategorieWithoutProduits;
@Autowired
@Qualifier("jsonMapperProduitWithoutCategorie")
private ObjectMapper jsonMapperProduitWithoutCategorie;
//کلاس [MyController] یک نمونهٔ واحد است و تنها یکبار برای دانهٔ
public MyController() {
// System.out.println("MyController");
}
@RequestMapping(value = "/addProduits", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8", produces = "application/json; charset=UTF-8")
@ResponseBody
public String addProduits(HttpServletRequest request) throws JsonProcessingException {
...
}
- خط ۲۸: تگ [@Controller] این کلاس را به یک کامپوننت Spring تبدیل میکند؛
- خطوط ۳۲–۳۳: تزریق مرجع به کلاس [ApplicationModel];
- خطوط ۳۶–۵۰: مراجع به مپرهای jSON تزریق میشوند؛
- خط ۵۸: URL نمایانشده، [/addProduits] است. کلاینت باید از یک متد [POST] برای ارسال درخواست خود استفاده کند (متد = RequestMethod.POST). آنها باید مقدار ارسالشده را بهعنوان یک رشته jSON (consumes = "application/json; charset=UTF-8") ارسال کنند. خود متد پاسخ را به کلاینت بازمیگرداند (خط 59). این یک رشته خواهد بود (خط 60). سربرگ HTTP [Content-type : application/json; charset=UTF-8] برای اطلاعرسانی به کلاینت ارسال میشود که در آستانه دریافت یک رشته jSON است (خط 58);
- خط ۶۰: متد [addProduits] رشته jSON را از لیست محصولات اضافه شده به پایگاه داده بازمیگرداند؛
13.5.5.3. پاسخ متدهای کنترلر
تمام متدهای کنترلر پاسخ زیر از نوع [Response] را برمیگردانند:
![]() |
package spring.webjson.service;
import java.util.List;
public class Response<T> {
// ----------------- ویژگیها
//وضعیت عملیات
private int status;
//هرگونه پیام خطا
private List<String> messages;
// متن پاسخ
private T body;
// سازندهها
public Response() {
}
public Response(int status, List<String> messages, T body) {
this.status = status;
this.messages = messages;
this.body = body;
}
// گیرنده و تنظیمکننده
...
}
- خط ۵: پاسخ شامل یک نوع T است؛
- خط ۱۳: پاسخ از نوع T؛
- خطوط ۹–۱۱: یک متد ممکن است با یک استثنا مواجه شود. در این صورت، یک پاسخ با: بازخواهد گشت
- خط ۹: status!=0;
- خط ۱۱: فهرست خطاهای رخداده؛
13.5.5.4. L'URL [/addProduits]
L'URL [/addProduits] توسط روش زیر مدیریت میشود:
@RequestMapping(value = "/addProduits", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8", produces = "application/json; charset=UTF-8")
@ResponseBody
public String addProduits(HttpServletRequest request) throws JsonProcessingException {
// پاسخ
Response<List<Produit>> response;
try {
// بازیابی مقدار ارسالشده
String body = CharStreams.toString(request.getReader());
List<Produit> produits = jsonMapperProduitWithoutCategorie.readValue(body, new TypeReference<List<Produit>>() {
});
// برقراری مجدد ارتباط بین محصولات و دستهبندیها
for (Produit produit : produits) {
produit.setCategorie(application.getCategorieByIdWithoutProduits(produit.getIdCategorie()));
}
// پایدارسازی محصولات
application.addProduits(produits);
response = new Respon se<List<Produit>>(0, null, produits);
} catch (DaoException e1) {
response = new Response<List<Produit>>(1000, e1.getErreurs(), null);
} catch (Exception e2) {
response = new Response<List<Produit>>(1000, getErreursForException(e2), null);
}
// پاسخ jSON
return jsonMapperProduitWithoutCategorie.writeValueAsString(response);
}
- خط ۳: متد [HttpServletRequest request] را بهعنوان پارامتر میپذیرد، که تمام اطلاعات مربوط به درخواست مشتری را در خود جای داده است؛
- خط ۵: پاسخی که باید به کلاینت ارسال شود: فهرستی از محصولات؛
- خط ۸: مقدار ارسالشده بازیابی میشود. کلاس [CharStreams] متعلق به کتابخانه [Google Guava] است که مرجع آن در فایل [pom.xml] اضافه شده است. ما رشته ارسالشده توسط کلاینت با مقدار jSON را دریافت میکنیم. این مقدار باید برای پردازش، از حالت سریالی خارج شود؛
- خطوط ۸–۱۰: سریالیسازی معکوس انجام میشود. این کار یک لیست از محصولات را بازمیگرداند که در آن هر محصول دارای فیلدی به نام [categorie=null] است؛
- خطوط ۱۲–۱۴: فیلد [categorie] برای تمام محصولات موجود در لیست ریست میشود. برای این کار، از فیلد [idCategorie] محصول که قبلاً مقداردهی اولیه شده است، استفاده میکنیم؛
- خط ۱۶: محصولات در پایگاه داده درج میشوند؛
- خط ۱۷: شیء [response] با لیست محصولات مقداردهی اولیه میشود؛
- خطوط ۱۸–۱۹: مواردی که متد با استثناءای از لایه [DAO] مواجه میشود. پاسخ با [status=1000] (کد خطا) [messages=e1.getMessages()] مقداردهی اولیه میشود، یعنی فهرست خطاهای رخداده در سمت سرور به کلاینت ارسال میشود؛
- خطوط ۲۰–۲۱: مواردی که متد با نوع دیگری از استثنا مواجه میشود. پاسخ با [status=1000] (کد خطا) آغاز میشود [messages=getErreursForException(e)]، که در آن [getErreursForException] یک متد خصوصی از کلاس است که لیست خطاهای مرتبط با استثناها در استک استثنای `e` را برمیگرداند، و [body=null]؛
- خط ۲۴: رشته jSON در پاسخ بازگردانده میشود؛
13.5.5.5. URL [/getAllProduits]
URL [/getAllProduits] توسط روش زیر مدیریت میشود:
@RequestMapping(value = "/getAllProduits", method = RequestMethod.GET, produces = "application/json; charset=UTF-8")
@ResponseBody
public String getAllProduits() throws JsonProcessingException {
//پاسخ
Response<List<Produit>> response;
try {
response = new Response<List<Produit>>(0, null, application.getAllProduits());
} catch (DaoException e1) {
response = new Response<List<Produit>>(1003, e1.getErreurs(), null);
} catch (Exception e2) {
response = new Response<List<Produit>>(1003, getErreursForException(e2), null);
}
// پاسخ jSON
return jsonMapperProduitWithoutCategorie.writeValueAsString(response);
}
- خط ۱: URL [/getAllProduits] با استفاده از عملیات [GET] درخواست میشود. این عملیات خروجی jSON را تولید میکند؛
- خط ۲: خود متد پاسخ jSON را به کلاینت ارسال میکند؛
- خط ۵: متد رشته jSON از نوع [Response<List<Produit>>] را ارسال میکند؛
- خط ۷: محصولات بدون دستهٔ خود درخواست میشوند؛
- خطوط ۸–۱۲: در صورت بروز خطا، پاسخ با یک کد خطا و پیامهای خطا مقداردهی اولیه میشود؛
- خط 14: پاسخ jSON به کلاینت ارسال میشود؛
13.5.5.6. Conclusion
ما سایر متدهای موجود در کنترلر را پوشش نخواهیم داد. آنها مشابه یکی از دو متدی هستند که به تازگی شرح دادیم.
13.5.6. کلاس اجرای سرویس وب / jSON
![]() |
کلاس [Boot] کلاس قابل اجرای پروژه است:
package spring.webjson.boot;
import org.springframework.boot.SpringApplication;
import spring.webjson.server.config.AppConfig;
public class Boot {
public static void main(String[] args) {
SpringApplication.run(AppConfig.class, args);
}
}
- خط ۱۰: متد استاتیک [SpringApplication.run] اجرا میشود. کلاس [SpringApplication] کلاسی در پروژه [Spring Boot] (خط ۳) است. دو پارامتر به آن ارسال میشود:
- [AppConfig.class]: کلاسی که کل برنامه را پیکربندی میکند؛
- [args]: هر آرگومانی که به متد [main] در خط 9 ارسال میشود. این پارامتر در اینجا استفاده نمیشود؛
هنگامی که این کلاس اجرا میشود، لاگهای زیر تولید میشوند:
- خطوط 17–19: راهاندازی سرور Tomcat که سرویس وب / jSON را اجرا خواهد کرد؛
- خطوط ۲۵–۳۳: ساخت لایه [DAO]؛
- خطوط ۳۲–۵۱: URL نمایانشده شناسایی میشوند؛
13.5.7. آزمایش سرویس وب / jSON
برای انجام آزمایشها، ما پایگاه داده MySQL [dbintrospringdata] را با استفاده از اسکریپت SQL [dbintrospringdata.sql] تولید میکنیم:
![]() |
پس از انجام این کار، از کلاینت [Advanced Rest Client] (به بخش 22.5 مراجعه کنید) برای پرسوجو از منابع URL که توسط سرویس وب / jSON ارائه شدهاند، استفاده میکنیم. (وب سرویس / jSON باید در حال اجرا باشد).
![]() |
- در [1-3]، ما از طریق یک فرمان برای HTTP و GET، URL و [/getAllCategories] را درخواست میکنیم؛
پاسخ زیر را دریافت میکنیم:
![]() |
- در [1]، درخواست کلاینت HTTP;
- به [2]، پاسخ سرور HTTP است؛
- به [3]، وضعیت [200 OK] نشان میدهد که سرور درخواست را با موفقیت پردازش کرده است؛
- در [4]، پاسخ سرور jSON;
پاسخ کامل jSON به شرح زیر است:
{"status":0,"messages":null,"body":[{"id":415,"version":0,"nom":"categorie0","produits":[{"id":1849,"version":0,"nom":"produit00","idCategorie":415,"prix":100.0,"description":"desc00"},{"id":1850,"version":0,"nom":"produit01","idCategorie":415,"prix":101.0,"description":"desc01"},{"id":1851,"version":0,"nom":"produit02","idCategorie":415,"prix":102.0,"description":"desc02"},{"id":1852,"version":0,"nom":"produit03","idCategorie":415,"prix":103.0,"description":"desc03"},{"id":1853,"version":0,"nom":"produit04","idCategorie":415,"prix":104.0,"description":"desc04"}]},{"id":416,"version":0,"nom":"categorie1","produits":[{"id":1856,"version":0,"nom":"produit12","idCategorie":416,"prix":112.0,"description":"desc12"},{"id":1857,"version":0,"nom":"produit13","idCategorie":416,"prix":113.0,"description":"desc13"},{"id":1858,"version":0,"nom":"produit14","idCategorie":416,"prix":114.0,"description":"desc14"},{"id":1854,"version":0,"nom":"produit10","idCategorie":416,"prix":110.0,"description":"desc10"},{"id":1855,"version":0,"nom":"produit11","idCategorie":416,"prix":111.0,"description":"desc11"}]}]}
- status:0 به این معنی است که هیچ خطای سمت سرور وجود نداشته است؛
- messages: null به این معنی است که هیچ پیام خطایی وجود ندارد؛
- body: بدنه پاسخ است، در این مورد فهرست دستهبندیها به همراه محصولاتشان. دو دستهبندی وجود دارد، هر کدام با ۵ محصول؛
ما قصد داریم محصول [produit15] را به دسته [categorie1] اضافه کنیم. برای این کار، از URL [/addCategories] استفاده خواهیم کرد که کد زیر را دارد:
@RequestMapping(value = "/addCategories", method = RequestMethod.POST, consumes = "application/json; charset=UTF-8", produces = "application/json; charset=UTF-8")
@ResponseBody
public String addCategories(HttpServletRequest request) throws JsonProcessingException {
Response<List<Categorie>> response;
ObjectMapper mapper = context.getBean(ObjectMapper.class);
// دستهبندیها حفظ میشوند
try {
// مقدار ارسالشده را بازیابی میکند
String body = CharStreams.toString(request.getReader());
mapper.setFilters(jsonFilterCategorieWithProduits);
List<Categorie> categories = mapper.readValue(body, new TypeReference<List<Categorie>>() {
});
// برقراری مجدد ارتباط بین محصولات و دستهبندیها
for (Categorie categorie : categories) {
Set<Produit> produits = categorie.getProduits();
if (produits != null) {
for (Produit produit : categorie.getProduits()) {
produit.setCategorie(categorie);
}
}
}
// دستهبندیها حفظ میشوند
application.addCategories(categories);
response = new Response<List<Categorie>>(0, null, categories);
} catch (Exception e) {
response = new Response<List<Categorie>>(1004, getErreursForException(e), null);
}
// پاسخ jSON
return mapper.writeValueAsString(response);
}
- خط ۱: مشتری باید یک POST ایجاد کند و مقدار ارسالشده باید یک رشته jSON باشد؛
- خطوط ۹–۱۲: مقدار ارسالشده باید شامل یک لیست از دستهبندیها به همراه محصولات مرتبط با آنها باشد؛
ما یک دستهبندی [categorie2] را با یک محصول [produit21] ایجاد خواهیم کرد. بنابراین رشته jSON که باید ارسال شود به شرح زیر است:
[{"id":null,"version":0,"nom":"categorie2","produits":[{"id":null,"version":0,"nom":"produit21","idCategorie":null,"prix":111.0,"description":"desc21"}]}]
درخواست به سرویس وب /jSON به صورت زیر ارسال میشود:
![]() |
- در [1]، URL درخواست شده است؛
- در [2]، از طریق عملیاتی POST درخواست میشود؛
- در [3]، رشته jSON ارسال میشود؛
- در [4]، به سرور اطلاع داده میشود که jSON برای آن ارسال خواهد شد؛
پاسخ سرور به شرح زیر است:
![]() |
- در [1] میبینیم که هم دستهبندی و هم محصول آن اکنون دارای کلید اصلی هستند، که نشان میدهد احتمالاً در پایگاه داده وارد شدهاند. ما این را با استفاده از URL و [/getCategorieByNameWithProduits/categorie2] تأیید خواهیم کرد:
![]() |
ما نتیجه زیر را به دست میآوریم:
![]() |
ما در واقع دسته [categorie2] را با تنها محصول آن [produit21] بازیابی کردهایم. ما همچنین میتوانیم فقط محصول را بازیابی کنیم. برای انجام این کار، بیایید از URL [/getProduitByIdWithoutCategorie/1859] استفاده کنیم:
![]() |
ما به نتیجه زیر میرسیم:
![]() |
تمام عملیات [GET] را میتوان در یک مرورگر وب استاندارد انجام داد:
![]() |
از خوانندگان دعوت میشود تا سایر عملیات URL سرویس وب / json را آزمایش کنند.
13.6. یک برنامهٔ کلاینت برای سرویس وب /jSON
اکنون که پایگاه داده [dbintrospringdata] در وب در دسترس است، ما یک برنامه خواهیم نوشت که از آن استفاده میکند. سپس معماری کلاینت/سرور زیر را خواهیم داشت:
![]() |
برنامهٔ کلاینت دو لایه خواهد داشت:
- یک لایه [DAO] [2] برای ارتباط با برنامه وب / jSON که پایگاه داده را در معرض دید قرار میدهد؛
- یک لایه تست (JUnit, [1]) برای تأیید اینکه کلاینت و سرور به درستی کار میکنند؛
13.6.1. پروژه اکلیپس
پروژه Eclipse سمت کلاینت به شرح زیر است:
![]() |
- پوشه [src/main/java] لایه [DAO] را پیادهسازی میکند؛
- پوشه [src/test/java] تستهای JUnit را پیادهسازی میکند؛
13.6.2. پیکربندی Maven پروژه
این پروژه یک پروژه Maven است که توسط فایل زیر پیکربندی شده است: [pom.xml]:
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>istia.st.webjson</groupId>
<artifactId>intro-client-webjson-01</artifactId>
<version>0.0.1-SNAPSHOT</version>
<description>Client console du serveur web / jSON</description>
<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<java.version>1.8</java.version>
</properties>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>1.2.7.RELEASE</version>
</parent>
<dependencies>
<!-- اسپرینگ -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-web</artifactId>
</dependency>
<!-- jSON کتابخانهای که توسط Spring استفاده میشود -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-core</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<!-- کامپوننت مورد استفاده توسط Spring RestTemplate -->
<dependency>
<groupId>org.apache.httpcomponents</groupId>
<artifactId>httpclient</artifactId>
</dependency>
<!-- گوگل گوآوا -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<version>16.0.1</version>
<scope>test</scope>
</dependency>
<!--کتابخانهٔ لاگگیری -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</dependency>
<!-- آزمون Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- Spring Boot -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<!-- پلاگینها -->
<build>
<plugins>
<plugin>
<artifactId>maven-assembly-plugin</artifactId>
<configuration>
<descriptorRefs>
<descriptorRef>jar-with-dependencies</descriptorRef>
</descriptorRefs>
</configuration>
</plugin>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.18.1</version>
</plugin>
</plugins>
</build>
<name>intro-client-webjson-01</name>
</project>
- خطوط 14–18: پروژهٔ والد Maven [spring-boot-starter-parent]، که به ما امکان میدهد تعدادی وابستگی را بدون مشخص کردن نسخههایشان تعریف کنیم، زیرا این نسخهها در پروژهٔ والد تعریف شدهاند؛
- خطوط 22–25: اگرچه ما در حال نوشتن یک برنامه وب نیستیم، اما به وابستگی [spring-web] نیاز داریم که شامل کلاس [RestTemplate] است و به ما امکان میدهد تا به راحتی با یک برنامه وب / jSON تعامل داشته باشیم؛
- خطوط ۲۷–۳۴: کتابخانهای به نام jSON;
- خطوط ۳۶–۳۹: یک وابستگی که به ما امکان میدهد یک timeout را به درخواستهای HTTP کلاینت متصل کنیم. timeout حداکثر زمانی است که برای پاسخدادن سرور مجاز است. اگر این زمان بیش از حد شود، کلاینت با پرتاب یک استثنا، خطای timeout را گزارش میکند؛
- خطوط ۴۱–۴۶: کتابخانه Google Guava که در تست JUnit استفاده شده است. به همین دلیل، ما دامنه آن را روی [test] (خط ۴۵) تنظیم کردهایم. این بدان معناست که این وابستگی تنها زمانی گنجانده میشود که کد از شاخه [src/test/java] اجرا شود؛
- خطوط ۴۸–۵۱: کتابخانهٔ لاگگیری؛
- خطوط ۵۲–۶۳: وابستگی برای تست، JUnit. بهویژه، این شامل کتابخانه JUnit است که برای تست لازم است. این وابستگیها دارای ویژگی [<scope>test</scope>] هستند که نشان میدهد اینها فقط برای مرحله تست مورد نیازند. آنها در آرشیو نهایی پروژه گنجانده نمیشوند؛
13.6.3. پیادهسازی لایه [DAO]
![]() |
![]() |
- پکیج [spring.client.config] حاوی پیکربندی Spring برای لایه [DAO] است؛
- پکیج [spring.client.dao] شامل پیادهسازی لایه [DAO] است؛
- پکیج [spring.client.entities] حاوی اشیایی است که با سرویس وب / jSON مبادله میشوند؛
13.6.3.1. Configuration
![]() |
کلاس [DaoConfig] پیکربندی Spring را برای لایه [DAO] مدیریت میکند. کد آن به شرح زیر است:
package spring.client.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.ComponentScan;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.ser.impl.SimpleBeanPropertyFilter;
import com.fasterxml.jackson.databind.ser.impl.SimpleFilterProvider;
@ComponentScan({ "spring.client.dao" })
public class DaoConfig {
// ثوابت
static private final int TIMEOUT = 1000;
static private final String URL_WEBJSON = "http://localhost:8080";
@Bean
public RestTemplate restTemplate(int timeout) {
//ایجاد کامپوننت RestTemplate
HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory();
RestTemplate restTemplate = new RestTemplate(factory);
// اتمام زمان ارتباط
factory.setConnectTimeout(timeout);
factory.setReadTimeout(timeout);
// نتیجه
return restTemplate;
}
@Bean
public int timeout() {
return TIMEOUT;
}
@Bean
public String urlWebJson() {
return URL_WEBJSON;
}
// فیلترها jSON
@Bean(name = "jsonMapper")
public ObjectMapper jsonMapper() {
return new ObjectMapper();
}
@Bean(name = "jsonMapperCategorieWithProduits")
public ObjectMapper jsonMapperCategorieWithProduits() {
// نقشهکش jSON
ObjectMapper mapper = new ObjectMapper();
// فیلترها
mapper.setFilters(
new SimpleFilterProvider().addFilter("jsonFilterCategorie", SimpleBeanPropertyFilter.serializeAllExcept())
.addFilter("jsonFilterProduit", SimpleBeanPropertyFilter.serializeAllExcept("categorie")));
// نتیجه
return mapper;
}
@Bean(name = "jsonMapperProduitWithCategorie")
public ObjectMapper jsonMapperProduitWithCategorie() {
// نقشهکش jSON
ObjectMapper mapper = new ObjectMapper();
// فیلترها
mapper.setFilters(
new SimpleFilterProvider().addFilter("jsonFilterProduit", SimpleBeanPropertyFilter.serializeAllExcept())
.addFilter("jsonFilterCategorie", SimpleBeanPropertyFilter.serializeAllExcept("produits")));
// نتیجه
return mapper;
}
@Bean(name = "jsonMapperCategorieWithoutProduits")
public ObjectMapper jsonMapperCategorieWithoutProduits() {
// نقشهکش jSON
ObjectMapper mapper = new ObjectMapper();
// فیلترها
mapper.setFilters(new SimpleFilterProvider().addFilter("jsonFilterCategorie",
SimpleBeanPropertyFilter.serializeAllExcept("produits")));
// نتیجه
return mapper;
}
@Bean(name = "jsonMapperProduitWithoutCategorie")
public ObjectMapper jsonMapperProduitWithoutCategorie() {
// نقشهساز jSON
ObjectMapper mapper = new ObjectMapper();
// فیلترها
mapper.setFilters(new SimpleFilterProvider().addFilter("jsonFilterProduit",
SimpleBeanPropertyFilter.serializeAllExcept("categorie")));
// نتیجه
return mapper;
}
}
- خط ۱۳: این کلاس یک کلاس پیکربندی Spring است – کامپوننتهای Spring در پکیج [spring.client.dao] قرار دارند؛
- خط 17: یک timeout به مدت یک ثانیه (1000 میلیثانیه) تنظیم شده است؛
- خطوط ۳۲–۳۵: بین (bean) که این مقدار را برمیگرداند؛
- خط ۱۸: URL برای سرویس وب / jSON;
- خطوط ۳۷–۴۰: بین (bean) که این مقدار را برمیگرداند؛
- خطوط ۲۰–۳۰: پیکربندی کلاس [RestTemplate]، که ارتباط با سرویس وب / jSON را مدیریت میکند. هنگامی که نیازی به پیکربندی آن نباشد، میتوان به سادگی در کد به آن به عنوان [new RestTemplate()] ارجاع داد. در اینجا، میخواهیم timeout را برای ارتباط با سرویس وب / jSON تنظیم کنیم. بیان [timeout] در خط 36 به عنوان پارامتر به متد [restTemplate] در خط 24 ارسال میشود؛
- خط ۲۳: کامپوننت [HttpComponentsClientHttpRequestFactory] همان کامپوننتی است که به ما امکان میدهد timeout را برای تبادلات (خطوط ۲۹–۳۰) تنظیم کنیم؛
- خط 24: کلاس [RestTemplate] با استفاده از این کامپوننت ساخته میشود. از آنجایی که این کلاس برای ارتباط با سرویس وب / jSON به این کامپوننت متکی است، تبادلها در واقع تابع timeout خواهند بود؛
- کلاینت و سرور خطوط متنی را مبادله خواهند کرد. یک مبدل مسئول سریالسازی یک شیء به متن و بالعکس، دسریالسازی متن به یک شیء است. ممکن است چندین مبدل با کلاس [RestTemplate] مرتبط باشند و مبدلی که در هر لحظه انتخاب میشود به هدرهای HTTP ارسالشده توسط سرور بستگی دارد. در این حالت، ما هیچ مبدلی نخواهیم داشت. بنابراین، کامپوننت [RestTemplate] تلاش نخواهد کرد که دو عنصر زیر را به هیچ وجه تبدیل کند:
- متن ارسالشده؛
- متن دریافتشده در پاسخ؛
این متنها رشتههای jSON خواهند بود، و بنابراین توسط کامپوننت [RestTemplate] دستنخورده باقی میمانند. این ما، توسعهدهندگان، هستیم که سریالیسازی و دِسریالیسازی لازم jSON را انجام خواهیم داد. این به این دلیل است که فیلترهای اعمالشده بر مقدار ارسالشده و پاسخ دریافتی ممکن است متفاوت باشند، و تجربه نشان میدهد که مدیریت آنها به صورت دستی آسانتر از تلاش برای پیکربندی کامپوننت [RestTemplate] برای استفاده از مبدل صحیح jSON است؛
- خطوط ۴۲–۹۲: فیلترهای jSON را تعریف کنید. این فیلترها همانهایی هستند که در سرور وجود دارند، همانطور که در بخش 13.5.3.1 ارائه و توضیح داده شدهاند؛
- خطوط ۴۳–۴۶: یک مپر jSON بدون فیلترها؛
- خطوط ۶۴–۶۸: یک مپر jSON برای ایجاد یک دستهبندی بدون هیچ محصولی؛
- خطوط ۴۸–۵۸: یک مپر jSON برای نمایش یک دستهبندی همراه با محصولاتش؛
- خطوط ۸۳–۹۲: یک مپر jSON برای نمایش یک محصول بدون دستهبندی آن؛
- خطوط ۶۰–۷۰: یک مپر jSON برای ایجاد یک محصول با دستهبندی آن؛
تمام این بیینها در دسترس کدهای لایه [DAO] و همچنین تست Junit قرار خواهند گرفت.
13.6.3.2. اشیاء
![]() |
اشیایی که توسط لایه [DAO] مدیریت میشوند، همانهایی هستند که با سرویس وب /jSON مبادله میشوند. اینها اقلام و محصولات هستند. در سمت سرور، این اشیاء دارای حاشیهنویسیهای پایداری JPA بودند. در اینجا، این حاشیهنویسیها حذف شدهاند. کد اشیاء را برای مرجع بازتولید میکنیم:
[AbstractEntity]
package spring.client.entities;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
public abstract class AbstractEntity {
// ویژگیها
protected Long id;
protected Long version;
// سازندهها
public AbstractEntity() {
}
public AbstractEntity(Long id, Long version) {
this.id = id;
this.version = version;
}
// تعریف مجدد [equals] و [hashcode]
@Override
public int hashCode() {
return (id != null ? id.hashCode() : 0);
}
@Override
public boolean equals(Object entity) {
if (!(entity instanceof AbstractEntity)) {
return false;
}
String class1 = this.getClass().getName();
String class2 = entity.getClass().getName();
if (!class2.equals(class1)) {
return false;
}
AbstractEntity other = (AbstractEntity) entity;
return id != null && this.id == other.id.longValue();
}
// امضا jSON
public String toString() {
ObjectMapper mapper = new ObjectMapper();
try {
return mapper.writeValueAsString(this);
} catch (JsonProcessingException e) {
e.printStackTrace();
return null;
}
}
// گیرنده و تنظیمکننده
...
}
[Categorie]
package spring.client.entities;
import java.util.HashSet;
import java.util.Set;
import com.fasterxml.jackson.annotation.JsonFilter;
@JsonFilter("jsonFilterCategorie")
public class Categorie extends AbstractEntity {
// ویژگیها
private String nom;
// محصولات مرتبط
public Set<Produit> produits = new HashSet<Produit>();
// سازندهها
public Categorie() {
}
public Categorie(String nom) {
this.nom = nom;
}
// روشها
public void addProduit(Produit produit) {
// افزودن محصول
produits.add(produit);
// کategori آن را تنظیم کنید
produit.setCategorie(this);
}
//گیرنده و تنظیمکننده
...
}
[Produit]
package spring.webjson.client.entities;
import com.fasterxml.jackson.annotation.JsonFilter;
@JsonFilter("jsonFilterProduit")
public class Produit extends AbstractEntity {
// نام
private String nom;
// شمارهٔ دستهبندی
private Long idCategorie;
// قیمت
private double prix;
// توضیحات
private String description;
// دستهبندی
private Categorie categorie;
// تولیدکنندگان
public Produit() {
}
public Produit(String nom, double prix, String description) {
this.nom = nom;
this.prix = prix;
this.description = description;
}
// گیرندهها و تنظیمکنندهها
...
}
13.6.3.3. کلاس [DaoException]
![]() |
زمانی که لایه [DAO] با خطایی مواجه میشود، یک استثنا از نوع [DaoException] را پرتاب میکند. این کلاس در سمت سرور استفاده میشود و در بخش 11.3.7 توضیح داده شده است.
13.6.3.4. رابط لایه [DAO]
![]() |
لایه [DAO] رابط [IDao] را که در بخش 11.3.7 توصیف شده است، فراهم میکند.
package spring.client.dao;
import java.util.List;
import spring.client.entities.Categorie;
import spring.client.entities.Produit;
public interface IDao {
// افزودن فهرستی از محصولات
public List<Produit> addProduits(List<Produit> produits);
// حذف تمام محصولات
public void deleteAllProduits();
// بهروزرسانی یک فهرست محصولات
public List<Produit> updateProduits(List<Produit> produits);
// بازیابی تمام محصولات
public List<Produit> getAllProduits();
// وارد کردن فهرست دستهبندیها
public List<Categorie> addCategories(List<Categorie> categories);
// حذف تمام دستهبندیها
public void deleteAllCategories();
//بهروزرسانی فهرست دستهبندیها
public List<Categorie> updateCategories(List<Categorie> categories);
// بازیابی تمام دستهبندیها
public List<Categorie> getAllCategories();
// یک محصول خاص
public Produit getProduitByIdWithCategorie(Long idProduit);
public Produit getProduitByIdWithoutCategorie(Long idProduit);
public Produit getProduitByNameWithCategorie(String nom);
public Produit getProduitByNameWithoutCategorie(String nom);
//یک دستهبندی خاص
public Categorie getCategorieByIdWithProduits(Long idCategorie);
public Categorie getCategorieByIdWithoutProduits(Long idCategorie);
public Categorie getCategorieByNameWithProduits(String nom);
public Categorie getCategorieByNameWithoutProduits(String nom);
}
13.6.3.5. پاسخ سرویس وب / jSON
![]() |
ما دیدهایم که تمام URL از سرویس وب / jSON نوع [Response] را که در بخش 13.5.5.3 تعریف شده است، بازمیگردانند. ما این کلاس را در اینجا بازتولید میکنیم:
package spring.client.dao;
import java.util.List;
public class Response<T> {
// ----------------- ویژگیها
//وضعیت عملیات
private int status;
// هرگونه پیام خطا
private List<String> messages;
// بدنه پاسخ
private T body;
// سازندهها
public Response() {
}
public Response(int status, List<String> messages, T body) {
this.status = status;
this.messages = messages;
this.body = body;
}
// گیرندهها و تنظیمکنندهها
...
}
13.6.3.6. پیادهسازی مبادلات با سرویس وب / jSON
![]() |
کلاس [AbstractDao] ارتباط با سرویس وب / jSON را پیادهسازی میکند:
package spring.client.dao;
import java.net.URI;
import java.net.URISyntaxException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.MediaType;
import org.springframework.http.RequestEntity;
import org.springframework.web.client.RestTemplate;
public abstract class AbstractDao {
// دادهها
@Autowired
protected RestTemplate restTemplate;
@Autowired
protected String urlServiceWebJson;
// درخواست عمومی
protected String getResponse(String url, String jsonPost) {
// URL: URL برای تماس
// jsonPost: مقدار jSON برای ارسال
try {
// اجرای درخواست
RequestEntity<?> request;
if (jsonPost != null) {
//درخواست POST
request = RequestEntity.post(new URI(String.format("%s%s", urlServiceWebJson, url)))
.header("Content-Type", "application/json").accept(MediaType.APPLICATION_JSON).body(jsonPost);
} else {
// درخواست GET
request = RequestEntity.get(new URI(String.format("%s%s", urlServiceWebJson, url)))
.accept(MediaType.APPLICATION_JSON).build();
}
//پرسوجو اجرا میشود
return restTemplate.exchange(request, new ParameterizedTypeReference<String>() {
}).getBody();
} catch (URISyntaxException e1) {
throw new DaoException(20, e1);
} catch (RuntimeException e2) {
throw new DaoException(21, e2);
}
}
}
- خطوط ۱۵–۱۶: تزریق کامپوننت [RestTemplate] که مسئول ارتباط با سرور است؛
- خطوط 17–18: تزریق کامپوننت URL از سرویس وب / jSON;
پیادهسازی روشهای ارتباط با سرور به روش [getResponse] تفکیک شده است:
- خط ۲۱: این متد دو پارامتر میگیرد:
- [url]: URL درخواستی؛
- [jsonPost]: رشته jSON برای ارسال، در غیر این صورت null. اگر [jsonPost==null] باشد، درخواست برای URL با استفاده از یک GET انجام میشود، در غیر این صورت با استفاده از یک POST انجام میشود؛
- خط ۳۸: دستوری که درخواست را به سرور ارسال کرده و پاسخ آن را دریافت میکند. کامپوننت [RestTemplate] طیف گستردهای از متدها را برای ارتباط با سرور ارائه میدهد. ما در اینجا متد [exchange] را انتخاب کردهایم، اما متدهای دیگری نیز در دسترس هستند؛
- خطوط ۲۷–۳۶: ما باید درخواست از نوع [RequestEntity] را بسازیم. این امر بسته به اینکه از GET یا POST برای ارسال درخواست استفاده شود، متفاوت است؛
- خطوط ۳۰–۳۱: درخواست برای GET. کلاس [RequestEntity] متدهای ایستا برای ایجاد پرسوجوهای GET، POST، HEAD، ... را فراهم میکند. متد [RequestEntity.get] به شما امکان میدهد تا با زنجیرهسازی متدهای مختلف که آن را میسازند، یک پرسوجوی GET ایجاد کنید:
- متد [RequestEntity.get] هدف URL را به صورت یک نمونه URI به عنوان پارامتر میپذیرد،
- متد [accept] به شما امکان میدهد تا عناصر هدرهای HTTP و [Accept] را تعریف کنید. در اینجا، ما مشخص میکنیم که نوع [application/json] را که سرور ارسال خواهد کرد، میپذیریم؛
- روش [build] از این اطلاعات برای ساخت نوع درخواست [RequestEntity] استفاده میکند؛
- خطوط ۳۴–۳۵: درخواست برای POST. متد [RequestEntity.post] به ما امکان میدهد تا با زنجیرهسازی متدهای مختلف که آن را میسازند، یک درخواست POST ایجاد کنیم:
- متد [RequestEntity.post] هدف URL را به صورت یک نمونه از URI به عنوان پارامتر میپذیرد،
- روش [header] یک هدر HTTP را تعریف میکند. در اینجا، ما هدر [Content-Type: application/json] را به سرور ارسال میکنیم تا نشان دهیم که مقدار ارسالشده به صورت یک رشته jSON دریافت خواهد شد؛
- متد [accept] برای نشان دادن اینکه ما نوع [application/json] را که سرور ارسال خواهد کرد، میپذیریم، استفاده میشود؛
- متد [body] مقدار ارسالشده را تعیین میکند. این پارامتر چهارم متد عمومی [getResponse] (خط 1) است؛
- خط ۳۸: متد [RestTemplate].exchange نوع [ResponseEntity<String>] را بازمیگرداند که کل پاسخ سرور را در بر میگیرد: سربرگهای HTTP و بدنه سند. متد [ResponseEntity].getBody() به شما امکان میدهد به این بدنه که نمایانگر پاسخ سرور است دسترسی پیدا کنید—در این مورد، یک رشته؛
13.6.3.7. پیادهسازی رابط [IDao]
![]() |
کلاس [Dao] رابط [IDao] را پیادهسازی میکند:
package spring.client.dao;
import java.io.IOException;
import java.util.List;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
import com.fasterxml.jackson.core.type.TypeReference;
import com.fasterxml.jackson.databind.ObjectMapper;
import spring.client.entities.Categorie;
import spring.client.entities.Produit;
@Component
public class Dao extends AbstractDao implements IDao {
@Autowired
private ApplicationContext context;
// فیلترها jSON
@Autowired
@Qualifier("jsonMapper")
private ObjectMapper jsonMapper;
@Autowired
@Qualifier("jsonMapperCategorieWithProduits")
private ObjectMapper jsonMapperCategorieWithProduits;
@Autowired
@Qualifier("jsonMapperProduitWithCategorie")
private ObjectMapper jsonMapperProduitWithCategorie;
@Autowired
@Qualifier("jsonMapperCategorieWithoutProduits")
private ObjectMapper jsonMapperCategorieWithoutProduits;
@Autowired
@Qualifier("jsonMapperProduitWithoutCategorie")
private ObjectMapper jsonMapperProduitWithoutCategorie;
@Override
public List<Produit> addProduits(List<Produit> produits) {
// ----------- افزودن محصولات (بدون دستهبندی آنها)
...
}
- خط 17: کلاس [Dao] یک کامپوننت Spring است که بنابراین میتوان کامپوننتهای دیگر Spring را در آن تزریق کرد؛
- خط ۱۸: کلاس [Dao] از کلاس [AbstractDao] که همیناکنون دیدیم، ارث میبرد و رابط [IDao] را پیادهسازی میکند؛
- خطوط ۲۰–۲۱: کانکست اسپرینگ تزریق میشود تا به بنهای آن دسترسی پیدا شود؛
- سطور ۲۴–۳۸: تزریق مپکنندههای jSON که در کلاس [AppConfig] توصیفشده در بخش ۱۳.۶.۲ تعریف شدهاند؛
پیادهسازیهای روشهای مختلف رابط [IDao] همگی از یک الگوی یکسان پیروی میکنند. ما دو روش ارائه خواهیم داد، یکی مبتنی بر عملیات [POST] و دیگری مبتنی بر عملیات [GET].
یک مثال از [GET]: [getCategorieByNameWithProduits]
@Override
public Categorie getCategorieByNameWithProduits(String nom) {
// ----------- بازیابی یک دستهبندی بر اساس نام، همراه با محصولات آن
try {
// پرسوجو
Response<Categorie> response = jsonMapperCategorieWithProduits.readValue(
getResponse(String.format("/getCategorieByNameWithProduits/%s", nom), null),
new TypeReference<Response<Categorie>>() {
});
// خطا؟
if (response.getStatus() != 0) {
// ۱ استثنا پرتاب میشود
throw new DaoException(response.getStatus(), response.getMessages());
} else {
// بازگرداندن بخش اصلی پاسخ سرور
return response.getBody();
}
} catch (DaoException e1) {
throw e1;
} catch (RuntimeException | IOException e2) {
throw new DaoException(113, e2);
}
}
- خط ۷: متد [getResponse] از کلاس والد فراخوانی میشود. این متد است که ارتباط با سرویس وب / jSON را مدیریت میکند. پارامترهای آن به شرح زیر است:
getResponse(String.format("/getCategorieByNameWithProduits/%s", nom), null)
- (ادامه)
- URL سرویس مورد پرسوجو، [/getCategorieByNameWithProduits/nom]؛
- مقدار در حال ارسال. در این مورد، هیچ مقداری وجود ندارد؛
متد [getResponse] یک نوع String را بازمیگرداند که پاسخ jSON ارسالشده توسط سرور است. این پاسخ jSON به شرح زیر دِسیریلایز میشود:
jsonMapperCategorieWithProduits.readValue(
jsonResponse,
new TypeReference<Response<Categorie>>() {
});
زیرا رشته jSON سریالیسازی یک نوع [Response<Categorie>] است؛
- خطوط ۱۱–۱۷: وضعیت پاسخ را بررسی میکنیم. اگر وضعیت ۰ نباشد، یعنی خطایی در سمت سرور رخ داده است. سپس یک استثنا (exception) پرتاب میکنیم (خط ۱۳) و اطلاعات موجود در پاسخ (وضعیت و لیست پیامهای خطا) را در آن گنجاندهایم؛
- خط ۱۶: اگر خطای سمت سرور وجود نداشته باشد، بدنه نوع [Response<Categorie>] بازگردانده میشود، یعنی دسته درخواستی؛
- خطوط ۱۸–۱۹: استثنایی که در خط ۱۶ پرتاب شده است، مدیریت میشود؛
- خطوط ۲۰–۲۲: رسیدگی به سایر استثناها؛
یک مثال از [POST]: [addCategories]
@Override
public List<Categorie> addCategories(List<Categorie> categories) {
// ----------- افزودن دستهبندیها (به همراه محصولاتشان)
try {
// درخواست
Response<List<Categorie>> response = jsonMapperCategorieWithProduits.readValue(
getResponse("/addCategories", jsonMapperCategorieWithProduits.writeValueAsString(categories)),
new TypeReference<Response<List<Categorie>>>() {
});
// خطا؟
if (response.getStatus() != 0) {
// یک استثنا پرتاب میشود
throw new DaoException(response.getStatus(), response.getMessages());
} else {
// بدنه پاسخ سرور را بازمیگرداند
return response.getBody();
}
} catch (DaoException e1) {
throw e1;
} catch (RuntimeException | IOException e2) {
throw new DaoException(104, e2);
}
}
- خط ۲: متد [addCategories] برای ذخیره کردن دستهبندیهای ارسال شده به عنوان پارامتر در پایگاه داده استفاده میشود. این متد همان دستهبندیها را به همراه کلیدهای اصلیشان برمیگرداند. اگر دستهبندیها به همراه محصولات ارسال شوند، آنها نیز ذخیره میشوند؛
- خط ۷: متد [getResponse] والد برای مدیریت ارتباط با سرویس وب / jSON فراخوانی میشود؛
- پارامتر اول URL [/addCategories] است؛
- پارامتر دوم مقدار ارسالشده است، در این مورد لیست دستهبندیهایی که باید ذخیره شوند؛
getResponse("/addCategories", jsonMapperCategorieWithProduits.writeValueAsString(categories))
سپس رشته حاصل jSON سریالیسازی معکوس میشود تا نوع مورد انتظار [Response<List<Categorie>] به دست آید:
Response<List<Categorie>> response = jsonMapperCategorieWithProduits.readValue(
jsonResponse,
new TypeReference<Response<List<Categorie>>>() {
});
- خطوط ۱۱–۱۷: رسیدگی به پاسخ سرور (خطا یا غیرخطا)؛
- خطوط ۲۰–۲۲: مدیریت استثنا؛
تمام روشهای دیگر از الگوی دو روش ارائهشده پیروی میکنند.
13.6.4. آزمون JUnit
بیایید به معماری کلاینت/سرور که در حال حاضر در دست توسعه است بازگردیم:
![]() |
ما یک لایه [DAO] [2] با همان رابط لایه [DAO] [4] ساختهایم. برای آزمایش لایه [DAO] [2]، بنابراین میتوانیم از تست JUnit که برای آزمایش لایه [DAO] [4] استفاده شده بود، استفاده کنیم. بهعنوان یادآوری، این به شرح زیر است:
![]() |
package spring.client.junit;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
import org.junit.Assert;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.common.collect.Lists;
import spring.client.config.DaoConfig;
import spring.client.dao.DaoException;
import spring.client.dao.IDao;
import spring.client.entities.Categorie;
import spring.client.entities.Produit;
@SpringApplicationConfiguration(classes = DaoConfig.class)
@RunWith(SpringJUnit4ClassRunner.class)
public class Test01 {
// [DAO] لایه
@Autowired
private IDao dao;
// jSON فیلترها
@Autowired
@Qualifier("jsonMapper")
private ObjectMapper jsonMapper;
@Autowired
@Qualifier("jsonMapperCategorieWithProduits")
private ObjectMapper jsonMapperCategorieWithProduits;
@Autowired
@Qualifier("jsonMapperProduitWithCategorie")
private ObjectMapper jsonMapperProduitWithCategorie;
@Autowired
@Qualifier("jsonMapperCategorieWithoutProduits")
private ObjectMapper jsonMapperCategorieWithoutProduits;
@Autowired
@Qualifier("jsonMapperProduitWithoutCategorie")
private ObjectMapper jsonMapperProduitWithoutCategorie;
@Before
public void cleanAndFill() {
// پایگاه داده قبل از هر آزمون پاک میشود
log("Vidage de la base de données", 1);
//جدول [CATEGORIES] تخلیه میشود – در نتیجه، جدول [PRODUITS] نیز تخلیه خواهد شد
dao.deleteAllCategories();
// --------------------------------------------------------------------------------------
log("Remplissage de la base", 1);
//جداول پر میشوند
List<Categorie> categories = new ArrayList<Categorie>();
for (int i = 0; i < 2; i++) {
Categorie categorie = new Categorie(String.format("categorie%d", i));
for (int j = 0; j < 5; j++) {
categorie.addProduit(new Produit(String.format("produit%d%d", i, j), 100 * (1 + (double) (i * 10 + j) / 100),
String.format("desc%d%d", i, j)));
}
categories.add(categorie);
}
// کategori را اضافه کنید – در نتیجه، محصولات نیز درج خواهند شد
categories = dao.addCategories(categories);
}
@Test
public void showDataBase() throws BeansException, JsonProcessingException {
// فهرست دستهبندیها
log("Liste des catégories", 2);
List<Categorie> categories = dao.getAllCategories();
affiche(categories, jsonMapperCategorieWithoutProduits);
// فهرست محصولات
log("Liste des produits", 2);
List<Produit> produits = dao.getAllProduits();
affiche(produits, jsonMapperProduitWithoutCategorie);
// چند بررسی
Assert.assertEquals(2, categories.size());
Assert.assertEquals(10, produits.size());
Categorie categorie = findCategorieByName("categorie0", categories);
Assert.assertNotNull(categorie);
Produit produit = findProduitByName("produit03", produits);
Assert.assertNotNull(produit);
Long idCategorie = produit.getIdCategorie();
Assert.assertEquals(categorie.getId(), idCategorie);
}
@Test
public void getCategorieByNameWithProduits() {
log("getCategorieByNameWithProduits", 1);
Categorie categorie1 = dao.getCategorieByNameWithProduits("categorie1");
Assert.assertNotNull(categorie1);
Assert.assertEquals(5, categorie1.getProduits().size());
}
@Test
public void getCategorieByNameWithoutProduits() {
log("getCategorieByNameWithoutProduits", 1);
Categorie categorie1 = dao.getCategorieByNameWithoutProduits("categorie1");
Assert.assertNotNull(categorie1);
Assert.assertEquals("categorie1", categorie1.getNom());
}
@Test
public void getCategorieByIdWithProduits() {
log("getCategorieByIdWithProduits", 1);
Categorie categorie1 = dao.getCategorieByNameWithProduits("categorie1");
Categorie categorie2 = dao.getCategorieByIdWithProduits(categorie1.getId());
Assert.assertNotNull(categorie2);
Assert.assertEquals(categorie1.getId(), categorie2.getId());
Assert.assertEquals(categorie1.getNom(), categorie2.getNom());
}
@Test
public void getCategorieByIdWithoutProduits() {
log("getCategorieByIdWithoutProduits", 1);
Categorie categorie1 = dao.getCategorieByNameWithProduits("categorie1");
Categorie categorie2 = dao.getCategorieByIdWithoutProduits(categorie1.getId());
Assert.assertNotNull(categorie2);
Assert.assertEquals(categorie1.getNom(), categorie2.getNom());
}
@Test
public void getProduitByNameWithCategorie() {
log("getProduitByNameWithCategorie", 1);
Produit produit = dao.getProduitByNameWithCategorie("produit03");
Assert.assertNotNull(produit);
Assert.assertNotNull(produit.getCategorie());
}
@Test
public void getProduitByNameWithoutCategorie() {
log("getProduitByNameWithoutCategorie", 1);
Produit produit = dao.getProduitByNameWithoutCategorie("produit03");
Assert.assertNotNull(produit);
Assert.assertEquals("produit03", produit.getNom());
}
@Test
public void getProduitByIdWithCategorie() {
log("getProduitByNameWithCategorie", 1);
Produit produit = dao.getProduitByNameWithCategorie("produit03");
Produit produit2 = dao.getProduitByIdWithCategorie(produit.getId());
Assert.assertNotNull(produit2);
Assert.assertEquals(produit2.getNom(), produit.getNom());
Assert.assertEquals(produit2.getId(), produit.getId());
Assert.assertEquals(produit.getCategorie().getId(), produit2.getCategorie().getId());
}
@Test
public void getProduitByIdWithoutCategorie() {
log("getProduitByIdWithoutCategorie", 1);
Produit produit = dao.getProduitByNameWithCategorie("produit03");
Produit produit2 = dao.getProduitByIdWithoutCategorie(produit.getId());
Assert.assertNotNull(produit2);
Assert.assertEquals(produit2.getNom(), produit.getNom());
Assert.assertEquals(produit2.getId(), produit.getId());
}
@Test
public void doInsertsInTransaction() {
log("Ajout d'une catégorie [cat1] avec deux produits de même nom", 1);
// وارد کردن دادهها
Categorie categorie = new Categorie("cat1");
categorie.addProduit(new Produit("x", 1.0, ""));
categorie.addProduit(new Produit("x", 1.0, ""));
// افزودن دستهبندی – محصولات نیز بهطور خودکار درج خواهند شد
try {
categorie = dao.addCategories(Lists.newArrayList(categorie)).get(0);
} catch (DaoException e) {
show("Les erreurs suivantes se sont produites :", e.getErreurs());
}
// بررسیها
List<Categorie> categories = dao.getAllCategories();
Assert.assertEquals(2, categories.size());
List<Produit> produits = dao.getAllProduits();
Assert.assertEquals(10, produits.size());
}
@Test
public void updateDataBase() {
log("Mise à jour du prix des produits de [categorie1]", 1);
Categorie categorie1 = dao.getCategorieByNameWithProduits("categorie1");
Categorie categorie1Saved = dao.getCategorieByNameWithProduits("categorie1");
Set<Produit> produits = categorie1.getProduits();
for (Produit produit : produits) {
produit.setPrix(1.1 * produit.getPrix());
}
List<Produit> produits2 = Lists.newArrayList(produits);
produits2 = dao.updateProduits(produits2);
// بررسیها
List<Produit> produitsSaved = Lists.newArrayList(categorie1Saved.getProduits());
for (Produit produit2 : produits2) {
Produit produit = findProduitByName(produit2.getNom(), produitsSaved);
Assert.assertEquals(produit2.getPrix(), produit.getPrix() * 1.1, 1e-6);
}
}
@Test
public void addProduits() throws BeansException, JsonProcessingException {
log("Ajout de deux produits de catégorie [categorie0]", 1);
Categorie categorie0 = dao.getCategorieByNameWithoutProduits("categorie0");
Long idCategorie = categorie0.getId();
Produit p1 = new Produit("x", 1, "");
p1.setIdCategorie(idCategorie);
p1.setCategorie(categorie0);
Produit p2 = new Produit("y", 1, "");
p2.setIdCategorie(idCategorie);
p2.setCategorie(categorie0);
List<Produit> produits = new ArrayList<Produit>();
produits.add(p1);
produits.add(p2);
produits = dao.addProduits(produits);
// بررسی
affiche(produits, jsonMapperProduitWithoutCategorie);
}
// -------------- متدهای خصوصی
private Produit findProduitByName(String nom, List<Produit> produits) {
for (Produit produit : produits) {
if (produit.getNom().equals(nom)) {
return produit;
}
}
return null;
}
private Categorie findCategorieByName(String nom, List<Categorie> categories) {
for (Categorie categorie : categories) {
if (categorie.getNom().equals(nom)) {
return categorie;
}
}
return null;
}
//نمایش یک عنصر از نوع T
static private <T> void affiche(T element, ObjectMapper jsonMapper) throws JsonProcessingException {
System.out.println(jsonMapper.writeValueAsString(element));
}
// نمایش فهرستی از عناصر از نوع T
static private <T> void affiche(List<T> elements, ObjectMapper jsonMapper) throws JsonProcessingException {
for (T element : elements) {
affiche(element, jsonMapper);
}
}
private static void log(String message, int mode) {
// پیامی را نمایش میدهد
String toPrint = null;
switch (mode) {
case 1:
toPrint = String.format("%s --------------------------------", message);
break;
case 2:
toPrint = String.format("-- %s", message);
break;
}
System.out.println(toPrint);
}
private static void show(String title, List<String> messages) {
// عنوان
System.out.println(String.format("%s : ", title));
// پیامها
for (String message : messages) {
System.out.println(String.format("- %s", message));
}
}
}
این با موفقیت اجرا میشود و نتایج زیر را در کنسول تولید میکند:
Vidage de la base de données --------------------------------
Remplissage de la base --------------------------------
Ajout de deux produits de catégorie [categorie0] --------------------------------
{"id":6285,"version":0,"nom":"x","idCategorie":1319,"prix":1.0,"description":""}
{"id":6286,"version":0,"nom":"y","idCategorie":1319,"prix":1.0,"description":""}
Vidage de la base de données --------------------------------
Remplissage de la base --------------------------------
Mise à jour du prix des produits de [categorie1] --------------------------------
Vidage de la base de données --------------------------------
Remplissage de la base --------------------------------
getCategorieByIdWithoutProduits --------------------------------
Vidage de la base de données --------------------------------
Remplissage de la base --------------------------------
getProduitByNameWithoutCategorie --------------------------------
Vidage de la base de données --------------------------------
Remplissage de la base --------------------------------
getCategorieByNameWithProduits --------------------------------
Vidage de la base de données --------------------------------
Remplissage de la base --------------------------------
getCategorieByNameWithoutProduits --------------------------------
Vidage de la base de données --------------------------------
Remplissage de la base --------------------------------
getProduitByNameWithCategorie --------------------------------
Vidage de la base de données --------------------------------
Remplissage de la base --------------------------------
getProduitByNameWithCategorie --------------------------------
Vidage de la base de données --------------------------------
Remplissage de la base --------------------------------
getProduitByIdWithoutCategorie --------------------------------
Vidage de la base de données --------------------------------
Remplissage de la base --------------------------------
-- Liste des catégories
{"id":1337,"version":0,"nom":"categorie0"}
{"id":1338,"version":0,"nom":"categorie1"}
-- Liste des produits
{"id":6367,"version":0,"nom":"produit00","idCategorie":1337,"prix":100.0,"description":"desc00"}
{"id":6368,"version":0,"nom":"produit01","idCategorie":1337,"prix":101.0,"description":"desc01"}
{"id":6369,"version":0,"nom":"produit02","idCategorie":1337,"prix":102.0,"description":"desc02"}
{"id":6370,"version":0,"nom":"produit03","idCategorie":1337,"prix":103.0,"description":"desc03"}
{"id":6371,"version":0,"nom":"produit04","idCategorie":1337,"prix":104.0,"description":"desc04"}
{"id":6372,"version":0,"nom":"produit10","idCategorie":1338,"prix":110.0,"description":"desc10"}
{"id":6373,"version":0,"nom":"produit11","idCategorie":1338,"prix":111.0,"description":"desc11"}
{"id":6374,"version":0,"nom":"produit12","idCategorie":1338,"prix":112.0,"description":"desc12"}
{"id":6375,"version":0,"nom":"produit13","idCategorie":1338,"prix":113.0,"description":"desc13"}
{"id":6376,"version":0,"nom":"produit14","idCategorie":1338,"prix":114.0,"description":"desc14"}
Vidage de la base de données --------------------------------
Remplissage de la base --------------------------------
getCategorieByIdWithProduits --------------------------------
Vidage de la base de données --------------------------------
Remplissage de la base --------------------------------
Ajout d'une catégorie [cat1] avec deux produits de même nom --------------------------------
Les erreurs suivantes se sont produites :
- org.hibernate.exception.ConstraintViolationException: could not execute statement
- could not execute statement
- Duplicate entry 'x' for key 'NOM'
11:24:37.650 [Thread-1] INFO o.s.c.a.AnnotationConfigApplicationContext - Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@f8c1ddd: startup date [Fri Nov 20 11:24:34 CET 2015]; root of context hierarchy



























































