Skip to content

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 (مدل–نما–کنترل‌کننده) را به شرح زیر پیاده‌سازی می‌کند:

پردازش یک درخواست مشتری به شرح زیر انجام می‌شود:

  1. درخواست 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 درخواستی یافت نشد.
  2. پردازش
    • عمل انتخاب‌شده می‌تواند از پارامترهای parami که توسط [Front Controller] به آن ارسال شده‌اند، استفاده کند. این پارامترها ممکن است از چندین منبع منشأ بگیرند:
    • مسیر [/param1/param2/...] از URL،
    • پارامترهای [p1=v1&p2=v2] از URL,
    • پارامترهای ارسال‌شده توسط مرورگر در درخواست آن؛
    • هنگام پردازش درخواست کاربر، ممکن است اقدام به لایه‌های [métier] و [2b] نیاز داشته باشد. پس از پردازش درخواست مشتری، ممکن است پاسخ‌های مختلفی ایجاد شود. یک مثال کلاسیک این است:
    • یک صفحهٔ خطا اگر درخواست نتوانست به‌درستی پردازش شود
    • در غیر این صورت، یک صفحه تأیید
    • اقدام دستور نمایش یک نمای مشخص را صادر می‌کند [3]. این نما داده‌هایی را که به عنوان مدل نما شناخته می‌شوند، نمایش می‌دهد. این همان M در MVC است. اقدام این مدل M [2c] را ایجاد کرده و دستور نمایش یک نما V [3] را صادر می‌کند؛
  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 به طور خودکار پروژه را به این شکل پیکربندی کند؛
  • خط ۱۴: آناوتیشن [@RequestMappingURL را که توسط متد مدیریت می‌شود، مشخص می‌کند، در این مورد 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"} را ایجاد خواهد کرد. در نهایت، رشته‌ای که توسط متد کنترل‌کننده تولید می‌شود، به شکل زیر خواهد بود:

{"id":2,"content":"Hello, World!"}

یا

{"id":2,"content":"Hello, John!"}

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. اجرای پروژه

بیایید پروژه را اجرا کنیم:

 

خروجی‌های کنسول زیر تولید می‌شوند:

  .   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v1.1.9.RELEASE)

2014-11-28 15:22:55.005  INFO 3152 --- [           main] hello.Application                        : Starting Application on Gportpers3 with PID 3152 (started by ST in D:\data\istia-1415\spring mvc\dvp-final\gs-rest-service)
2014-11-28 15:22:55.046  INFO 3152 --- [           main] ationConfigEmbeddedWebApplicationContext : Refreshing org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@62e136d3: startup date [Fri Nov 28 15:22:55 CET 2014]; root of context hierarchy
2014-11-28 15:22:55.762  INFO 3152 --- [           main] o.s.b.f.s.DefaultListableBeanFactory     : Overriding bean definition for bean 'beanNameViewResolver': replacing [Root bean: class [null]; scope=; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration$WhitelabelErrorViewConfiguration; factoryMethodName=beanNameViewResolver; initMethodName=null; destroyMethodName=(inferred); defined in class path resource [org/springframework/boot/autoconfigure/web/ErrorMvcAutoConfiguration$WhitelabelErrorViewConfiguration.class]] with [Root bean: class [null]; scope=; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration$WebMvcAutoConfigurationAdapter; factoryMethodName=beanNameViewResolver; initMethodName=null; destroyMethodName=(inferred); defined in class path resource [org/springframework/boot/autoconfigure/web/WebMvcAutoConfiguration$WebMvcAutoConfigurationAdapter.class]]
2014-11-28 15:22:56.567  INFO 3152 --- [           main] .t.TomcatEmbeddedServletContainerFactory : Server initialized with port: 8080
2014-11-28 15:22:56.738  INFO 3152 --- [           main] o.apache.catalina.core.StandardService   : Starting service Tomcat
2014-11-28 15:22:56.740  INFO 3152 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet Engine: Apache Tomcat/7.0.56
2014-11-28 15:22:56.869  INFO 3152 --- [ost-startStop-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2014-11-28 15:22:56.870  INFO 3152 --- [ost-startStop-1] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 1827 ms
2014-11-28 15:22:57.478  INFO 3152 --- [ost-startStop-1] o.s.b.c.e.ServletRegistrationBean        : Mapping servlet: 'dispatcherServlet' to [/]
2014-11-28 15:22:57.481  INFO 3152 --- [ost-startStop-1] o.s.b.c.embedded.FilterRegistrationBean  : Mapping filter: 'hiddenHttpMethodFilter' to: [/*]
2014-11-28 15:22:57.685  INFO 3152 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**/favicon.ico] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2014-11-28 15:22:57.879  INFO 3152 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/greeting],methods=[],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public hello.Greeting hello.GreetingController.greeting(java.lang.String)
2014-11-28 15:22:57.884  INFO 3152 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error],methods=[],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>> org.springframework.boot.autoconfigure.web.BasicErrorController.error(javax.servlet.http.HttpServletRequest)
2014-11-28 15:22:57.885  INFO 3152 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error],methods=[],params=[],headers=[],consumes=[],produces=[text/html],custom=[]}" onto public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest)
2014-11-28 15:22:57.906  INFO 3152 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/webjars/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2014-11-28 15:22:57.907  INFO 3152 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2014-11-28 15:22:58.231  INFO 3152 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
2014-11-28 15:22:58.318  INFO 3152 --- [           main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8080/http
2014-11-28 15:22:58.319  INFO 3152 --- [           main] hello.Application                        : Started Application in 3.788 seconds (JVM running for 4.424)
  • خط ۱۳: سرور 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] را ببینید. این پلاگینی است که آرشیو قابل اجرا را تولید می‌کند.

