İçeriğe geç
academia.sh

Ders 11 / 19

Nesne ve Tablo Bağlaması

Bir varlık nesnesinin dört alanından ikisi yükleme anında, ikisi ilk erişimde yükleniyor. İkinci erişim tabloyu tekrar okumuyor, önbellekten dönüyor; ama nesne kaptan ayrıldıktan sonra hiç erişilmemiş bir alana erişildiğinde tablo okunamıyor ve kusur sessizce boş bir değer olarak dönüyor.

İçindekiler

Önceki ders bir arayüzün arkasında hangi gerçekleştirimin durduğunu ölçtü, ama gerçekleştirimin kendi alanlarının nesne ile depo arasında nasıl eşleştiği hiç sorulmadı. Bu ders bunu ele alıyor: bir nesnenin alanları ile bir tablonun sütunları arasındaki bağlamanın kaynakta bir açıklamadan geldiğini, ve erişilmeyen bir alanın hiç yüklenmediğini ölçüyor. Kursun terim kararında bu bağlantı nesne–tablo bağlamasıdır; nesnenin kendisi ise bir varlık nesnesi.

Tablo burada da modellenir: gerçek bir veritabanı bağlantısı açılmaz, gerçek bir sorgu çalıştırılmaz. Bir “tablo”, satır kimliğine göre erişilen bir sütun adı–değer yapısından ibaret — Map<Integer, Map<String, Object>>. Bir varlık nesnesi bu satırlardan biriyle bağlanır, ve nesnenin hangi alanının hangi sütuna karşılık geldiği kaynakta iki açıklamayla belirtiliyor: @Sutun her zaman yüklenen alanlar için, @TembelSutun yalnız erişildiğinde yüklenen alanlar için.

Bu ayrımın bir maliyet sorusu olduğu unutulmamalı. Gerçek bir depoda her sütun bir disk erişimi, bir ağ gecikmesi taşıyabilir; bir satırın yalnız iki sütununu her zaman okuyup kalanını yalnız gerektiğinde okumak, bir nesnenin hiç kullanılmayacak alanları için hiç ödeme yapmamak anlamına gelir. Bu derste maliyetin kendisi ölçülmüyor — süre ve gecikme yazılmıyor — ama maliyetin nereye düştüğü ölçülüyor: yukSirasi listesi, hangi sütunun ne zaman okunduğunu tutan bir günlük, ve bu günlük hem yükleme anındaki hem erişim anındaki her okumayı aynı biçimde kaydediyor.

Yükleme Anında İki Alan, Erişimde İki Alan

  • WV20. UrunKarti dört alan taşıyor: ad ve miktar @Sutun, aciklamaOnbellek ve resimYoluOnbellek @TembelSutun. Alan adları sütun adlarıyla birebir aynı olmak zorunda değil; açıklamanın ad parametresi eşleşmeyi kaynaktaki alan adından bağımsız kılıyor.
  • WV21. Tembel alanların gerçek değeri bir “önbellek” alanında tutuluyor; ilk kez null olan bu alan, ilk başarılı yüklemeden sonra bir daha hiç sıfırlanmıyor.

Kap.yukle bir satırı bir nesneye dönüştürürken yalnız @Sutun taşıyan alanları dolduruyor; @TembelSutun taşıyanlara hiç dokunmuyor. Nesne, hangi kaba ve hangi satıra ait olduğunu VarlikNesnesi üst sınıfından gelen oturum ve satirId alanlarıyla taşıyor — bu ikisi @Sutun değil, nesne kabının kendi kurduğu bir bağ.

// Kap.java — nesne-tablo baglamasi aciklamadan doguyor, tembel alan erisim aninda yukleniyor
import java.lang.annotation.*;
import java.lang.reflect.*;
import java.util.*;

public class Kap {

    @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.TYPE)
    @interface Tablo { String ad(); }

    @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD)
    @interface Sutun { String ad(); }

    @Retention(RetentionPolicy.RUNTIME) @Target(ElementType.FIELD)
    @interface TembelSutun { String ad(); }

    final Map<Integer, Map<String, Object>> tablo = new LinkedHashMap<>();
    final List<String> yukSirasi = new ArrayList<>();

    void satirEkle(int id, Map<String, Object> sutunlar) { tablo.put(id, sutunlar); }

    Object sutunOku(int id, String sutunAdi, String etiket) {
        yukSirasi.add(sutunAdi + ": " + etiket);
        return tablo.get(id).get(sutunAdi);
    }

    <T> T yukle(Class<T> tip, int id) throws ReflectiveOperationException {
        T nesne = tip.getDeclaredConstructor().newInstance();
        for (Field f : tip.getDeclaredFields()) {
            f.setAccessible(true);
            Sutun s = f.getAnnotation(Sutun.class);
            if (s != null) f.set(nesne, sutunOku(id, s.ad(), "yukleme-aninda"));
        }
        Field oturumAlani = tip.getSuperclass().getDeclaredField("oturum");
        Field idAlani = tip.getSuperclass().getDeclaredField("satirId");
        oturumAlani.setAccessible(true);
        idAlani.setAccessible(true);
        oturumAlani.set(nesne, this);
        idAlani.set(nesne, id);
        return nesne;
    }

    void ayril(VarlikNesnesi nesne) throws ReflectiveOperationException {
        Field oturumAlani = VarlikNesnesi.class.getDeclaredField("oturum");
        oturumAlani.setAccessible(true);
        oturumAlani.set(nesne, null);
    }

    public static void main(String[] args) throws Exception {
        Kap kap = new Kap();
        kap.satirEkle(1, Map.of("ad", "vida", "miktar", 12, "aciklama", "6mm somunlu vida", "resimYolu", "gorsel/vida.png"));

        System.out.println("-- kap ile yukleme: sadece iki sutun aninda okunuyor --");
        UrunKarti urun = kap.yukle(UrunKarti.class, 1);
        System.out.println("yukleme sonrasi yuk sirasi: " + kap.yukSirasi);
        System.out.println("toplam alan: 4, yukleme aninda okunan: "
                + kap.yukSirasi.stream().filter(s -> s.endsWith("yukleme-aninda")).count());

        System.out.println("-- tembel alana erisim: ilk cagri tabloyu okuyor --");
        System.out.println("aciklama: " + urun.aciklama());
        System.out.println("aciklama tekrar: " + urun.aciklama());
        System.out.println("yuk sirasi (aciklama sonrasi): " + kap.yukSirasi);
        System.out.println("aciklama icin kac kez tablo okundu: "
                + kap.yukSirasi.stream().filter(s -> s.startsWith("aciklama:") && s.endsWith("erisim-aninda")).count());
        System.out.println("resimYolu hic erisilmedi, kayitta hic gecmiyor: "
                + kap.yukSirasi.stream().noneMatch(s -> s.startsWith("resimYolu")));

        System.out.println("-- kaptan ayrilma sonrasi hic erisilmemis tembel alan --");
        kap.ayril(urun);
        System.out.println("resimYolu (kap ayrildiktan sonra ilk erisim): '" + urun.resimYolu() + "'");
        System.out.println("kusur turu: sessiz (istisna yok, bos deger sessizce dondu)");

        System.out.println("-- elle kurulan nesne: kap hic devrede degil --");
        UrunKarti elleKurulan = new UrunKarti();
        System.out.println("elle kurulan nesnede aciklama ilk erisimde: '" + elleKurulan.aciklama() + "'");
    }
}

class VarlikNesnesi {
    transient Kap oturum;
    transient int satirId;

    Object tembelOku(String sutunAdi, String onbellek) {
        if (onbellek != null) return onbellek;
        if (oturum == null) return "";
        return String.valueOf(oturum.sutunOku(satirId, sutunAdi, "erisim-aninda"));
    }
}

@Kap.Tablo(ad = "urun_karti")
class UrunKarti extends VarlikNesnesi {
    @Kap.Sutun(ad = "ad") String ad;
    @Kap.Sutun(ad = "miktar") int miktar;
    @Kap.TembelSutun(ad = "aciklama") String aciklamaOnbellek;
    @Kap.TembelSutun(ad = "resimYolu") String resimYoluOnbellek;

    String aciklama() {
        aciklamaOnbellek = (String) tembelOku("aciklama", aciklamaOnbellek);
        return aciklamaOnbellek;
    }

    String resimYolu() {
        resimYoluOnbellek = (String) tembelOku("resimYolu", resimYoluOnbellek);
        return resimYoluOnbellek;
    }
}
-- kap ile yukleme: sadece iki sutun aninda okunuyor --
yukleme sonrasi yuk sirasi: [ad: yukleme-aninda, miktar: yukleme-aninda]
toplam alan: 4, yukleme aninda okunan: 2
-- tembel alana erisim: ilk cagri tabloyu okuyor --
aciklama: 6mm somunlu vida
aciklama tekrar: 6mm somunlu vida
yuk sirasi (aciklama sonrasi): [ad: yukleme-aninda, miktar: yukleme-aninda, aciklama: erisim-aninda]
aciklama icin kac kez tablo okundu: 1
resimYolu hic erisilmedi, kayitta hic gecmiyor: true
-- kaptan ayrilma sonrasi hic erisilmemis tembel alan --
resimYolu (kap ayrildiktan sonra ilk erisim): ''
kusur turu: sessiz (istisna yok, bos deger sessizce dondu)
-- elle kurulan nesne: kap hic devrede degil --
elle kurulan nesnede aciklama ilk erisimde: ''

urun.yukSirasi’nin ilk hâli yalnız iki girdi taşıyor: ad ve miktar. aciklama ile resimYolu bu listede hiç görünmüyor, çünkü yukle onları hiç okumadı — dört alanlı bir nesne, tablonun yalnız iki sütununu okuyarak kuruldu. Bu, kaynak metinle çelişen bir sonuç değil, kaynağın tam olarak söylediği şey: @Sutun yalnız iki alanda var, @TembelSutun diğer ikisinde, ve yukle yalnız @Sutun taşıyanları dolduruyor. Ama satırı okuyan biri UrunKarti’nin dört alanının da bir tablo satırından geldiğini bilir; hangi ikisinin ne zaman geldiğini bilmek için Kap.yukle yönteminin gövdesini okumak gerekiyor.

Tembel Alan Yalnız Bir Kez Yükleniyor

  • WV22. aciklama() yöntemi çağrıldığında önce kendi önbellek alanına bakıyor; alan doluysa tabloya hiç gitmiyor. Bu, yöntemin adının “tembel” olmasının ikinci anlamı: yalnız ilk erişimde değil, en fazla bir kez yükleniyor.

aciklama() iki kez çağrılıyor ve yuk sirasi listesine yalnız bir "aciklama: erisim-aninda" girdisi ekleniyor. İlk çağrı aciklamaOnbellek alanının null olduğunu görüyor, tabloyu okuyor, sonucu aynı alana yazıyor. İkinci çağrı aynı alanın artık dolu olduğunu görüyor ve tabloya hiç gitmiyor. Bu, kap içindeki tekil kapsamla aynı aileden bir davranış — orada bir bileşenin ikinci isteği aynı örneği döndürüyordu, burada bir alanın ikinci erişimi aynı değeri döndürüyor — ama kaynağı farklı: tekillik kabın kendi kural tablosundaydı, burada önbellekleme tembelOku yönteminin kendi gövdesinde yazılı bir kontrol. resimYolu alanına hiç erişilmediği için yuk sirasi listesinde bu sütun için hiçbir girdi yok — dört alanlı nesnenin dördüncü alanı, program sonuna kadar tabloya hiç sorulmadan kalabiliyor. Bu, tembel yüklemenin asıl kazancı: bir nesnenin bütün alanlarını taşıması, hepsinin gerçekten okunacağı anlamına gelmiyor, ve okunmayan alan için hiçbir maliyet ödenmiyor.

Önbellekleme yalnız bir performans kararı değil, bir tutarlılık kararı da taşıyor. Tabloya aciklama() çağrısı arasında başka bir yol aynı satırın aciklama sütununu değiştirseydi, urun nesnesi bunu asla görmezdi — önbellek dolduktan sonra tembelOku bir daha tabloya hiç bakmıyor. Bu ders bu senaryoyu koşturmuyor, ama sonucu kaynaktan çıkarılabilir: bir tembel alan yalnız ilk okunduğu anki değeri taşıyor, o andan sonra tablo değişse de nesne değişmiyor. Bir varlık nesnesini uzun süre elde tutan bir çağıran için bu, yükleme anının gerçek bir anlık görüntü olduğu, ve o görüntünün süresiz doğru kalacağının hiçbir yerde vaat edilmediği anlamına geliyor.

Kap Dışında Tembel Yükleme Çalışmıyor: Sessiz Bir Boşluk

  • WV23. ayril yalnız oturum alanını null yapıyor; nesnenin diğer alanları, önbelleğe alınmış olan ad ve miktar dahil, hiç değişmiyor.
  • WV24. tembelOku, oturum == null gördüğünde bir istisna fırlatmak yerine boş bir dizge döndürüyor. Bu, dersin ikinci sessiz kusuru: erişim başarısız olmuyor, yalnız yanlış bir değer üretiyor.

kap.ayril(urun) çağrısı, urun’un kabın oturumuyla bağını kesiyor — gerçek bir uygulamada bu, bir isteğin işlenmesi bittikten sonra nesnenin kap dışına, örneğin bir başka katmana taşınmasının karşılığı. Bu noktaya kadar resimYolu hiç erişilmemişti; ayril’dan sonra ilk kez çağrıldığında tembelOku artık oturum’un null olduğunu görüyor ve tabloya sorabileceği hiçbir yol kalmıyor. Yöntem bir istisna fırlatmak yerine boş bir dizge döndürüyor, ve çağıran taraf bu boş dizgeyi gerçek bir “resim yolu yok” durumundan ayırt edemiyor. Kusur burada da sessiz: program çökmüyor, hiçbir uyarı basmıyor, yalnız yanlış bir değerle devam ediyor.

