Jdbi: Jadikan @CreateSqlObject tidak terlalu membingungkan.

Dibuat pada 1 Feb 2018  ·  20Komentar  ·  Sumber: jdbi/jdbi

Masalah
Dokumentasi resmi JDBI membuat pengembang percaya bahwa anotasi @CreateSqlObject adalah mekanisme untuk menambahkan dukungan transaksional ke DAO yang berbeda.

http://jdbi.org/#__createsqlobject

Yang tampaknya agak menyesatkan dengan perilaku aktual yang dapat menyebabkan kesalahan status saat digunakan dengan mekanisme pembuatan jdbi.onDemand yang disukai.

Ada beberapa pesan di forum yang menggambarkan kebingungan ini:

Meminta
Tidak yakin apa solusi terbaik untuk masalah ini. Beberapa yang terlintas dalam pikiran:

  • Bantahan
  • Dokumentasi yang lebih baik untuk kekurangannya
  • Solusi lain untuk memisahkan kueri di beberapa DAO, tetapi izinkan semantik transaksional logis untuk menyatukannya. Khususnya, dalam mendukung perpaduan transaksi read-only/write.
cleanup improvement

Komentar yang paling membantu

Maaf baru komen lagi. Apakah ada sesuatu di peta jalan Anda yang akan membantu dalam hal ini?

Tidak ada masalah sama sekali. Ini jelas merupakan sumber kebingungan, dan sesuatu yang ingin kami perbaiki -- kami hanya benar-benar ingin memastikan untuk melakukannya dengan benar kali ini, karena kami tidak melakukannya terakhir kali :)

(Seperti yang disebutkan oleh @svlada itu cukup umum di kerangka kerja lain untuk menggunakan @Transactional di tingkat yang lebih tinggi)

Ya, tapi sejauh yang saya mengerti, itu semua dilakukan melalui kait AOP di kerangka kerja DI Anda (seperti Spring). Karena JDBI tidak "membungkus" semua objek layanan Anda, kami tidak memiliki kesempatan untuk memberikan solusi seperti AOP untuk objek yang tidak kami buat sendiri. Bahkan implementasi cglib yang lama hanya akan melihat @Transactional pada daos itu sendiri, itu tidak akan pernah bekerja pada kelas layanan yang terpisah.

Jika akan membantu, kami dapat mempertimbangkan untuk menambahkan, misalnya, pengikatan Spring dan/atau Guice AOP untuk memungkinkan transaksi pada tingkat yang lebih tinggi. Ini tidak akan menjadi bagian dari core tetapi misalnya bagian dari ekstensi spring . Ini tidak mungkin menjadi sesuatu yang dilakukan oleh pengembang inti, tetapi kontribusi akan dipertimbangkan secara serius untuk dimasukkan. Mungkin itu menyelesaikan masalah Anda dengan cara yang "lebih bersih"?

Kami memutuskan untuk beralih dari cglib ke Proxy sebagian besar karena alasan kompatibilitas -- Proxy adalah jdk api yang didukung, sedangkan cglib (atau lebih khusus asm ) cenderung putus dengan setiap rilis utama (rusak pada 8, 9, 11, ...) yang berubah menjadi sakit kepala pemeliharaan yang besar.

Semua 20 komentar

Terima kasih telah melaporkan ini. Saya menduga kita akan berakhir dengan perubahan yang melanggar teknis di sini, tetapi menurut saya perilaku yang ada cukup buruk sehingga kami membuat perubahan yang melanggar teknis di awal siklus rilis 3.x. Harap tandai masalah ini jika Anda bergantung pada perilaku yang ada atau tidak setuju dengan kemungkinan mengirimkan perubahan kecil yang melanggar di sini.

Hanya untuk konteks _why_ @CreateSqlObject tidak cocok dengan permintaan:

  • Sesuai permintaan adalah abstraksi tingkat inti, diimplementasikan menggunakan proxy. Saat Anda memanggil metode pada proxy sesuai permintaan, objek SQL nyata dibuat dan pemanggilan metode didelegasikan ke objek SQL nyata. Pegangan yang mendukung instance sebenarnya ditutup setelah panggilan metode delegasi kembali.
  • @CreateSqlObject diimplementasikan menggunakan handle.attach(sqlObjectType) , dengan pegangan dukungan Objek SQL asli.

Jadi, backing handle untuk Objek SQL yang dibuat ditutup sebelum Objek SQL dapat dikembalikan.

Tak satu pun di atas diatur dalam batu--itu hanya bagaimana itu diterapkan sekarang.

Saya tidak yakin kita harus merusak kompatibilitas untuk memperbaikinya.

Maaf, saya tidak begitu mengerti... Apa masalahnya dengan onDemand dan CreateSqlObject sebenarnya? Saya tidak pernah punya masalah dengan itu, dan sesuatu tentang penjelasan @qualidafial hanya membuat pikiran saya miring...

image

Koneksi diperoleh untuk panggilan ke fooProxy.usecase . Foo.usecase memanggil bilah metode createSqlObject , yang mengembalikan Bilah baru setelah melampirkannya ke pegangan ( usecase ) saat ini. pegangan usecase ditutup saat usecase kembali. Selama Anda tidak melakukan sesuatu yang tidak sinkron dengan Bar yang dikembalikan, bagaimana pegangan bar bisa kedaluwarsa terlalu dini? siklus hidup dan penggunaan bar terbatas pada badan Foo.usecase , seperti pegangannya...

Saya tidak dapat berbicara dengan internal JDBI, namun saya dapat mengomentari perilaku yang kami lihat dalam produksi.

Pada dasarnya, di lingkungan kita yang lebih rendah semuanya baik-baik saja. Tetapi dengan peningkatan beban dalam produksi, kami terus-menerus akan melihat pernyataan SQL tidak dijalankan terhadap node database yang tepat. Misalnya, pilihan Baca Saja akan mengenai node Master, dan Sisipan akan mengenai replika baca. Selain itu, kami akan mendapatkan kesalahan koneksi tertutup saat berada di tengah operasi.

Pada dasarnya, sepertinya @CreateSqlObject menciptakan masalah keamanan utas - di mana tanda baca-saja diubah oleh permintaan yang bersaing.

Untuk "menyelesaikan" masalah - kami menghapus semua penggunaan @CreateSqlObject , dan berakhir dengan sesuatu seperti ini:

  • class FooDao
  • class BarDao
  • class CombinedDao extends FooDao, BarDao

Dan kemudian kami jdbi.onDemand(CombinedDao) dan hanya menggunakan akses melalui itu.
Beralih ke pola itu, meskipun tidak secantik itu, menghilangkan semua kesalahan produksi yang disebutkan di atas.

Sekadar info, saya berpikir untuk kembali ke JDBI 2 karena semua kode saya menggunakan onDemand dengan kelas abstrak saat ini, dan saya tidak dapat menemukan cara untuk merestrukturisasi dengan cara yang akan saya senangi!

Saya cenderung memiliki antarmuka layanan, dengan kelas implementasi abstrak yang berisi logika tetapi tidak ada SQL, dengan metode yang dijelaskan sebagai transaksional, menggunakan CreateSqlObject untuk menyediakan akses ke kelas DAO yang murni SQL. Contoh yang disederhanakan:

Antarmuka

public interface AccountService {
    void addAccount(Account account, User user);
}  

Penerapan

public abstract class AccountServiceJdbi implements AccountService {

    <strong i="11">@Override</strong>  
    <strong i="12">@Transaction</strong>  
    public final void addAccount(@BindBean() Account account, User user) {
        long accountId =  accountDao().insertAccount(account);
        accountDao().linkAccountToOwner(accountId, user.getId());
    }

    <strong i="13">@CreateSqlObject</strong>
    abstract AccountDao accountDao();
}

(Anda dapat membayangkan Dao)

Ini memberikan pemisahan logika dan akses data yang sangat bagus, sambil memungkinkan transaksi melalui beberapa metode DAO. Tes unit untuk layanan ini mudah ditulis dan dipahami dengan mengimplementasikan antarmuka DAO.

Saya sudah mencoba melakukan hal serupa di JDBI 3, tetapi saya pikir itu berarti implementasi kelas layanan saya harus berupa antarmuka dengan metode default untuk yang berisi logika. Metode default tidak dapat dibuat final, jadi kodenya tidak terasa ringkas, dan memberi saya lebih sedikit kendali atas bagaimana kelas saya dapat digunakan.

