5 Ocak 2009 Pazartesi

Veri Tabanı Hatalarını Yakalama

Çalıştığım web projesinde silme ekranlarından veri silerken bazı kayıtlarda "ORA-02292 Constraint violation - child records found" hatası alıyorduk. Ancak hata sonucunda silinemeyen verinin hangi tablolarda ve o tabloların hangi kayıtlarında kullanıldığını bilemiyorduk. Bunun üzerine yaptığım çalışma sonucunda spring, jsf, hibernate ve oracle db bağlamında bir çözüm buldum.

Öncelikle veri tabanı hatasını transaction sonunda yakalayabilmek için spring frame work'un kullandığı TransactionInterceptor yerine ondan extends eden CustomTransactionInterceptor sınıfını yazdım. Bu sınıf transaction ile ilgili işleri TransactionInterceptor'a delege ediyor ve kendisi sadece bir db hatası alınırsa, bu hatayı işleyerek bir wrapper sınıf içinde faces context'te yerleştiriyor.

Hata bilgisi facescontext'te yerleştirildikten sonra CustomTransactionInterceptor hatayı kendisine geldiği biçimde throw ediyor. Hata CustomActionListener tarafından tekrar yakalanıyor. CustomActionListener yakaladığı hatayı inceleyip, db hatası değilse CustomActionListener'dan bahsettiğim notumdaki şekliyle çalışıyor. Ancak bir db hatası olduğuna karar verirse, hatayı aynı şekilde session'a koyar ancak bu sefer başka bir hata sayfasına yönlenir.

Eğer hata yazılan jsf sayfasının pagecode'undan yakalanıp, modüle özgü bir hata mesajı yayınlanıyorsa, bu durumda hata sayfasına yönlenme olmamaktadır. Çünkü CustomActionListener'a hata gelmemektedir. Bu durumda sayfada gösterilen mesajda 'detay' butonu gösterilmektedir. Bu butona basıldığında ilgili hata sayfasına yönlenme gerçekleşmektedir. Ancak hata pagecode'da yakalanmıyorsa o durumda direk hata sayfasına yönlenir. 'detay' butonunun gözükmesi içinde MessagesRenderer extends edilip CustomMessagesRenderer yazılmalıdır. ve tabi her jsp'yede bu mesaj tag'i konmalıdır.

Yukarıda hatanın nasıl yakalanıp sunulduğu anlatılmıştır. Şimdi ise hatanın detay bilgisinin nasıl elde edildiği anlatılacaktır. "ORA-02292" kodlu oracle db hatasından contraint name,şema adı elde edilmektedir. Veri tabanının sistem sorguları kullanılarak constraint adı ile constraint'in detay bilgisi elde edilir. Ayrıca silinemeyen kaydın eldeki constraint dışındaki silinememeye yol açacak olan diğer constraint'leride elde edilir. Böylece veriyi sildirmeyen tablolar elde edilir. Ayrıca veriyi sildirmeyen tabloların hangi alan üzerinden silinemeyen verinin tablosuna referans yaptığı elde edilmiş olur. Bu seviyede bir kayıt silinemediğinde kaydın hangi tablolardaki veriler yüzünden silinemediği elde edilir.

Şimdi bir üst seviyeye geçerek silinemeyen kaydı, sildirmeyen tablolardaki hangi kayıtlar yüzünden silinemediğini, bu kayıtların hangi modüllerden girildiği ve bu kayıtları temsil edecek tekil bilgileri elde etmektir. Burada bu bilgilerin elde edilmesinde audit log mekanizmasına ihtiyaç vardır. kayıtların hangi modülden girildiği servis seviyesindeki audit logla, kayıtların(varlık,pojo) kimlik bilgileride varlık seviyesindeki audit logla elde edilir.


Sildirmeyen tablolardaki sildirmeyen kayıtlar, referans alanı ve silinemeyen kaydın idsi ile join yapılarak oluşturulan sorgu ile elde edilebilir. Ancak kayıtların audit mesajlarını elde edebilmek için tablo kayıtları değil o kayıtlara karşılık gelen pojolara ihtiyacımız vardır. Hibernate üzerinden sistemdeki tüm pojoların metadatası elde edilir. bu metadata'da pojoya karşılık gelen tablo bilgisi bulunmaktadır. bütün metadatalar dolaşılarak veriyi sildirmeyen tabloların pojoları elde edilir. Ayrıca pojo alanları ve tablo alanları eşleşmeside metadatadan elde edilir. böylece join işleminde kullanılan alanların pojo karşılıkları kullanılarak oluşturulan hql ile veriyi sildirmeyen varlıklar elde edilir. Bu varlığın böylece kimlik bilgisine erişilir. Varlığın sınıf adı ve id'si kullanılarak servis audit kayıtları üzerinden sorgu yapılarak varlığın hangi modülden girildiğide tespit edilir.

