Panduan Mapping Inheritance


Pada perancangan object-oriented, terdapat hubungan yang disebut sebagai inheritance.  Permasalahannya adalah database relasional pada dasarnya tidak mendukung inheritance sehingga perlu di-’akalin’ oleh JPA.  Sebagai contoh, saya akan mengubah class Transaksi yang telah dibuat di Panduan Mapping Asosiasi JPA sehingga menyertakan inheritance seperti yang terlihat pada gambar berikut ini:

Contoh class diagram dengan inheritance

Contoh class diagram dengan inheritance

Pada perancangan di class diagram di atas, sebuah Transaksi hanya boleh memiliki satu Pembayaran (dan juga sebaliknya; relasi one-to-one).  Tetapi Pembayaran adalah sebuah class abstract yang tidak dapat dipakai secara langsung.  Developer wajib memakai salah satu turunannya, yaitu Tunai, KartuKredit atau Voucher.  Dengan demikian, bila terdapat sebuah metode pembayaran baru, yang perlu dilakukan developer adalah membuat turunan dari class Pembayaran.

Bagaimana cara menyimpan object dari class tersebut ke dalam database nantinya?  JPA mendukung beberapa strategi untuk menyimpan inheritance, misalnya TABLE_PER_CLASS, SINGLE_TABLE, dan JOINED.

Seorang yang pernah belajar perancangan tabel yang baik pasti akan memilih TABLE_PER_CLASS dimana setiap turunan disimpan dalam tabel-nya masing-masing.  Tapi ini sebenarnya adalah pilihan yang tidak disarankan!  Alasannya adalah setiap kali ingin mengambil instance turunan, harus melakukan operasi join antar tabel super-class dan subclass.  Semakin dalam tingkat inheritance, semakin banyak join yang terjadi.  Selain itu, strategi ini tidak memungkinkan penggunaan id yang di-generate otomatis.  Hal ini karena untuk menghubungkan antar tabel superclass dan tabel subclass dibutuhkan id yang sama, sementara itu bila id di-generate secara otomatis di masing-masing tabel, belum tentu bisa sama.

Oleh sebab itu, saya akan memakai strategi SINGLE_TABLE.  Tabel yang dihasilkan akan terlihat denormalisasi (berlawanan dengan normalisasi) karena seluruh subclass akan disimpan dalam sebuah tabel yang sama.  Mereka dapat dibedakan berdasarkan sebuah nilai discriminator.  Walaupun tabel yang dihasilkan adalah tabel denormalisasi, proses query untuk mendapatkan sebuah object menjadi cepat (tidak perlu join antar-tabel) dan id otomatis tetap bisa dipakai.

Berikut ini adalah contoh mapping yang mungkin:

@DomainModel
@Entity
@TupleConstructor
@ToString
class Transaksi {

    @Size(min=5, max=5)
    @Column(unique=true)
    String kodeTransaksi

    @OneToMany(cascade=CascadeType.ALL)
    @JoinColumn
    @OrderColumn
    List<ItemTransaksi> listItemTransaksi = []

    @OneToOne(cascade=CascadeType.ALL)
    Pembayaran pembayaran

    void tambahTransaksi(ItemTransaksi itemTransaksi) {
        listItemTransaksi << itemTransaksi
    }

    BigDecimal getTotal() {
        listItemTransaksi.sum { ItemTransaksi it -> it.nominalTransaksi }
    }

}

@DomainModel
@Entity
@TupleConstructor
@ToString(excludes = "transaksi")
class ItemTransaksi {

    @Size(min=3, max=50)
    String namaItem

    @NotNull
    BigDecimal nominalTransaksi

}

@DomainModel @Entity
@TupleConstructor @ToString
abstract class Pembayaran {

    @OneToOne(mappedBy="pembayaran")
    Transaksi transaksi

    abstract boolean isLunas()

}

@DomainModel @Entity
@TupleConstructor @ToString
@Inheritance(strategy=InheritanceType.SINGLE_TABLE)
@DiscriminatorValue("Tunai")
class Tunai extends Pembayaran {

    @Override
    boolean isLunas() {
        true
    }

}

@DomainModel @Entity
@TupleConstructor @ToString
@Inheritance(strategy=InheritanceType.SINGLE_TABLE)
@DiscriminatorValue("KartuKredit")
class KartuKredit extends Pembayaran {

    @Size(min=15, max=15)
    String nomorKartuKredit

    @Size(min=3, max=100)
    String nama

    String status

    @Override
    boolean isLunas() {
        // hubungi gateway bank untuk memeriksa
        false
    }
}

@DomainModel @Entity
@TupleConstructor @ToString
@Inheritance(strategy=InheritanceType.SINGLE_TABLE)
@DiscriminatorValue("Voucher")
class Voucher extends Pembayaran{

    @Size(min=20, max=20)
    String kode

    @NotNull
    Integer nominal

    @Override
    boolean isLunas() {
        true
    }

}

Mapping di atas akan menyimpan seluruh instance class Tunai, kartuKredit, dan Voucher ke dalam satu tabel yaitu tabel Pembayaran, yang isinya terlihat seperti berikut ini:

mysql> desc pembayaran;
+------------------+--------------+------+-----+---------+-------+
| Field            | Type         | Null | Key | Default | Extra |
+------------------+--------------+------+-----+---------+-------+
| DTYPE            | varchar(31)  | NO   |     | NULL    |       |
| id               | bigint(20)   | NO   | PRI | NULL    |       |
| createdDate      | datetime     | YES  |     | NULL    |       |
| deleted          | varchar(255) | YES  |     | NULL    |       |
| modifiedDate     | datetime     | YES  |     | NULL    |       |
| nama             | varchar(100) | YES  |     | NULL    |       |
| nomorKartuKredit | varchar(15)  | YES  |     | NULL    |       |
| status           | varchar(255) | YES  |     | NULL    |       |
| kode             | varchar(20)  | YES  |     | NULL    |       |
| nominal          | int(11)      | YES  |     | NULL    |       |
+------------------+--------------+------+-----+---------+-------+

Berikut ini adalah contoh kode program yang menyimpan dan menbaca instance Pembayaran:

Transaksi transaksi1 = new Transaksi("TR001")
transaksi1.tambahTransaksi(new ItemTransaksi("ITEM1", new BigDecimal("99999.99")))
transaksi1.tambahTransaksi(new ItemTransaksi("ITEM2", new BigDecimal("0.01")))
transaksi1.pembayaran = new Tunai()
transaksi1.pembayaran.transaksi = transaksi1
persist(transaksi1)

Transaksi transaksi2 = new Transaksi("TR002")
transaksi2.tambahTransaksi(new ItemTransaksi("ITEM1", new BigDecimal("99999.99")))
transaksi2.pembayaran = new KartuKredit("378282246319955", "Liquid Snake")
transaksi2.pembayaran.transaksi = transaksi2
persist(transaksi2)

findAllPembayaran().each { Pembayaran pembayaran ->
   println "Pembayaran Untuk Transaksi ${pembayaran.transaksi.kodeTransaksi} adalah ${pembayaran}"
}

Output dari program di atas adalah:

Pembayaran Untuk Transaksi TR001 adalah domain.Tunai()
Pembayaran Untuk Transaksi TR002 adalah domain.KartuKredit(378282246319955, Liquid Snake, null)

Program di atas akan menyimpan data pembayaran ke dalam tabel Pembayaran seperti yang terlihat pada hasil berikut ini:

mysql> select * from pembayaran;
+-------------+----+---------------------+---------+--------------+--------------+------------------+--------+------+---------+
| DTYPE       | id | createdDate         | deleted | modifiedDate | nama         | nomorKartuKredit | status | kode | nominal |
+-------------+----+---------------------+---------+--------------+--------------+------------------+--------+------+---------+
| Tunai       |  1 | 2013-04-17 19:11:35 | N       | NULL         | NULL         | NULL             | NULL   | NULL |    NULL |
| KartuKredit |  2 | 2013-04-17 19:11:35 | N       | NULL         | Liquid Snake | 378282246319955  | NULL   | NULL |    NULL |
+-------------+----+---------------------+---------+--------------+--------------+------------------+--------+------+---------+
About these ads

Perihal Solid Snake
I'm nothing...

Apa komentar Anda?

Please log in using one of these methods to post your comment:

WordPress.com Logo

You are commenting using your WordPress.com account. Logout / Ubah )

Twitter picture

You are commenting using your Twitter account. Logout / Ubah )

Facebook photo

You are commenting using your Facebook account. Logout / Ubah )

Google+ photo

You are commenting using your Google+ account. Logout / Ubah )

Connecting to %s

Ikuti

Get every new post delivered to your Inbox.

Bergabunglah dengan 29 pengikut lainnya.

%d bloggers like this: