Product Repository Refactoring:NHibernate Helper
Pada bagian sebelumnya, kita telah belajar bagaimana melakukan operasi CRUD. Kita memanggil operasi CRUD secara eksplisit melalui session. Bagaimana kalau pemanggilan itu kita buat secara implisit. Jadi, operasinya akan terlihat seperti ini
Untuk meyimpan product:
productRepository.Save(product)
Untuk memanggil kembali product dengan id:
IProduct product=productRepository.GetById(productId)
atau dengan nama:
IProduct product =productRepository.GetByName("Apple")
Untuk menghapus product:
productRepository.Remove(product)
Untuk memanggil List Product berdasarkan kategori:
IList products=productRepository.GetByCategory("Fruits")
Jadi, kita membutuhkan object ProductRepository yang didalamnya terdapat NHibernateHelper. NHibernateHelper memiliki method OpenSession.
NHibernate Helper
Pertama sekali kita buat class test baru
[TestFixture] public class NHibernateHelperFixture { [Test] public void OpenSessionTest() { INHibernateHelper nhHelper=new NHibernateHelper(); ISession session=nhHelper.OpenSession(); Assert.IsNotNull(session); } }
Buat project baru untuk repository, katakanlah bernama: fatur.sample.NHibernateHelloWorld.Repository. Jika project dibuat dengan IDE dan monodevelop , biasanya sudah ada file AssemblyInfo.cs didalamnya. Karena target build untuk app, dalam project nant, membuat semua code didalam folder app menjadi 1 assembly, maka AssemblyInfo.cs tidak boleh lebih dari satu. So, kita harus mengeluarkannya dari daftar yang akan dicompile. Gunakan element exclude untuk mengeluarkan file dari daftar.
Buat interface INHibernateHelper di project Repository.
namespace fatur.sample.NHibernateHelloWorld.Repository { public interface INHibernateHelper { ISession OpenSession(); } }
Buat class NHibernateHelper yang mengimplementasikan interface diatas.
namespace fatur.sample.NHibernateHelloWorld.Repository { public class NHibernateHelper:INHibernateHelper { public NHibernateHelper() { } public ISession OpenSession() { return null; } } }
Untuk sementara saya biarkan me-return null.
Agar bisa dicompile kita harus menambahkan reference NHibernate kedalam reference.
Sampai disini nunit mengatakan error karena null. OK, memang itu yang kita harapkan. Kita coba implemantasi langsung saja. Saya akan copikan apa yang ada didalam SetupFixture sebelumnya,
public class NHibernateHelper:INHibernateHelper { private ISessionFactory _sessionFactory; private Configuration _configuration; public NHibernateHelper() { _configuration = new Configuration(); _configuration.Configure(); _configuration.AddAssembly(typeof (Product).Assembly); _configuration.AddXmlFile("Product.hbm.xml"); _sessionFactory = _configuration.BuildSessionFactory(); } public ISession OpenSession() { return _sessionFactory.OpenSession(); } }
Jalankan test, dan seharusnya sukses.
Create database Menggunakan Schema
Kita juga harus membuat helper bisa membantu men-generate database. Kita buat teest untuk export schema. Disini method saya beri nama “CreateDatabase” agar lebih bermakna sesuai konteks.
[Test] public void SchemaExport() { INHibernateHelper nhHelper=new NHibernateHelper(); nhHelper.CreateDatabase(); }
Untuk memenuhi test ini, kita tambahkan method CreateDatabase didalam interface
public interface INHibernateHelper { ISession OpenSession(); void CreateDatabase(); }
dan implementasinya di class NHibernateHelper,
public class NHibernateHelper:INHibernateHelper { private ISessionFactory _sessionFactory; private Configuration _configuration; ... public void CreateDatabase() { } }
Sementara kita biarkan tanpa ada implementasi apapun. Jalankan test, pastikan tidak ada error.
Sekarang kita buat implementasinya. Saya kopikan saja schema export dari test sebelumnya.
public class NHibernateHelper:INHibernateHelper { private ISessionFactory _sessionFactory; private Configuration _configuration; ... public void CreateDatabase() { new SchemaExport(_configuration).Execute(false, true, false, false); } }
Test saya jalankan. Tidak ada masalah. Good.
Repository Test Refactoring
Kembali ke class ProductRepositoryFixture. Saya tambahkan helper ke dalam class
[TestFixture] public class ProductRepositoryFixture { private ISessionFactory _sessionFactory; private Configuration _configuration; private INHibernateHelper _hibernateHelper; [TestFixtureSetUp] public void TestFixtureSetUp() { _configuration = new Configuration(); _configuration.Configure(); _configuration.AddAssembly(typeof (Product).Assembly); _configuration.AddXmlFile("Product.hbm.xml"); _sessionFactory = _configuration.BuildSessionFactory(); _hibernateHelper=new NHibernateHelper(); } }
Test saya jalankan. No problem.
Next. Kita coba yang pertama, schemaexport saya ganti menjadi
[SetUp] public void SetupContext() { _hibernateHelper.CreateDatabase(); }
Test saya jalankan. Sukses.
Add product
Sekarang kita coba untuk insert product. Hah, kita belum punya class repository. Ok. kita buat dulu. Untuk membuatnya kita mulai dari test. Saya remark sebagian test sebelumnya dan saya ganti dengan definsisi baru:
[Test] public void Can_add_new_product() { var product= new Product {Name = "Apple", Category = "Fruits"}; IProductRepository productRepo=new ProductRepository(_hibernateHelper); productRepo.Add(product); /* using (ISession session = _sessionFactory.OpenSession()) using (ITransaction transaction = session.BeginTransaction()) { session.Save(product); transaction.Commit(); } */ }
Dari implementasi baru diatas, kita belum memiliki IProductRepository dan ProductRepository. Tambahkan class baru di project repository interface dan class berikut ini:
Interface
namespace fatur.sample.NHibernateHelloWorld.Repository { public interface IProductRepository { void Add(IProduct product); } }
Class
namespace fatur.sample.NHibernateHelloWorld.Repository { public class ProductRepository:IProductRepository { private INHibernateHelper _nhHelper; public ProductRepository(INHibernateHelper nhHelper) { this._nhHelper =nhHelper; } public void Add(IProduct product){ } } }
Lagi, implementasi Add saya biarkan kosong. Semua test yang memanggil add product error. Tidak apa-apa, karena memang belum kita implementasikan.
Untuk implemantasinya, saya kopikan saja code yang saya remark ke dalam method Add.
public void Add(IProduct product) { using (ISession session = _nhHelper.OpenSession()) using (ITransaction transaction = session.BeginTransaction()) { session.Save(product); transaction.Commit(); } }
Test saya jalankan dan sukses. Bagian test yang saya remark, saya hapus.
Langkah diatas saya ulang-ulang untuk operasi lainnya. Hasilnya,
Class Test
[TestFixture] public class ProductRepositoryFixture { private INHibernateHelper _hibernateHelper; [TestFixtureSetUp] public void TestFixtureSetUp() { _hibernateHelper=new NHibernateHelper(); } [SetUp] public void SetupContext() { _hibernateHelper.CreateDatabase(); } [Test] public void Can_add_new_product() { var product= new Product {Name = "Apple", Category = "Fruits"}; IProductRepository productRepo=new ProductRepository(_hibernateHelper); productRepo.Add(product); } [Test] public void Can_get_product() { IProduct product; this.Can_add_new_product(); product=getProduct(); Assert.AreEqual("Apple",product.Name); } private IProduct getProduct() { IProductRepository productRepo= new ProductRepository(_hibernateHelper); return productRepo.GetById(1L); } [Test] public void Can_update_existing_product() { this.Can_add_new_product(); IProduct product=this.getProduct(); product.Name="Semangka"; IProductRepository productRepo=new ProductRepository(_hibernateHelper); productRepo.Update(product); IProduct reloadProduct=this.getProduct(); Assert.AreEqual("Semangka",reloadProduct.Name); } [Test] public void Can_remove_existing_product() { this.Can_add_new_product(); IProduct product=this.getProduct(); IProductRepository productRepo=new ProductRepository(_hibernateHelper); productRepo.Remove(product); var fromDb=this.getProduct(); Assert.IsNull(fromDb); } [Test] public void Can_get_existing_product_by_name() { this.Can_add_new_product(); IProductRepository productRepo= new ProductRepository(_hibernateHelper); IProduct product=productRepo.GetByName("Apple"); Assert.AreEqual("Apple",product.Name); } [Test] public void Can_get_existing_products_by_category() { this.addProducts(); IProductRepository productRepo= new ProductRepository(_hibernateHelper); ICollection products = productRepo.GetByCategory("Fruits"); Assert.AreEqual(2,products.Count); } private void addProducts() { var apple= new Product {Name = "Apple", Category = "Fruits"}; var durian=new Product {Name="Durian", Category ="Fruits"}; var tomato=new Product {Name= "Tomato", Category = "Vegetable"}; IProductRepository productRepo= new ProductRepository(_hibernateHelper); productRepo.Add(apple); productRepo.Add(durian); productRepo.Add(tomato); } }
Class ProductRepository
public class ProductRepository:IProductRepository { private INHibernateHelper _nhHelper; public ProductRepository(INHibernateHelper nhHelper) { this._nhHelper =nhHelper; } public void Add(IProduct product){ using (ISession session = _nhHelper.OpenSession()) using (ITransaction transaction = session.BeginTransaction()) { session.Save(product); transaction.Commit(); } } public IProduct GetById(long id){ using (ISession session = _nhHelper.OpenSession()) return session.Get(1L); } public void Update(IProduct product){ using (ISession session = _nhHelper.OpenSession()) using (ITransaction transaction = session.BeginTransaction()) { session.Update(product); transaction.Commit(); } } public void Remove(IProduct product){ using (ISession session = _nhHelper.OpenSession()) using (ITransaction transaction = session.BeginTransaction()) { session.Delete(product); transaction.Commit(); } } public IProduct GetByName(string name){ using (ISession session=_nhHelper.OpenSession()) { return session.CreateQuery("from Product p where p.Name=:name").SetString("name", "Apple").UniqueResult(); } } public ICollection GetByCategory(string name){ using (ISession session = _nhHelper.OpenSession()) { return session.CreateCriteria(typeof(IProduct)) .Add(Restrictions.Eq("Category", "Fruits")).List(); } } }
Dan interface
public interface IProductRepository { void Add(IProduct product); IProduct GetById(long id); IProduct GetByName(string name); void Update(IProduct product); void Remove(IProduct product); ICollection GetByCategory(string name); }
What Next
Cara diatas sudah saya pakai beberapa kali untuk production, tetapi masih skala kecil. Ketika software skalanya membesar, kita harus berhati-hati menghandle Session Factory. Karena itu daripada menggunakan helper seperti diatas saya lebih memilih menggunakan Spring.Net yang didalamnya terdapat HibernateTemplate support–yang pada dasarnya sama dengan helper yang kita buat, tetapi dengan fasilitas yang lebih lengkap.
Berikutnya kita akan belajar mengenai collection, proxy dan lazy loading.
Mengkoneksikan domain dengan database menggunakan NHibernate.
First Application Using NHibernate
Pada bagian sebelumnya saya telah menerangkan bagaimana membuat automatisasi build dan test sekaligus untuk mendukung TDD (Test Driven Development). Sekarang kita akan belajar bagaimana membuat domain/model sederhana, kemudian kita lempar ke NHibernate yang kemudian melemparnya ke database. Kita juga akan belajar bagaimana memanggil kembali model yang telah tersimpan dalam database melalui NHibernate. Demikian juga bagaimana mengupdate, menghapus dan yang terakhir mengquery data.
Membuat Domain/Model
Mari kita buat model yang sederhana saja, katakanlah bernama Product. Pada umumnya product mempunyai attribute: Name, Category dan Discontinued. Anda bisa juga baca di Hibernating Rhinos.
Karena kita menjalankan TDD maka kita harus memulainya dari test. Buat sebuah project. Masukkan ke dalam folder test. Buat class test ProductFixture:
using NUnit.Framework; namespace fatur.sample.NHibernateHelloWorldTest { [TestFixture] public class ProductFixture { [Test] public void CreateProductTest() { IProduct apel=new Product{Name = "Apple", Category = "Fruits"}; Assert.AreEqual("Apple",apel.Name); Assert.AreEqual("Fruits",apel.Category); Assert.IsFalse(apel.Discontinued); } } }
Untuk memenuhi test diatas, kita harus membuat sebuah interface IProduct dan sebuah class Product yang mengimplementasikan IProduct. Buat sebuah file kosong dalam folder app, bernama IProduct.cs
using System; namespace fatur.sample.NHibernateHelloWorld { public interface IProduct { long Id { get;set; } string Name { get; set; } string Category { get; set; } bool Discontinued { get; set; } } }
Dan sebuah file untuk class Product, bernama Product.cs
using System; namespace fatur.sample.NHibernateHelloWorld { public class Product:IProduct { public Product(){} public long Id { get; set; } public string Name { get; set; } public string Category { get; set; } public bool Discontinued { get; set; } } }
Jalankan test dengan memanggil NAnt. Ini adalah implementasi yang paling sederhana
Menggunakan NHibernate
Model sudah kita buat. Sebelum melakukan perintah memasukkan model ke dalam database, kita harus memastikan terlebih dahulu apakah NHibernate bisa dibangunkan dan apakah nHibernate bisa terhubung dengan database.
Configurasi NHibernate
Untuk itu kita akan melakukan test konfigurasi. Test ini bertujuan untuk menguji apakah semua library yang dibutuhkan sudah tersedia, dan apakah semua konfigurasi yang ada di file hibernate.cfg.xml bisa dieksekusi:
[Test] public void NHibernateConfiguration() { NHibernate.Cfg.Configuration cfg = new Configuration(); cfg.Configure(); }
Untuk memenuhi test diatas, buat file hibernate.cfg.xml di folder test. Lalu kopikan xml berikut ini kedalamnya:
<?xml version="1.0" encoding="utf-8" ?> <hibernate-configuration xmlns="urn:nhibernate-configuration-2.2"> <session-factory> <property name="connection.provider">NHibernate.Connection.DriverConnectionProvider</property> <property name="dialect">NHibernate.Dialect.PostgreSQLDialect</property> <property name="connection.driver_class">NHibernate.Driver.NpgsqlDriver</property> <property name="connection.connection_string">Server=localhost;database=dbtest;User ID=postgres;Password=123456;</property> <property name="show_sql">true</property> </session-factory> </hibernate-configuration>
Konfigurasi diatas mengatakan, kita bermaksud melakukan koneksi ke database Postgresql dengan driver npsql.
“show_sql” diset true dimaksudkan agar semua SQL statement yang dikirim ke
Postgres dinampakkan di console test.
Jalankan test. Patikan tidak ada error.
Membuat Mapping
Langkah selanjutnya adalah membuat mapping Product. File mapping dibuat dengan format xml. NHibernate mengenali file mapping berdasarkan nama:ada extension hbm didalam nama itu. Jadi formatnya ***.hbm.xml. Untuk mapping product, saya akan memberi nama Product.hbm.xml.
Sebelum membuat mapping kita akan buat test terlebih dahulu. Dalam test ini, kita memasukkan assembly dimana class product berada dan file mapping (yang nanti kita buat) kedalam konfigurasi nhibernate. Setelah itu kita perintahkan nhibernate untuk mengirim file schema ke postgres agar dibuat tabel dan kawan-kawannya.
[Test] public void Can_generate_schema() { NHibernate.Cfg.Configuration cfg = new Configuration(); cfg.Configure(); cfg.AddAssembly(typeof (Product).Assembly); cfg.AddXmlFile("Product.hbm.xml"); new SchemaExport(cfg).Execute(false, true, false, false); }
Agar test ini sukses kita harus membuat mapping. Buat folder mapping didalam project app. Letakkan didalamnya sebuah file dan berinama hibernate.cfg.xml. Lalu, masukkan xml dibawah ini:
<?xml version="1.0" encoding="utf-8" ?> <hibernate-mapping xmlns="urn:nhibernate-mapping-2.2" assembly="fatur.sample.NHibernateHelloWorld" namespace="fatur.sample.NHibernateHelloWorld"> <class name="Product" proxy="IProduct"> <id name="Id"> <generator class="sequence"> <param name="sequence">productid_sequence</param> </generator> </id> <property name="Name" /> <property name="Category" /> <property name="Discontinued" /> </class> </hibernate-mapping>
Attribute assembly dan namespace masing-masing mengacu pada nama assembly dari model dan nama namespace dimana class Product berada. Gunanya, agar ketika kita menyebutkan Product didalam mapping tidak perlu menyebutkan secara lengkap (namanamespace.Product, namaassembly), tapi cukup dengan Product. Ini sama dengan penggunaan using dalam c#.
Sub element berikutnya “class”, didalam element ini kita sebutkan class apa yang akan dimapping. Attribute proxy, ini menarik, tapi tidak akan saya bahas detail disini. By default, NHibernate men-set class mamping sebagai lazy=true. Dengan setting seperti ini, object baru benar-benar dipanggil dari database ketika benar-benar diperlukan. Sebelum diperlukan NHibernate hanya akan menyediakan proxy. Agar sebuah class bisa dibuat proxy, NHibernate menuntut sebuah class turunan dari sebuah interface atau attribute-attribute class itu dibuat virtual. Disini saya lebih memilih menggunakan interface. Ini kaitannya erat sekali dengan code to interface not to implementation.
Sub element dari class ada id, property dan masih banyak lagi. Dalam contoh ini saya hanya mengunakan id dan propety. ID adalah primary key dalam database. Selain itu, saya biasa menggunakannya sebagai default identity dari object, kecuali kita mendefinisikan busines key untuk product dengan cara yang berbeda, misalnya dengan composite key. Dalam mapping kita ini, saya menggunakan autonumber dari postgres. Jadi saya menggunakan generator class sequence dengan parameter sequence yang didefinisikan didalam database. Kita tidak perlu membuat tabel dan squence secara manual. Jika test ini sukses, berarti ExportSchema dari nhibernate telah berhasil mengirimkan perintah-perintah untuk membuat tabel dan kawan-kawannya di database.
Jalankan test. Patikan tabel dan squence tercreate dalam database.
What Next
Sampai disini saya harapkan anda bisa memahami bagaimana mensetup konfigurasi dan mapping NHibernate. Untuk mapping lebih jauh kita akan bahas dilain kesempatan. Berikutnya kita akan mencoba melakukan operasi create, read, update, delete (CRUD).
CRUD Operation menggunakan NHibernate
Pada tulisan saya sebelumnya, Mengkonfigurasi NHibernate, saya telah paparkan bagaimana mengkonfigurasi NHibernate dan sedikit mapping. Tulisan ini merupakan lanjutan dari tulisan sebelumnya.
Konfigurasi Test Repository
Mengacu pada Domain Driven Design (DDD), Repository adalah entry point untuk mengakses data. Lewat repository-lah data dimasukkan, dipanggil kembali, diupdate dan dihapus.
Karena test kita kali ini menyentuh inftastuktur (database), kita harus membuat sedemikian hingga diawal dan diakhir test kondisi infrastruktur tidak boleh berubah. Sehingga test satu tidak mempengaruhi test yang lainnya.
Pada saat dijalankan kondisi database adalah hanya berisi tabel product yang kosong. Dalam contoh ini, saya hanya akan memenuhi kondisi: diawal setiap setiap test tersedia tabel product yang kosong. Karena itu saya membuat siklus test seperti berikut ini:
[TestFixture] public class ProductRepositoryFixture { private ISessionFactory _sessionFactory; private Configuration _configuration; [TestFixtureSetUp] public void TestFixtureSetUp() { _configuration = new Configuration(); _configuration.Configure(); _configuration.AddAssembly(typeof (Product).Assembly); _configuration.AddXmlFile("Product.hbm.xml"); _sessionFactory = _configuration.BuildSessionFactory(); } [SetUp] public void SetupContext() { new SchemaExport(_configuration).Execute(false, true,false, false); } }
Jadi buatlah file test baru, ProductRepositoryFixture.cs, dalam project test anda. SessionFactory adalah object yang sangat berat inisiasinya, karena disitulah semua mapping diterjemahkan dan disimpan. Jadi ide-nya harus singleton, dibuat sekali dan dipakai bersama-sama oleh keseluruhan test. Atribute [TestFixtureSetup] memastikan bahwa method TestFixtureSetup hanya dijalankan sekali, yaitu pada saat object dibuat.
Kemudian, karena kita mengharapkan setiap kali test hendak dijalankan tabel product adalah baru dibuat, maka kita gunakan attribute [SetUp]. Attribute ini akan memastikan kalau method SetupContext dijalankan setiap kali test akan dijalankan. ExportSchema akan menghapus tabel product dan membuat tabel yang baru.
Insert Product
Untuk membuat dan memasukkan product baru ke dalam database, kita buat test berikut ini:
[Test] public void Can_add_new_product() { var product= new Product {Name = "Apple", Category = "Fruits"}; using (ISession session = _sessionFactory.OpenSession()) using (ITransaction transaction = session.BeginTransaction()) { session.Save(product); transaction.Commit(); } }
Jadi, kita buat sesi baru dengan memanggil method OpenSession() dari
SessionFactory. Dari session yang telah kita dapatkan itu kita buat
transaction dengan memanggil BeginTransaction() dari Session.
Untuk menyimpan Product cukup memanggil method Save() dari session.
Untuk menutup transaksi cukup panggil method Commit() dari object
transaction. Kita akan bahas lebih khusus nanti menangani kebijakan
bagaimana menggunakan session dan transaction.
Using kita gunakan untuk memastikan session dan transaction ditutup setelah digunakan.
Jika tidak ada pesan kesalahan, berarti test ini sukses.
Memanggil kembali Product
Berikut ini adalah test untuk mengambil product dari database:
[Test] public void Can_get_product() { IProduct product; this.Can_add_new_product(); product=getProduct(); Assert.AreEqual("Apple",product.Name); } private IProduct getProduct() { using (ISession session = _sessionFactory.OpenSession()) return session.Get<Product>(1L); }
Untuk mengambil data, kita harus membuat session. Product saya ambil
dengan id 1L. Ingat, id kita serahkan ke database untuk dibuat dengan sequence.
Jadi 1L adalah nilai yang pasti untuk product key.
Jika tidak ada pesan kesalahan, product yang keluar harus bernama “Apple”.
Update Product
Untuk melakukan test update product, kita harus panggil product dari database, kita update property-nya, kemudian kita simpan kembali.
[Test] public void Can_update_existing_produc() { this.Can_add_new_product(); IProduct product=this.getProduct(); using (ISession session = sessionFactory.OpenSession()) using (ITransaction transaction= session.BeginTransaction()) { product.Name="Semangka"; session.Update(product); transaction.Commit(); } IProduct reloadProduct=this.getProduct(); Assert.AreEqual("Semangka",reloadProduct.Name); }
Product kita panggil kembali, kemudian kita ganti namanya dari “Apple” menjadi “Semangka”.
Menghapus Product
Untuk menghapus Product, kita panggil product kemudian kita perintahkan nhibernate untuk menghapus melalui session.
[Test] public void Can_remove_existing_product() { this.Can_add_new_product(); IProduct product=this.getProduct(); using (ISession session = _sessionFactory.OpenSession()) using (ITransaction transaction = session.BeginTransaction()) { session.Delete(product); transaction.Commit(); } var fromDb=this.getProduct(); Assert.IsNull(fromDb); }
Memanggil Product dengan HQL
Berikut ini adalah cara bagaimana memanggil product berdasarkan nama. Disini kita menggunkan HQL (hibernate query language). Kita bahas lebih khusus nanti dibagian advance query.
[Test] public void Can_get_existing_product_by_name( { this.Can_add_new_product(); IProduct product; using (ISession session=_sessionFactory.OpenSession()) { product = session.CreateQuery("from Product p where p.Name=:name").SetString("name","Apple").UniqueResult<Product>(); } Assert.AreEqual("Apple",product.Name); }
Parameter selalu ditandai dengan prefix “:”.
Memanggil Product dengan Creteria
Selain dengan HQL, kita juga bisa membuat query dengan kriteria:
[Test] public void Can_get_existing_product_by_criteria() { this.Can_add_new_product(); IProduct product; using (ISession session=_sessionFactory.OpenSession()) { product = session.CreateCriteria(typeof(Pro</span>duct)).Add(Restrictions.Eq("Name</span>", "Apple")).UniqueResult<Product>(); } Assert.AreEqual("Apple",product.Name); }
Jangan lupa untuk me-use: NHibernate.Criterion.
Memanggil List Product Berdasarkan Kategori
Test-test sebelumnya kita memanggil object tunggal. Bagaimana cara memanggil product dalam bentuk list? Pertama-tama kita harus tambahkan ke dalam database beberapa product. Kemudian kita panggil product-product itu dengan kriteria:
[Test] public void Can_get_existing_products_by_category() { this.addProducts(); ICollection<Product> products; using (ISession session =_sessionFactory.OpenSession()) { products = session.CreateCriteria(typeof(Product).Add(Restrictions.Eq("Cate</span>gory", "Fruits")).List<Product>(); } Assert.AreEqual(2,products.Count); } private void addProducts() { var apple= new Product {Name = "Apple", Category = "Fruits"}; var durian=new Product {Name="Durian", Category ="Fruits"}; var tomato=new Product {Name= "Tomato", Category = Vegetable"}; using (ISession session =_sessionFactory.OpenSession()) using (ITransaction transaction =session.BeginTransaction()) { session.Save(apple); session.Save(durian); session.Save(tomato); transaction.Commit(); } }
What Next
Kita telah melakukan test CRUD. Berikutnya kita akan pindahkan repository dalam assembly khusus. Kita buat abstraksi disana. Great Refactoring!
NHibernate Project.Build a sample
NHibernate Project.Build a sample
<?xml version="1.0"?>
<project name="Sampel.NHibernate" default="run-tests">
<property name="build.dir" value="build" />
<property name="asm.test.name" value="fatur.sample.NHibernateHelloWorldTest" />
<property name="src.test.dir" value="src/test"/>
<property name="build.debug" value="true"/>
<property name="lib.dir" value="lib"/>
<property name="asm.app.name" value="fatur.sample.NHibernateHelloWorld" />
<property name="src.app.dir" value="src/app"/>
<property name="nhibernate.lib.dir" value="tools/nhibernate/bin/net-2.0"/>
<property name="npsql.lib.dir" value="tools/postgres"/>
<fileset id="app.sources" failonempty="true">
<include name="${src.app.dir}/*/*.cs" />
</fileset>
<fileset id="test.sources" failonempty="true">
<include name="${src.test.dir}/*/*.cs" />
</fileset>
<assemblyfileset basedir="${build.dir}" id="test.references">
<include name="System.dll" />
<include name="nunit.framework.dll"/>
<include name="NMock2.dll"/>
<include name="${asm.app.name}.dll"/>
<include name="NHibernate.dll"/>
</assemblyfileset>
<assemblyfileset basedir="${build.dir}" id="app.references">
<include name="System.dll" />
</assemblyfileset>
<target name="build-test" depends="build-app, copy-asm, copy-nhibernate-lib" description="Compile test sources into library">
<csc target="library" debug="${build.debug}" output="${build.dir}/${asm.test.name}.dll">
<sources refid="test.sources"/>
<references refid="test.references" />
</csc>
</target>
<target name="build-app" description="Compile application sources into library">
<csc target="library" debug="${build.debug}" output="${build.dir}/${asm.app.name}.dll">
<sources refid="app.sources"/>
<references refid="app.references" />
</csc>
</target>
<target name="run-tests" depends="build-test, copy-hbm-file, copy-nhibernate-config, copy-npgsql-lib" description="Run NUnit tests">
<nunit2>
<formatter type="Xml" usefile="true" extension=".xml" outputdir="${build.dir}"/>
<test assemblyname="${build.dir}/${asm.test.name}.dll"/>
</nunit2>
</target>
<target name="copy-asm" description="Copy library from lib to build for references">
<copy todir="${build.dir}">
<fileset basedir="${lib.dir}">
<include name="nunit.framework.dll" />
<include name="NMock2.dll" />
</fileset>
</copy>
</target>
<target name="copy-nhibernate-lib" description="Copy NHibernate library from lib to build for references">
<copy todir="${build.dir}">
<fileset basedir="${nhibernate.lib.dir}">
<include name="*.dll" />
</fileset>
</copy>
</target>
<target name="copy-nhibernate-config" description="Copy Nhibernate config to build for nunit run test">
<copy todir="${build.dir}">
<fileset basedir="${src.test.dir}/fatur.sample.NHibernateHelloWorldTest">
<include name="hibernate.cfg.xml" />
</fileset>
</copy>
</target>
<target name="copy-hbm-file" description="Copy hbm file to build for mapping run test">
<copy todir="${build.dir}">
<fileset basedir="${src.app.dir}/fatur.sample.NHibernateHelloWorld/mapping">
<include name="Product.hbm.xml" />
</fileset>
</copy>
</target>
<target name="copy-npgsql-lib" description="Copy npgsql lib to build for db connection run test">
<copy todir="${build.dir}">
<fileset basedir="${npsql.lib.dir}">
<include name="*.dll" />
</fileset>
</copy>
</target>
</project>
Bagaimana Membuat Automatisasi build dan Run Test dengan NAnt.
Dalam post sebelumnya saya telah menerangkan bagaimana mempersiapakan lingkungan NHibernate. Disini saya akan menerangkan bagaimana file build dengan NAnt.
Apa itu NAnt? Saya tidak akan menerangkan lebih jauh, singkatnya atau salah satu kegunaanya adalah untuk mengkompile program .Net. Selain mengkompile kita juga akan menggunakan NAnt untuk menjalankan test.
Apa yang harus dipersiapkan? Karena Operating System kita UBuntu, gunakan Synaptic Package Manager untuk instalasinya, jika belum terinstall. Untuk memastikan sudah terintall atau belum, Ketik NAnt di terminal, dan anda akan mendapat pesan berikut:
fatur@fatur-laptop:~$ nant
NAnt 0.85 (Build 0.85.2478.0; release; 10/14/2006)
Copyright (C) 2001-2006 Gerry Shaw
http://nant.sourceforge.net
BUILD FAILED
Could not find a ‘*.build’ file in ‘/home/fatur’
For more information regarding the cause of the build failure, run the build again in debug mode.
Try ‘nant -help’ for more information
Jika pesan diatas sudah keluar, itu artinya NAnt sudah terinstall.
File Build
Dalam post sebelumnya saya telah terangkan struktur file dan folder dari project. Didalam folder project terdapat file project.build. Apa isi file itu?
File build berformat xml. Ujung paling atas dari xml ini adalah tag project:
&lt;project name="Sampel.NHibernate" default="run-tests"&gt; &lt;/project&gt;Tag ini menyatakan, project bernama “Sampel.NHibernate”. Default target yang akan dipanggil NAnt bernama “run-tests”.
Property Project
Didalam tag project terdapat tag property yang berisi variabel-variabel project:
&lt;property name="build.dir" value="build" /&gt; &lt;property name="asm.test.name" value="fatur.sample.NHibernateHelloWorldTest" /&gt; &lt;property name="src.test.dir" value="src/test"/&gt; &lt;property name="build.debug" value="true"/&gt; &lt;property name="lib.dir" value="lib"/&gt; &lt;property name="asm.app.name" value="fatur.sample.NHibernateHelloWorld" /&gt; &lt;property name="src.app.dir" value="src/app"/&gt; &lt;property name="nhibernate.lib.dir" value="tools/nhibernate/bin/net-2.0"/&gt; &lt;property name="npsql.lib.dir" value="tools/postgres"/&gt;Property build.dir, src.test.dir, lib.dir, src.app.dir, nhibernate.lib.dir, npsql.lib.dir masing-masing berisi variabel path dari sebuah folder. Root dari folder-folder ini adalah folder project.
Property asm.test.name menyatakan nama assembly dari test. Begitu juga dengan property asm.app.name menyatakan nama assembly dari applikasi/library.
Property build.debug menyatakan apakah status dari project ini. Property ini akan dipakai NAnt untuk memberitahu kompiler mengenai perlu tidaknya membuat file debug.
Application Build Target
Pertama-tama yang harus dilakukan oleh NAnt adalah mengkompile aplikasi. Berikut ini adalah tag target untuk kompilasi applikasi:
&lt;target name="build-app" description="Compile application sources into library"&gt; &lt;csc target="library" debug="${build.debug}" output="${build.dir}/${asm.app.name}.dll"&gt; &lt;sources refid="app.sources"/&gt; &lt;references refid="app.references" /&gt; &lt;/csc&gt; &lt;/target&gt;Target bernama build-app. Target ini memanggil tag csc. NAnt akan mengarahkan kompilasi ke mono. Target dari kompilasi ini adalah library. Tag source menyatakan lokasi file source code, disini mengarah ke fileset app.source:
&lt;fileset id="app.sources" failonempty="true"&gt; &lt;include name="${src.app.dir}/*/*.cs" /&gt; &lt;/fileset&gt;Fileset menyatakan, “semua file yang berekstensi cs dalam source directory dan sub directory didalamnya”.
Sedangkan tag references menyatakan lokasi file reference library dari luar applikasi, disini mengarah ke fileset app.references:
&lt;assemblyfileset basedir="${build.dir}" id="app.references"&gt; &lt;include name="System.dll" /&gt; &lt;/assemblyfileset&gt;Kebetulan kita hanya merefensi System.dll.
Untuk menjalankan target ini, cukup ketikkan NAnt [nama target]:
fatur@fatur-laptop:/media/Data/Fatur.Data/workspace/project/NHibernate$ nant build-app
NAnt 0.85 (Build 0.85.2478.0; release; 10/14/2006)
Copyright (C) 2001-2006 Gerry Shaw
http://nant.sourceforge.netBuildfile: file:///media/Data/Fatur.Data/workspace/project/NHibernate/project.buid
Target framework: Mono 2.0 Profile
Target(s) specified: build-appbuild-app:
BUILD SUCCEEDED
Total time: 0.2 seconds.
Jangan lupa untuk mounting terlebih dahulu ke folder project. Jika source code belum ada buat sebuah file berekstensi cs dan letakkan dalam folder source.
Test Build Target
Target untuk buil source code test hampir sama dengan target build untuk applikasi. Bedanya terletak pada dependency library, baik terhadap tools (nunit dan nhibernate) juta terhadap library yang akan kita test.
&lt;target name="build-test" depends="build-app, copy-asm, copy-nhibernate-lib" description="Compile test sources into library"&gt; &lt;csc target="library" debug="${build.debug}" output="${build.dir}/${asm.test.name}.dll"&gt; &lt;sources refid="test.sources"/&gt; &lt;references refid="test.references" /&gt; &lt;/csc&gt; &lt;/target&gt;Attribute depends menyatakan kalau target ini tergantung pada beberapa target lain. Target-target lain ini harus di jalankan terlebih dahulu. Jadi, build.app akan dijalankan, kemudian copy-asm dan copy-nhibernate-lib. Berikut ini adalah target masing-masing copy:
&lt;target name="copy-asm" description="Copy library from lib to build for references"&gt; &lt;copy todir="${build.dir}"&gt; &lt;fileset basedir="${lib.dir}"&gt; &lt;include name="nunit.framework.dll" /&gt; &lt;include name="NMock2.dll" /&gt; &lt;/fileset&gt; &lt;/copy&gt; &lt;/target&gt;Target diatas mengkopi nunit.framework dan NMock dari lib ke build.
&lt;target name="copy-nhibernate-lib" description="Copy NHibernate library from lib to build for references"&gt; &lt;copy todir="${build.dir}"&gt; &lt;fileset basedir="${nhibernate.lib.dir}"&gt; &lt;include name="*.dll" /&gt; &lt;/fileset&gt; &lt;/copy&gt; &lt;/target&gt;Target diatas mengkopi semua library yang ada didalam folder nhibernate ke dalam folder build.
Sementara tag source dan reference masing-masing bisa dilihat dibawah ini:&lt;fileset id="test.sources" failonempty="true"&gt; &lt;include name="${src.test.dir}/*/*.cs" /&gt; &lt;/fileset&gt; &lt;assemblyfileset basedir="${build.dir}" id="test.references"&gt; &lt;include name="System.dll" /&gt; &lt;include name="nunit.framework.dll"/&gt; &lt;include name="NMock2.dll"/&gt; &lt;include name="${asm.app.name}.dll"/&gt; &lt;include name="NHibernate.dll"/&gt; &lt;/assemblyfileset&gt;Cukup jelas.
Untuk menjalankan target ini ketikkan NAnt build-test:
fatur@fatur-laptop:/media/Data/Fatur.Data/workspace/project/NHibernate$ nant build-test
NAnt 0.85 (Build 0.85.2478.0; release; 10/14/2006)
Copyright (C) 2001-2006 Gerry Shaw
http://nant.sourceforge.netBuildfile: file:///media/Data/Fatur.Data/workspace/project/NHibernate/project.build
Target framework: Mono 2.0 Profile
Target(s) specified: build-testbuild-app:
copy-asm:
copy-nhibernate-lib:
build-test:
BUILD SUCCEEDED
Total time: 0.2 seconds.
Running Test
Untuk menjalankan test tidaklan susah, cukup sediakan semua dependency, library test, library yang hendak ditest dan tentu saja NUnit. Target untuk running test bisa anda lihat dibawah ini:
&lt;target name="run-tests"depends="build-test, copy-hbm-file, copy-nhibernate-config, copy-npgsql-lib" description="Run NUnit tests"&gt; &lt;nunit2&gt; &lt;formatter type="Xml" usefile="true" extension=".xml" outputdir="${build.dir}"/&gt; &lt;test assemblyname="${build.dir}/${asm.test.name}.dll"/&gt; &lt;/nunit2&gt; &lt;/target&gt;Kita perlu mengkopi file-file hbm (kita akan bahas dipost berikutnya), file configurasi nhibernate, dan driver postgres untuk nhibernate, npsql. Masing-masing target bisa anda lihat dibawah ini:
&lt;target name="copy-hbm-file" description="Copy hbm file to build for mapping run test"&gt; &lt;copy todir="${build.dir}"&gt; &lt;fileset basedir="${src.app.dir}/fatur.sample.NHibernateHelloWorld/mapping"&gt; &lt;include name="Product.hbm.xml" /&gt; &lt;/fileset&gt; &lt;/copy&gt; &lt;/target&gt;Hbm yang kita kopi adalah hbm yang berada didalam source code applikasi.
&lt;target name="copy-nhibernate-config" description="Copy Nhibernate config to build for nunit run test"&gt; &lt;copy todir="${build.dir}"&gt; &lt;fileset basedir="${src.test.dir}/fatur.sample.NHibernateHelloWorldTest"&gt; &lt;include name="hibernate.cfg.xml" /&gt; &lt;/fileset&gt; &lt;/copy&gt; &lt;/target&gt;File konfigurasi nhibernate bernama hibernate.cfg.xml, sementara ini saya letakkan didalam sourcecode test. Jika project banyak, file ini bisa diletakkan didalam folder khusus, katakanlah “config” dibawah folder project langsung atau sejajar dengan folder src.
&lt;target name="copy-npgsql-lib" description="Copy npgsql lib to build for db connection run test"&gt; &lt;copy todir="${build.dir}"&gt; &lt;fileset basedir="${npsql.lib.dir}"&gt; &lt;include name="*.dll" /&gt; &lt;/fileset&gt; &lt;/copy&gt; &lt;/target&gt;Target ini mengkopi driver npsql.
Untuk menjalankan target ini, cukup ketikkan NAnt, nant akan menjalankan test karena test adalah default target.
What Next
Source lengkap dari file ini dapat anda lihat di nhibernate project.build. Berikutnya kita akan membahas bagaimana mengkoneksikan domain dengan database menggunakan NHibernate.
Prepare your system for NHibernate
Dalam post ini saya akan coba menerangkan bagaimana mempersiapkan lingkungan mesin(komputer) developer(programmer) agar bisa menggunakan NHibernate sebagai alat ORM (object relational mapping).
Apa yang harus dipersiapkan?
(yang jelas komputernya harus ada he he he)
Pertama, NHibernate
Downlaod Nhibernate 2.0.1.GA. NHibernate adalah produk open source, jadi tidak diperlukan lisensi berbayar alias free.
Kedua, Operating System
Saya menggunakan UBuntu 8.10 (linux). By default, di dalam UBuntu sudah terinstall mono. Namun ada beberapa yang harus kita install sendiri. Gunakan synaptic package manager untuk menginstall NAnt, dan NUnit. Untuk IDE bisa menggunakan monodevelop. Semua paket ini free untuk digunakan.
Pengaturan file-file project
Saya mengatur file dan folder project saya seperti terlihat di gambar bawah ini

Structure-Folder
Structure ini terletak didalam sebuah folder project. Folder Build digunakan untuk menaruh file hasil kompilasi dan juga seluruh dependency. Folder Lib digunakan untuk menyimpan semua library yang digunakan. Folder src adalah folder untuk source code. Didalam folder src terdapat dua folder “app” dan “test”. Folder app berisi source code applikasi. Folder test berisi source code untuk test (unit test, component test, stres test, dll). Folder tools, berisikan software-software pendukung. NHibernate kita letakkan didalam folder ini.
File project.build berisi target-target yang nantinya akan dieksekusi NAnt. Target bisa berupa kompilasi, running program, eksekusi test, integrasi test dan lain-lain. Didalam folder ini juga berisi file-file solusi dari IDE (disini saya menggunakan monodevelop, jadi yang muncul file mds).
Database
Saya menggunakan database PostgreSql. Sekali lagi gunakan Synaptic Package Manager untuk menginstallnya.
Setelah terinstall kita harus mereset pasword yang sudah ada dengan command berikut:
$ sudo su postgres -c psql template1
template1=# ALTER USER postgres WITH PASSWORD ‘password’;
template1=# \q
Perintah-perintah diatas digunakan untuk merubah password dari user postgres didalam database. Kita juga perlu merubah password postgres untuk linux. Gunakan perintah berikut:
$ sudo passwd -d postgres
$ sudo su postgres -c passwd
Posgres Driver
Untuk menghubungkan NHibernate dengan Postgres Database kita memerlukan driver. Anda bisa mendownload npsql, driver .Net untuk postgres yang telah dibuat komunitas, secara free. Letakkan driver tersebut di folder tools/postgres.
What Next
Sampai disini kita telah mempersiapkan sistem untuk menggunakan NHibernate. Berikutnya saya akan jelaskan bagaimana membuat automatisasi build dan run test dengan NAnt.