Sonuç olarak bir veri silinemediğinde o veriye referansı olan diğer veriler, bu verilerin ayırt edilmesini sağlayan kimlik bilgileri ve bu verilerin girildiği modüller elde edilebilir.

Ayrıca geliştirdiğim teknik ile diğer bazı oracle hatalarıda işlenmektedir. Ancak uygulamamızda en önemli detaylandırılması gereken hata silme hatasıdır. O yüzden diğer hataların detayına girmeyecem.

CustomTransactionListener'ı devreye sokmak için spring-beans-dev-db.xml dosyası aşağıdaki gibi değiştirildi.

<bean id="transactionInterceptor" class="dev.interceptors.transaction.CustomTransactionInterceptor">
<property name="transactionManager">
<ref bean="transactionManager" />
</property>
<property name="transactionAttributeSource">
<ref bean="transactionAttributeSource" />
</property>
</bean>


Hata alındıktan sonra hata sayfasının açılabilmesi için faces-config'e bir tane navigation rule eklenmelidir.

<navigation-rule>
<navigation-case>
<from-outcome>customDBErrorPage</from-outcome>
<to-view-id>/customDBErrorPage.jsp</to-view-id>
</navigation-case>
</navigation-rule>




CustomTransactionInterceptor.java

package dev.interceptors.transaction;

import org.aopalliance.intercept.MethodInvocation;
import org.springframework.dao.DataIntegrityViolationException;
import org.springframework.jdbc.UncategorizedSQLException;
import org.springframework.transaction.interceptor.TransactionInterceptor;
import dev.utils.OracleExceptionUtils;

public class CustomTransactionInterceptor extends TransactionInterceptor {
  public CustomTransactionInterceptor() {

  }

  public Object invoke(MethodInvocation invocationthrows Throwable {
    try {      
      return super.invoke(invocation);
    }
    catch (DataIntegrityViolationException dive) {
      new OracleExceptionUtils().make(dive);            
      throw dive;
    }    
    catch (UncategorizedSQLException ucse) {
      new OracleExceptionUtils().make(ucse);            
      throw ucse;
    }
    catch (Exception e) {     
      throw e;
    }
  }
}


Kaydı sildirmeyen kayıtların tablolarını bulan sorgu cümlesi;

select dd.TABLE_NAME as sildirmeyentablo,cc.COLUMN_NAME as referans_sutunu,rc.COLUMN_NAME as silinmeyenverinintablosu_id
from all_constraints ac ,all_constraints dd,all_cons_columns cc,all_cons_columns rc
where ac.CONSTRAINT_NAME='constraint_name' and ac.OWNER='db_name'
and ac.OWNER=dd.OWNER and ac.R_CONSTRAINT_NAME=dd.R_CONSTRAINT_NAME
and cc.OWNER=dd.OWNER and cc.CONSTRAINT_NAME=dd.CONSTRAINT_NAME
and rc.OWNER=dd.OWNER and rc.CONSTRAINT_NAME=dd.R_CONSTRAINT_NAME

Sistemteki tüm pojoların metadata bilgileri;

Collection classMetaDatas = getHibernateTemplate().getSessionFactory().getAllClassMetadata().values();

Ayrıca sildirmeyen kayıt bir varlığın detay kayıtlarından biriyse, bu durumda o kaydın ana kaydını bulup o kaydın kimlik bilgilerini göstermesi içinde bir çalışma yapılmıştır.

Collection collectionMetadatas = getHibernateTemplate().getSessionFactory().getAllCollectionMetadata().values();

collectionMetadatas, sistemdeki her bir pojonun içinde liste olarak tutulan diğer pojonun ilişkilerinin metadasını döner. Böylece datay kayıttan ana kayıt elde edilebilir.

Bu yazıyla ilgili daha detaylı bilgi elde edebilmek için bana mail atabilirsiniz.

Hiç yorum yok: