স্প্রিং ডেটা জেপিএতে ফেচমোড কীভাবে কাজ করে


95

আমার প্রকল্পে তিনটি মডেল অবজেক্টের (পোস্টের শেষে মডেল এবং সংগ্রহস্থলের স্নিপেটের) মধ্যে আমার একটি সম্পর্ক রয়েছে।

যখন আমি PlaceRepository.findByIdএটি বলি তখন তিনটি নির্বাচিত প্রশ্নের উদ্রেক ঘটে:

("স্কয়ার")

  1. SELECT * FROM place p where id = arg
  2. SELECT * FROM user u where u.id = place.user.id
  3. SELECT * FROM city c LEFT OUTER JOIN state s on c.woj_id = s.id where c.id = place.city.id

এটি বরং অস্বাভাবিক আচরণ (আমার জন্য)। হাইবারনেট ডকুমেন্টেশন পড়ার পরে আমি যতদূর বলতে পারি এটি সর্বদা জিন কোয়েরি ব্যবহার করা উচিত। সেখানে প্রশ্নের যখন কোন পার্থক্য নেই FetchType.LAZYপরিবর্তিত FetchType.EAGERমধ্যে Placeবর্গ (অতিরিক্ত নির্বাচন সঙ্গে ক্যোয়ারী), একই Cityক্লাসে যখন FetchType.LAZYপরিবর্তিত FetchType.EAGER(JOIN সঙ্গে ক্যোয়ারী)।

আমি CityRepository.findByIdদমনকারী আগুন ব্যবহার করার সময় দুটি নির্বাচন করে:

  1. SELECT * FROM city c where id = arg
  2. SELECT * FROM state s where id = city.state.id

আমার লক্ষ্যটি হ'ল সমস্ত পরিস্থিতিতে সম আচরণ করা (সর্বদা যোগদান করুন বা নির্বাচন করুন, যদিও যোগ দিন)।

মডেল সংজ্ঞা:

স্থান:

@Entity
@Table(name = "place")
public class Place extends Identified {

    @Fetch(FetchMode.JOIN)
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "id_user_author")
    private User author;

    @Fetch(FetchMode.JOIN)
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "area_city_id")
    private City city;
    //getters and setters
}

নগরী:

@Entity
@Table(name = "area_city")
public class City extends Identified {

    @Fetch(FetchMode.JOIN)
    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "area_woj_id")
    private State state;
    //getters and setters
}

ভান্ডারগুলি:

প্লেস রেপোসিটরি

public interface PlaceRepository extends JpaRepository<Place, Long>, PlaceRepositoryCustom {
    Place findById(int id);
}

ইউজাররেপোসিটোরি:

public interface UserRepository extends JpaRepository<User, Long> {
        List<User> findAll();
    User findById(int id);
}

নগরী

public interface CityRepository extends JpaRepository<City, Long>, CityRepositoryCustom {    
    City findById(int id);
}

হাওয়া অলস সম্পর্কের সূচনা করার জন্য পাঁচটি উপায় দেখুন: চিন্তা
ভাবনা- java.org/…

উত্তর:


114

আমি মনে করি যে স্প্রিং ডেটা ফেচমোড উপেক্ষা করে। আমি সবসময় ব্যবহার @NamedEntityGraphএবং @EntityGraphযখন বসন্ত ডেটা নিয়ে কাজ টীকা

@Entity
@NamedEntityGraph(name = "GroupInfo.detail",
  attributeNodes = @NamedAttributeNode("members"))
public class GroupInfo {

  // default fetch mode is lazy.
  @ManyToMany
  List<GroupMember> members = new ArrayList<GroupMember>();

  …
}

@Repository
public interface GroupRepository extends CrudRepository<GroupInfo, String> {

  @EntityGraph(value = "GroupInfo.detail", type = EntityGraphType.LOAD)
  GroupInfo getByGroupName(String name);

}

ডকুমেন্টেশন এখানে পরীক্ষা করুন