Bu, dersin sınırlayıcı ölçümü: tembel yükleme kabın dışında çalışmaz; nesne kaptan çıktıktan sonra erişilen alan boş kalır. ad ve miktar bu sorunu yaşamıyor, çünkü ikisi de yükleme anında dolduruldu ve ayril’dan etkilenmiyor — sorun yalnız hiç erişilmemiş tembel alanlarda ortaya çıkıyor. Bu da yükleme stratejisinin gizli bir kuralını gösteriyor: bir varlık nesnesinin kap içinde ne kadar süre kaldığı, hangi tembel alanlarına erişildiğini belirliyor, ve erişim penceresi kapandıktan sonra erişilmemiş kalan her tembel alan bir daha asla doğru değerine ulaşamıyor.

Son ölçüm bu kuralı en baştan kuran bir nesneyle sınıyor: new UrunKarti() ile elle kurulan nesnenin oturum alanı hiçbir zaman bir kaba bağlanmadı, baştan null. Bu nesnede aciklama() çağrıldığında sonuç yine boş dizge — ayril çağrılan nesneyle birebir aynı davranış, aynı sessiz sonuç. İki yol da aynı noktaya çıkıyor: nesne kabın oturumuyla ya hiç bağlanmadı ya da bağı sonradan koptu, ve her iki durumda da tembel alan erişimi aynı boş değerle sonuçlanıyor. @TembelSutun açıklaması UrunKarti sınıfının üzerinde durmaya devam ediyor — kaldırılmadı — ama onu yerine getirecek bir oturum yoksa, önceki derslerde görülen kuralın bir kez daha doğrulanması: açıklama bir istektir, isteği karşılayacak taraf yoksa hiçbir şey olmaz.

Bu ikisi arasındaki tek fark, kusurun ne kadar sürpriz olduğu. Elle kurulan bir nesnede kap hiç devrede olmadığı zaten baştan belli — bir geliştirici bu nesneyi new ile kurarken tembel yüklemenin çalışmayacağını beklemek zorunda. Ama ayril’dan geçen nesne az önce kabın içindeydi; ad ve miktar alanları doğru değerlerini taşıyor, nesne her bakımdan “gerçek” görünüyor. Bu görünüş, resimYolu gibi hiç erişilmemiş bir alanın da aynı ölçüde güvenilir olduğu izlenimini veriyor — oysa o alan, nesnenin görünüşte tamamlanmış olmasına rağmen hâlâ kabın oturumuna bağımlı. Sınırlayıcı ölçümün asıl değeri burada: sorun rastgele bir alanda değil, tam olarak kap dışına en son çıkan ve hiç dokunulmamış alanlarda ortaya çıkıyor, ve bu ikisinin kesişimi kaynak metinde hiçbir yerde işaretli değil.

Özet

  • UrunKarti’nin dört alanından ikisi (ad, miktar) yükleme anında, ikisi (aciklamaOnbellek, resimYoluOnbellek) yalnız ilk erişimde tablodan okundu.
  • Bir tembel alana ikinci erişim tabloya hiç gitmedi; önbellek alanı doluysa tembelOku doğrudan onu döndürdü.
  • Hiç erişilmeyen bir tembel alan (resimYolu) programın sonuna kadar tabloya hiç sorulmadı; bu, tembel yüklemenin okunmayan alan için maliyet ödetmeyen tarafı.
  • Nesne kaptan ayrıldıktan sonra hiç erişilmemiş bir tembel alana erişildiğinde tabloya ulaşılamadı ve yöntem istisna yerine boş bir dizge döndürdü; kusur sessiz kaldı.
  • Elle kurulan bir nesnede aynı tembel alana erişim, kaptan ayrılan nesneyle birebir aynı boş sonucu verdi; ikisinde de eksik olan şey kabın oturumuydu.

Sonraki Adım

Bu ders bir nesnenin alanlarının ne zaman yüklendiğini ölçtü, ama bu yüklemenin ve üzerindeki değişikliklerin nerede başlayıp nerede bittiği hiç sorulmadı. Sıradaki ders bunu ele alıyor: bildirimsel bir işlem sınırının kaynakta bir açıklama olduğunu, ve bir bileşenin kendi kendini çağırmasının bu sınırı nasıl atladığını ölçüyor.

İlerlemeni kaydetmek ve not almak için Giriş yap

Notlarım

Not almak için giriş yapmalısın.

Aramak için yazmaya başlayın.

↑↓ Esc gezin · aç · kapat