جاوا و تکنولوژی های آن

java programming language

در این وبلاگ به بررسی نکات موجود در جاوا و تکنولوژی های آن می پردازیم

طبقه بندی موضوعی


در JPA 2.1 روشی پیچیده تر برای حل مشکل Performance Load معرفی شده است بنام Entity Graph که در این بخش به چگونگی ایجاد Entity Graph و استفاده از ویژگی های آن میپردازیم


تا JPA 2.0 عیبی که داشت این بود که اگر Entity موجود با Entity دیگر رابطه ای داشت که ان هم باید جداگانه Fetch میشد و این بار اضافی را تحمیل میکرد و برای لود کردن یک entity دو استراتژی وجود داشت : FetchType.LAZY و FetchType.EAGER  که هنگامی که یکی از استراتژی ها انتخاب میشد امکان سویچ کردن داینامیک روی دیگری نبود

و Entity Graph برای همین منظور ارائه شد تا پرفرمانس بهتری در Runtime داشته باشیم در حقیقت JPA کلیه فیلد های مورد نیاز کلاس جاری و روابط آنها را یکجا Fetch میکند و دیگر LAZY و EAGER بودن آن مهم نیست چون در یک کوئری دریافت میشود 



مثالی از طراحی Model ها :

فرض کنید یک سیستم بلاگ داریم که User میتواند Post بگذارد و هر Post میتواند تعدادی Comment داشته باشد 

@Entity
public class User {
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String name;
    private String email;
 
    //...
}
@Entity
public class Post {
 
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
    private String subject;
    @OneToMany(mappedBy = "post")
    private List<Comment> comments = new ArrayList<>();
 
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn
    private User user;
     
    //...
}


کلاس Post دارای رابطه Many to One با User و One to Many با Comment دارد 


@Entity
public class Comment {
 
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;
 
    private String reply;
 
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn
    private Post post;
 
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn
    private User user;
     
    //...
}


کلاس Comment دارای رابطه Many to One با کلاس User و Post است



و در بلاگ بدین صورت دریافت میشود :

Post  ->  user:User
      ->  comments:List<Comment>
            comments[0]:Comment -> user:User
            comments[1]:Comment -> user:User





لود شدن کلاس های مرتبط با استراتژی های دریافت آنها :


دو روش برای Fetch کردن موجود است :


- FetchType.EAGER : این نوع دیتاها باید Fetch شوند و این حالت پیش فرض برای روابط Basic , @ManyToOne@ و @OneToOne 

- FetchType.LAZY : این نوع دیتاها با اولین دسترسی Fetch میشوند و اتفاق می افتد که بصورت EAGER لود شوند این حالت پیش فرض روابط OneToMany @ManyToMany @ElementCollection@ است


برای مثال وقتی ما یک Post Entity را Fetch میکنیم Comment های مرتبط همانند Fetch Type کلاس Post لود نمیشوند و از آنجا که OneToMany@ بصورت LAZY است میتوانیم این حالت را تغییر دهیم :


@OneToMany(mappedBy = "post", fetch = FetchType.EAGER)
private List<Comment> comments = new ArrayList<>();


در مقایسه با هنگامی که میخواهیم یک Comment Entity را لود کنیم پدرش که Post است و چون رابطه آن از نوع ManyToOne@ است بصورت EAGER لود شده است که میتوانیم آنرا تغییر دهیم :


@ManyToOne(fetch = FetchType.LAZY) 
@JoinColumn(name = "post_id") 
private Post post;


همانطور که میبینید تغییرات داده شده بصورت ایستا هستند و امکان تغییر آنها در Runtime نیست و حالا ببینیم Graph Entity چطور این مسئله را حل میکند :





تعریف Graph Entity با NamedEntityGraph@ :


استفاده از NamedEntityGraph@ اجازه میدهد که روابط کلاس Entity را مشخص کنیم 


در زیر کلاس Post را برای entity graph باز تعریف کردیم که کلاس های User و Comment مربوطه را هم لود میکند :


@NamedEntityGraph(
  name = "post-entity-graph",
  attributeNodes = {
    @NamedAttributeNode("subject"),
    @NamedAttributeNode("user"),
    @NamedAttributeNode("comments"),
  }
)
@Entity
public class Post {
 
    @OneToMany(mappedBy = "post")
    private List<Comment> comments = new ArrayList<>();
     
    //...
}


حالا بیاییم طراحی را کمی پیچیده تر کنیم و User هایی که Comment گذاشته اند را هم لود کنیم برای این منظور از NamedAttributeNode@ و ویژگی subgraph برای ارجاع دادن استفاده میکنیم 


@NamedEntityGraph(
  name = "post-entity-graph-with-comment-users",
  attributeNodes = {
    @NamedAttributeNode("subject"),
    @NamedAttributeNode("user"),
    @NamedAttributeNode(value = "comments", subgraph = "comments-subgraph"),
  },
  subgraphs = {
    @NamedSubgraph(
      name = "comments-subgraph",
      attributeNodes = {
        @NamedAttributeNode("user")
      }
    )
  }
)
@Entity
public class Post {
 