4
আমার পক্ষে কাজ করছে বলে মনে হচ্ছে না। মানে এটি কাজ করে তবে ... যখন আমি '@EntityGraph' এর সাহায্যে সংগ্রহস্থলটি টিকা দেই তখন এটি নিজেই কাজ করে না (সাধারণত)। উদাহরণস্বরূপ: `Find FindById (int id);` কাজ করে তবে List<Place> findAll();ব্যতিক্রমের সাথে শেষ হয় org.springframework.data.mapping.PropertyReferenceException: No property find found for type Place!। আমি ম্যানুয়ালি যুক্ত করার সময় এটি কাজ করে @Query("select p from Place p")। যদিও কাজের মত মনে হচ্ছে।
স্যারকোমেটা

সম্ভবত এটি FindAll () তে কাজ করে না কারণ এটি JPAPepository ইন্টারফেস থেকে একটি বিদ্যমান পদ্ধতি যখন আপনার অন্যান্য পদ্ধতি "FindById" রানটাইম সময়ে উত্পন্ন একটি কাস্টম ক্যোয়ারী পদ্ধতি।
wesker317

আমি সেরা উত্তর হিসাবে এটি সঠিক উত্তর হিসাবে চিহ্নিত করার সিদ্ধান্ত নিয়েছে। যদিও এটি নিখুঁত নয়। এটি বেশিরভাগ পরিস্থিতিতে কাজ করে তবে এখনও অবধি আমি আরও জটিল এনটিটিগ্রাফ সহ স্প্রিং-ডেটা-জেপিতে বাগগুলি লক্ষ্য করেছি। ধন্যবাদ :)
স্যারকোমেটা

4
@EntityGraphযেহেতু এটি উল্লেখ করা নাকিসুরে বাস্তব পরিস্থিতিতে প্রায় ununsable ধরনের কি Fetchআমরা ব্যবহার করতে চান ( JOIN, SUBSELECT, SELECT, BATCH)। এটি @OneToManyঅ্যাসোসিয়েশনের সাথে সংমিশ্রণে এবং হাইবারনেট পুরো টেবিলটিকে স্মৃতিতে আনতে সক্ষম করে এমনকি আমরা কোয়েরি ব্যবহার করি MaxResults
ওন্দ্রেজ বোজেক

4
ধন্যবাদ, আমি বলতে চেয়েছিলাম যে জেপিকিউএল কোয়েরিগুলি নির্বাচিত আনয়ন নীতি দিয়ে ডিফল্ট আনার কৌশলটি ওভাররাইড করতে পারে ।
adrhc

53

প্রথমত, @Fetch(FetchMode.JOIN)এবং @ManyToOne(fetch = FetchType.LAZY)বৈরিতাবাদী, একজন ইগ্রার আনার নির্দেশ দিচ্ছেন, অন্যটি অলস আনার পরামর্শ দিচ্ছেন।

আগ্রহী আনয়ন খুব কমই ভাল পছন্দ এবং অনুমানযোগ্য আচরণের জন্য, আপনি ক্যোয়ারী-সময় JOIN FETCHনির্দেশিকা ব্যবহার করা থেকে ভাল :

public interface PlaceRepository extends JpaRepository<Place, Long>, PlaceRepositoryCustom {

    @Query(value = "SELECT p FROM Place p LEFT JOIN FETCH p.author LEFT JOIN FETCH p.city c LEFT JOIN FETCH c.state where p.id = :id")
    Place findById(@Param("id") int id);
}

public interface CityRepository extends JpaRepository<City, Long>, CityRepositoryCustom { 
    @Query(value = "SELECT c FROM City c LEFT JOIN FETCH c.state where c.id = :id")   
    City findById(@Param("id") int id);
}

4
ক্রিটারিয়া এপিআই এবং স্প্রিং ডেটা স্পেসিফিকেশনের সাথে একই ফলাফল অর্জনের উপায় কি?
সোভালদা

4
আনার অংশ নয়, যার জন্য জেপিএ আনার প্রোফাইল প্রয়োজন।
ভ্লাদ মিহলসিয়া

ভ্লাদ মিহলসিয়া, আপনি কীভাবে উদাহরণস্বরূপ লিঙ্কটি ভাগ করে নিতে পারেন স্প্রিং ডেটা জেপিএ মানদণ্ড (স্পেসিফিকেশন) ব্যবহার করে এটি কীভাবে করবেন? দয়া করে
ইয়ান খোন্সকি

আমার কাছে এরকম কোনও উদাহরণ নেই তবে আপনি স্প্রিং ডেটা জেপিএ টিউটোরিয়ালগুলিতে অবশ্যই একটি খুঁজে পেতে পারেন।
ভ্লাদ মিহলসিয়া