Apakah ada cara saya dapat menyusun kode saya di JDBI 3 untuk memiliki metode transaksional akhir?

Anda benar bahwa tidak ada cara untuk mendefinisikan metode antarmuka sebagai final.

Namun Anda _can_ mendefinisikan anotasi metode SQL Anda sendiri yang setara dengan @SqlQuery , @SqlUpdate , dll, dan menyediakan implementasi statis untuk metode dengan anotasi itu.

Namun Anda _can_ mendefinisikan anotasi metode SQL Anda sendiri yang setara dengan @SqlQuery , @SqlUpdate , dll, dan menyediakan implementasi statis untuk metode dengan anotasi itu.

Terima kasih atas jawabannya - Saya tidak cukup mengikuti bagaimana itu membantu saya. Apa cara yang disarankan untuk memiliki kueri di beberapa DAO, tetapi menjalankannya dalam transaksi yang sama?

cara untuk memiliki kueri di beberapa DAO, tetapi menjalankannya dalam transaksi yang sama?

Secara pribadi saya menggunakan sesuatu seperti

interface Dao1 {
  @SqlQuery("...")
  void query1();
}

interface Dao2 {
  @SqlQuery("...")
  void query2();
}

interface JdbiServiceImpl extends Service {
  <strong i="8">@CreateSqlObject</strong>
  Dao1 dao1();
  <strong i="9">@CreateSqlObject</strong>
  Dao2 dao2();

  <strong i="10">@Transaction</strong>
  <strong i="11">@Override</strong>
  void businessCase() {
    dao1().query1();
    dao2().query2();
  }
}

Service service = handle.attach(JdbiServiceImpl.class);
service.businessCase();

Bekerja sangat baik untuk saya dengan cara ini. @CreateSqlObject pada dasarnya seperti injeksi ketergantungan pegas oleh pengambil/penyetel. Saya menempatkan instance onDemand pada konteks pegas saya sebagai kacang sehingga berfungsi persis seperti layanan biasa, penelepon tidak perlu tahu tentang jdbi atau antarmuka implementasi.

image

StockReductionCase yang tampak anti-pola yang Anda lihat adalah contoh "injeksi ketergantungan" jdbi bersarang dengan CreateSqlObject. Ini memiliki dependensi sendiri di dalamnya, seperti implementasi layanan. Ini pada dasarnya adalah layanan sendiri, saya hanya menyebutnya Kasus alih-alih Layanan untuk menghindari ketergantungan siklik dengan hanya menempatkan Kasus (diperlukan di banyak tempat) di dalam Layanan (logika tingkat atas yang tidak dapat digunakan kembali) dan Kueri ke dalam keduanya.

image

Saya telah mendengar beragam laporan keberhasilan membuat transaksi tercakup dengan benar dengan @CreateSqlObject , terutama bila dikombinasikan dengan onDemand() .

Cara paling pasti untuk menjalankan beberapa objek SQL dalam transaksi yang sama adalah menjalankan transaksi melalui Handle.inTransaction() atau Jdbi.inTransaction() . Di dalam callback, setiap DAO yang dibuat melalui Handle.attach() akan menjadi bagian dari transaksi handle:

jdbi.useTransaction(handle -> {
  Dao1 dao1 = handle.attach(Dao1.class);
  Dao2 dao2 = handle.attach(Dao2.class);

  dao1.doStuff();
  dao2.doMoreStuff();
});

Terima kasih atas balasannya
@qualidafial ini sepertinya cara saya harus pergi, tetapi itu membuat sulit untuk menulis tes unit untuk kelas layanan. Karena instantiate Daos itu sendiri, saya tidak dapat mengganti tiruan Daos untuk unit test.

@TheRealMarnes terima kasih juga - ini terlihat mirip dengan apa yang saya coba, saya hanya tidak suka menggunakan metode default karena dapat diganti.

@qualidafial Saya ingin mengonfirmasi dengan Anda bahwa kami tidak dapat menggunakan anotasi @Transaction jdbi pada metode layanan yang menggunakan beberapa metode dao?

Jika asumsi ini benar, perilaku ini sangat berbahaya tanpa didokumentasikan dengan baik sebagai bagian dari dokumentasi resmi. Sejumlah besar pengembang memiliki pengalaman dengan tumpukan Spring dan menggunakan anotasi @Transactional adalah cara yang sangat standar untuk mendukung transaksi di beberapa DAO.

@svlada Anotasi @Transaction _only_ bekerja pada metode objek SQL. Ketika Anda mengatakan "metode layanan", saya mendapat kesan Anda menggunakan anotasi pada beberapa objek di luar pengaruh Jdbi, misalnya kelas yang disuntikkan.

Contoh dari rangkaian pengujian kami:

<strong i="9">@Test</strong>
public void testInsertAndFind() {
    Foo foo = handle.attach(Foo.class);
    Something s = foo.insertAndFind(1, "Stephane");
    assertThat(s).isEqualTo(new Something(1, "Stephane"));
}

<strong i="10">@Test</strong>
public void testTransactionPropagates() {
    Foo foo = dbRule.getJdbi().open().attach(Foo.class);

    assertThatExceptionOfType(Exception.class)
        .isThrownBy(() -> foo.insertAndFail(1, "Jeff"));

    Something n = foo.createBar().findById(1);
    assertThat(n).isNull();
}

public interface Foo {
    <strong i="11">@CreateSqlObject</strong>
    Bar createBar();

    @SqlUpdate("insert into something (id, name) values (:id, :name)")
    int insert(@Bind("id") int id, @Bind("name") String name);

    <strong i="12">@Transaction</strong>
    default Something insertAndFind(int id, String name) {
        insert(id, name);
        return createBar().findById(id);
    }

    <strong i="13">@Transaction</strong>
    default Something insertAndFail(int id, String name) {
        insert(id, name);
        return createBar().explode();
    }
}

public interface Bar {
    @SqlQuery("select id, name from something where id = :id")
    Something findById(@Bind("id") int id);

    default Something explode() {
        throw new RuntimeException();
    }
}

Saya juga ingin mengklarifikasi sehubungan dengan onDemand() + @CreateSqlObject :

  • @CreateSqlObject DAO hanya dapat digunakan dari dalam metode DAO yang membuatnya--seperti pada contoh di atas.
  • Memanggil fooDao.createBar().findById() akan mengeluarkan pengecualian yang menyatakan bahwa koneksi ditutup.

Maaf baru komen lagi. Apakah ada sesuatu di peta jalan Anda yang akan membantu dalam hal ini?

Saya masih sangat merindukan bisa menggunakan CreateSqlObject di kelas abstrak. Harus melakukan segala sesuatu di antarmuka masih terasa membatasi; sering saya ingin membuat metode jenis "layanan" transaksional, tetapi ini berarti kelas Layanan saya juga harus menjadi antarmuka menggunakan metode default dan saya mulai kehilangan kejelasan pemisahan masalah, metode akhir yang konkret, dll.

@tamslinn sejauh menyangkut masalah Anda dengan antarmuka alih-alih kelas abstrak, saya pikir saya dapat dengan aman mengatakan bahwa kami tidak akan kembali dari keputusan itu dalam waktu dekat. Jdbi3 menggunakan proksi jdk, yang hanya mendukung antarmuka.

Saya tidak berpikir modul terpisah yang dikontribusikan yang dibangun di atas cglib atau semacamnya yang menawarkan fitur ini sepenuhnya tidak mungkin, tetapi itu bukan masalah sekarang.

Jika aksioma sqlobject sangat cocok untuk Anda, Anda selalu dapat memfaktorkan ulang Layanan Anda menjadi kelas reguler yang tidak ditingkatkan oleh jdbi melainkan memiliki instance Jdbi yang disuntikkan untuk menggunakan API yang lancar sebagai gantinya.

Adapun subjek asli dari perilaku antara dukungan transaksi kami, onDemand, dll, itu menjadi semakin fokus untuk dikerjakan, meskipun saya rasa kami belum memiliki rencana yang benar-benar konkret. Tapi kita sedang menuju ke sana.

Terima kasih banyak atas pembaruannya. Benar-benar masuk akal kembali proxy.
Batasan khusus bagi saya adalah tidak dapat memulai/mengakhiri transaksi di luar kelas JDBI.
(Seperti yang disebutkan oleh @svlada itu cukup umum dalam kerangka kerja lain untuk menggunakan @Transactional pada tingkat yang lebih tinggi)
Saya sangat menyukai semua hal lain tentang JDBI, jadi saya akan terus melakukannya, mungkin saya akan membuat pola desain yang saya senangi untuk kelas layanan saya :)

Maaf baru komen lagi. Apakah ada sesuatu di peta jalan Anda yang akan membantu dalam hal ini?

Tidak ada masalah sama sekali. Ini jelas merupakan sumber kebingungan, dan sesuatu yang ingin kami perbaiki -- kami hanya benar-benar ingin memastikan untuk melakukannya dengan benar kali ini, karena kami tidak melakukannya terakhir kali :)

(Seperti yang disebutkan oleh @svlada itu cukup umum di kerangka kerja lain untuk menggunakan @Transactional di tingkat yang lebih tinggi)

Ya, tapi sejauh yang saya mengerti, itu semua dilakukan melalui kait AOP di kerangka kerja DI Anda (seperti Spring). Karena JDBI tidak "membungkus" semua objek layanan Anda, kami tidak memiliki kesempatan untuk memberikan solusi seperti AOP untuk objek yang tidak kami buat sendiri. Bahkan implementasi cglib yang lama hanya akan melihat @Transactional pada daos itu sendiri, itu tidak akan pernah bekerja pada kelas layanan yang terpisah.

Jika akan membantu, kami dapat mempertimbangkan untuk menambahkan, misalnya, pengikatan Spring dan/atau Guice AOP untuk memungkinkan transaksi pada tingkat yang lebih tinggi. Ini tidak akan menjadi bagian dari core tetapi misalnya bagian dari ekstensi spring . Ini tidak mungkin menjadi sesuatu yang dilakukan oleh pengembang inti, tetapi kontribusi akan dipertimbangkan secara serius untuk dimasukkan. Mungkin itu menyelesaikan masalah Anda dengan cara yang "lebih bersih"?

Kami memutuskan untuk beralih dari cglib ke Proxy sebagian besar karena alasan kompatibilitas -- Proxy adalah jdk api yang didukung, sedangkan cglib (atau lebih khusus asm ) cenderung putus dengan setiap rilis utama (rusak pada 8, 9, 11, ...) yang berubah menjadi sakit kepala pemeliharaan yang besar.

Hai,

Terima kasih atas infonya @stevenschlansker. Saya sebenarnya tidak menggunakan Spring dengan JDBI untuk proyek saya saat ini, hanya disebutkan untuk perbandingan. Saya benar-benar mengerti tentang hal-hal AOP, hanya mencoba mencari jalan keluar yang berarti saya dapat membagi kode seperti yang saya inginkan - kelas abstrak dulu bekerja untuk saya, tetapi masuk akal untuk menjauh dari cglib

Saya berpikir sekarang saya hanya akan membangun kelas layanan saya ketika saya membutuhkannya, dengan begitu saya dapat membangunnya di dalam suatu transaksi.

         try (Handle handle = jdbi.open()) {
                handle.useTransaction(h -> {
                    AccountService accountService = new AccountServiceImpl(h.attach(AccountDao.class));                    
                    accountService.addAccount(a, u);
                });
          }

Saya pikir ini berarti saya dapat menguji unit layanan dengan Mock Dao, menjaga metode layanan tetap final dan tidak bergantung pada metode default di antarmuka untuk manajemen transaksi, dan overhead tambahan dari instantiasi kelas layanan seharusnya tidak berdampak signifikan.

Cukup balas dengan cepat contoh kode di komentar terakhir Anda: Anda dapat melewati blok try dan langsung menelepon jdbi.useTransaction() . Metode ini mengalokasikan pegangan sementara yang secara otomatis ditutup ketika panggilan balik Anda kembali.

Halo semuanya, ada PR #1579 -- mulai jdbi 3.10.0, CreateSqlObject dan onDemand seharusnya (akhirnya) bermain bersama dengan baik.

Apakah halaman ini membantu?
0 / 5 - 0 peringkat

Masalah terkait

anjeyy picture anjeyy  ·  3Komentar

qualidafial picture qualidafial  ·  3Komentar

nonameplum picture nonameplum  ·  5Komentar

Romqa picture Romqa  ·  5Komentar

buremba picture buremba  ·  5Komentar