    @OneToMany(mappedBy = "post")
    private List<Comment> comments = new ArrayList<>();
    //...
}

با استفاده از NamedSubgraph@ توانستیم روابط زیرین را پوشش بدهیم و بدین صورت یک گراف کامل را ایجاد کنیم 



همچنین ما میتوانیم در orm.xml به تعریف Graph Entity نیز بپردازیم :

<entity-mappings>
  <entity class="com.baeldung.jpa.entitygraph.Post" name="Post">
    ...
    <named-entity-graph name="post-entity-graph">
            <named-attribute-node name="comments" />
    </named-entity-graph>
  </entity>
  ...
</entity-mappings>






تعریف Graph Entity با JPA API :


ما میتوانیم با EntityManager ساختار entity graph را ابجاد کنیم 

EntityGraph<Post> entityGraph = entityManager.createEntityGraph(Post.class);

و با متد زیر Attribute هایی به ریشه گراف اضافه کنیم :

entityGraph.addAttributeNodes("subject");
entityGraph.addAttributeNodes("user");

همینطور sub graph اضافه کنیم :

entityGraph.addSubgraph("comments")
  .addAttributeNodes("user");





لود کردن Entity Graph :


روش های مختلفی برای بدست آوردن Entity Graph وجود دارد که یکی از آنها استفاده از متد find ابجکت EntityManager است و Fetch Typeپیش فرض همان چیزی است که برایش تعریف کرده ایم که میتواند LAZY و EAGER باشد :


Post post = entityManager.find(Post.class, 1L);


لاگ کوئری آن :

select
    post0_.id as id1_1_0_,
    post0_.subject as subject2_1_0_,
    post0_.user_id as user_id3_1_0_ 
from
    Post post0_ 
where
    post0_.id=?


همانطور که در لاگ قابل مشاهده است User و Comment دریافت نشده است و برای دریافت آن ما میتوانیم نتیجه را در یک Map ذخیره کنیم و سپس آنها را لود کنیم :

EntityGraph entityGraph = entityManager.getEntityGraph("post-entity-graph");
Map<String, Object> properties = new HashMap<>();
properties.put("javax.persistence.fetchgraph", entityGraph);
Post post = entityManager.find(Post.class, id, properties);

اگر دوباره لاگ را ببینیم مشاهده میکنیم که User و Comment نیز دریافت میشوند :


select
    post0_.id as id1_1_0_,
    post0_.subject as subject2_1_0_,
    post0_.user_id as user_id3_1_0_,
    comments1_.post_id as post_id3_0_1_,
    comments1_.id as id1_0_1_,
    comments1_.id as id1_0_2_,
    comments1_.post_id as post_id3_0_2_,
    comments1_.reply as reply2_0_2_,
    comments1_.user_id as user_id4_0_2_,
    user2_.id as id1_2_3_,
    user2_.email as email2_2_3_,
    user2_.name as name3_2_3_ 
from
    Post post0_ 
left outer join
    Comment comments1_ 
        on post0_.id=comments1_.post_id 
left outer join
    User user2_ 
        on post0_.user_id=user2_.id 
where
    post0_.id=?



استفاده از JPQL :

EntityGraph entityGraph = entityManager.getEntityGraph("post-entity-graph-with-comment-users");
Post post = entityManager.createQuery("select p from Post p where p.id = :id", Post.class)
  .setParameter("id", id)
  .setHint("javax.persistence.fetchgraph", entityGraph)
  .getSingleResult();



استفاده از Criteria API :

EntityGraph entityGraph = entityManager.getEntityGraph("post-entity-graph-with-comment-users");
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<Post> criteriaQuery = criteriaBuilder.createQuery(Post.class);
Root<Post> root = criteriaQuery.from(Post.class);
criteriaQuery.where(criteriaBuilder.equal(root.<Long>get("id"), id));
TypedQuery<Post> typedQuery = entityManager.createQuery(criteriaQuery);
typedQuery.setHint("javax.persistence.loadgraph", entityGraph);
Post post = typedQuery.getSingleResult();



در دو مثال بالا از متد setHint بجای Map در مثال اول استفاده شده است 






Entity Graph در Spring Data JPA  :


برای ساخت Entity Graph در Spring Data JPA میتوانیم از ترکیب NamedEntityGraph@ و EntityGraph@ استفاده کنیم و یا با استفاده از آرگومان attributePaths از EntityGraph@ که بصورت ad-hoc در NmedEntityGraph@ میتوان استفاده کرد و یک Graph Entity ایجاد کنیم 


@Entity
@NamedEntityGraph(name = "Item.characteristics",
    attributeNodes = @NamedAttributeNode("characteristics")
)
public class Item {
    //...
}


در Repository به یکی از متد ها EntityGraph@ را اختصاص دهیم :


public interface ItemRepository extends JpaRepository<Item, Long> {
 