[INFO] --- spring-boot-maven-plugin:1.1.0.RELEASE:repackage (default) @ gs-rest-service ---

با استفاده از کنسول، به پوشهٔ تولیدشده بروید:


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 که توسط این کنترلر ارائه شده‌اند به شرح زیر است:


    @RequestMapping(value = "/addProduits",
method = RequestMethod.POST,
consumes = "application/json; charset=UTF-8")
    public String addProduits(HttpServletRequest request) {
...
    }
محصولات را به پایگاه داده اضافه می‌کند. این موارد ارسال می‌شوند. پاسخ، رشته jSON است که شامل فهرست محصولات اضافه شده همراه با کلیدهای اصلی آن‌ها می‌باشد.

    @RequestMapping(value = "/deleteAllProduits",
method = RequestMethod.GET)
    public String deleteAllProduits() {
..
    }
حذف تمام محصولات از پایگاه داده.

    @RequestMapping(value = "/updateProduits",
method = RequestMethod.POST,
consumes = "application/json; charset=UTF-8")
    public String updateProduits(HttpServletRequest request) {
..
    }

محصولات را در پایگاه داده به‌روزرسانی می‌کند. این موارد ارسال می‌شوند. پاسخ، رشته jSON از فهرست محصولات به‌روزرسانی‌شده است.

    @RequestMapping(value = "/getAllProduits",
method = RequestMethod.GET)
    public String getAllProduits() {
..
    }

رشته jSON را برای همه محصولات بازیابی می‌کند.

    @RequestMapping(value = "/addCategories",
method = RequestMethod.POST,
consumes = "application/json; charset=UTF-8")
    public String addCategories(HttpServletRequest request) {
..
    }

دسته‌بندی‌ها را به پایگاه داده اضافه می‌کند. این عملیات ارسال می‌شود. پاسخ رشته‌ای به شکل jSON است که شامل فهرست دسته‌بندی‌های اضافه شده همراه با کلیدهای اصلی آن‌ها می‌باشد. اگر دسته‌بندی‌ها شامل محصولاتی باشند، آن‌ها نیز به پایگاه داده اضافه می‌شوند.

    @RequestMapping(value = "/deleteAllCategories",
method = RequestMethod.GET)
    public String deleteAllCategories() {
...
    }

تمام دسته‌بندی‌ها را از پایگاه داده، به همراه تمام محصولات موجود در آن‌ها، حذف می‌کند. پس از این، پایگاه داده خالی خواهد بود.

    @RequestMapping(value = "/updateCategories",
method = RequestMethod.POST,
consumes = "application/json; charset=UTF-8")
    public String updateCategories
(HttpServletRequest request) {
...
    }

دسته‌بندی‌ها را در پایگاه داده به‌روزرسانی می‌کند. این عملیات ارسال می‌شود. پاسخ، فهرستی از دسته‌بندی‌های به‌روزرسانی‌شده است. اگر دسته‌بندی‌ها شامل محصولاتی باشند، آن‌ها نیز در پایگاه داده به‌روزرسانی می‌شوند. رشته‌ی jSON را برای دسته‌بندی‌های اصلاح‌شده بازمی‌گرداند؛

    @RequestMapping(value = "/getAllCategories",
method = RequestMethod.GET)
    public String getAllCategories() {
...
    }

رشته jSON را از همه دسته‌بندی‌ها بازیابی می‌کند.

    @RequestMapping(value = "/getProduitByIdWithCategorie/{idProduit}",
method = RequestMethod.GET)
    public String getProduitByIdWithCategorie
(@PathVariable("idProduit") Long idProduit) {
...
    }

رشته jSON را برای محصولی که با شناسه آن شناسایی شده است، به همراه دسته‌بندی آن بازیابی می‌کند.

    @RequestMapping(value = "/getProduitByIdWithoutCategorie/{idProduit}",
method = RequestMethod.GET)
    public String getProduitByIdWithoutCategorie
@PathVariable("idProduit") Long idProduit) {
...
    }

رشته jSON را برای محصولی که با شناسه آن شناسایی شده است، به استثنای دسته‌بندی آن، بازیابی می‌کند.

    @RequestMapping(value = "/getProduitByNameWithCategorie/{nom}",
method = RequestMethod.GET)
    public String getProduitByNameWithCategorie(
@PathVariable("nom") String nom) {
...
    }

رشته jSON را برای محصولی که با نامش شناسایی شده است، همراه با دسته‌بندی آن، بازیابی می‌کند.

    @RequestMapping(value = "/getProduitByNameWithoutCategorie/{nom}",
method = RequestMethod.GET)
    public String getProduitByNameWithoutCategorie
(@PathVariable("nom") String nom) {
...
    }

رشته jSON را برای محصولی که با نامش شناسایی شده است، بدون در نظر گرفتن دسته‌بندی آن، بازیابی می‌کند.

    @RequestMapping(value = "/getCategorieByIdWithProduits/{idCategorie}",
 method = RequestMethod.GET)
    public String getCategorieByIdWithProduits
@PathVariable("idCategorie") Long idCategorie) {
...
    }

رشته jSON را برای دسته‌ای که با شناسه آن مشخص شده است، به همراه محصولاتش بازیابی می‌کند.

    @RequestMapping(value = "/getCategorieByNameWithProduits/{nom}",
method = RequestMethod.GET)
    public String getCategorieByNameWithProduits
(@PathVariable("nom") String nom) {
...
    }

رشته jSON را برای یک دسته‌بندی مشخص‌شده با نام، به همراه محصولات آن، بازیابی می‌کند.

    @RequestMapping(value = "/getCategorieByNameWithoutProduits/{nom}",
method = RequestMethod.GET)
    public String getCategorieByNameWithoutProduits(
@PathVariable("nom") String nom) {
...
    }

بازیابی رشته jSON از یک دسته‌بندی مشخص‌شده با نام آن، به استثنای محصولات آن.

    @RequestMapping(value = "/getCategorieByIdWithoutProduits/{idCategorie}",
method = RequestMethod.GET)
    public String getCategorieByIdWithoutProduits(
@PathVariable("idCategorie") Long idCategorie) {
...
    }

رشته 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 ارسال می‌شود. این پارامتر در اینجا استفاده نمی‌شود؛

هنگامی که این کلاس اجرا می‌شود، لاگ‌های زیر تولید می‌شوند:

.   ____          _            __ _ _
 /\\ / ___'_ __ _ _(_)_ __  __ _ \ \ \ \
( ( )\___ | '_ | '_| | '_ \/ _` | \ \ \ \
 \\/  ___)| |_)| | | | | || (_| |  ) ) ) )
  '  |____| .__|_| |_|_| |_\__, | / / / /
 =========|_|==============|___/=/_/_/_/
 :: Spring Boot ::        (v1.2.2.RELEASE)

2015-03-24 16:22:46.608  INFO 9492 --- [           main] spring.webjson.server.boot.Boot          : Starting Boot on Gportpers3 with PID 9492 (D:\data\istia-1415\eclipse\intro-web-json\intro-webjson-server-02\target\classes started by ST in D:\data\istia-1415\eclipse\intro-web-json\intro-webjson-server-02)
2015-03-24 16:22:46.654  INFO 9492 --- [           main] ationConfigEmbeddedWebApplicationContext : Refreshing org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@1d7acb34: startup date [Tue Mar 24 16:22:46 CET 2015]; root of context hierarchy
2015-03-24 16:22:47.521  INFO 9492 --- [           main] o.s.b.f.s.DefaultListableBeanFactory     : Overriding bean definition for bean 'beanNameViewResolver': replacing [Root bean: class [null]; scope=; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=org.springframework.boot.autoconfigure.web.ErrorMvcAutoConfiguration$WhitelabelErrorViewConfiguration; factoryMethodName=beanNameViewResolver; initMethodName=null; destroyMethodName=(inferred); defined in class path resource [org/springframework/boot/autoconfigure/web/ErrorMvcAutoConfiguration$WhitelabelErrorViewConfiguration.class]] with [Root bean: class [null]; scope=; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=org.springframework.boot.autoconfigure.web.WebMvcAutoConfiguration$WebMvcAutoConfigurationAdapter; factoryMethodName=beanNameViewResolver; initMethodName=null; destroyMethodName=(inferred); defined in class path resource [org/springframework/boot/autoconfigure/web/WebMvcAutoConfiguration$WebMvcAutoConfigurationAdapter.class]]
2015-03-24 16:22:47.569  INFO 9492 --- [           main] o.s.b.f.s.DefaultListableBeanFactory     : Overriding bean definition for bean 'entityManagerFactory': replacing [Root bean: class [null]; scope=; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=false; factoryBeanName=spring.data.config.DaoConfig; factoryMethodName=entityManagerFactory; initMethodName=null; destroyMethodName=(inferred); defined in class spring.data.config.DaoConfig] with [Root bean: class [null]; scope=; abstract=false; lazyInit=false; autowireMode=3; dependencyCheck=0; autowireCandidate=true; primary=true; factoryBeanName=org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration; factoryMethodName=entityManagerFactory; initMethodName=null; destroyMethodName=(inferred); defined in class path resource [org/springframework/boot/autoconfigure/orm/jpa/HibernateJpaAutoConfiguration.class]]
2015-03-24 16:22:48.137  INFO 9492 --- [           main] trationDelegate$BeanPostProcessorChecker : Bean 'org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration' of type [class org.springframework.transaction.annotation.ProxyTransactionManagementConfiguration$$EnhancerBySpringCGLIB$$405db6ba] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2015-03-24 16:22:48.162  INFO 9492 --- [           main] trationDelegate$BeanPostProcessorChecker : Bean 'transactionAttributeSource' of type [class org.springframework.transaction.annotation.AnnotationTransactionAttributeSource] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2015-03-24 16:22:48.172  INFO 9492 --- [           main] trationDelegate$BeanPostProcessorChecker : Bean 'transactionInterceptor' of type [class org.springframework.transaction.interceptor.TransactionInterceptor] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2015-03-24 16:22:48.178  INFO 9492 --- [           main] trationDelegate$BeanPostProcessorChecker : Bean 'org.springframework.transaction.config.internalTransactionAdvisor' of type [class org.springframework.transaction.interceptor.BeanFactoryTransactionAttributeSourceAdvisor] is not eligible for getting processed by all BeanPostProcessors (for example: not eligible for auto-proxying)
2015-03-24 16:22:48.586  INFO 9492 --- [           main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat initialized with port(s): 8080 (http)
2015-03-24 16:22:48.850  INFO 9492 --- [           main] o.apache.catalina.core.StandardService   : Starting service Tomcat
2015-03-24 16:22:48.852  INFO 9492 --- [           main] org.apache.catalina.core.StandardEngine  : Starting Servlet Engine: Apache Tomcat/8.0.20
2015-03-24 16:22:48.992  INFO 9492 --- [ost-startStop-1] o.a.c.c.C.[Tomcat].[localhost].[/]       : Initializing Spring embedded WebApplicationContext
2015-03-24 16:22:48.992  INFO 9492 --- [ost-startStop-1] o.s.web.context.ContextLoader            : Root WebApplicationContext: initialization completed in 2342 ms
2015-03-24 16:22:49.645  INFO 9492 --- [ost-startStop-1] o.s.b.c.e.ServletRegistrationBean        : Mapping servlet: 'dispatcherServlet' to [/]
2015-03-24 16:22:49.650  INFO 9492 --- [ost-startStop-1] o.s.b.c.embedded.FilterRegistrationBean  : Mapping filter: 'characterEncodingFilter' to: [/*]
2015-03-24 16:22:49.651  INFO 9492 --- [ost-startStop-1] o.s.b.c.embedded.FilterRegistrationBean  : Mapping filter: 'hiddenHttpMethodFilter' to: [/*]
2015-03-24 16:22:50.380  INFO 9492 --- [           main] j.LocalContainerEntityManagerFactoryBean : Building JPA container EntityManagerFactory for persistence unit 'default'
2015-03-24 16:22:50.392  INFO 9492 --- [           main] o.hibernate.jpa.internal.util.LogHelper  : HHH000204: Processing PersistenceUnitInfo [
    name: default
    ...]
2015-03-24 16:22:50.478  INFO 9492 --- [           main] org.hibernate.Version                    : HHH000412: Hibernate Core {4.3.8.Final}
2015-03-24 16:22:50.480  INFO 9492 --- [           main] org.hibernate.cfg.Environment            : HHH000206: hibernate.properties not found
2015-03-24 16:22:50.483  INFO 9492 --- [           main] org.hibernate.cfg.Environment            : HHH000021: Bytecode provider name : javassist
2015-03-24 16:22:50.697  INFO 9492 --- [           main] o.hibernate.annotations.common.Version   : HCANN000001: Hibernate Commons Annotations {4.0.5.Final}
2015-03-24 16:22:50.806  INFO 9492 --- [           main] org.hibernate.dialect.Dialect            : HHH000400: Using dialect: org.hibernate.dialect.MySQLDialect
2015-03-24 16:22:51.058  INFO 9492 --- [           main] o.h.h.i.ast.ASTQueryTranslatorFactory    : HHH000397: Using ASTQueryTranslatorFactory
2015-03-24 16:22:52.581  INFO 9492 --- [           main] s.w.s.m.m.a.RequestMappingHandlerAdapter : Looking for @ControllerAdvice: org.springframework.boot.context.embedded.AnnotationConfigEmbeddedWebApplicationContext@1d7acb34: startup date [Tue Mar 24 16:22:46 CET 2015]; root of context hierarchy
2015-03-24 16:22:52.654  INFO 9492 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/addProduits],methods=[POST],params=[],headers=[],consumes=[application/json;charset=UTF-8],produces=[],custom=[]}" onto public spring.webjson.webjson.models.Response<java.util.List<spring.data.entities.Produit>> spring.webjson.server.service.Controller.addProduits(javax.servlet.http.HttpServletRequest)
2015-03-24 16:22:52.655  INFO 9492 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/updateProduits],methods=[POST],params=[],headers=[],consumes=[application/json;charset=UTF-8],produces=[],custom=[]}" onto public spring.webjson.webjson.models.Response<java.util.List<spring.data.entities.Produit>> spring.webjson.server.service.Controller.updateProduits(javax.servlet.http.HttpServletRequest)
2015-03-24 16:22:52.655  INFO 9492 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/getAllProduits],methods=[GET],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public spring.webjson.webjson.models.Response<java.util.List<spring.data.entities.Produit>> spring.webjson.server.service.Controller.getAllProduits()
2015-03-24 16:22:52.655  INFO 9492 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/getAllCategories],methods=[GET],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public spring.webjson.webjson.models.Response<java.util.List<spring.data.entities.Categorie>> spring.webjson.server.service.Controller.getAllCategories()
2015-03-24 16:22:52.655  INFO 9492 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/addCategories],methods=[POST],params=[],headers=[],consumes=[application/json;charset=UTF-8],produces=[],custom=[]}" onto public spring.webjson.webjson.models.Response<java.util.List<spring.data.entities.Categorie>> spring.webjson.server.service.Controller.addCategories(javax.servlet.http.HttpServletRequest)
2015-03-24 16:22:52.655  INFO 9492 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/updateCategories],methods=[POST],params=[],headers=[],consumes=[application/json;charset=UTF-8],produces=[],custom=[]}" onto public spring.webjson.webjson.models.Response<java.util.List<spring.data.entities.Categorie>> spring.webjson.server.service.Controller.updateCategories(javax.servlet.http.HttpServletRequest)
2015-03-24 16:22:52.656  INFO 9492 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/getCategorieByNameWithoutProduits/{nom}],methods=[GET],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public spring.webjson.webjson.models.Response<spring.data.entities.Categorie> spring.webjson.server.service.Controller.getCategorieByNameWithoutProduits(java.lang.String)
2015-03-24 16:22:52.656  INFO 9492 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/getProduitByNameWithoutCategorie/{nom}],methods=[GET],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public spring.webjson.webjson.models.Response<spring.data.entities.Produit> spring.webjson.server.service.Controller.getProduitByNameWithoutCategorie(java.lang.String)
2015-03-24 16:22:52.656  INFO 9492 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/getProduitByNameWithCategorie/{nom}],methods=[GET],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public spring.webjson.webjson.models.Response<spring.data.entities.Produit> spring.webjson.server.service.Controller.getProduitByNameWithCategorie(java.lang.String)
2015-03-24 16:22:52.656  INFO 9492 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/getProduitByIdWithCategorie/{idProduit}],methods=[GET],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public spring.webjson.webjson.models.Response<spring.data.entities.Produit> spring.webjson.server.service.Controller.getProduitByIdWithCategorie(java.lang.Long)
2015-03-24 16:22:52.656  INFO 9492 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/getCategorieByNameWithProduits/{nom}],methods=[GET],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public spring.webjson.webjson.models.Response<spring.data.entities.Categorie> spring.webjson.server.service.Controller.getCategorieByNameWithProduits(java.lang.String)
2015-03-24 16:22:52.657  INFO 9492 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/getCategorieByIdWithProduits/{idCategorie}],methods=[GET],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public spring.webjson.webjson.models.Response<spring.data.entities.Categorie> spring.webjson.server.service.Controller.getCategorieByIdWithProduits(java.lang.Long)
2015-03-24 16:22:52.657  INFO 9492 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/deleteAllCategories],methods=[GET],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public spring.webjson.webjson.models.Response<java.lang.Void> spring.webjson.server.service.Controller.deleteAllCategories()
2015-03-24 16:22:52.657  INFO 9492 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/getCategorieByIdWithoutProduits/{idCategorie}],methods=[GET],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public spring.webjson.webjson.models.Response<spring.data.entities.Categorie> spring.webjson.server.service.Controller.getCategorieByIdWithoutProduits(java.lang.Long)
2015-03-24 16:22:52.657  INFO 9492 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/deleteAllProduits],methods=[GET],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public spring.webjson.webjson.models.Response<java.lang.Void> spring.webjson.server.service.Controller.deleteAllProduits()
2015-03-24 16:22:52.658  INFO 9492 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/getProduitByIdWithoutCategorie/{idProduit}],methods=[GET],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public spring.webjson.webjson.models.Response<spring.data.entities.Produit> spring.webjson.server.service.Controller.getProduitByIdWithoutCategorie(java.lang.Long)
2015-03-24 16:22:52.659  INFO 9492 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error],methods=[],params=[],headers=[],consumes=[],produces=[],custom=[]}" onto public org.springframework.http.ResponseEntity<java.util.Map<java.lang.String, java.lang.Object>> org.springframework.boot.autoconfigure.web.BasicErrorController.error(javax.servlet.http.HttpServletRequest)
2015-03-24 16:22:52.659  INFO 9492 --- [           main] s.w.s.m.m.a.RequestMappingHandlerMapping : Mapped "{[/error],methods=[],params=[],headers=[],consumes=[],produces=[text/html],custom=[]}" onto public org.springframework.web.servlet.ModelAndView org.springframework.boot.autoconfigure.web.BasicErrorController.errorHtml(javax.servlet.http.HttpServletRequest)
2015-03-24 16:22:52.691  INFO 9492 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/webjars/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2015-03-24 16:22:52.692  INFO 9492 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2015-03-24 16:22:52.742  INFO 9492 --- [           main] o.s.w.s.handler.SimpleUrlHandlerMapping  : Mapped URL path [/**/favicon.ico] onto handler of type [class org.springframework.web.servlet.resource.ResourceHttpRequestHandler]
2015-03-24 16:22:53.001  INFO 9492 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
2015-03-24 16:22:53.106  INFO 9492 --- [           main] s.b.c.e.t.TomcatEmbeddedServletContainer : Tomcat started on port(s): 8080 (http)
2015-03-24 16:22:53.108  INFO 9492 --- [           main] spring.webjson.server.boot.Boot          : Started Boot in 6.752 seconds (JVM running for 7.433)
  • خطوط 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