Spring Data JPA in Action | اسپرینگ دیتا جی پی ای در عمل
توضیحات جلسه
جزوه و مستندات
مفاهیم کلیدی
- Spring Data JPA
بخشی از اکوسیستم بزرگ Spring Data که با ایجاد یک لایه انتزاعی بر روی استانداردهای JPA، عملیات ذخیرهسازی، خواندن و مدیریت موجودیتها در دیتابیسهای رابطهای را بدون نیاز به پیادهسازی دستی ریپازیتوریها میسر میسازد.
- JPA Starter Dependency
وابستگی آغازگر در اسپرینگ بوت (spring-boot-starter-data-jpa) که علاوه بر ماژول Spring Data JPA، پیادهسازی پیشفرض JPA یعنی Hibernate را به همراه تمام پیشنیازهای ترنزتیو به ساختار پروژه اضافه میکند.
@Entity
انوتیشن بنیادین در استاندارد JPA که مشخص میکند کلاس نشانهگذاریشده یک موجودیت پایگاه داده است و چرخه حیات و نگاشت ردیفهای آن توسط ارائهدهنده پایداری داده مدیریت میشود.
@GeneratedValue
انوتیشنی برای مشخص کردن استراتژی تولید کلید اصلی خودکار توسط پایگاه داده؛ مانند GenerationType.AUTO که نوع تخصیص شناسه را به رفتار ارائهدهنده پایداری داده و پایگاه داده واگذار میکند.
- Entity Relationships
انوتیشنهای نگاشت رابطه در مدل شیء-رابطهای؛ شامل @ManyToMany برای نمایش وابستگی چندبهچند (مانند رابطه بین Taco و Ingredient) و @OneToMany برای ارتباط یکبهچند (مانند رابطه بین TacoOrder و Taco).
- Cascade Operations
مکانیزمی در نگاشت روابط که اعمال یک عملیات روی موجودیت والد را به موجودیتهای وابسته سرایت میدهد؛ به عنوان نمونه مقدار CascadeType.ALL در حذف سفارش سبب حذف خودکار تمام تاکوهای درون آن سفارش میشود.
- JPA Constructor Contract
قاعده استاندارد در JPA مبنی بر الزام موجودیتها به داشتن یک سازنده بدون آرگومان (No-args Constructor) جهت نمونهسازی پویا در لایه بازتابی؛ توسعهدهندگان معمولاً با استفاده از انوتیشن Lombok نظیر @NoArgsConstructor(access = AccessLevel.PRIVATE, force = true) این سازنده را بدون باز گذاشتن دسترسی عمومی ایجاد میکنند.
موارد مصاحبه ای
- تفاوت پکیج انوتیشن
@Idدر Spring Data JDBC و Spring Data JPA چیست؟
در Spring Data JPA انوتیشن @Id از پکیج استانداردهای جاوا (javax.persistence یا jakarta.persistence) تامین میشود؛ در حالی که در Spring Data JDBC از پکیج اختصاصی org.springframework.data.annotation استفاده میگردد و جابهجایی نادرست آنها باعث خطای عدم شناسایی شناسه میشود.
- چرا اینترفیس
Persistableدر Spring Data JPA بر خلاف Spring Data JDBC مورد نیاز نیست؟
در Spring Data JDBC تعیین وضعیت شیء (جدید بودن یا بهروزرسانی بودن) در موارد فاقد شناسه خودکار نیازمند پیادهسازی متد isNew() از طریق Persistable بود؛ اما در JPA کانتکست پایداری و متدهای فریمورک به طور خودکار با ردیابی کانتکست پایداری موجودیت، دستور مناسب (persist یا merge) را صادر میکنند.
- چگونه میتوان پیادهساز پیشفرض JPA (یعنی Hibernate) را با پیادهساز دیگری مانند EclipseLink تعویض کرد؟
باید ماژول hibernate-core را درون بلاک وابستگی spring-boot-starter-data-jpa از طریق تگ <exclusions> مستثنی کرد و سپس وابستگی مربوط به کتابخانه مورد نظر (نظیر org.eclipse.persistence.jpa) را به فایل pom.xml اضافه نمود.
- چرا هنگام استفاده همزمان از
@Dataو@NoArgsConstructorدر کلاسهای JPA، انوتیشن@RequiredArgsConstructorبه شکل صریح ضروری میشود؟
انوتیشن @Data سازنده فیلدهای اجباری (final) را فقط زمانی به شکل پیشفرض تولید میکند که هیچ سازنده دیگری تعریف نشده باشد؛ به محض تعریف @NoArgsConstructor، سازنده فیلدهای اجباری حذف میشود و باید با انوتیشن @RequiredArgsConstructor به صورت دستی بازگردانده شود.
- نقش اینترفیس
CrudRepositoryدر همگرایی پروژههای مختلف Spring Data چیست؟
اینترفیس CrudRepository به عنوان یک قرارداد یکنواخت انتزاعی در کلیه زیرپروژههای Spring Data (شامل JDBC، JPA، MongoDB و ...) عمل میکند؛ به همین دلیل تعریف کدهای ریپازیتوری در سطح اینترفیس هنگام مهاجرت از Spring Data JDBC به Spring Data JPA بدون تغییر باقی میماند.
سناریو کاربردی
- مدلسازی موجودیتهای دامنه با نگاشت JPA و برقراری روابط آبشاری
در این سناریو کلاسهای سفارش و آیتمهای تاکو از ساختار قبلی JDBC به مدل استاندارد JPA تبدیل شده و رابطه یکبهچند همراه با انتشار آبشاری پیکربندی میشود:
@Data
@Entity
@AllArgsConstructor
@NoArgsConstructor(access = AccessLevel.PRIVATE, force = true)
public class Ingredient {
@Id
private String id;
private String name;
private Type type;
public enum Type {
WRAP, PROTEIN, VEGGIES, CHEESE, SAUCE
}
}
@Data
@Entity
public class Taco {
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String name;
private Date createdAt = new Date();
@ManyToMany
private List<Ingredient> ingredients = new ArrayList<>();
}
@Data
@Entity
@Table(name = "Taco_Order")
public class TacoOrder implements Serializable {
private static final long serialVersionUID = 1L;
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private Date placedAt = new Date();
private String deliveryName;
private String deliveryStreet;
private String deliveryCity;
private String deliveryState;
private String deliveryZip;
private String ccNumber;
private String ccExpiration;
private String ccCVV;
@OneToMany(cascade = CascadeType.ALL)
private List<Taco> tacos = new ArrayList<>();
public void addTaco(Taco taco) {
this.tacos.add(taco);
}
}
و در لایه دسترسی به داده، اینترفیسها مستقیماً بر مبنای CrudRepository سازماندهی میشوند:
public interface OrderRepository extends CrudRepository<TacoOrder, Long> {
}
بیشتر بدانید
- ویژگی force در NoArgsConstructor برای فیلدهای final
هنگامی که در موجودیتها فیلدهای نهایی (final) تعریف شده باشند، اضافه کردن آرگومان force = true در انوتیشن @NoArgsConstructor الزامی است تا کامپایلر لومبوک این فیلدها را در سازنده پیشفرض با مقادیر اولیه خام (مانند null یا صفر یا false) مقداردهی کند و خطای کامپایل جاوا رفع شود.
- انتخاب سطح دسترسی سازنده برای JPA
تنظیم access = AccessLevel.PRIVATE در انوتیشن سازنده بدون آرگومان باعث میشود این متد از دید سایر لایههای کد تجاری پروژه مخفی بماند و فقط از طریق سازوکارهای درونی Reflection فریمورک JPA در زمان اجرای برنامه فراخوانی گردد.
