Skip to content

5. مقدمه‌ای بر Spring Data JPA

در این فصل، معماری زیر را بررسی خواهیم کرد:

یک لایه [JPA] (Java Persistence API) بین لایه [DAO] و درایور JDBC از SGBD قرار می‌گیرد. اکنون لایه JPA است که دستورات SQL را که برای SGBD در نظر گرفته شده‌اند، صادر می‌کند. لایه [DAO] دیگر دستورات SQL را پردازش نمی‌کند، بلکه تنها با اشیایی که به عنوان موجودیت‌های JPA شناخته می‌شوند کار می‌کند، که نمایانگر جداول مختلف در پایگاه داده مورد استفاده هستند. میدان‌های این اشیاء از طریق حاشیه‌نویسی‌های جاوا به‌طور منحصربه‌فرد با ستون‌های جدول مرتبط می‌شوند. این امر به لایه JPA امکان می‌دهد تا عملیات لایه [DAO] را که بر روی اشیاء JPA انجام می‌شود، به SQL ترجمه کند.

اسپرینگ دیتا شاخه‌ای از اسپرینگ است که بر دسترسی به داده‌ها تمرکز دارد، چه داده‌ها در یک پایگاه داده رابطه‌ای (SGBDR)، یک پایگاه داده NOSQL، یا انواع دیگر ذخیره‌سازی ذخیره شده باشند. در اینجا، ما تنها به SGBDR و دسترسی به آنها از طریق JPA می‌پردازیم. در ادامه، ممکن است [Spring JPA] را بنویسیم تا در واقع به [Spring Data JPA] ارجاع دهد. در معماری فوق، لایه [Spring Data] امکانات لازم را برای لایه [DAO] جهت مدیریت موجودیت‌های JPA فراهم می‌کند.

JPA در واقع یک مشخصه است. ما سه پیاده‌سازی آن را آزمایش خواهیم کرد:

5.1. Exemple-01

وب‌سایت Spring شامل آموزش‌های متعددی است که به شما کمک می‌کند کار با Spring [http://spring.io/guides] را آغاز کنید. ما از یکی از این آموزش‌ها برای معرفی Spring Data استفاده خواهیم کرد. برای این کار، از Spring Tool Suite (STS) استفاده می‌کنیم.

  • در [1]، ما یکی از آموزش‌های [spring.io/guides] را وارد می‌کنیم؛
  • در [2]، ما آموزش [Accessing Data Jpa] را انتخاب می‌کنیم که نشان می‌دهد چگونه با استفاده از Spring Data به یک پایگاه داده دسترسی پیدا کنیم؛
  • در [3]، ما پروژه‌ای را که توسط Maven پیکربندی شده است، انتخاب می‌کنیم؛
  • در [4]، آموزش در دو قالب موجود است: [initial]، که یک نسخه خالی است که هنگام دنبال کردن آموزش آن را پر می‌کنید، یا [complete]، که نسخه نهایی آموزش است. ما مورد دوم را انتخاب می‌کنیم؛
  • در [5]، می‌توانید انتخاب کنید که آموزش را در یک مرورگر مشاهده کنید؛
  • در [6]، پروژه نهایی.

5.1.1. پیکربندی Maven پروژه

وابستگی‌های Maven پروژه در فایل [pom.xml] پیکربندی شده‌اند:


    <groupId>org.springframework</groupId>
    <artifactId>gs-accessing-data-jpa</artifactId>
    <version>0.1.0</version>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.2.3.RELEASE</version>
    </parent>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
        </dependency>
    </dependencies>

    <properties>
        <!-- برای همه چیز از UTF-8 استفاده کنید -->
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
        <start-class>hello.Application</start-class>
</properties>
  • خطوط ۵–۹: تعریف یک پروژه Maven والد. این بخش بیشتر وابستگی‌های پروژه را مشخص می‌کند. این وابستگی‌ها ممکن است کافی باشند، که در این صورت هیچ وابستگی دیگری اضافه نمی‌شود، یا ممکن است کافی نباشند، که در این صورت وابستگی‌های missing اضافه می‌شوند؛
  • خطوط ۱۲–۱۵: وابستگی به [spring-boot-starter-data-jpa] را تعریف می‌کند. این آرتیکفکت شامل کلاس‌های Spring Data است؛
  • خطوط 16–19: تعریف وابستگی به SGBD و H2 که امکان ایجاد و مدیریت پایگاه‌های داده در حافظه را فراهم می‌کنند.

بیایید نگاهی به کلاس‌های ارائه‌شده توسط این وابستگی‌ها بیندازیم:

تعداد زیادی از آن‌ها وجود دارد:

  • برخی به اکوسیستم Spring (همان‌هایی که با «spring» شروع می‌شوند) تعلق دارند؛
  • سایر متعلق به اکوسیستم هایبرنیت (hibernate, jboss) هستند که ما در اینجا پیاده‌سازی JPA آن را استفاده می‌کنیم؛
  • سایر کتابخانه‌ها مربوط به تست هستند (junit, hamcrest);
  • سایر کتابخانه‌های لاگ‌گیری هستند (log4j، logback، slf4j

ما قصد داریم همه‌ی آن‌ها را نگه داریم. برای یک برنامه‌ی تولیدی، باید فقط آن‌هایی را که ضروری هستند نگه داریم.

در خط ۲۶ فایل [pom.xml]، خط زیر را می‌یابیم:


<start-class>hello.Application</start-class>

این خط به خطوط زیر مرتبط است:


<build>
        <plugins>
            <plugin> 
                <artifactId>maven-compiler-plugin</artifactId>
            </plugin>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

خطوط ۶–۹: افزونه [spring-boot-maven-plugin] برای تولید فایل JAR قابل اجرای برنامه استفاده می‌شود. بنابراین خط ۲۶ از فایل [pom.xml] کلاس قابل اجرا را در این JAR مشخص می‌کند.

5.1.2. لایه [JPA]

دسترسی به پایگاه داده از طریق لایه [JPAJava Persistence API:

  

این برنامه ساده است و با مشتریان [Customer] کار می‌کند. کلاس [Customer] بخشی از لایه [JPA] است و به شرح زیر است:


package hello;

import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;

@Entity
public class Customer {

    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;
    private String firstName;
    private String lastName;

    protected Customer() {
    }

    public Customer(String firstName, String lastName) {
        this.firstName = firstName;
        this.lastName = lastName;
    }

    @Override
    public String toString() {
        return String.format("Customer[id=%d, firstName='%s', lastName='%s']", id, firstName, lastName);
    }

}

یک مشتری دارای شناسه [id]، نام [firstName] و نام خانوادگی [lastName] است. هر نمونه [Customer] نمایانگر یک سطر در یک جدول پایگاه داده است.

  • خط ۸: انوتیشن JPA، که به این معنی است که پایداری نمونه‌های [Customer] (ایجاد، خواندن، به‌روزرسانی، حذف) توسط پیاده‌سازی JPA مدیریت خواهد شد. طبق وابستگی‌های Maven، می‌توان دید که پیاده‌سازی JPA / Hibernate در حال استفاده است؛
  • خطوط ۱۱–۱۲: حاشیه‌نویسی‌های JPA که فیلد [id] را با کلید اصلی جدول [Customer] مرتبط می‌کنند. خط ۱۲ نشان می‌دهد که پیاده‌سازی JPA از روش تولید کلید اصلی خاص SGBD مورد استفاده، در این مورد H2، استفاده خواهد کرد؛

هیچ یادداشت دیگری برای JPA وجود ندارد. بنابراین از مقادیر پیش‌فرض استفاده خواهد شد:

  • جدول [Customer] به نام کلاس نام‌گذاری خواهد شد، یعنی [Customer
  • ستون‌های این جدول به نام فیلدهای کلاس نام‌گذاری خواهند شد: [id, firstName, lastName]، با در نظر گرفتن اینکه در نام‌های ستون جدول به حروف بزرگ و کوچک توجه نمی‌شود؛

شایان ذکر است که پیاده‌سازی JPA هرگز نام‌گذاری نمی‌شود.

5.1.3. لایه [Spring Data]

کلاس [CustomerRepository] لایه دسترسی به جدول [Customer] را پیاده‌سازی می‌کند. کد آن به شرح زیر است:

  

package hello;

import java.util.List;

import org.springframework.data.repository.CrudRepository;

public interface CustomerRepository extends CrudRepository<Customer, Long> {

    List<Customer> findByLastName(String lastName);
}

بنابراین این یک رابط است و نه یک کلاس (خط ۷). این رابط، رابط [CrudRepository] را که یک رابط Spring Data است (خط ۵)، گسترش می‌دهد. این رابط توسط دو نوع تعریف می‌شود: اولی نوع عناصر مدیریت‌شده است، در این مورد نوع [Customer]؛ دومی نوع کلید اصلی عناصر مدیریت‌شده است، در این مورد نوع [Long]. رابط [CrudRepository] به شرح زیر است:


package org.springframework.data.repository;

import java.io.Serializable;

@NoRepositoryBean
public interface CrudRepository<T, ID extends Serializable> extends Repository<T, ID> {

    <S extends T> S save(S entity);

    <S extends T> Iterable<S> save(Iterable<S> entities);

    T findOne(ID id);

    boolean exists(ID id);

    Iterable<T> findAll();

    Iterable<T> findAll(Iterable<ID> ids);

    long count();

    void delete(ID id);

    void delete(T entity);

    void delete(Iterable<? extends T> entities);

    void deleteAll();
}

این رابط عملیات CRUD (ایجاد – خواندن – به‌روزرسانی – حذف) را که می‌توان روی یک نوع JPA T انجام داد، تعریف می‌کند:

  • خط ۸: متد save به یک موجودیت T اجازه می‌دهد در پایگاه داده پایدارسازی شود. این متد موجودیت را با استفاده از کلید اصلی که توسط SGBD به آن اختصاص داده شده است، پایدارسازی می‌کند. همچنین به یک موجودیت T که با کلید اصلی id شناسایی می‌شود، اجازه می‌دهد به‌روزرسانی شود. انتخاب بین این دو اقدام به مقدار کلید اصلی id بستگی دارد: اگر مقدار آن null باشد، عملیات پایداری انجام می‌شود؛ در غیر این صورت، عملیات به‌روزرسانی انجام می‌شود؛
  • خط ۱۰: همین امر صدق می‌کند، اما برای فهرستی از موجودیت‌ها؛
  • خط ۱۲: متد findOne برای بازیابی یک موجودیت T که با کلید اصلی id شناسایی شده است، استفاده می‌شود؛
  • خط ۲۲: متد حذف برای حذف یک موجودیت T که با کلید اصلی آن id شناسایی شده است، استفاده می‌شود؛
  • خطوط ۲۴–۲۸: گونه‌های متد [delete
  • خط ۱۶: متد [findAll] تمام انتیت‌های ذخیره‌شده T را بازیابی می‌کند؛
  • خط ۱۸: مشابه مورد قبل، اما محدود به آن دسته از انتیت‌ها که فهرستی از شناسه‌ها برای آن‌ها ارسال شده است؛

بیایید به رابط [CustomerRepository] بازگردیم:


package hello;

import java.util.List;

import org.springframework.data.repository.CrudRepository;

public interface CustomerRepository extends CrudRepository<Customer, Long> {

    List<Customer> findByLastName(String lastName);
}
  • خط ۹ به شما امکان می‌دهد یک [Customer] را با نام [lastName] آن بازیابی کنید؛

و این تمام چیزی است که برای لایه [DAO] وجود دارد. هیچ کلاس پیاده‌سازی‌ای برای رابط قبلی وجود ندارد. این توسط [Spring Data] در زمان اجرا تولید می‌شود. متدهای رابط [CrudRepository] به‌طور خودکار پیاده‌سازی می‌شوند. در مورد متدهای اضافه شده به رابط [CustomerRepository]، بستگی دارد. بیایید به تعریف [Customer] بازگردیم:


    private long id;
    private String firstName;
private String lastName;

متد در خط ۹ به‌طور خودکار توسط [Spring Data] پیاده‌سازی می‌شود زیرا به فیلد [lastName] (خط ۳) از [Customer] ارجاع می‌دهد. وقتی Spring Data با متد [findBySomething] در رابطی که باید پیاده‌سازی شود مواجه می‌شود، آن را با استفاده از پرس‌وجوی زیر JPQL (زبان پرس‌وجوی پایداری جاوا) پیاده‌سازی می‌کند:

select t from T t where t.something=:value

بنابراین نوع T باید دارای یک فیلد با نام [something] باشد. بدین ترتیب، متد

List<Customer> findByLastName(String lastName);

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

return [em].createQuery("select c from Customer c where c.lastName=:value").setParameter("value",lastName).getResultList()

که در آن [em] به زمینه پایداری JPA اشاره دارد. این تنها در صورتی امکان‌پذیر است که کلاس [Customer] دارای فیلدی به نام [lastName] باشد، که چنین است.

در نتیجه، در موارد ساده، Spring Data به ما امکان می‌دهد لایه [DAO] را با استفاده از یک رابط ساده پیاده‌سازی کنیم.

5.1.4. لایه [console]

  

کلاس [Application] به شرح زیر است:


package hello;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class Application implements CommandLineRunner {

    @Autowired
    CustomerRepository repository;
    
    public static void main(String[] args) {
        SpringApplication.run(Application.class);
    }

    @Override
    public void run(String... strings) throws Exception {
        //ذخیره چند مشتری
        repository.save(new Customer("Jack", "Bauer"));
        repository.save(new Customer("Chloe", "O'Brian"));
        repository.save(new Customer("Kim", "Bauer"));
        repository.save(new Customer("David", "Palmer"));
        repository.save(new Customer("Michelle", "Dessler"));

        // بازیابی همه مشتریان
        System.out.println("Customers found with findAll():");
        System.out.println("-------------------------------");
        for (Customer customer : repository.findAll()) {
            System.out.println(customer);
        }
        System.out.println();

        //بازیابی یک مشتری منفرد با ID
        Customer customer = repository.findOne(1L);
        System.out.println("Customer found with findOne(1L):");
        System.out.println("--------------------------------");
        System.out.println(customer);
        System.out.println();

        //بازیابی مشتریان بر اساس نام خانوادگی
        System.out.println("Customer found with findByLastName('Bauer'):");
        System.out.println("--------------------------------------------");
        for (Customer bauer : repository.findByLastName("Bauer")) {
            System.out.println(bauer);
        }
    }

}
  • خط ۹: کلاس این رابط را پیاده‌سازی می‌کند [CommandLineRunner]، که رابطی از [Spring Boot] (خط ۴) است. این رابط تنها یک متد دارد، همان متد در خط ۱۹؛
  • خط ۸: @SpringBootApplication یک تگ متا (annotation) است که چندین تگ [Spring Boot] را گروهبندی می‌کند:
    • @Configuration: نشان می‌دهد که این کلاس یک کلاس پیکربندی است؛
    • @EnableAutoConfiguration: به [Spring Boot] دستور می‌دهد که بر اساس ویژگی‌های مختلف، به‌ویژه محتویات کلاس‌پث پروژه، تعداد مشخصی از بیین‌ها را خود ایجاد کند. از آنجا که کتابخانه‌های Hibernate در classpath قرار دارند، bean [entityManagerFactory] با استفاده از Hibernate پیاده‌سازی خواهد شد. از آنجایی که کتابخانه SGBD H2 در کلاس‌پث قرار دارد، بین [dataSource] با استفاده از H2 پیاده‌سازی خواهد شد. در بیان [dataSource]، نام کاربری و رمز عبور نیز باید تعریف شوند. در اینجا، Spring Boot از مدیر پیش‌فرض در H2 استفاده می‌کند که رمز عبور ندارد. از آنجایی که کتابخانه [spring-tx] در مسیر کلاس‌ها (classpath) وجود دارد، از مدیر تراکنش Spring استفاده خواهد شد؛
    • @EnableWebMvc: اگر کتابخانه [spring-mvc] در مسیر کلاس‌ها (classpath) موجود باشد. در این صورت، برنامه وب به‌طور خودکار پیکربندی می‌شود؛
    • @ComponentScan: که به Spring می‌گوید کجا به دنبال سایر beans، پیکربندی‌ها و سرویس‌ها بگردد. به طور پیش‌فرض، این موارد در بسته‌ای که شامل کلاس دارای آنوتیشن است، یعنی بسته [hello]، جستجو می‌شوند. بنابراین، کلاس‌های [Customer] و [CustomerRepository] پیدا خواهند شد. از آنجا که مورد اول دارای آنوتیشن [@Entity] است، به عنوان یک موجودیت که باید توسط Hibernate مدیریت شود، کاتالوگ خواهد شد. از آنجا که مورد دوم اینترفیس [CrudRepository] را گسترش می‌دهد، به عنوان یک بین Spring ثبت خواهد شد؛
  • خطوط ۱۱–۱۲: بین [CustomerRepository] به کد کلاس اصلی تزریق می‌شود؛
  • خط ۱۵: متد استاتیک [run] از کلاس [SpringApplication] در پروژه Spring Boot اجرا می‌شود. پارامتر آن کلاسی است که دارای انوتیشن [Configuration] یا [EnableAutoConfiguration] است. سپس همه چیزهایی که قبلاً توضیح داده شد، انجام می‌شود. نتیجه یک زمینهٔ برنامهٔ Spring است، یعنی مجموعه‌ای از Beans که توسط Spring مدیریت می‌شوند؛
  • خطوط ۱۹–۴۸: عملیات‌های بعدی به سادگی از متدهای بیانی که رابط [CustomerRepository] را پیاده‌سازی می‌کند، استفاده می‌کنند؛

خروجی کنسول به شرح زیر است:

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

2015-03-10 15:35:43.661  INFO 5784 --- [           main] hello.Application                        : Starting Application on Gportpers3 with PID 5784 (started by ST in C:\Users\Serge Tahé\Documents\workspace-sts-3.6.3.RELEASE\gs-accessing-data-jpa-complete)
2015-03-10 15:35:43.708  INFO 5784 --- [           main] s.c.a.AnnotationConfigApplicationContext : Refreshing org.springframework.context.annotation.AnnotationConfigApplicationContext@5d11346a: startup date [Tue Mar 10 15:35:43 CET 2015]; root of context hierarchy
2015-03-10 15:35:45.230  INFO 5784 --- [           main] j.LocalContainerEntityManagerFactoryBean : Building JPA container EntityManagerFactory for persistence unit 'default'
2015-03-10 15:35:45.254  INFO 5784 --- [           main] o.hibernate.jpa.internal.util.LogHelper  : HHH000204: Processing PersistenceUnitInfo [
    name: default
    ...]
2015-03-10 15:35:45.331  INFO 5784 --- [           main] org.hibernate.Version                    : HHH000412: Hibernate Core {4.3.8.Final}
2015-03-10 15:35:45.332  INFO 5784 --- [           main] org.hibernate.cfg.Environment            : HHH000206: hibernate.properties not found
2015-03-10 15:35:45.334  INFO 5784 --- [           main] org.hibernate.cfg.Environment            : HHH000021: Bytecode provider name : javassist
2015-03-10 15:35:45.651  INFO 5784 --- [           main] o.hibernate.annotations.common.Version   : HCANN000001: Hibernate Commons Annotations {4.0.5.Final}
2015-03-10 15:35:45.754  INFO 5784 --- [           main] org.hibernate.dialect.Dialect            : HHH000400: Using dialect: org.hibernate.dialect.H2Dialect
2015-03-10 15:35:45.877  INFO 5784 --- [           main] o.h.h.i.ast.ASTQueryTranslatorFactory    : HHH000397: Using ASTQueryTranslatorFactory
2015-03-10 15:35:46.154  INFO 5784 --- [           main] org.hibernate.tool.hbm2ddl.SchemaExport  : HHH000227: Running hbm2ddl schema export
2015-03-10 15:35:46.169  INFO 5784 --- [           main] org.hibernate.tool.hbm2ddl.SchemaExport  : HHH000230: Schema export complete
2015-03-10 15:35:46.779  INFO 5784 --- [           main] o.s.j.e.a.AnnotationMBeanExporter        : Registering beans for JMX exposure on startup
Customers found with findAll():
-------------------------------
Customer[id=1, firstName='Jack', lastName='Bauer']
Customer[id=2, firstName='Chloe', lastName='O'Brian']
Customer[id=3, firstName='Kim', lastName='Bauer']
Customer[id=4, firstName='David', lastName='Palmer']
Customer[id=5, firstName='Michelle', lastName='Dessler']

Customer found with findOne(1L):
--------------------------------
Customer[id=1, firstName='Jack', lastName='Bauer']

Customer found with findByLastName('Bauer'):
--------------------------------------------
Customer[id=1, firstName='Jack', lastName='Bauer']
Customer[id=3, firstName='Kim', lastName='Bauer']
2015-03-10 15:35:47.040  INFO 5784 --- [           main] hello.Application                        : Started Application in 3.623 seconds (JVM running for 4.324)
2015-03-10 15:35:47.042  INFO 5784 --- [       Thread-1] s.c.a.AnnotationConfigApplicationContext : Closing org.springframework.context.annotation.AnnotationConfigApplicationContext@5d11346a: startup date [Tue Mar 10 15:35:43 CET 2015]; root of context hierarchy
2015-03-10 15:35:47.044  INFO 5784 --- [       Thread-1] o.s.j.e.a.AnnotationMBeanExporter        : Unregistering JMX-exposed beans on shutdown
2015-03-10 15:35:47.046  INFO 5784 --- [       Thread-1] j.LocalContainerEntityManagerFactoryBean : Closing JPA EntityManagerFactory for persistence unit 'default'
2015-03-10 15:35:47.047  INFO 5784 --- [       Thread-1] org.hibernate.tool.hbm2ddl.SchemaExport  : HHH000227: Running hbm2ddl schema export
2015-03-10 15:35:47.051  INFO 5784 --- [       Thread-1] org.hibernate.tool.hbm2ddl.SchemaExport  : HHH000230: Schema export complete
  • خطوط ۱–۸: لوگوی پروژه Spring Boot؛
  • خط ۹: کلاس [hello.Application] اجرا می‌شود؛
  • خط ۱۰: [AnnotationConfigApplicationContext] کلاسی است که رابط [ApplicationContext] اسپرینگ را پیاده‌سازی می‌کند. این یک مخزن بین (bean) است؛
  • خط ۱۱: بین [entityManagerFactory] با استفاده از کلاس [LocalContainerEntityManagerFactory]، یک کلاس Spring، پیاده‌سازی می‌شود. این کلاس لایه [JPA] را مدیریت می‌کند؛
  • خط ۱۲: کلاس [Hibernate] ظاهر می‌شود. این پیاده‌سازی، JPA، است که انتخاب شده است؛
  • خط ۱۹: یک گویش Hibernate، نوع SQL است که باید با SGBD استفاده شود. در اینجا، گویش [H2Dialect] نشان می‌دهد که Hibernate با SGBD و H2 کار خواهد کرد؛
  • خطوط ۲۱–۲۲: پایگاه داده ایجاد می‌شود. جدول [CUSTOMER] ایجاد می‌شود. این بدان معناست که Hibernate برای تولید جدول‌ها از تعاریف JPA پیکربندی شده است؛ در اینجا، تعریف JPA از کلاس [Customer
  • خطوط ۲۶–۳۰: نتیجه متد [findAll] اینفریس؛
  • خط ۳۴: نتیجه متد [findOne] اینفریس؛
  • خطوط ۳۸–۳۹: نتایج متد [findByLastName
  • از خط ۴۱ به بعد: لاگ‌های مربوط به بسته‌شدن کانکست Spring.

5.1.5. پیکربندی دستی پروژه Spring Data

ما پروژه قبلی را در پروژه [gs-accessing-data-jpa-02] کپی می‌کنیم:

  

در این پروژه جدید، ما به پیکربندی خودکار ارائه‌شده توسط Spring Boot متکی نخواهیم بود. ما آن را به‌صورت دستی پیکربندی خواهیم کرد. این ممکن است زمانی مفید باشد که پیکربندی‌های پیش‌فرض با نیازهای ما مطابقت نداشته باشند.

ابتدا، وابستگی‌های لازم را در فایل [pom.xml] مشخص می‌کنیم:


<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd"
    xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance">
    <modelVersion>4.0.0</modelVersion>

    <groupId>org.springframework</groupId>
    <artifactId>gs-accessing-data-jpa-02</artifactId>
    <version>0.1.0</version>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.2.3.RELEASE</version>
    </parent>

    <dependencies>
        <!-- Spring Data -->
        <dependency>
            <groupId>org.springframework.data</groupId>
            <artifactId>spring-data-jpa</artifactId>
        </dependency>
        <!-- Hibernate -->
        <dependency>
            <groupId>org.hibernate</groupId>
            <artifactId>hibernate-entitymanager</artifactId>
        </dependency>
        <!-- H2 پایگاه داده -->
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
        </dependency>
        <!-- Tomcat JDBC -->
        <dependency>
            <groupId>org.apache.tomcat</groupId>
            <artifactId>tomcat-jdbc</artifactId>
        </dependency>
    </dependencies>

    <properties>
        <!--برای همه چیز از UTF-8 استفاده کنید -->
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
    </properties>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

    <repositories>
        <repository>
            <id>spring-releases</id>
            <name>Spring Releases</name>
            <url>https://repo.spring.io/libs-release</url>
        </repository>
        <repository>
            <id>org.jboss.repository.releases</id>
            <name>JBoss Maven Release Repository</name>
            <url>https://repository.jboss.org/nexus/content/repositories/releases</url>
        </repository>
    </repositories>

    <pluginRepositories>
        <pluginRepository>
            <id>spring-releases</id>
            <name>Spring Releases</name>
            <url>https://repo.spring.io/libs-release</url>
        </pluginRepository>
    </pluginRepositories>

</project>
  • خطوط ۱۰–۱۴: پروژهٔ والد Maven که کتابخانه‌های آن را استفاده خواهیم کرد؛
  • خطوط ۱۸–۲۱: Spring Data، که برای دسترسی به پایگاه داده استفاده می‌شود؛
  • خطوط 23–26: پیاده‌سازی Hibernate از مشخصات JPA؛
  • خطوط ۲۸–۳۱: SGBD و H2;
  • خطوط ۳۳–۳۶: پایگاه‌های داده اغلب با استخرهای اتصال (connection pools) استفاده می‌شوند که از باز و بسته کردن مکرر اتصالات جلوگیری می‌کنند. در اینجا، پیاده‌سازی مورد استفاده متعلق به [tomcat-jdbc] است؛

در پروژه جدید، انتیت [Customer] و اینترفیس [CustomerRepository] بدون تغییر باقی می‌مانند. ما کلاس [Application] را اصلاح خواهیم کرد که به دو کلاس تقسیم خواهد شد:

  • [Config]، که کلاس پیکربندی خواهد بود:
  • [Main]، که کلاس اجرایی خواهد بود؛
  

کلاس اجرایی [Application] اکنون به شرح زیر است:


package console;

import org.springframework.context.annotation.AnnotationConfigApplicationContext;

import repositories.CustomerRepository;
import config.AppConfig;
import entities.Customer;

public class Application {
    public static void main(String[] args) {
        // آشکارسازی زمینهٔ Spring
        AnnotationConfigApplicationContext context = new AnnotationConfigApplicationContext(AppConfig.class);
        CustomerRepository repository = context.getBean(CustomerRepository.class);

        // ذخیره چند مشتری
        repository.save(new Customer("Jack", "Bauer"));
        repository.save(new Customer("Chloe", "O'Brian"));
        repository.save(new Customer("Kim", "Bauer"));
        repository.save(new Customer("David", "Palmer"));
        repository.save(new Customer("Michelle", "Dessler"));

        ...

        // بستن زمینه
        context.close();
    }

}
  • خط ۹: کلاس [Application] دیگر هیچ نشانه‌گذاری پیکربندی ندارد؛
  • خطوط ۳–۷: توجه کنید که دیگر هیچ وارداتی از پکیج [Spring Boot] وجود ندارد؛
  • خط ۱۲: بین‌های Spring ایجاد می‌شوند. ما کانکست Spring را به دست می‌آوریم که حاوی ارجاعات به بین‌های ایجاد شده است؛
  • خط ۱۳: یک مرجع به نوع [CustomerRepository] درخواست می‌شود؛

کلاس [Config] که پروژه را پیکربندی می‌کند، به شرح زیر است:


package config;

import javax.persistence.EntityManagerFactory;

import org.apache.tomcat.jdbc.pool.DataSource;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.JpaVendorAdapter;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.orm.jpa.vendor.Database;
import org.springframework.orm.jpa.vendor.HibernateJpaVendorAdapter;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;

@EnableTransactionManagement
@EnableJpaRepositories(basePackages = { "repositories" })
@Configuration
// @ComponentScan(basePackages={"package1","package2"})
public class AppConfig {

    // پایگاه داده H2
    @Bean
    public DataSource dataSource() {
        // منبع داده TomcatJdbc
        DataSource dataSource = new DataSource();
        // پیکربندی دسترسی JDBC
        dataSource.setDriverClassName("org.h2.Driver");
        dataSource.setUrl("jdbc:h2:./demo");
        dataSource.setUsername("sa");
        dataSource.setPassword("");
        //یک اتصال در ابتدا باز
        dataSource.setInitialSize(1);
        // نتیجه
        return dataSource;
    }

    // ارائه‌دهنده JPA
    @Bean
    public JpaVendorAdapter jpaVendorAdapter() {
        HibernateJpaVendorAdapter hibernateJpaVendorAdapter = new HibernateJpaVendorAdapter();
        hibernateJpaVendorAdapter.setShowSql(false);
        hibernateJpaVendorAdapter.setGenerateDdl(true);
        hibernateJpaVendorAdapter.setDatabase(Database.H2);
        return hibernateJpaVendorAdapter;
    }

    // EntityManagerFactory
    @Bean
    public EntityManagerFactory entityManagerFactory(JpaVendorAdapter jpaVendorAdapter, DataSource dataSource) {
        LocalContainerEntityManagerFactoryBean factory = new LocalContainerEntityManagerFactoryBean();
        factory.setJpaVendorAdapter(jpaVendorAdapter);
        factory.setPackagesToScan("entities");
        factory.setDataSource(dataSource);
        factory.afterPropertiesSet();
        return factory.getObject();
    }

    // مدیر تراکنش
    @Bean
    public PlatformTransactionManager transactionManager(EntityManagerFactory entityManagerFactory) {
        JpaTransactionManager txManager = new JpaTransactionManager();
        txManager.setEntityManagerFactory(entityManagerFactory);
        return txManager;
    }

}
  • خط 17: آناوتیشن [@EnableTransactionManagement] نشان می‌دهد که آناوتیشن‌های [@Transactional] باید تفسیر شوند. متدهای اینترفیس‌های [CrudRepository] دارای این آناوتیشن‌ها هستند. بنابراین، آن‌ها در داخل یک تراکنش اجرا می‌شوند؛
  • خط ۱۸: تگ [@EnableJpaRepositories] برای مشخص کردن دایرکتوری‌هایی استفاده می‌شود که رابط‌های Spring Data با شناسه [CrudRepository] در آن‌ها قرار دارند. این رابط‌ها به کامپوننت‌های Spring تبدیل شده و در داخل کانکست Spring در دسترس خواهند بود؛
  • خط ۱۹: آناوتیشن [@Configuration] کلاس [Config] را به یک کلاس پیکربندی Spring تبدیل می‌کند؛
  • خط ۲۰: آناوتیشن [@ComponentScan] فهرست دایرکتوری‌هایی را که باید در آن‌ها به دنبال کامپوننت‌های Spring گشت، مشخص می‌کند. کامپوننت‌های اسپرینگ کلاس‌هایی هستند که با آنوتیشن‌های اسپرینگ مانند @Service، @Component، @Controller و غیره تگ شده‌اند. در اینجا، آنوتیشن دیگری به جز آن‌هایی که در کلاس [AppConfig] تعریف شده‌اند وجود ندارد، بنابراین آنوتیشن کامنت شده است؛
  • خطوط ۲۴–۳۷: منبع داده، پایگاه داده H2 را تعریف می‌کنند. این @Bean annotation در خط ۲۵ است که شیء ایجادشده توسط این متد را به یک مؤلفهٔ مدیریت‌شده توسط Spring تبدیل می‌کند. نام متد می‌تواند هر چیزی باشد. با این حال، اگر EntityManagerFactory در خط ۵۱ وجود نداشته باشد و از طریق پیکربندی خودکار تعریف شده باشد، باید متد [dataSource] نام‌گذاری شود؛
  • خط ۳۰: پایگاه داده با نام [demo] نام‌گذاری شده و در پوشه پروژه ایجاد خواهد شد؛
  • خطوط 40–47: پیاده‌سازی مورد استفاده JPA را تعریف می‌کنند، در این مورد یک پیاده‌سازی Hibernate. نام متد در اینجا می‌تواند هر چیزی باشد؛
  • خط ۴۳: هیچ لاگی برای SQL ثبت نمی‌شود؛
  • خط ۴۴: پایگاه داده در صورت عدم وجود ایجاد خواهد شد؛
  • خطوط 50–58: متد EntityManagerFactory را که به پایداری JPA رسیدگی می‌کند، تعریف کنید. نام این متد باید [entityManagerFactory] باشد؛
  • خط ۵۱: متد دو پارامتر از همان نوع دو بیانی که قبلاً تعریف شده‌اند دریافت می‌کند. این‌ها سپس توسط Spring به‌عنوان پارامترهای متد نمونه سازی و تزریق می‌شوند؛
  • خط ۵۳: پیاده‌سازی JPA را که باید استفاده شود، مشخص می‌کند؛
  • خط ۵۴: دایرکتوری‌هایی را مشخص می‌کند که در آن‌ها می‌توان اشیاء JPA را یافت؛
  • خط ۵۵: منبع داده‌ای را که باید مدیریت شود مشخص می‌کند؛
  • خطوط ۶۱–۶۶: مدیر تراکنش. این متد باید با نام [transactionManager] نامگذاری شود. این متد، بین (bean) موجود در خطوط ۵۱–۵۸ را به عنوان پارامتر دریافت می‌کند؛
  • خط ۶۴: مدیر تراکنش با EntityManagerFactory مرتبط می‌شود؛

روش‌های فوق را می‌توان به هر ترتیبی تعریف کرد.

اجرای پروژه نتایج یکسانی را تولید می‌کند. یک فایل جدید در پوشه پروژه ظاهر می‌شود: فایل پایگاه داده H2:

  

5.1.6. ایجاد یک آرشیو قابل اجرا

برای ایجاد یک آرشیو قابل اجرا از پروژه، مراحل زیر را دنبال کنید:

  • در [1]: یک پیکربندی اجرا ایجاد کنید؛
  • در [2]: از نوع [Java Application]
  • در [3]: پروژهٔ اجرایی را مشخص کنید (از دکمهٔ Browse استفاده کنید);
  • در [4]: کلاسی را که باید اجرا شود مشخص می‌کند؛
  • در [5]: نام پیکربندی اجرا – می‌تواند هر چیزی باشد؛
  • در [6]: پروژه صادر می‌شود؛
  • در [7]: به شکل یک آرشیو اجرایی JAR؛
  • در [8]: مسیر و نام فایل اجرایی که باید ایجاد شود را مشخص می‌کند؛
  • در [9]: نام پیکربندی اجرای ایجاد شده در [5];
10
  
  • در [10]، آرشیو ایجاد شده؛

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

.....\dist>dir
12/06/2014  09:11        15 104 869 gs-accessing-data-jpa-02.jar

آرشیو به شرح زیر اجرا می‌شود:


.....\dist>java -jar gs-accessing-data-jpa-02.jar

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

SLF4J: Failed to load class "org.slf4j.impl.StaticLoggerBinder".
SLF4J: Defaulting to no-operation (NOP) logger implementation
SLF4J: See http://www.slf4j.org/codes.html#StaticLoggerBinder برای جزئیات بیشتر.
mars 10, 2015 5:27:20 PM org.hibernate.jpa.internal.util.LogHelper logPersistenceUnitInformation
INFO: HHH000204: Processing PersistenceUnitInfo [
    name: default
    ...]
mars 10, 2015 5:27:20 PM org.hibernate.Version logVersion
INFO: HHH000412: Hibernate Core {4.3.8.Final}
mars 10, 2015 5:27:20 PM org.hibernate.cfg.Environment <clinit>
INFO: HHH000206: hibernate.properties not found
mars 10, 2015 5:27:20 PM org.hibernate.cfg.Environment buildBytecodeProvider
INFO: HHH000021: Bytecode provider name : javassist
mars 10, 2015 5:27:22 PM org.hibernate.annotations.common.reflection.java.JavaReflectionManager <clinit>
INFO: HCANN000001: Hibernate Commons Annotations {4.0.5.Final}
mars 10, 2015 5:27:22 PM org.hibernate.dialect.Dialect <init>
INFO: HHH000400: Using dialect: org.hibernate.dialect.H2Dialect
mars 10, 2015 5:27:22 PM org.hibernate.hql.internal.ast.ASTQueryTranslatorFactory <init>
INFO: HHH000397: Using ASTQueryTranslatorFactory
mars 10, 2015 5:27:22 PM org.hibernate.tool.hbm2ddl.SchemaUpdate execute
INFO: HHH000228: Running hbm2ddl schema update
mars 10, 2015 5:27:22 PM org.hibernate.tool.hbm2ddl.SchemaUpdate execute
INFO: HHH000102: Fetching database metadata
mars 10, 2015 5:27:22 PM org.hibernate.tool.hbm2ddl.SchemaUpdate execute
INFO: HHH000396: Updating schema
mars 10, 2015 5:27:22 PM org.hibernate.tool.hbm2ddl.DatabaseMetadata getTableMetadata
INFO: HHH000262: Table not found: Customer
mars 10, 2015 5:27:22 PM org.hibernate.tool.hbm2ddl.DatabaseMetadata getTableMetadata
INFO: HHH000262: Table not found: Customer
mars 10, 2015 5:27:22 PM org.hibernate.tool.hbm2ddl.DatabaseMetadata getTableMetadata
INFO: HHH000262: Table not found: Customer
mars 10, 2015 5:27:22 PM org.hibernate.tool.hbm2ddl.SchemaUpdate execute
INFO: HHH000232: Schema update complete
Customers found with findAll():
-------------------------------
Customer[id=1, firstName='Jack', lastName='Bauer']
Customer[id=2, firstName='Chloe', lastName='O'Brian']
Customer[id=3, firstName='Kim', lastName='Bauer']
Customer[id=4, firstName='David', lastName='Palmer']
Customer[id=5, firstName='Michelle', lastName='Dessler']

Customer found with findOne(1L):
--------------------------------
Customer[id=1, firstName='Jack', lastName='Bauer']

Customer found with findByLastName('Bauer'):
--------------------------------------------
Customer[id=1, firstName='Jack', lastName='Bauer']
Customer[id=3, firstName='Kim', lastName='Bauer']

5.1.7. ایجاد پروژه [Spring Data]

برای ایجاد قالب پروژه Spring Data، مراحل زیر را دنبال کنید:

  • در [1]، یک پروژه جدید ایجاد کنید؛
  • در [2]: از نوع [Spring Starter Project];
  • پروژه تولیدشده یک پروژه Maven خواهد بود. در [3]، نام گروه پروژه را مشخص کنید؛
  • در [4]: نام محصول (در این مورد یک JAR) را که هنگام ساخت پروژه ایجاد می‌شود، مشخص کنید؛
  • در [5]: نام اکلیپس پروژه – این می‌تواند هر چیزی باشد (لازم نیست که با [4] یکسان باشد)؛
  • در [7]: این مشخص می‌کند که یک پروژه با یک لایه [JPA] حاوی SGBD و MySQL ایجاد خواهد شد. وابستگی‌های مورد نیاز برای چنین پروژه‌ای سپس در فایل [pom.xml] گنجانده خواهند شد؛
  • در [8]، نام پوشه پروژه را وارد کنید؛
  • در [9]، جادوگر را تکمیل کنید؛
  • در [10]: پروژه ایجاد شد؛

فایل [pom.xml] شامل وابستگی‌های مورد نیاز برای پروژه JPA است:


<?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>istia.st.springdata</groupId>
    <artifactId>intro-spring-data-01</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <packaging>jar</packaging>

    <name>intro-spring-data-01</name>
    <description>démo spring data avec table de produits</description>

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.2.3.RELEASE</version>
        <relativePath/> <!-- جستجوی والد از مخزن -->
    </parent>

    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <start-class>demo.IntroSpringData01Application</start-class>
        <java.version>1.7</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
            <scope>runtime</scope>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>
  • خطوط ۱۴–۱۹: پروژهٔ والد Maven؛
  • خطوط ۲۸–۳۱: وابستگی مورد نیاز JPA – شامل [Spring Data] خواهد بود؛
  • خطوط ۳۲–۳۶: وابستگی به درایور JDBC از MySQL;
  • خطوط ۳۷–۴۱: وابستگی‌های مورد نیاز برای تست‌های JUnit یکپارچه با Spring؛

کلاس قابل اجرا [Application] هیچ کاری انجام نمی‌دهد اما از پیش پیکربندی شده است:


package demo;

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;

@SpringBootApplication
public class IntroSpringData01Application {

    public static void main(String[] args) {
        SpringApplication.run(IntroSpringData01Application.class, args);
    }
}
  • علامت‌گذاری [@SpringBootApplication] این کلاس را به یک کلاس پیکربندی خودکار پروژه تبدیل می‌کند؛

کلاس تست [ApplicationTests] هیچ کاری انجام نمی‌دهد اما از پیش پیکربندی شده است:


package demo;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = IntroSpringData01Application.class)
public class IntroSpringData01ApplicationTests {

    @Test
    public void contextLoads() {
    }

}
  • خط ۹: anotation [@SpringApplicationConfiguration] امکان استفاده از فایل پیکربندی [IntroSpringData01Application] را فراهم می‌کند. بنابراین کلاس تست از تمام beanهای تعریف‌شده در این فایل بهره‌مند خواهد شد؛
  • خط ۸: حاشیه‌نویسی [@RunWith] امکان یکپارچه‌سازی Spring با JUnit را فراهم می‌کند: این کلاس قادر خواهد بود به‌عنوان تست JUnit اجرا شود. [@RunWith] یک حاشیه‌نویسی JUnit است (خط ۴)، در حالی که کلاس [SpringJUnit4ClassRunner] یک کلاس Spring است (خط ۶)؛

اکنون که اسکلت برنامه JPA را داریم، می‌توانیم آن را تکمیل کنیم.