Memakai Fitur Auditing di Spring Data JPA


Auditing untuk domain class adalah salah satu fitur yang sering kali dibutuhkan.  Stakeholder perlu mengetahui siapa yang terakhir kali mengubah data sebuah entitas stok.  Mereka juga mungkin ingin tahu kapan sebuah stok dibuat dan dimodifikasi.   Oleh sebab itu saya perlu menambahkan atribut seperti createdBy, createdDate, lastModifiedBy, dan lastModifiedDate pada setiap domain class yang perlu di-audit.  Beruntungnya, jika saya memakai Spring Data JPA, saya tidak perlu menambahkan atribut beserta logic kode program tersebut secara manual.  Spring Data JPA sudah memiliki fitur auditing.  Syaratnya adalah saya harus menambahkan dependency ke artifact spring-aspects.

Dukungan auditing di Spring Data JPA bisa dibilang masih sangat sederhana bila dibandingkan dengan Hibernate Enver yang memiliki tabel riwayat audit.  Tapi kadang-kadang kebutuhan auditing tidak perlu selengkap itu.

Untuk memakai fasilitas auditing di Spring Data JPA, saya perlu mengimplementasikan interface Auditable<U, ID extends Serializable>.  Cara yang lebih cepat untuk memperoleh semua atribut auditing (createdBy, createdDate, lastModifiedBy, dan lastModifiedDate) adalah dengan menurunkan domain class dari class AbstractAuditable<U, PK extends Serializable>.  Sebagai contoh, bila saya ingin menambahkan fitur auditing di domain class Pemesanan,  maka kode program untuk domain class tersebut akan terlihat seperti:

@Entity
@Table(name="pemesanan")
public class Pemesanan extends AbstractAuditable<User, Long> implements Serializable {

  @ElementCollection(fetch=FetchType.EAGER)
  @CollectionTable(joinColumns=@JoinColumn(name="pemesanan_id"))
  private List<ItemPemesanan> listItemPemesanan;

  @Column(name="status")
  @private Status status;

  // ...
  // getter dan setter diabaikan
  // ...
}

Pada class Pemesanan, saya hanya perlu mendefinisikan atribut yang berhubungan dengan pemesanan.  Dengan meng-extends class AbstractAuditable<User, Long>, saya secara otomatis telah memperoleh atribut createdBy, createdDate, lastModifiedBy, dan lastModifiedDate beserta getter dan setter-nya.

Type parameter U di AbstractAuditable untuk class Pemesanan di atas diwakili oleh User, dimana User adalah sebuah domain class buatan saya yang mewakili pengguna yang dapat login ke aplikasi.  Type parameter ini menentukan nilai yang akan dikembalikan oleh getCreatedBy() dan getLastModifiedBy().

Type parameter PK di AbstractAuditable untuk class Pemesanan di atas diwakili oleh Long.   Tipe ini menunjukkan tipe untuk id (primary key) dari domain class yang bersangkutan.   Dengan menurunkan domain class dari AbstractAuditable, saya TIDAK perlu lagi mendefinisikan id secara manual karena domain class secara otomatis akan memilih method getId(), setId(), dan isNew().

Langkah berikutnya saya perlu menambahkan entity listener pada setiap domain class.  Cara no-brainer (ga pakai mikir) adalah dengan menambahkan annotation @EntityListener di setiap domain class yang ada.  Tapi ada cara otomatis yang lebih disarankan.  Saya perlu menambahkan file orm.xml yang akan memberikan definisi entity listener secara global.   Saya akan membuat file orm.xml bila belum ada, dengan men-klik kanan pada  folder src/main/resources/META-INF, memilih menu New, Other, lalu pada dialog yang muncul, saya memilih JPA, JPA ORM Mapping File, seperti yang terlihat pada gambar berikut ini:

Membuat file orm.xml

Membuat file orm.xml

