সবাই কেমন আছেন? আজ আমরা শিখব কীভাবে Spring Data JPA ব্যবহার করে ডেটাবেসের সাথে আরও দ্রুত এবং কার্যকরভাবে কাজ করা যায়। বিশেষ করে 'Many-to-Many' রিলেশনে Sets এবং Lists এর মধ্যে কোনটি ব্যবহার করা ভালো এবং কেন, তা নিয়ে আমরা বিস্তারিত আলোচনা করব।
Introduction (ভূমিকা)
যখন আমরা জাভা দিয়ে কোনো বড় অ্যাপ্লিকেশন তৈরি করি, তখন ডেটাবেস থেকে ডেটা আনা বা সেভ করার জন্য Spring Data JPA এবং Hibernate ব্যবহার করি। শুরুতে এটি খুব সহজ মনে হলেও, ডেটার পরিমাণ যখন বাড়তে থাকে (যেমন প্রোডাকশনে লাখ লাখ ডেটা), তখন অ্যাপ্লিকেশন স্লো হয়ে যেতে পারে। এই ভিডিওতে থরবেন জ্যানসেন (Thorben Janssen) দেখিয়েছেন কীভাবে ছোট ছোট ভুলের কারণে আমাদের অ্যাপ্লিকেশন ধীরগতিসম্পন্ন হয়ে যায় এবং কীভাবে 'Sets' ব্যবহার করে আমরা পারফরম্যান্স বহুগুণ বাড়িয়ে নিতে পারি।
১. পারফরম্যান্স সমস্যা খুঁজে বের করা (Generating Statistics)
অ্যাপ্লিকেশনের কোথায় সমস্যা হচ্ছে তা না জানলে সমাধান করা কঠিন। হাইবারনেট (Hibernate) আমাদের একটি চমৎকার সুবিধা দেয় যার মাধ্যমে আমরা দেখতে পারি একটি কাজের জন্য কয়টি কুয়েরি (Query) চলছে।
রেফারেন্স: [03:58]
বিস্তারিত: আপনার application.properties ফাইলে নিচের কোডটি যুক্ত করলে আপনি কনসোলে দেখতে পাবেন কতটি JDBC স্টেটমেন্ট চলছে এবং কত সময় লাগছে।
Properties
hibernate.generate_statistics=true
logging.level.org.hibernate.stat=debug
ব্যাখ্যা: এই কোডটি মূলত একটি রিপোর্টের মতো কাজ করে। এটি আপনাকে বলে দিবে যে একটি কাজ করতে গিয়ে কি ডেটাবেসে ১টি কুয়েরি গিয়েছে নাকি ভুলে ১০০টি চলে গিয়েছে।
- সহজ ব্যাখ্যা: Statistics মানে হলো পরিসংখ্যান বা তথ্য উপাত্ত। এখানে এটি দিয়ে বোঝানো হচ্ছে আপনার কোড ডেটাবেসের সাথে কতবার যোগাযোগ করছে তার হিসাব।
২. N+1 Select সমস্যা এবং সমাধান
এটি JPA-তে সবচেয়ে কমন একটি পারফরম্যান্স সমস্যা। মনে করুন আপনি ১০ জন খেলোয়াড়ের নাম আনলেন, কিন্তু তাদের খেলার লিস্ট আনতে গিয়ে হাইবারনেট আরও ১০টি আলাদা কুয়েরি চালালো। এটাই হলো N+1 সমস্যা।
রেফারেন্স: [12:51]
সমাধান (Join Fetch): আপনি Join Fetch ব্যবহার করে এক কুয়েরিতেই সব ডেটা নিয়ে আসতে পারেন।
Java
@Query("SELECT p FROM Player p LEFT JOIN FETCH p.tournaments WHERE p.id = :id")
Player findByPlayerIdWithTournaments(Long id);
ব্যাখ্যা: এখানে JOIN FETCH ব্যবহার করার ফলে প্লেয়ার এবং তার টুর্নামেন্ট ডেটা একসাথেই চলে আসে, ফলে বারবার ডেটাবেসে যেতে হয় না।
৩. Many-to-Many: কেন List এর চেয়ে Set ভালো? (মূল বিষয়)
এটি এই আলোচনার সবচেয়ে গুরুত্বপূর্ণ অংশ। আমরা সাধারণত জাভাতে List ব্যবহার করতে অভ্যস্ত, কিন্তু 'Many-to-Many' অ্যাসোসিয়েশনের ক্ষেত্রে List ব্যবহার করলে হাইবারনেট খুব অদ্ভুত আচরণ করে।
সমস্যা (List-এর ক্ষেত্রে): যদি আপনি List ব্যবহার করেন এবং সেখান থেকে মাত্র একটি রেকর্ড ডিলিট করতে চান, তবে হাইবারনেট নিচের কাজগুলো করে: ১. ওই রিলেশন টেবিলের সব ডেটা ডিলিট করে দেয়। ২. তারপর যেগুলোকে রাখার কথা, সেগুলোকে আবার একটা একটা করে ইনসার্ট (Insert) করে। এটি বিশাল বড় একটি পারফরম্যান্স লস!
সমাধান (Set-এর ক্ষেত্রে): আপনি যদি List এর বদলে Set ব্যবহার করেন, তবে হাইবারনেট শুধুমাত্র নির্দিষ্ট ওই একটি রো (Row) ডিলিট করে।
Java
// Tournament Entity-তে এভাবে লিখুন
@ManyToMany
private Set<Player> players = new HashSet<>();
কেন এটি করবেন? Set ডুপ্লিকেট ডেটা রাখে না। হাইবারনেট জানে যে Set-এ ডেটা ইউনিক, তাই সে সরাসরি একটি ডিলিট কুয়েরি চালাতে পারে। এতে কুয়েরির সংখ্যা অনেক কমে যায় (যেমন ভিডিওতে ১৮টি কুয়েরি থেকে ৪টিতে নেমে এসেছে)।
- সহজ ব্যাখ্যা: Association মানে হলো সম্পর্ক। এখানে প্লেয়ার এবং টুর্নামেন্টের মধ্যের সম্পর্ক বোঝানো হয়েছে। Set হলো এমন এক ধরনের ব্যাগ যেখানে একই জিনিস দুইবার রাখা যায় না।
৪. প্রজেকশন (Projections) - শুধু প্রয়োজনীয় ডেটা আনা
অনেক সময় আমাদের প্লেয়ারের সব তথ্যের প্রয়োজন হয় না, শুধু নাম দরকার হয়। সেক্ষেত্রে পুরো এনটিটি (Entity) লোড করা বোকামি।
রেফারেন্স: [27:00]
বিস্তারিত: আপনি DTO (Data Transfer Object) বা Interface Projection ব্যবহার করতে পারেন।
Java
public record PlayerNameDTO(String firstName, String lastName) {}
// রিপোজিটরিতে:
List<PlayerNameDTO> findByFirstName(String firstName);
ব্যাখ্যা: এটি ব্যবহার করলে ডেটাবেস থেকে প্লেয়ারের বায়ো, ছবি বা জন্ম তারিখের মতো ভারী ডেটাগুলো আসবে না, শুধু নাম আসবে। এতে মেমোরি কম খরচ হয়।
৫. ক্যাশিং (Caching)
সবশেষে আসে ক্যাশিং। যদি একই ডেটা বারবার লাগে, তবে সেটি মেমোরিতে জমিয়ে রাখা ভালো।
রেফারেন্স: [41:33]
আমার চিন্তাভাবনা: ক্যাশিং করার আগে আপনার কোড অপ্টিমাইজ করা জরুরি। থরবেনের মতে, যদি কোনো ডেটা পরিবর্তনের তুলনায় ১০ গুণ বেশি পড়া হয়, তবেই সেটি ক্যাশ করা উচিত। অন্যথায় ক্যাশ ম্যানেজ করতেই বেশি সময় নষ্ট হবে।
এনালাইসিস এবং বাস্তবতা (Analysis & Thinking)
সারাংশ: কন্টেন্ট ক্রিয়েটর থরবেন জ্যানসেন এখানে বোঝাতে চেয়েছেন যে, ডিফল্ট কনফিগারেশনের ওপর ভরসা না করে আমাদের বোঝা উচিত পর্দার আড়ালে (Under the hood) হাইবারনেট কীভাবে কাজ করছে।
বাস্তবতা ও পরামর্শ: ১. সব সময় Set ব্যবহার করুন: Many-to-Many রিলেশনে অন্ধভাবে Set ব্যবহার করা শুরু করুন, কারণ বাস্তবে আমাদের ডুপ্লিকেট রিলেশনের খুব একটা প্রয়োজন হয় না। ২. Statistics চেক করুন: ডেভেলপমেন্টের সময় generate_statistics চালু রাখা উচিত যাতে আপনি বুঝতে পারেন আপনার একটি কোড ডেটাবেসকে কতটা চাপে ফেলছে। ৩. বিকল্প চিন্তা: অনেক সময় JPA-র বদলে খুব জটিল কুয়েরির জন্য Native SQL বা QueryDSL ব্যবহার করা পারফরম্যান্সের জন্য ভালো হতে পারে।
সহজ কথায় উপদেশ: "আপনার কোড যদি প্রোডাকশনে গিয়ে অনেক ধীর হয়ে যায়, তবে বুঝে নিবেন আপনি হয়তো List ব্যবহার করছেন অথবা N+1 সমস্যায় পড়েছেন। আজই Set এবং Join Fetch ট্রাই করুন!"
[
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