যদি ক্যোয়ারী-টাইম ব্যবহার করে ..... আপনি এখনও সত্তায় @OneToMany ... ইত্যাদি সংজ্ঞায়িত করতে হবে?
এরিক হুয়াং

19

স্প্রিং-জেপি সত্তা ব্যবস্থাপক ব্যবহার করে ক্যোয়ারী তৈরি করে, এবং হাইবারনেট যদি সত্তা ব্যবস্থাপক দ্বারা কোয়েরিটি তৈরি করা হত তবে আনতে হবে এমন মোড উপেক্ষা করবে।

নিম্নলিখিত যে কাজগুলি আমি ব্যবহার করেছি তা নিম্নলিখিত:

  1. একটি কাস্টম সংগ্রহস্থল প্রয়োগ করুন যা সিম্পলজেপাআরপোজিটরি থেকে উত্তরাধিকার সূত্রে প্রাপ্ত

  2. পদ্ধতিটি ওভাররাইড করুন getQuery(Specification<T> spec, Sort sort):

    @Override
    protected TypedQuery<T> getQuery(Specification<T> spec, Sort sort) { 
        CriteriaBuilder builder = entityManager.getCriteriaBuilder();
        CriteriaQuery<T> query = builder.createQuery(getDomainClass());
    
        Root<T> root = applySpecificationToCriteria(spec, query);
        query.select(root);
    
        applyFetchMode(root);
    
        if (sort != null) {
            query.orderBy(toOrders(sort, root, builder));
        }
    
        return applyRepositoryMethodMetadata(entityManager.createQuery(query));
    }
    

    পদ্ধতির মাঝামাঝি সময়ে, applyFetchMode(root);হাইবারনেটকে সঠিক জোড় দিয়ে কোয়েরি তৈরি করতে, ফেচ মোডটি প্রয়োগ করতে যুক্ত করুন।

    (দুর্ভাগ্যক্রমে আমাদের বেস ক্লাস থেকে পুরো পদ্ধতি এবং সম্পর্কিত ব্যক্তিগত পদ্ধতিগুলি অনুলিপি করা দরকার কারণ অন্য কোনও এক্সটেনশন পয়েন্ট ছিল না))

  3. বাস্তবায়ন applyFetchMode:

    private void applyFetchMode(Root<T> root) {
        for (Field field : getDomainClass().getDeclaredFields()) {
    
            Fetch fetch = field.getAnnotation(Fetch.class);
    
            if (fetch != null && fetch.value() == FetchMode.JOIN) {
                root.fetch(field.getName(), JoinType.LEFT);
            }
        }
    }
    

দুর্ভাগ্যক্রমে এটি সংগ্রহস্থল পদ্ধতির নাম ব্যবহার করে উত্পন্ন প্রশ্নের জন্য কাজ করে না।
ওন্দ্রেজ বোজেক

আপনি দয়া করে সমস্ত আমদানির বিবৃতি যুক্ত করতে পারেন? ধন্যবাদ.
গ্রানাডা কোডার

3

" FetchType.LAZY" শুধুমাত্র প্রাথমিক টেবিলের জন্য আগুন জ্বালাবে। যদি আপনার কোডটিতে আপনি অন্য কোনও পদ্ধতিতে কল করেন যার পিতামাতার টেবিল নির্ভরতা রয়েছে তবে সেই টেবিলের তথ্য পাওয়ার জন্য এটি ক্যোয়ারী ফায়ার করবে। (আগুন একাধিক নির্বাচন)

" FetchType.EAGER" প্রাসঙ্গিক প্যারেন্ট টেবিলগুলি সহ সরাসরি সমস্ত সারণীর যোগদান তৈরি করবে। (ব্যবহার JOIN)

কখন ব্যবহার করবেন: ধরুন আপনাকে বাধ্যতামূলকভাবে নির্ভরশীল পিতা-মাতার টেবিলের তথ্যাদি ব্যবহার করার দরকার পরে চয়ন করুন FetchType.EAGER। আপনার যদি কেবল নির্দিষ্ট রেকর্ডগুলির জন্য তথ্য প্রয়োজন তবে ব্যবহার করুন FetchType.LAZY