    @EntityGraph(value = "Item.characteristics")
    Item findByName(String name);
}


همانطور که قابل مشاهده است ما از نامی که به کلاس Item برای Entity Graph در NamedEntityGraph@ داده بودیم در کلاس Repository و EntityGraph@ استفاده کردیم و هنگامی که این متد فراخوانی شود کوئری ساخته شده آن توسط Spring Data استفاده میشود 


مقدار پیش فرض value در EntityGraph@ اگر چیزی ننویسیم EntityGraphType.FETCH است و در این صورت Spring Data استراتژی FetchType.EAGER را روی نودهای مشخصی اعمال میکند و بقیه نود ها FetchType.LAZY خواهند بود 


و در این کد مشخصه characteristics بصورت eager لود میشود در صورتی که استراتژی پیش فرض OneToMany@ از نوع Lazy است


* نکته ای که وجود دارد این است که اگر Fetch strategy از نوع Eager معین شده باشد ما نمیتوانیم آنرا به Lazy تغییر دهیم چون در عملیات بعدی احتمال دارد به نوع Eager نیاز باشد 





Entity Graph بدون NamedEntityGraph@ :


ما میتوانیم یکad-hoc  Entity Graph تعریف کنیم و از attributePaths کمک بگیریم

بیاییم در CharacteristicsRepository یک ad-hoc Entity Graph تعریف کنیم که Item را بصورت Eager لود میکند حتی اگر Entity ما استراتژی Lazy برایش تعریف شده باشد 

این روش زمانی مفید است که بدون ارجاع به نام Entity Graph واقعی و بصورت inline یک Entity Graph ایجاد کنیم 

public interface CharacteristicsRepository 
  extends JpaRepository<Characteristic, Long> {
     
    @EntityGraph(attributePaths = {"item"})
    Characteristic findByType(String type);    
}




تست :


@DataJpaTest
@RunWith(SpringRunner.class)
@Sql(scripts = "/entitygraph-data.sql")
public class EntityGraphIntegrationTest {
    
    @Autowired
    private ItemRepository itemRepo;
     
    @Autowired
    private CharacteristicsRepository characteristicsRepo;
     
    @Test
    public void givenEntityGraph_whenCalled_shouldRetrunDefinedFields() {
        Item item = itemRepo.findByName("Table");
        assertThat(item.getId()).isEqualTo(1L);
    }
     
    @Test
    public void givenAdhocEntityGraph_whenCalled_shouldRetrunDefinedFields() {
        Characteristic characteristic = characteristicsRepo.findByType("Rigid");
        assertThat(characteristic.getId()).isEqualTo(1L);
    }
}


تست اول از Entity Graph ای که با NamedEntityGraph@ تعریف شده بود استفاده میکند

SQL ساخته شده :


select
    item0_.id as id1_10_0_,
    characteri1_.id as id1_4_1_,
    item0_.name as name2_10_0_,
    characteri1_.item_id as item_id3_4_1_,
    characteri1_.type as type2_4_1_,
    characteri1_.item_id as item_id3_4_0__,
    characteri1_.id as id1_4_0__
from
    item item0_ 
left outer join
    characteristic characteri1_ 
on
    item0_.id=characteri1_.item_id 
where
    item0_.name=?


اگر در کلاس Repository و روی متد آن از EntityGraph@ استفاده نکنیم و دوباره تست بگیریم SQL ساخته شده بصورت زیر خواهد بود :


select
    item0_.id as id1_10_,
    item0_.name as name2_10_ 
from
    item item0_ 
where
    item0_.name=?


همانطور که فرق آنها قابل مشاهده است در کوئری دوم پراپرتی های Characteristic Entity لود نشده اند و فقط  Item Entity دریافت شده


نتیجه کوئری تست دوم  را یکبار با EntityGraph@ و یکبار بدون آن مقایسه میکنیم :


select
    characteri0_.id as id1_4_0_,
    item1_.id as id1_10_1_,
    characteri0_.item_id as item_id3_4_0_,
    characteri0_.type as type2_4_0_,
    item1_.name as name2_10_1_ 
from
    characteristic characteri0_ 
left outer join
    item item1_ 
on
    characteri0_.item_id=item1_.id 
where
    characteri0_.type=?



select
    characteri0_.id as id1_4_,
    characteri0_.item_id as item_id3_4_,
    characteri0_.type as type2_4_ 
from
    characteristic characteri0_ 
where
    characteri0_.type=?










نظرات  (۰)

هیچ نظری هنوز ثبت نشده است

ارسال نظر

ارسال نظر آزاد است، اما اگر قبلا در بیان ثبت نام کرده اید می توانید ابتدا وارد شوید.
شما میتوانید از این تگهای html استفاده کنید:
<b> یا <strong>، <em> یا <i>، <u>، <strike> یا <s>، <sup>، <sub>، <blockquote>، <code>، <pre>، <hr>، <br>، <p>، <a href="" title="">، <span style="">، <div align="">
تجدید کد امنیتی