Introduction
আজকালকার সফটওয়্যার ডেভেলপমেন্টে ডেটাবেস থেকে ডেটা আনা বা সেভ করার জন্য আমরা সাধারণত Spring Data JPA ব্যবহার করি। এটি কাজ সহজ করে দিলেও, বড় প্রজেক্টে বা অনেক ডেটা নিয়ে কাজ করতে গেলে অ্যাপ্লিকেশন স্লো হয়ে যেতে পারে। এই ভিডিওতে Thorben Janssen দেখিয়েছেন কীভাবে Entities, DTOs এবং Records ব্যবহার করে ডেটাবেস অপারেশনগুলোকে অনেক বেশি দ্রুত বা এফিসিয়েন্ট (Efficient) করা যায়। বিশেষ করে অদরকারি ডেটা লোড না করে এবং কিছু স্মার্ট টেকনিক ব্যবহার করে অ্যাপ্লিকেশনের পারফরম্যান্স বাড়ানোই এই আলোচনার মূল উদ্দেশ্য।
১. পারফরম্যান্স সমস্যা খুঁজে বের করা (Generating Hibernate Statistics)
ভিডিও রেফারেন্স: [03:58]
ডেটাবেস কেন স্লো কাজ করছে তা বোঝার জন্য হাইবারনেট (Hibernate) আমাদের কিছু পরিসংখ্যান বা স্ট্যাটিস্টিক্স দেয়। আমাদের অ্যাপ্লিকেশন প্রোপার্টিজে নিচের লাইনটি যুক্ত করলে এটি সক্রিয় হয়:
Properties
hibernate.generate_statistics=true
বিস্তারিত ব্যাখ্যা: ডেভেলপমেন্টের সময় অনেক সময় বোঝা যায় না যে প্রোডাকশনে গিয়ে কোড কেন স্লো হচ্ছে। কারণ আমাদের লোকাল পিসিতে ডেটা থাকে ১০-২০টি, কিন্তু রিয়েল সার্ভারে থাকে লক্ষ লক্ষ। এই স্ট্যাটিস্টিক্স অন করলে কনসোলে দেখা যাবে একটা কুয়েরি চালাতে কত সময় লাগছে এবং কয়টি কুয়েরি চলছে।
- সহজ ব্যাখ্যা (Difficult Word - Statistics): এখানে স্ট্যাটিস্টিক্স মানে হলো আপনার কোড কতবার ডেটাবেসে নক করছে এবং কত সময় নিচ্ছে তার একটি হিসাব বা রিপোর্ট।
২. N+1 সিলেক্ট সমস্যা এবং সমাধান (Fixing N+1 Select Issue)
ভিডিও রেফারেন্স: [12:51]
এটি JPA-এর সবচেয়ে কমন সমস্যা। ধরুন আপনি ২০ জন প্লেয়ারের লিস্ট আনলেন, এখন কোড যদি প্রতিজন প্লেয়ারের জন্য আলাদা আলাদা করে আবার ডেটাবেসে কুয়েরি করে তাদের টুর্নামেন্টের নাম আনতে যায়, তবে ২০+১ = ২১টি কুয়েরি চলবে। একেই বলে N+1 সমস্যা।
সমাধান ১: Join Fetch
Java
@Query("SELECT p FROM ChessPlayer p LEFT JOIN FETCH p.tournaments")
List<ChessPlayer> findAllWithTournaments();
ব্যাখ্যা: এই কোডটি একটিমাত্র কুয়েরি ব্যবহার করে প্লেয়ার এবং তাদের টুর্নামেন্টের সব ডেটা একসাথে নিয়ে আসে। এতে ডেটাবেসে বারবার নক করার প্রয়োজন হয় না।
সমাধান ২: Entity Graph
Java
@EntityGraph(attributePaths = {"tournaments"})
List<ChessPlayer> findByLastName(String lastName);
ব্যাখ্যা: এনটিটি গ্রাফ ব্যবহার করলে কুয়েরি না লিখেও বলা যায় যে কোন কোন রিলেটেড ডেটাগুলো একসাথে লোড করতে হবে।
- আমার চিন্তা: সবসময়
JOIN FETCHব্যবহার করা ভালো যদি আপনি নিশ্চিত থাকেন আপনার ওই ডেটাগুলো লাগবেই। এটি কুয়েরি সংখ্যা কমিয়ে অ্যাপ্লিকেশনের গতি অনেক বাড়িয়ে দেয়।
৩. লিস্ট বনাম সেট (List vs Set in Many-to-Many)
ভিডিও রেফারেন্স: [21:46]
Many-to-Many রিলেশনে List ব্যবহার করলে হাইবারনেট খুব অদ্ভুত আচরণ করে। আপনি যদি লিস্ট থেকে একটি আইটেম রিমুভ করেন, হাইবারনেট পুরো টেবিল খালি করে আবার সব ইনসার্ট করে!
কোড উদাহরণ:
Java
// ভুল পদ্ধতি (বেশি কুয়েরি জেনারেট করে)
private List<ChessPlayer> players = new ArrayList<>();
// সঠিক পদ্ধতি (এফিসিয়েন্ট)
private Set<ChessPlayer> players = new HashSet<>();
কেন সেট ব্যবহার করবেন? সেট (Set) ডুপ্লিকেট এলাও করে না। হাইবারনেট যখন দেখে আপনি সেট ব্যবহার করছেন, তখন সে স্মার্টলি শুধু নির্দিষ্ট রো-টি ডিলিট বা আপডেট করে, যার ফলে কুয়েরি সংখ্যা অনেক কমে যায় (যেমন ভিডিওতে ১৮টি কুয়েরি থেকে ৪টিতে নেমে এসেছে)।
৪. প্রজেকশন (Projections: DTOs and Records)
ভিডিও রেফারেন্স: [27:00]
পুরো এনটিটি (Entity) লোড করা মানে হলো টেবিলের সব কলামের ডেটা মেমরিতে আনা। কিন্তু আপনার যদি শুধু ইউজারের নাম দরকার হয়, তবে পুরো প্রোফাইল লোড করার দরকার নেই। এর জন্য Record বা Interface ব্যবহার করা হয়।
Record ব্যবহার করে প্রোজেকশন:
Java
public record PlayerNameDto(String firstName, String lastName) {}
রেপোজিটরি কোড:
Java
List<PlayerNameDto> findByFirstName(String firstName);
অর্জিত লক্ষ্য: এই কোডটি ডেটাবেস থেকে শুধু first_name এবং last_name কলাম দুটি আনবে। বাকি কলামগুলো (যেমন: ছবি, বায়ো, জন্মতারিখ) আনবে না, যা মেমরি বাঁচায় এবং গতি বাড়ায়।
- সহজ ব্যাখ্যা (Difficult Word - Projection): ডেটাবেস টেবিলের অনেকগুলো কলামের মধ্য থেকে শুধু আপনার প্রয়োজনীয় কলামগুলো বেছে নেওয়াকে প্রজেকশন বলে।
৫. ক্যাশিং (Caching with Hibernate)
ভিডিও রেফারেন্স: [41:00]
ক্যাশিং মানে হলো একবার আনা ডেটা মেমরিতে জমিয়ে রাখা যাতে বারবার ডেটাবেসে যেতে না হয়।
-
First Level Cache: এটি ডিফল্টভাবেই থাকে। একটি ট্রানজ্যাকশনের মধ্যে একই ডেটা দুবার চাইলে সে মেমরি থেকে দেয়।
-
Second Level Cache: এটি পুরো অ্যাপ্লিকেশনের জন্য কাজ করে। এটি চালু করতে
@Cacheableব্যবহার করতে হয়।
সতর্কতা: সব ডেটা ক্যাশ করবেন না। যে ডেটা বারবার পড়া হয় কিন্তু খুব কম পরিবর্তন হয় (যেমন- দেশের তালিকা), শুধু সেগুলোই ক্যাশ করা উচিত।
বিশ্লেষণ এবং আমার মতামত (Analysis & Thinking)
মূল উদ্দেশ্য: কন্টেন্ট ক্রিয়েটর বোঝাতে চেয়েছেন যে Spring Data JPA ব্যবহার করলেই অ্যাপ ফাস্ট হবে না। ডেভেলপারকে বুঝতে হবে কুয়েরি কীভাবে জেনারেট হচ্ছে।
বাস্তবতা ও সম্ভাবনা: ১. N+1 সমস্যা এড়ানো: বড় প্রজেক্টে এটিই পারফরম্যান্স কিলার। সবসময় Join Fetch বা DTO ব্যবহারের চেষ্টা করা উচিত। ২. DTO vs Entity: শুধু ডেটা দেখানোর জন্য (Read-only) DTO বা Record ব্যবহার করা সবচেয়ে সেরা উপায়। এনটিটি শুধু ডেটা সেভ বা এডিট করার সময় ব্যবহার করা উচিত। ৩. বিকল্প চিন্তা: অনেক সময় জটিল কুয়েরির জন্য JPA-এর বদলে Native Query বা QueryDSL ব্যবহার করা সহজ হতে পারে, যা ভিডিওতেও হালকা ইঙ্গিত দেওয়া হয়েছে।
পরামর্শ: প্রজেক্টের শুরুতে hibernate.generate_statistics অন রাখুন। কোড করার সময় কনসোলে খেয়াল করুন কয়টি কুয়েরি চলছে। যদি দেখেন একটি কাজের জন্য অনেক কুয়েরি হচ্ছে, তবে বুঝবেন সেখানে অপ্টিমাইজেশনের সুযোগ আছে। এটি আপনার অ্যাপ্লিকেশনকে প্রোডাকশনে অনেক বেশি স্টেবল এবং ফাস্ট রাখবে।
[
Build faster persistence layers with Spring Data JPA 3 by Thorben Janssen @ Spring I/O 2024
Spring I/O · 30K views
](http://www.youtube.com/watch?v=t27Uozc2Z58)

মন্তব্যসমূহ
একটি মন্তব্য পোস্ট করুন
আপনার সমস্যাটি কমেন্ট করে আমাদের জানান :-d