মনে রাখবেন, FetchType.LAZYআপনার কোডের সেই জায়গায় একটি সক্রিয় ডিবি সেশন কারখানা দরকার যেখানে আপনি পিতামাতার সারণী তথ্য পুনরুদ্ধার করতে চান।

যেমন LAZY:

.. Place fetched from db from your dao loayer
.. only place table information retrieved
.. some code
.. getCity() method called... Here db request will be fired to get city table info

অতিরিক্ত রেফারেন্স


মজার বিষয় হচ্ছে, এই উত্তরটি আমাকে ব্যবহারের সঠিক পথে পেয়েছে NamedEntityGraphযেহেতু আমি একটি হাইড্রেটেড অবজেক্ট গ্রাফ চেয়েছিলাম।
জেজে জব্বার

এই উত্তর আরও upvotes প্রাপ্য। এটি সংযোগযুক্ত এবং আমাকে কেন অনেকগুলি "যাদুকরীভাবে ট্রিগারড" কোয়েরি দেখছেন তা বুঝতে অনেক সাহায্য করেছিল ... অনেক অনেক ধন্যবাদ!
ক্লিন্ট ইস্টউড

3

আনয়ন মোড কেবল আইডি দ্বারা ব্যবহার করে অর্থ ব্যবহারের সময় নির্বাচন করবে entityManager.find()। যেহেতু স্প্রিং ডেটা সর্বদা একটি কোয়েরি তৈরি করবে, তাই আনতে মোড কনফিগারেশন আপনার কোনও ব্যবহার করবে না। আপনি হয় ফেচ যোগদানের সাথে উত্সর্গীকৃত প্রশ্নের ব্যবহার করতে পারেন বা সত্তা গ্রাফ ব্যবহার করতে পারেন।

আপনি যখন সেরা পারফরম্যান্স চান, আপনার কেবলমাত্র প্রয়োজন ডেটার উপসেটটি নির্বাচন করা উচিত। এটি করার জন্য, সাধারণত অপ্রয়োজনীয় ডেটা আনার জন্য একটি ডিটিও পদ্ধতির ব্যবহার করার পরামর্শ দেওয়া হয়, তবে এটি সাধারণত প্রচুর পরিমাণে ত্রুটিযুক্ত প্রবণ বয়লারপ্লিট কোডের ফলস্বরূপ, যেহেতু আপনার একটি ডিপিও মডেল জেপিপিএল এর মাধ্যমে তৈরি করে এমন একটি উত্সর্গীকৃত ক্যোয়ারী সংজ্ঞায়িত করা দরকার need কনস্ট্রাক্টর এক্সপ্রেশন।

স্প্রিং ডেটা অনুমানগুলি এখানে সহায়তা করতে পারে তবে এক পর্যায়ে আপনার ব্লেজ-দৃ !়তা সত্তা মতামতের মত সমাধানের প্রয়োজন হবে যা এটি বেশ সহজ করে তোলে এবং এটির হাতাতে আরও অনেক বৈশিষ্ট্য রয়েছে যা কাজে আসবে! আপনি কেবল সত্তা প্রতি একটি ডিটিও ইন্টারফেস তৈরি করেন যেখানে প্রাপ্তরা আপনার প্রয়োজনীয় ডেটার উপসেটটি উপস্থাপন করে। আপনার সমস্যার সমাধান এর মতো দেখতে পারে

@EntityView(Identified.class)
public interface IdentifiedView {
    @IdMapping
    Integer getId();
}

@EntityView(Identified.class)
public interface UserView extends IdentifiedView {
    String getName();
}

@EntityView(Identified.class)
public interface StateView extends IdentifiedView {
    String getName();
}

@EntityView(Place.class)
public interface PlaceView extends IdentifiedView {
    UserView getAuthor();
    CityView getCity();
}

@EntityView(City.class)
public interface CityView extends IdentifiedView {
    StateView getState();
}

public interface PlaceRepository extends JpaRepository<Place, Long>, PlaceRepositoryCustom {
    PlaceView findById(int id);
}

public interface UserRepository extends JpaRepository<User, Long> {
    List<UserView> findAllByOrderByIdAsc();
    UserView findById(int id);
}

public interface CityRepository extends JpaRepository<City, Long>, CityRepositoryCustom {    
    CityView findById(int id);
}

দাবি অস্বীকার, আমি ব্লেজ-অধ্যবসায়ের লেখক তাই আমার পক্ষপাতিত্ব হতে পারে।


2

এটি নেস্টেড হাইবারনেট টীকাগুলি হ্যান্ডেল করার জন্য আমি স্বপ্নের 83619 উত্তরটি বিস্তারিতভাবে বর্ণনা করেছি @Fetch। নেস্টেড সম্পর্কিত ক্লাসে টীকাগুলি খুঁজতে আমি পুনরাবৃত্তির পদ্ধতি ব্যবহার করেছি।

সুতরাং আপনাকে কাস্টম সংগ্রহস্থল এবং ওভাররাইড getQuery(spec, domainClass, sort)পদ্ধতিটি প্রয়োগ করতে হবে । দুর্ভাগ্যক্রমে আপনাকে সমস্ত রেফারেন্সযুক্ত ব্যক্তিগত পদ্ধতিগুলিও অনুলিপি করতে হবে :(।

এখানে কোড, অনুলিপিযুক্ত ব্যক্তিগত পদ্ধতি বাদ দেওয়া হয়েছে।
সম্পাদনা: বাকি ব্যক্তিগত পদ্ধতি যুক্ত করা হয়েছে।

@NoRepositoryBean
public class EntityGraphRepositoryImpl<T, ID extends Serializable> extends SimpleJpaRepository<T, ID> {

    private final EntityManager em;
    protected JpaEntityInformation<T, ?> entityInformation;

    public EntityGraphRepositoryImpl(JpaEntityInformation<T, ?> entityInformation, EntityManager entityManager) {
        super(entityInformation, entityManager);
        this.em = entityManager;
        this.entityInformation = entityInformation;
    }

    @Override
    protected <S extends T> TypedQuery<S> getQuery(Specification<S> spec, Class<S> domainClass, Sort sort) {
        CriteriaBuilder builder = em.getCriteriaBuilder();
        CriteriaQuery<S> query = builder.createQuery(domainClass);

        Root<S> root = applySpecificationToCriteria(spec, domainClass, query);

        query.select(root);
        applyFetchMode(root);

        if (sort != null) {
            query.orderBy(toOrders(sort, root, builder));
        }

        return applyRepositoryMethodMetadata(em.createQuery(query));
    }

    private Map<String, Join<?, ?>> joinCache;

    private void applyFetchMode(Root<? extends T> root) {
        joinCache = new HashMap<>();
        applyFetchMode(root, getDomainClass(), "");
    }

    private void applyFetchMode(FetchParent<?, ?> root, Class<?> clazz, String path) {
        for (Field field : clazz.getDeclaredFields()) {
            Fetch fetch = field.getAnnotation(Fetch.class);

            if (fetch != null && fetch.value() == FetchMode.JOIN) {
                FetchParent<?, ?> descent = root.fetch(field.getName(), JoinType.LEFT);
                String fieldPath = path + "." + field.getName();
                joinCache.put(path, (Join) descent);

                applyFetchMode(descent, field.getType(), fieldPath);
            }
        }
    }

    /**
     * Applies the given {@link Specification} to the given {@link CriteriaQuery}.
     *
     * @param spec can be {@literal null}.
     * @param domainClass must not be {@literal null}.
     * @param query must not be {@literal null}.
     * @return
     */
    private <S, U extends T> Root<U> applySpecificationToCriteria(Specification<U> spec, Class<U> domainClass,
        CriteriaQuery<S> query) {

        Assert.notNull(query);
        Assert.notNull(domainClass);
        Root<U> root = query.from(domainClass);

        if (spec == null) {
            return root;
        }

        CriteriaBuilder builder = em.getCriteriaBuilder();
        Predicate predicate = spec.toPredicate(root, query, builder);

        if (predicate != null) {
            query.where(predicate);
        }

        return root;
    }

    private <S> TypedQuery<S> applyRepositoryMethodMetadata(TypedQuery<S> query) {
        if (getRepositoryMethodMetadata() == null) {
            return query;
        }

        LockModeType type = getRepositoryMethodMetadata().getLockModeType();
        TypedQuery<S> toReturn = type == null ? query : query.setLockMode(type);

        applyQueryHints(toReturn);

        return toReturn;
    }

    private void applyQueryHints(Query query) {
        for (Map.Entry<String, Object> hint : getQueryHints().entrySet()) {
            query.setHint(hint.getKey(), hint.getValue());
        }
    }

    public Class<T> getEntityType() {
        return entityInformation.getJavaType();
    }

    public EntityManager getEm() {
        return em;
    }
}

আমি আপনার সমাধানটি চেষ্টা করছি তবে কপির অনুলিপি দেওয়ার একটি পদ্ধতিতে আমার একটি ব্যক্তিগত মেটাডেটা ভেরিয়েবল রয়েছে। আপনি চূড়ান্ত কোড ভাগ করতে পারেন?
হোমার 1980ar

পুনরাবৃত্তি আনতে কাজ করে না। আমার যদি ওয়ানটোমানি থাকে তবে এটি java.util.Lut পরের পুনরাবৃত্তির তালিকায় রাখুন
এন্টোহোহো

এটি এখনও ভালভাবে পরীক্ষা করা হয়নি, তবে মনে করুন ফিল্ড.জেটটাইপ () এর পরিবর্তে এই জাতীয় ((যোগদান) বংশোদ্ভূত) .get জাভাটাইপ () এর পরিবর্তে কল করুন যখন পুনরাবৃত্তভাবে প্রয়োগ করুন ফ্যাচমোড
এন্টোহো

2

http://jdpgrailsdev.github.io/blog/2014/09/09/spring_data_hibernate_join.html
এই লিঙ্ক থেকে:

আপনি যদি হাইবারনেটের শীর্ষে জেপিএ ব্যবহার করে থাকেন তবে হাইবারনেট দ্বারা ব্যবহৃত ফেচমোডকে জোইনহোভারে সেট করার কোনও উপায় নেই, আপনি যদি হাইবারনেটের শীর্ষে জেপিএ ব্যবহার করে থাকেন তবে হাইবারনেট দ্বারা ব্যবহৃত ফেচমোডটি জোয়েন্টে সেট করার কোনও উপায় নেই।

স্প্রিং ডেটা জেপিএ লাইব্রেরি একটি ডোমেন ড্রাইভড ডিজাইন স্পেসিফিকেশন এপিআই সরবরাহ করে যা আপনাকে উত্পন্ন প্রশ্নের সাথে আচরণ নিয়ন্ত্রণ করতে দেয়।

final long userId = 1;

final Specification<User> spec = new Specification<User>() {
   @Override
    public Predicate toPredicate(final Root<User> root, final 
     CriteriaQuery<?> query, final CriteriaBuilder cb) {
    query.distinct(true);
    root.fetch("permissions", JoinType.LEFT);
    return cb.equal(root.get("id"), userId);
 }
};

List<User> users = userRepository.findAll(spec);

2

ভ্লাদ মিহালসিয়া অনুসারে ( https://vladmihalcea.com/hibernate-facts-the- Importance-of-fetch-strategy/ দেখুন ):

জেপিকিউএল কোয়েরিগুলি ডিফল্ট আনার কৌশলটি ওভাররাইড করতে পারে। অভ্যন্তরীণ বা বামে যোগদানের আনুষ্ঠানিক নির্দেশাবলী ব্যবহার করে আমরা কী আনতে চাই তা যদি আমরা স্পষ্টভাবে ঘোষণা না করি তবে ডিফল্ট নির্বাচিত আনয়ন নীতি প্রয়োগ করা হবে।

দেখে মনে হচ্ছে যে জেপিকিউএল ক্যোয়ারী আপনার ঘোষিত আনার কৌশলটিকে ওভাররাইড করতে পারে তাই আপনাকে join fetchআগ্রহী হয়ে কিছু সুনির্দিষ্ট সত্তা লোড করতে বা এজেন্টি ম্যানেজারের সাহায্যে আইডি দ্বারা লোড করতে হবে (যা আপনার আনার কৌশলটি মেনে চলবে তবে আপনার ব্যবহারের ক্ষেত্রে সমাধান হতে পারে না) )।

আমাদের সাইট ব্যবহার করে, আপনি স্বীকার করেছেন যে আপনি আমাদের কুকি নীতি এবং গোপনীয়তা নীতিটি পড়েছেন এবং বুঝতে পেরেছেন ।
Licensed under cc by-sa 3.0 with attribution required.