Post updated on 17th January, 2018 at 11:13 pm
SQLite database সিরিজের এটা তৃতীয় পর্ব; এই পর্বে দেখানো হবে কিভাবে টেবিলে ডেটা CREATE, READ, UPDATE ও DELETE করা যায়। এই চারটা অপারেশনকে সংক্ষেপে CRUD operation বলা হয়ে থাকে। আপনার যদি SQLite database সম্পর্কে ব্যাসিক ধারণা না থেকে থাকে তাহলে অবশ্যই এই সিরিজের প্রথম পোস্টটি পড়ে আসুন। দ্বিতীয় পোস্টে দেখানো হয়েছিল কিভাবে যে কোনো অ্যাপের জন্য ডেটাবেজ বানানো যায়। ডেটাবেজ বানানোর পর টেবিল বানানো সম্পর্কেও সেখানো আলোকপাত করা হয়েছে।
দ্বিতীয় পোস্টে স্যাম্পল অ্যাপের GIF screenshot সহ problem description দেয়া আছে। তাই এখানে প্রবলেম সম্পর্কে আর কথা বলছি না। ভিজিবিলিটির সুবিধার্থে GIF screenshot-টি আবার শেয়ার করছি।
Access Your Data Using SQLiteDatabase Class
ডেটাবেজে student নামের টেবিল তৈরি হয়ে গেছে। এখন আমরা চাইলে ইচ্ছা মত ডেটা create, read, update ও delete করতে পারব। নিচে এই অপারেশনগুলোর জন্য একেকটা মেথড উল্লেখ করা হলো। এই মেথডগুলো লিখেছি DatabaseQueryClass নামক আরেকটা নতুন ক্লাসে। এই ক্লাসের constructor এর প্যারামিটারে context পাঠানো হবে। এরপর এই context সবগুলো CRUD operation এর মেথডই ইউজ করা হবে।
CREATE (insert) data in SQLite Database
Database এ একটা row insert করার raw query:
INSERT INTO student(name, registration_no, phone, email) VALUES(‘John Doe’, 1001, ‘015256455’, ‘[email protected]’)
আমরা চাইলে sqliteDatabase.rawQuery() মেথডের প্যারামিটারে উপরের মত RAW SQL query লিখে ডেটাবেজে রিড-রাইট করতে পারি। কিন্তু Android SDK আমাদের কাজকে খানিকটা সহজ করে দিয়েছে। একদম কাঠখোট্টা কুয়েরি লিখে কাজ করা একটু সময়সাপেক্ষ। আর ছোটখাটো ভুলের জন্য ঘন্টার পর ঘন্টা পার হয়ে যাবার আশংকাও থাকে। তাই SQLiteDatabase class এর কিছু মেথড আছে যেগুলো আমাদের কাজকে সহজ করে দেয়। কিন্তু under the hood একদম raw query-ই এক্সিকিউট হয়। সেরকম একটা মেথড হচ্ছে insert(). একই কাজের জন্য আরেকটি মেথড ইউজ করা যায় insertOrThrow(). নিচে আমাদের লিখা DatabaseQueryClass ক্লাসের insertStudent() মেথডটি দেখা যাক।
public long insertStudent(Student student){ long id = -1; DatabaseHelper databaseHelper = DatabaseHelper.getInstance(context); SQLiteDatabase sqLiteDatabase = databaseHelper.getWritableDatabase(); ContentValues contentValues = new ContentValues(); contentValues.put(Config.COLUMN_STUDENT_NAME, student.getName()); contentValues.put(Config.COLUMN_STUDENT_REGISTRATION, student.getRegistrationNumber()); contentValues.put(Config.COLUMN_STUDENT_PHONE, student.getPhoneNumber()); contentValues.put(Config.COLUMN_STUDENT_EMAIL, student.getEmail()); try { id = sqLiteDatabase.insertOrThrow(Config.TABLE_STUDENT, null, contentValues); } catch (SQLiteException e){ Logger.d("Exception: " + e.getMessage()); Toast.makeText(context, "Operation failed: " + e.getMessage(), Toast.LENGTH_LONG).show(); } finally { sqLiteDatabase.close(); } return id; }
এই মেথডে Student ক্লাসের একটা অবজেক্ট পাঠালে স্টুডেন্টের ডেটাগুলো ডেটাবেজে insert করে দিবে। মেথডের শুরুতে DatabaseHelper ক্লাসের একটা instance নিয়েছি, নাম দিয়েছি databaseHelper. এরপরের লাইনে SQLiteDatabase ক্লাসের একটা অবজেক্ট নিচ্ছি। আর অবজেক্টের রেফারেন্সটা return করছে databaseHelper.getWritableDatabase() এই মেথডটি। আমরা যেহেতু ডেটাবেজে একটা স্টুডেন্টের ইনফরমেশন রাইট করতে চাই তাই getWritableDatabase() method কল করা হয়েছে। যদি কোনো ডেটা read করতে চাইতাম বা সার্চ করতে চাইতাম তাহলে getReadableDatabase() মেথড কল করতাম। যদিও উভয় মেথডই একই ডেটাবেজের রেফারেন্সই রিটার্ন করে। এরপরেও হালকা কিছু পার্থক্য আছে। কিউরিয়াস হলে গুগল করে জেনে নিতে পারেন। তবে সাধারনত যে কোনো সময় যে কোনো মেথড কল দিলেই কাজ করবে।
এরপর আমরা ContentValues ক্লাসের একটা অবজেক্ট নিলাম। এটাও Android SDK এর একটা ক্লাস। Map বা HashMap এর কথা মনে আছে? সেখানে কী করতে পারি? Key-Value pair হিসাবে কিছু ডেটা রাখতে পারি। ContentValues-ও এরকম একটা ক্লাস। এর অবজেক্টে Key হিসাবে সেট করতে হয় টেবিলের column এর নাম। আর value হিসাবে সেট করতে হয় ঐ কলামে যেই ভ্যালুটা insert করতে চাচ্ছি সেই ভ্যালুটা।
উপরের কোডটুকু এক্সিকিউট হলে আমরা এক্সপেক্ট করছি আমাদের ডেটাবেজের student table এ একটা নতুন row যুক্ত হবে। এই row এর কিন্তু মোট ৫ টা column আছে। _id, name, registration_no, email ও phone. আইডি যেহেতু অটো ইনক্রিমেন্ট হবে তাই এটা চিন্তা করা লাগবে না (কেন অটো ইনক্রিমেন্ট হবে? মাথা চুলকাতে চুলকাতে দ্বিতীয় পর্বটি পড়ে আসতে পারেন। সেখানে দেখানো হয়েছে টেবিল তৈরির সময় কেমন query চালিয়েছিলাম)। বাকি চারটা কলামের ডেটা KEY-VALUE pair হিসাবে contentValues অবজেক্টে সেট করা হয়েছে।
এরপর try{} ব্লকের মধ্যে দেখা যাচ্ছে sqLiteDatabase.insertOrThrow() এই মেথডটি কল করা হয়েছে। মেথডের নাম দেখেই বুঝতে পারছি এর কাজ হচ্ছে ডেটাবেজে একটা record ইনসার্ট করা অথবা exception throw করা। ইনসার্ট করার ব্যাপারটা বুঝলাম। থ্রো করা কী জিনিস? যদি কোনো কারণে ডেটা ইনসার্টের কাজটা সফল ভাবে শেষ না হয় তাহলে সে exception throw করবে আর কী কারণে exception ঘটলো সেটা আমরা catch(){} ব্লকে একটা Toast দিয়ে দেখাতে পারি বা প্রয়োজনীয় ব্যবস্থা নিতে পারি। কেন error ঘটতে পারে? যেমনঃ একটা স্টুডেন্টের রেজিস্ট্রেশন নাম্বার দিয়েছেন 1001. নতুন আরেকটা স্টুডেন্টের ইনফরমেশন নেয়ার সময় তার রেজিস্ট্রেশন নাম্বারও দিলেন 1001 তাহলে একটা exception খাবে! কারণ ডেটাবেজের টেবিল তৈরির সময় দ্বিতীয় পোস্টে registration_no column এর জন্য constraint সেট করে দিয়েছিলাম UNIQUE. তাই একই রেজিস্ট্রেশন নাম্বারের ২ জন স্টুডেন্টের এন্ট্রি দিতে চাইলে ডেটাবেজে ডেটা ইনসার্ট হবে না!
আপনি যদি insertOrThrow() মেথড ইউজ না করে insert() মেথড ইউজ করেন তাহলেও কাজ চলবে। কিন্তু এই exception-গুলো তখন আপনি handle করতে পারবেন না। Exception-গুলো insert() মেথডটি নিজেই হ্যান্ডেল করবে। ফলে আপনার অ্যাপ ঠিক ক্র্যাশ করবে না, কিন্তু কী কারণে ডেটা ইনসার্ট হলো না সেটাও জানা যাবে না।
এরপর আসি insertOrThrow() এর প্যারামিটারগুলো নিয়ে। প্রথম প্যারামিটারটি হচ্ছে টেবিলের নাম। যেটাতে আমরা ভ্যালু ইনসার্ট করতে চাই। দ্বিতীয় প্যারামিটারে প্রায় সব সময়ই null রাখা হয়। এটা হচ্ছে nullColumnHack এর প্যারামিটার। যদি কখনো আমাদের ContentValues এর অবজেক্টটি empty হয় তাহলে ডেটাবেজের টেবিলে কোনো empty row ইনসার্ট করতে দেয়া হয় না। কিন্তু আমরা যদি কোনো কারণে টেবিলে empty বা null ভ্যালু ইনসার্ট করতে চাই তখন এই দ্বিতীয় প্যারামিটারে কোনো একটা কলামের নাম বলে দিতে হবে। তাহলে ঐ row তে empty বা null স্টোর করতে পারব। আমি এখনো এমন কোনো কেস হ্যান্ডেল করি নাই। সব সময় এটা null-ই করে রাখি। তৃতীয় প্যারামিটারে পাঠানো হচ্ছে contentValues অবজেক্টটি। যার মাঝে রয়েছে key-value pair হিসাবে কলামের নাম আর সেই কলামের ডেটা। contentValues এ সেট করা column name-গুলো খুব important. কারণ insert() বা insertOrThrow() মেথড কল হবার পর এই contentValues এর মধ্যে থাকা কলামের নাম ধরে টেবিলে ডেটা ইনসার্ট করা হয়। যদি contentValues এর মধ্যে ভুলভাল কলামের নাম দেই তাহলে exception খাবে!
sqLiteDatabase.insertOrThrow() মেথডটির return type হচ্ছে long. যদি ডেটা ‘সহী-সালামতে’ ইনসার্ট করতে পারে তাহলে ঐ রেকর্ডের _id এর মান রিটার্ন করবে (যা হবে অবশ্যই শূন্যের চেয়ে বড়)। আর যদি ঝামেলা হয় তাহলে -1 রিটার্ন করবে। অ্যাপে স্টুডেন্টের ইনফরমেশন ইনপুট দিয়ে যখন CREATE বাটনে ক্লিক করা হয় সেই ক্লিক ইভেন্টের মেথডের কাছে আইডি পাঠানো হচ্ছে। অর্থাৎ insertStudent() কল করে কাজটা সফল হলে শূন্যের চেয়ে বড় একটা মান রিটার্ন করবে caller এর কাছে। অন্যথায় -1 রিটার্ন করবে।
ডেটাবেজে যে কোনো কুয়েরি সংক্রান্ত কাজের শেষে সবচেয়ে গুরুত্বপূর্ণ কাজ হচ্ছে অ্যাপের সাথে ডেটাবেজের কানেকশন close করা। এটা ক্লোজ করা না হলে সম্ভাবনা আছে ডেটাবেজ যতবার কল হবে অ্যাপের সাথে নতুন একটা কানেকশন তৈরি করবে। কানেকশনগুলো বাড়তে বাড়তে এক সময় লিমিট ক্রস করে অ্যাপ ক্র্যাশ করবে। তাই finally block এ sqliteDatabase.close() অবশ্যই কল করতে হবে। এটা কল না করলেও অ্যাপ এই মুহূর্তে কাজ করবে, কিন্তু একটা সময় গিয়ে ঠিকই ক্র্যাশ করবে।
UPDATE data in SQLite Database
Database এ একটা row update করার raw query:
UPDATE student SET name = ‘John Doe Mike’ , registration_no = 1022, phone = ‘0152332263’, email = ‘[email protected]’ WHERE _id = 1
Insert এর মত ডেটা আপডেট করার জন্যেও Android SDK এর মেথড রয়েছে। নিচের কোডটা দেখা যাকঃ
public long updateStudentInfo(Student student){ long rowCount = 0; DatabaseHelper databaseHelper = DatabaseHelper.getInstance(context); SQLiteDatabase sqLiteDatabase = databaseHelper.getWritableDatabase(); ContentValues contentValues = new ContentValues(); contentValues.put(Config.COLUMN_STUDENT_NAME, student.getName()); contentValues.put(Config.COLUMN_STUDENT_REGISTRATION, student.getRegistrationNumber()); contentValues.put(Config.COLUMN_STUDENT_PHONE, student.getPhoneNumber()); contentValues.put(Config.COLUMN_STUDENT_EMAIL, student.getEmail()); try { rowCount = sqLiteDatabase.update(Config.TABLE_STUDENT, contentValues, Config.COLUMN_STUDENT_ID + " = ? ", new String[] {String.valueOf(student.getId())}); } catch (SQLiteException e){ Logger.d("Exception: " + e.getMessage()); Toast.makeText(context, e.getMessage(), Toast.LENGTH_LONG).show(); } finally { sqLiteDatabase.close(); } return rowCount; }
শুরুর দিকের সব কিছু insert() বা insertOrThrow() এর মতই। পার্থক্য শুধু update() এর প্যারামিটারগুলো আর রিটার্ন ভ্যালুতে।
update() মেথডের প্রথম প্যারামিটারে আগের মতই টেবিলের নাম। দ্বিতীয় প্যারামিটারে contentValues. এতে সেট করা হয়েছে কোনো একটা রেকর্ডের updated value. তৃতীয় প্যারামিটার হচ্ছে WHERE CLAUSE এর জন্য। একটা row কে identify করার জন্য কোন কলামের মাধ্যমে identify করবেন সেই কলামের নাম এখানে দিতে হবে। স্ট্রিংয়ের ভিতরের ‘?’ চিহ্নটা হচ্ছে ডেটা বসানোর একটা placeholder. চতুর্থ প্যারামিটারে পাঠাতে হবে arguments. আমরা এখানে স্টুডেন্ট টেবিল থেকে student এর _id এর মাধ্যমে একটা স্টুডেন্টকে খুঁজে বের করছি। এরপর তার ডেটাগুলোকে আপডেট করেছি। চতুর্থ প্যারামিটারের আর্গুমেন্টটাই তৃতীয় প্যারামিটারের “?” এর জায়গায় বসে যাবে। ফলে WHERE CLAUSE-টা দাঁড়াবে এমনঃ ‘WHERE registration_no = 1001′. তৃতীয় ও চতুর্থ প্যারামিটারে একাধিক কলামের নাম ও ভ্যালু থাকতে পারে। মনে রাখতে হবে চতুর্থ প্যারামিটারের ভ্যালুগুলো অবশ্যই স্ট্রিং করে পাঠাতে হবে। তাই student এর _id একটা integer number হওয়ার পরও সেটাকে String বানিয়ে ইউজ করা হয়েছে।
update() মেথডটি কল হবার ফলে কয়টি row affected হয়েছে সেটা সে রিটার্ন করে। এই রিটার্ন করা ভ্যালুটা স্টোর করা হয়েছে rowCount variable এর মাঝে।
insertStudent() এর মত এই মেথডের finally block এ ডেটাবেজের কানেকশন ক্লোজ করা হয়েছে।
DELETE data from SQLite Database
Database এ একটা row delete করার raw query:
DELETE FROM student WHERE registration_no = 1001
আমরা যদি student টেবিল থেকে কোনো স্টুডেন্টের registration number দিয়ে স্টুডেন্টকে আইডেন্টিফাই করে তার ইনফরমেশনগুলো ডিলেট করতে চাই তাহলে উপরের raw query-টা কাজ করবে। Delete করার জন্য Android SDK এর রয়েছে delete() মেথড।
public long deleteStudentByRegNum(long registrationNum) { long deletedRowCount = -1; DatabaseHelper databaseHelper = DatabaseHelper.getInstance(context); SQLiteDatabase sqLiteDatabase = databaseHelper.getWritableDatabase(); try { deletedRowCount = sqLiteDatabase.delete(Config.TABLE_STUDENT, Config.COLUMN_STUDENT_REGISTRATION + " = ? ", new String[]{ String.valueOf(registrationNum)}); } catch (SQLiteException e){ Logger.d("Exception: " + e.getMessage()); Toast.makeText(context, e.getMessage(), Toast.LENGTH_LONG).show(); } finally { sqLiteDatabase.close(); } return deletedRowCount; }
try block এর ভিতরে delete() মেথড কল করা হয়েছে। প্রথম প্যারামিটারে টেবিলের নাম, দ্বিতীয় প্যারামিটারে WHERE CLAUSE আর তৃতীয় প্যারামিটারে arguments. যা আগে বর্ণনা করা update() মেথডের মতই। sqliteDatabase.delete() মেথডটি একটা long value রিটার্ন করবে। এই মেথডটি কল হবার কারণে যেই কয়টি row delete হয়েছে সেই সংখ্যাটাই সে রিটার্ন করবে। আমরা এই returned value চেক করে বলে দিতে পারি কোনো row ডিলেট হয়েছে কিনা। যদি এর মান শূন্য হয় তার মানে কোনো রেকর্ড ডিলেট হয় নি। যদি 1 হয় এর মানে একটি row delete হয়েছে।
আচ্ছা যদি কখনো সবগুলো স্টুডেন্টের ইনফরমেশন একবারে ডিলেট করার দরকার হয় তখন কী করব? উপরের মেথডটাকে স্টুডেন্টের রেজিস্ট্রেশন নাম্বার দিয়ে বারবার কল করলেই কিন্তু হয় তাই না? এত পরিশ্রম করার আসলে দরকার নাই। Android SDK খুব স্মার্ট একটা SDK. সে এটা করার জন্য খুব সহজ সিসটেম করে দিয়েছে।
sqLiteDatabase.delete(Config.TABLE_STUDENT, null, null);
এখানে delete() মেথড কল করে প্রথম প্যারামিটারে টেবিলের নাম দেয়া হয়েছে। দ্বিতীয় প্যারামিটারে WHERE CLAUSE না দিয়ে null এবং তৃতীয় প্যারামিটারে কোনো আর্গুমেন্ট না দিয়ে null দেয়া হয়েছে। এর মানে কমান্ডটা হচ্ছে স্টুডেন্ট টেবিলের ডেটা ডিলেট করো। কোন শর্ত মানলে ডেটা ডিলেট করব? আরে ব্যাটা কোনো শর্ত নাই! শর্ত থাকলে তো দিতামই! ধুমাইয়া চোখ বন্ধ করে টেবিল সাফা কইরা দাও!
এরপর finally block এ যতারীতি ডেটাবেজ কানেকশন ক্লোজ করা হয়েছে।
READ information of a single student from SQLite Database
যদি ডেটাবেজে এক বা একাধিক স্টুডেন্টের ইনফরমেশন থাকে তাহলে আমাদের স্যাম্পল অ্যাপের Home Activity-তে স্টুডেন্টের লিস্ট শো করে। স্টুডেন্ট লিস্টের কোনো আইটেমের “Edit” আইকনে ক্লিক করলে ঐ স্টুডেন্টের সব ইনফরমেশন একটা পপ আপ ডায়ালগে শো করে। সেখান থেকে আমরা ডেটা আপডেট করতে পারি।
তার মানে আমাদের দরকার হচ্ছে একটা স্টুডেন্টের ডেটা ডেটাবেজ থেকে রেজিস্ট্রেশন নাম্বারের মাধ্যমে খুঁজে বের করা। রেজিস্ট্রেশন নাম্বার দিয়ে স্টুডেন্টের রেকর্ড খুঁজে বের করার জন্য DatabaseQueryClass এ getStudentByRegNum() নামের একটা মেথড লিখব। এই মেথডের ভিতর Android SDK এর insert(), update() এর মত আরেকটা মেথড query()-কে কল করব। query() মেথডের basic form হচ্ছেঃ
.. public Cursor query(String table, String[] columns, String selection, String[] selectionArgs, String groupBy, String having, String orderBy) .
query() method এর return type হচ্ছে Cursor class এর একটা object. জাভার JDBC নিয়ে যদি কাজ করে থাকেন তাহলে এটাকে জাভার ResultSet ক্লাসের সাথে তুলনা করতে পারেন। টেবিল থেকে এক বা একাধিক কলামের ডেটা রিড করার জন্য কিছু parameter পাঠাতে হয়। প্রথমেই দিতে হয় টেবিলের নাম। এরপর একটা স্ট্রিংয়ের array. এখানে থাকবে আপনার desired column name. অর্থাৎ ঐ টেবিলের কোন কোন কলামের ডেটা আপনি চান। যদি সব কলামের ডেটা চান তাহলে এখানে null বসায় দিলেই হবে। এরপর সিলেকশন পার্ট। মানে WHERE CLAUSE আর তার আর্গুমেন্টস। একটা row কে রেজিস্ট্রেশন নাম্বার দিয়ে খুঁজে বের করবেন নাকি ফোন নাম্বার দিয়ে খুঁজবেন সেটা এখানে উল্লেখ করে দিতে হবে। যদি কোনো শর্ত না দিয়ে সব স্টুডেন্টের ডেটাই চান তাহলে তৃতীয় লাইনের দুইটা প্যারামিটারই হবে null. শেষের তিনটা প্যারামিটার হচ্ছে কুয়েরি করে প্রাপ্ত ডেটাগুলোকে সাজানো গোছানো। আমরা যদি প্রাপ্ত ডেটাগুলোকে গ্রুপ আকারে সাজাতে চাই বা ascending/descending order এ সর্ট করতে চাই তাহলে শেষ তিনটা প্যারামিটার ব্যবহার করা হয়। যদি এগুলো ব্যবহার করতে না চাই তাহলে null বসিয়ে দিতে হবে।
এবার getStudentByRegNum() method-টা দেখা যাকঃ
public Student getStudentByRegNum(long registrationNum){ DatabaseHelper databaseHelper = DatabaseHelper.getInstance(context); SQLiteDatabase sqLiteDatabase = databaseHelper.getReadableDatabase(); Cursor cursor = null; Student student = null; try { cursor = sqLiteDatabase.query(Config.TABLE_STUDENT, null, Config.COLUMN_STUDENT_REGISTRATION + " = ? ", new String[]{String.valueOf(registrationNum)}, null, null, null); /** // If you want to execute raw query then uncomment below 2 lines. And comment out above sqLiteDatabase.query() method. String SELECT_QUERY = String.format("SELECT * FROM %s WHERE %s = %s", Config.TABLE_STUDENT, Config.COLUMN_STUDENT_REGISTRATION, String.valueOf(registrationNum)); cursor = sqLiteDatabase.rawQuery(SELECT_QUERY, null); */ if(cursor.moveToFirst()){ int id = cursor.getInt(cursor.getColumnIndex(Config.COLUMN_STUDENT_ID)); String name = cursor.getString(cursor.getColumnIndex(Config.COLUMN_STUDENT_NAME)); long registrationNumber = cursor.getLong(cursor.getColumnIndex(Config.COLUMN_STUDENT_REGISTRATION)); String phone = cursor.getString(cursor.getColumnIndex(Config.COLUMN_STUDENT_PHONE)); String email = cursor.getString(cursor.getColumnIndex(Config.COLUMN_STUDENT_EMAIL)); student = new Student(id, name, registrationNumber, phone, email); } } catch (Exception e){ Logger.d("Exception: " + e.getMessage()); Toast.makeText(context, "Operation failed", Toast.LENGTH_SHORT).show(); } finally { if(cursor!=null) cursor.close(); sqLiteDatabase.close(); } return student; }
try block এর ভিতরের sqLiteDatabase.query() মেথড দেখলেই বুঝে যাবেন কী করা হয়েছে। প্রথমে টেবিলের নাম। এরপর বলা হয়েছে registration number দিয়ে স্টুডেন্টকে আইডেন্টিফাই করতে। শেষের তিনটা প্যারামিটারে null পাঠানো হয়েছে। কারণ আমরা আমাদের ডেটাকে aggregate বা order করতে চাই না। আপনি যদি raw query execute করতে চান সেটাও try এর ভিতরে কমেন্ট করা আছে। দরকার হলে ইউজ করতে পারেন।
তো query() method একটা cursor object return করলো। এটা আসলে কী জিনিস? এই অবজেক্টের ভিতরে আমাদের কাঙ্খিত ডেটাগুলো রয়েছে। সেই ডেটাগুলো এখন বের করে Student class এর একটা object বানাবো।
দ্বিতীয় পোস্টে স্টুডেন্টের টেবিলের একটা ছবি দিয়েছিলাম। যেখানে row-column অনুযায়ী ডেটা দেখানো হয়েছিল। Cursor এর ডেটাগুলোকে সেরকম কল্পনা করতে পারেন। কল্পনা করতে পারেন এর অবজেক্টের ভিতর row-column অনুযায়ী ডেটা থাকে। সেখানে একটা row থাকতে পারে। আবার একাধিক row থাকতে পারে। আমরা যেহেতু ইউনিক রেজিস্ট্রেশন নাম্বার দিয়ে একটা স্টুডেন্টকে খুঁজে বের করছি তাই এখানে একটাই row থাকবে। যদি query করে সব স্টুডেন্টের ইনফরমেশন নিয়ে আসতে চাইতাম তখন ডেটাবেজের টেবিলে ১০ টা স্টুডেন্ট থেকে থাকলে কার্সর অবজেক্টেও ১০ টা row থাকত। তো এই row-গুলোকে navigate করার জন্য Cursor class এ চারটা মেথড আছে। সেগুলো হচ্ছেঃ
- moveToFirst()
- moveToLast()
- moveToNext()
- moveToPrevious()
মেথডের নাম দেখেই বুঝা যাচ্ছে মেথডগুলোর কাজ। আমাদের উপরের কোডে প্রথম মেথডটি ব্যবহার করা হয়েছে। কারণ আমরা জানি একটাই মাত্র row পাওয়া যাবে cursor এ। আর সেজন্যেই cursor এর first position বা first row-তে থাকা ডেটাগুলো আমরা parse করব। IF এর ভিতর moveToFirst() call করা হয়েছে। এটা একটা boolean value রিটার্ন করে। যদি অন্তত একটা row থেকে থাকে cursor এ তাহলে সেটা হবে প্রথম row. এ ক্ষেত্রে true রিটার্ন করবে। কিন্তু যদি cursor এ কোনো কারণে কোনো ডেটাই না থাকে cursor.moveToFirst() কল করলে সে গিয়ে দেখবে “অারে! কোনো ডেটাই তো নাই! আমি ফার্স্ট খুঁজে পাব কই?” তখন সে false return করবে। IF এর কন্ডিশন true হলে পরের কোডটুকু এক্সিকিউট হবে।
if(cursor.moveToFirst()){ int id = cursor.getInt(cursor.getColumnIndex(Config.COLUMN_STUDENT_ID)); String name = cursor.getString(cursor.getColumnIndex(Config.COLUMN_STUDENT_NAME)); long registrationNumber = cursor.getLong(cursor.getColumnIndex(Config.COLUMN_STUDENT_REGISTRATION)); String phone = cursor.getString(cursor.getColumnIndex(Config.COLUMN_STUDENT_PHONE)); String email = cursor.getString(cursor.getColumnIndex(Config.COLUMN_STUDENT_EMAIL)); student = new Student(id, name, registrationNumber, phone, email); }
উপরের কোড খুব হিজিবিজি লাগছে? ঠিক আছে ঐটা দেখা দরকার নাই। আপাতত নিচেত simplified code-টুকু দেখেন।
if(cursor.moveToFirst()){ int id = cursor.getInt(0); String name = cursor.getString(1); long registrationNumber = cursor.getLong(2); String phone = cursor.getString(3); String email = cursor.getString(4); student = new Student(id, name, registrationNumber, phone, email); }
cursor.getInt(0) এর মানে বুঝায় যায় যে cursor অবজেক্টে থাকা কোনো একটা integer value সে রিটার্ন করবে। কিন্তু প্যারামিটারে থাকা ‘0’ দিয়ে কী বুঝাচ্ছে? এটা হচ্ছে column number. অর্থাৎ কার্সরে যেই টেবিল আছে সেই টেবিলের 0 number index এর কলামে থাকা integer value টা রিটার্ন করো। cursor.getString(1) এর মানে হচ্ছে কার্সরে যেই টেবিল আছে সেই টেবিলের ১ নাম্বার ইনডেক্সের কলামে থাকা স্ট্রিং ভ্যালুটা রিটার্ন করো। এভাবে বাকিগুলো।
আমরা জানি যে student table এর প্রথম কলাম আইডি, দ্বিতীয় কলামে নাম, তৃতীয় কলামে রেজিস্ট্রেশন নাম্বার ইত্যাদি রয়েছে। তাই সেই ক্রমানুসারে এখানে প্যারামিটার হিসাবে index number পাঠাচ্ছি। কিন্তু এটা কি ঠিক হচ্ছে? যদি টেবিল চেঞ্জ হয়? তাহলে কিন্তু হাতের কর গুণে হিসাব করে এই ইনডেক্সের নাম্বার ঠিকঠাক বের করতে হবে। এর চেয়ে ভাল কোনো বুদ্ধি কি আছে? আচ্ছা যদি এমন একটা কাজ করা যায় যে আমরা কলামের নামটা বলব কেউ আমাদেরকে সেই কলামের index number বলে দিবে, তাহলে কেমন হয়? সেই কাজটাই প্রথমে দেখিয়েছিলাম। কিন্তু কোড হিজিবিজি হওয়ায় simplify করেছি। সেই হিজিবিজি কোডের দিকে এইবার দেখা যাক।
আমরা কার্সর থেকে আইডি পাওয়ার জন্য নিচের মেথড কল করেছিলাম। দেখুন cursor.getInt() মেথডের প্যারামিটারের ভিতরে আরেকটা মেথড কল করা হয়েছে।
int id = cursor.getInt( cursor.getColumnIndex(Config.COLUMN_STUDENT_ID) );
cursor.getColumnIndex() এমন একটা মেথড যাতে প্যারামিটার হিসাবে কোনো একটা কলামের নাম পাঠালে সে বলে দিবে সেই কলাম কত নাম্বার ইনডেক্সে আছে! দারুণ না!!! এজন্য getColumnIndex() এর প্যারামিটার হিসাবে পাঠিয়েছি Config.COLUMN_STUDNET_ID. যা মূলত একটা স্ট্রিং ভ্যারিয়েবল। এর ভ্যালু হচ্ছে _id. একই ভাবে স্টুডেন্টের নাম পাওয়ার জন্য কল করেছি এই মেথডঃ
String name = cursor.getString(cursor.getColumnIndex(Config.COLUMN_STUDENT_NAME));
cursor.getString() দিয়ে cursor এর থেকে একটা String চাচ্ছি। সেই স্ট্রিংটা টেবিলের কোন ইনডেক্সে রয়েছে সেটা জানাচ্ছি cursor.getColumnIndex(Config.COLUMN_STUDENT_NAME) এই মেথড কল করে। এতে করে আমাদের hardcoded index number বসানো লাগলো না। ভুল হবার চান্স অনেকটাই কমে গেল।
এভাবেই cursor থেকে ডেটা নিয়ে Student class এর একটা অবজেক্ট বানানো হলো। এরপর getStudentByRegNum() মেথড সেই student object-টা return করে দিবে।
finally block এর ভিতরে cursor ও sqLiteDatabase-কে অবশ্যই close করতে হবে।
READ all student’s information from SQLite Database
কোনো শর্ত ছাড়াই এক্ষেত্রে সকল স্টুডেন্টের তথ্য আমরা রিড করতে চাই। তাই query() মেথডের প্রথম প্যারামিটারে টেবিলের নাম দিয়ে বাকি সবগুলোতে null বসিয়ে দিয়েছি।
public List<Student> getAllStudent(){ DatabaseHelper databaseHelper = DatabaseHelper.getInstance(context); SQLiteDatabase sqLiteDatabase = databaseHelper.getReadableDatabase(); Cursor cursor = null; try { cursor = sqLiteDatabase.query(Config.TABLE_STUDENT, null, null, null, null, null, null, null); /** // If you want to execute raw query then uncomment below 2 lines. And comment out above line. String SELECT_QUERY = String.format("SELECT %s, %s, %s, %s, %s FROM %s", Config.COLUMN_STUDENT_ID, Config.COLUMN_STUDENT_NAME, Config.COLUMN_STUDENT_REGISTRATION, Config.COLUMN_STUDENT_EMAIL, Config.COLUMN_STUDENT_PHONE, Config.TABLE_STUDENT); cursor = sqLiteDatabase.rawQuery(SELECT_QUERY, null); */ if(cursor!=null) if(cursor.moveToFirst()){ List<Student> studentList = new ArrayList<>(); do { int id = cursor.getInt(cursor.getColumnIndex(Config.COLUMN_STUDENT_ID)); String name = cursor.getString(cursor.getColumnIndex(Config.COLUMN_STUDENT_NAME)); long registrationNumber = cursor.getLong(cursor.getColumnIndex(Config.COLUMN_STUDENT_REGISTRATION)); String email = cursor.getString(cursor.getColumnIndex(Config.COLUMN_STUDENT_EMAIL)); String phone = cursor.getString(cursor.getColumnIndex(Config.COLUMN_STUDENT_PHONE)); studentList.add(new Student(id, name, registrationNumber, email, phone)); } while (cursor.moveToNext()); return studentList; } } catch (Exception e){ Logger.d("Exception: " + e.getMessage()); Toast.makeText(context, "Operation failed", Toast.LENGTH_SHORT).show(); } finally { if(cursor!=null) cursor.close(); sqLiteDatabase.close(); } return Collections.emptyList(); }
যেহেতু একাধিক স্টুডেন্টের রেকর্ড পাওয়ার সম্ভাবনা আছে তাই do-while loop চালিয়ে cursor এর ভিতরকার ডেটা row-গুলোতে navigate করা হচ্ছে। IF এর ভিতর একবার cursor.moveToFirst() কল করা হয়েছে। এরপর while এর কন্ডিশন হিসাবে cursor.moveToNext() কল করা হয়েছে। ফলে কারসরের প্রথম row থেকে একটার পর একটা navigate হয়ে শেষ পর্যন্ত ডেটা fetch করা সম্ভব হবে। আর প্রতিটা রেকর্ড দিয়েই একটা করে student object বানিয়ে studentList object এ add করে দেয়া হচ্ছে। সবশেষে সেই লিস্টটাকে রিটার্ন করে দেয়া হচ্ছে।
কষ্ট করে এত লম্বা পোস্ট পড়ার জন্য ধন্যবাদ। কোথাও কোনো ভুলত্রুটি চোখে পড়লে কমেন্ট করে জানাবেন প্লিজ। পুরো প্রোজেক্টের সোর্সকোড পাওয়া যাবে আমার গিটহাব রিপোজিটরিতে।
এই পোস্টে একটা মাত্র টেবিলের উপর CRUD Operation চালানো হয়েছে। পরের পর্বে একাধিক টেবিলের মধ্যে Foreign Key এর মাধ্যমে রিলেশন ও CRUD Operation দেখানো হবে। পরের পোস্টটি পড়া যাবে এখান থেকে।
from “StudentCreateListener() ” class these
2 lines I have found. Can you please explain the uses of these two lines.
public interface StudentCreateListener {
void onStudentCreated(Student student);
}
First of all, “StudentCreateListener” is not a class. It’s an interface.
StudentListActivity class implements this interface when it open the dialogFragment to create student. So this activity has to override the method “onStudentCreated()”. When student is successfully created in dialogFragment then there call onStudentCreated method and send “Student” object. Then Activity’s overridden method take steps what he should do.
You can study on “Java Interface” for more understanding.
Sorry for late reply.