Saya men-klik Next, lalu memastikan bahwa nama file adalah orm.xml.  Bila Eclipse tidak memberikan pilihan Next atau Finish, pastikan bahwa project facet JPA sudah diaktifkan (cara memeriksanya adalah dengan men-klik kanan nama proyek, memilih properties, lalu memilih Project Facets.  Saya boleh menghilangkan tanda centang di JPA setelah file ORM dibuat).  Saya kemudian men-klik tombol Next, mencentang pilih Add to persitence unit.  Setelah itu  saya men-klik Finish.  Kemudian saya melakukan perubahan sehingga isi file orm.xml terlihat seperti berikut ini:

<?xml version="1.0" encoding="UTF-8"?>
<entity-mappings version="2.0" xmlns="http://java.sun.com/xml/ns/persistence/orm" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence/orm http://java.sun.com/xml/ns/persistence/orm_2_0.xsd">
    <persistence-unit-metadata>
        <persistence-unit-defaults>
            <entity-listeners>
                <entity-listener class="org.springframework.data.jpa.domain.support.AuditingEntityListener" />
            </entity-listeners>
        </persistence-unit-defaults>
    </persistence-unit-metadata>
</entity-mappings>

Langkah berikutnya adalah membuat sebuah Spring bean yang meng-implementasi-kan inteface AuditorAware.  Bean ini diperlukan supaya Spring Data JPA tahu user mana yang sedang aktif atau sedang login.  Agar mudah, saya memakai ulang UserServiceImpl yang menyediakan services yang berkaitan dengan user.  Karena saya memakai Spring Security untuk menangani proses login, maka kode program di UserServiceImpl saya akan terlihat seperti:

@Service("userService")
@Repository
public class UserServiceImpl implements UserService, AuditorAware<User> {
   // ...
   // kode program yang sudah ada di UserServiceImpl tidak ditampilkan disini.
   // ...

   @Override
   public User getCurrentAuditor() {
      Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
      if (authentication==null) return null;
      return getUser(authentication.getName());
   }
}

Langkah terakhir adalah mendaftarkan userService di file konfigurasi Spring.  Saya menambahkan baris berikut pada file konfigurasi Spring:

<jpa:auditing auditor-aware-ref="userService" />

Pada bagian kode program yang melakukan penyimpan, saya melakukan sedikit perubahan sehingga terlihat seperti berikut ini:

public Pemesanan savePemesanan(Pemesanan pemesanan) {
  if (!pemesanan.isNew()) {
    Pemesanan oldPemesanan = pemesananRepository.findOne(pemesanan.getId());
    pemesanan.setCreatedBy(oldPemesanan.getCreatedBy());
    pemesanan.setCreatedDate(oldPemesanan.getCreatedDate());
  }
  return pemesananRepository.saveAndFlush(pemesanan);
}

Karena saya selalu membuat domain object baru dari web front end, baik proses insert maupun update, maka kode program di atas diperlukan agar nilai atribut createdBy dan createdDate tidak hilang bila proses update berlangsung.   Jika seandainya saya tetap memakai domain object yang sama, maka saya dapat  langsung menyimpan dengan saveAndFlush() di repository.

Setelah ini, setiap kali  domain object Pemesanan dibuat, maka atribut createdBy dan createdDate. Begitu juga lastModifiedBy dan lastModifiedDate akan di-isi secara otomatis pada saat domain object Pemesanan diubah.  Nilai createdBy dan lastModifiedBy akan diambil dari user yang sedang login.  Nilai createdDate dan lastModifiedDate akan diambil dari waktu saat proses create atau insert berlangsung.

Perihal Solid Snake
I'm nothing...

One Response to Memakai Fitur Auditing di Spring Data JPA

  1. Ping-balik: Membuat Implementasi Auditable Untuk Spring Data Auditing « The Solid Snake

Apa komentar Anda?

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

Logo WordPress.com

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

Gambar Twitter

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

Foto Facebook

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

Foto Google+

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

Connecting to %s

%d blogger menyukai ini: