Jdbi: اجعلCreateSqlObject أقل إرباكًا.

تم إنشاؤها على ١ فبراير ٢٠١٨  ·  20تعليقات  ·  مصدر: jdbi/jdbi

مشكلة
تقود وثائق JDBI الرسمية المطورين إلى الاعتقاد بأن التعليق التوضيحي CreateSqlObject هو آلية لإضافة دعم المعاملات إلى DAOs المتباينة.

http://jdbi.org/#__createsqlobject

وهو ما يبدو مضللًا بعض الشيء للسلوك الفعلي الذي يمكن أن يؤدي إلى أخطاء الحالة عند استخدامه مع آلية الإنشاء المفضلة jdbi.onDemand .

كانت هناك بعض الرسائل في المنتدى توضح هذا الالتباس:

طلب
لست متأكدًا من أفضل حل (حلول) لهذه المشكلة. بعض ما يتبادر إلى الذهن:

  • الاستنكار
  • توثيق أفضل لأوجه القصور
  • حل آخر لتقسيم الاستعلامات في DAOs متعددة ، ولكنه يسمح بدلالات المعاملات المنطقية لربطها معًا. على وجه التحديد ، في دعم مزيج من معاملات القراءة فقط / الكتابة.
cleanup improvement

التعليق الأكثر فائدة

آسف للتعليق على هذا مرة أخرى. هل هناك أي شيء في خارطة الطريق الخاصة بك من شأنه أن يساعد في ذلك؟

لا مشكلة على الإطلاق. من الواضح أن هذا مصدر ارتباك ، وشيء نرغب في إصلاحه - نريد حقًا التأكد من تصحيحه هذه المرة ، لأننا لم نقم آخر مرة :)

(كما ذكر من قبل svlada ، من الشائع جدًا في أطر العمل الأخرى استخدام Transactional على مستوى أعلى)

نعم ، ولكن بقدر ما أفهم ، يتم كل ذلك عبر خطافات AOP في إطار عمل DI (مثل Spring). نظرًا لأن JDBI لا "تلتف" جميع كائنات الخدمة الخاصة بك ، فليس لدينا أي فرصة لتقديم حلول شبيهة بـ AOP للكائنات التي لا نقوم بإنشائها بأنفسنا. حتى التنفيذ القديم cglib لن يلاحظ سوى @Transactional على daos نفسها ، فلن يعمل أبدًا في فئة خدمة منفصلة.

إذا كان ذلك مفيدًا ، فيمكننا التفكير في إضافة ارتباطات الربيع و / أو Guice AOP للسماح بالمعاملات على مستوى أعلى. لن يكون هذا جزءًا من core ولكن على سبيل المثال جزء من ملحقات spring . من غير المحتمل أن يكون هذا شيئًا يقفز عليه المطورون الأساسيون ، ولكن سيتم النظر بجدية في المساهمة في التضمين. ربما هذا يحل مشكلتك بطريقة "أنظف"؟

قررنا التبديل من cglib إلى Proxy في الغالب لأسباب تتعلق بالتوافق - Proxy هو واجهة برمجة تطبيقات jdk مدعومة ، بينما cglib (أو بشكل أكثر تحديدًا asm يميل

ال 20 كومينتر

شكرا على الإبلاغ عن هذا. أظن أننا سننتهي مع تغيير فاصل تقنيًا هنا ، لكنني أعتقد أن السلوك الحالي ضعيف بما يكفي لإجراء تغيير تقني في وقت مبكر من دورة الإصدار 3.x. يرجى وضع علامة على هذه المشكلة إذا كنت تعتمد على السلوك الحالي أو لا توافق على احتمال شحن تغيير صغير هنا.

فقط من أجل السياق _لماذا_ @CreateSqlObject لا يلعب بشكل جيد عند الطلب:

  • عند الطلب هو تجريد على المستوى الأساسي ، يتم تنفيذه باستخدام وكلاء. عند استدعاء طريقة على وكيل عند الطلب ، يتم إنشاء كائن SQL حقيقي وتفويض استدعاء الأسلوب إلى كائن SQL الحقيقي. يتم إغلاق المؤشر الذي يدعم هذا المثيل الحقيقي بعد إرجاع استدعاء أسلوب المفوض.
  • تم تنفيذ @CreateSqlObject باستخدام handle.attach(sqlObjectType) ، مع مقبض دعم كائن SQL الأصلي.

وبالتالي ، يتم إغلاق مقبض النسخ الخاص بكائن SQL الذي تم إنشاؤه قبل إرجاع كائن SQL.

لم يتم وضع أي مما سبق في حجر - إنها فقط كيفية تنفيذه الآن.

لست مقتنعًا بأنه سيتعين علينا كسر التوافق لإصلاح ذلك.

عُذْرًا ، لَا أَفْهَمْ ... مَا هِيَ الْمَشْكِلُونَ فِي onDemand و CreateSqlObject بالضبط؟ لم أواجه أي مشاكل في ذلك مطلقًا ، وهناك شيء يتعلق بشرحqualidafial هو فقط جعل ذهني يميل ...

image

يتم الحصول على اتصال لاستدعاء fooProxy.usecase . يستدعي Foo.usecase شريط أسلوب createSqlObject ، والذي يقوم بإرجاع شريط جديد بعد إرفاقه بالمقبض الحالي (حالة الاستخدام). يتم إغلاق مقبض حالة الاستخدام عند إرجاع حالة الاستخدام. طالما أنك لا تفعل أي شيء غير متزامن مع الشريط الذي تم إرجاعه ، فكيف يمكن أن تنتهي صلاحية مقبض الشريط مبكرًا جدًا؟ تقتصر دورة حياة شريط واستخدامه على جسم Foo.usecase ، كما هو الحال بالنسبة للمقبض ...

لا يمكنني التحدث إلى العناصر الداخلية لـ JDBI ، ولكن يمكنني التعليق على السلوك الذي رأيناه في الإنتاج.

في الأساس ، كانت الأمور على ما يرام في بيئاتنا الدنيا. ولكن مع زيادة الحمل في الإنتاج ، فإننا نرى باستمرار عبارات SQL لا يتم تنفيذها على عقدة قاعدة البيانات المناسبة. على سبيل المثال ، قد يصل تحديد "للقراءة فقط" إلى العقدة الرئيسية ، وسيؤدي "الإدخال" إلى الوصول إلى نسخة متماثلة للقراءة. بالإضافة إلى ذلك ، سوف نحصل على أخطاء إغلاق الاتصال حيث كانت في منتصف العملية.

بشكل أساسي ، يبدو أن CreateSqlObject قد أنشأ مشكلة أمان سلسلة الرسائل - حيث تم تغيير علامة القراءة فقط من خلال الطلبات المتنافسة.

لحل المشكلة - أزلنا جميع استخدامات @CreateSqlObject ، وانتهى بنا الأمر بشيء مثل هذا:

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

ثم نحن jdbi.onDemand(CombinedDao) ونستخدم الوصول عبر ذلك فقط.
التحول إلى هذا النمط ، رغم أنه ليس جميلًا ، تخلص من جميع الأخطاء المذكورة أعلاه في الإنتاج.

للحصول على معلومات فقط ، أفكر في العودة إلى JDBI 2 لأن كل التعليمات البرمجية الخاصة بي تستخدم onDemand مع فئات مجردة في الوقت الحالي ، ولا يمكنني معرفة كيفية إعادة الهيكلة بطريقة سأكون سعيدًا بها!

أميل إلى امتلاك واجهات خدمة ، مع فئات تطبيقات مجردة تحتوي على منطق ولكن بدون SQL ، مع طرق مشروحة على أنها معاملات ، باستخدام CreateSqlObject لتوفير الوصول إلى فئات DAO التي هي لغة SQL بحتة. مثال مبسط:

واجهه المستخدم

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

التنفيذ

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();
}

(يمكنك تخيل الداو)

يوفر هذا فصلًا رائعًا حقًا للمنطق والوصول إلى البيانات ، مع السماح بالمعاملات عبر طرق DAO المتعددة. من السهل كتابة وفهم اختبارات الوحدة للخدمة من خلال تنفيذ واجهات DAO.

لقد حاولت أن أفعل الشيء نفسه في JDBI 3 ، لكنني أعتقد أن هذا يعني أن تنفيذ فئة الخدمة الخاصة بي يجب أن يكون واجهة ذات طرق افتراضية لتلك التي تحتوي على منطق. لا يمكن جعل الطرق الافتراضية نهائية ، لذا فإن الكود لا يشعر بأنه موجز ، ويعطيني تحكمًا أقل في كيفية استخدام صفي.

هل هناك أي طريقة يمكنني من خلالها هيكلة الكود الخاص بي في JDBI 3 للحصول على طرق معاملات نهائية؟

أنت محق في أنه لا توجد طريقة لتعريف طريقة الواجهة على أنها نهائية.

ومع ذلك ، يمكنك تحديد التعليقات التوضيحية لطريقة SQL الخاصة بك على قدم المساواة مع @SqlQuery ، @SqlUpdate ، وما إلى ذلك ، وتقديم تنفيذ ثابت للطرق مع هذا التعليق التوضيحي.

ومع ذلك ، يمكنك تحديد التعليقات التوضيحية لطريقة SQL الخاصة بك على قدم المساواة مع @SqlQuery ، @SqlUpdate ، وما إلى ذلك ، وتقديم تنفيذ ثابت للطرق مع هذا التعليق التوضيحي.

شكرًا على الرد - أنا لا أتابع تمامًا كيف يساعدني ذلك. ما هي الطريقة الموصى بها للحصول على استفسارات في العديد من DAOs ، ولكن يتم تنفيذها في نفس المعاملة؟

طريقة للحصول على استفسارات في DAOs متعددة ، ولكن تنفيذها في نفس المعاملة؟

أنا شخصياً أستخدم شيئًا مثل

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();

يعمل بشكل جيد بالنسبة لي بهذه الطريقة. CreateSqlObject هو في الأساس مثل حقن التبعية في الربيع بواسطة getter / setter. لقد وضعت مثيلات onDemand في سياق الربيع الخاص بي مثل الفاصوليا بحيث تعمل تمامًا مثل الخدمات العادية ، ولا يحتاج المتصلون إلى معرفة jdbi أو واجهة التنفيذ.

image

الشيء المانع للنمط StockReductionCase الذي تراه هو مثال على "حقن التبعية" المتداخلة jdbi مع CreateSqlObject. لها تبعيات خاصة بها بداخلها ، تمامًا مثل تنفيذ الخدمة. إنها في الأساس خدمة من تلقاء نفسها ، أنا فقط أسميها الحالة بدلاً من الخدمة لتجنب التبعية الدورية من خلال وضع الحالات فقط (مطلوبة في أماكن متعددة) داخل الخدمات (المنطق الأعلى غير القابل لإعادة الاستخدام) والاستعلامات في أي منهما.

image

لقد سمعت تقارير متباينة عن نجاح الحصول على المعاملات في نطاقها بشكل صحيح مع @CreateSqlObject ، لا سيما عند دمجها مع onDemand() .

أضمن طريقة لتنفيذ كائنات SQL متعددة في نفس المعاملة هي تشغيل المعاملات عبر Handle.inTransaction() أو Jdbi.inTransaction() . داخل رد الاتصال ، فإن أي DAOs تم إنشاؤه عبر Handle.attach() سيكون جزءًا من معاملة المقبض:

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

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

شكرا على الردود
qualidafial يبدو هذا مثل الطريقة التي يجب أن أذهب بها ، لكنه يجعل من الصعب كتابة اختبارات الوحدة لفئة الخدمة. نظرًا لأنه يقوم بإنشاء Daos نفسه ، لا يمكنني استبدال Daos الوهمي لاختبارات الوحدة.

@ TheRealMarnes شكرًا أيضًا - يبدو هذا مشابهًا لما جربته ، لا أحب استخدام الأساليب الافتراضية حيث يمكن تجاوزها.

qualidafial أود أن أؤكد لك أنه لا يمكننا استخدام تعليق jdbi @Transaction على طرق الخدمة التي تستدعي طرق داو متعددة؟

إذا كان هذا الافتراض صحيحًا ، فإن هذا السلوك خطير جدًا دون توثيقه بشكل صحيح كجزء من التوثيق الرسمي. العدد الكبير من المطورين لديهم خبرة في Spring stack واستخدام التعليق التوضيحي @Transactional هو طريقة قياسية جدًا لدعم المعاملات عبر DAOs المتعددة.

svlada يعمل التعليق التوضيحي @Transaction _only_ على طرق كائنات SQL. عندما تقول "طرق الخدمة" لدي انطباع أنك تستخدم التعليق التوضيحي على شيء ما خارج تأثير جدبي ، على سبيل المثال فصل محقون.

مثال من مجموعة الاختبار الخاصة بنا:

<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();
    }
}

أريد أيضًا توضيح الأمر فيما يتعلق بـ onDemand() + @CreateSqlObject :

  • لا يجوز استخدام @CreateSqlObject DAOs إلا من داخل طرق DAO التي أنشأتها - كما في المثال أعلاه.
  • سيؤدي استدعاء fooDao.createBar().findById() إلى ظهور استثناء يفيد بأن الاتصال مغلق.

آسف للتعليق على هذا مرة أخرى. هل هناك أي شيء في خارطة الطريق الخاصة بك من شأنه أن يساعد في ذلك؟

ما زلت أفتقد حقًا القدرة على استخدام CreateSqlObject في فصول مجردة. لا تزال الحاجة إلى القيام بكل شيء في الواجهات تبدو مقيدة ؛ غالبًا ما أرغب في إجراء معاملات أسلوب نوع "الخدمة" ، ولكن هذا يعني أن فئات الخدمة الخاصة بي يجب أن تكون أيضًا واجهات تستخدم الأساليب الافتراضية وأبدأ في فقدان وضوح فصل الاهتمامات والطرق النهائية الملموسة وما إلى ذلك.

tamslinn بقدر ما يتعلق الأمر بمشكلتك مع الواجهات بدلاً من الفئات المجردة ، أعتقد أنه يمكنني القول بأمان أننا لن نعود من هذا القرار في المستقبل القريب. يستخدم Jdbi3 وكلاء jdk ، والذي يدعم الواجهات فقط.

لا أعتقد أن الوحدة المنفصلة المساهمة المبنية على cglib أو بعضها الذي يقدم هذه الميزة غير وارد تمامًا ، لكنها ليست مصدر قلق في الوقت الحالي.

إذا كانت بديهيات sqlobject تناسبك إلى هذا الحد ، فيمكنك دائمًا إعادة تشكيل خدماتك لتكون فئات عادية لا يتم تحسينها بواسطة jdbi بل يتم حقن مثيل Jdbi لاستخدام واجهة برمجة التطبيقات بطلاقة بدلاً من ذلك.

أما بالنسبة للموضوع الأصلي للسلوك بين دعم المعاملات ، onDemand ، وما إلى ذلك ، فقد أصبح التركيز أكثر فأكثر للعمل عليه ، على الرغم من أنني لا أعتقد أن لدينا خططًا محددة بالفعل حتى الآن. لكننا نصل إليه.

شكرا جزيلا على التحديث. من المنطقي تماما إعادة الوكلاء.
القيد المحدد بالنسبة لي هو عدم القدرة على بدء / إنهاء المعاملات خارج فئات JDBI.
(كما ذكر من قبل svlada ، من الشائع جدًا في أطر العمل الأخرى استخدام @Transactional في مستوى أعلى)
أنا حقًا أحب كل شيء آخر في JDBI ، لذلك سأستمر في ذلك ، ربما سأخرج بنمط تصميم يسعدني به لفصول الخدمة الخاصة بي :)

آسف للتعليق على هذا مرة أخرى. هل هناك أي شيء في خارطة الطريق الخاصة بك من شأنه أن يساعد في ذلك؟

لا مشكلة على الإطلاق. من الواضح أن هذا مصدر ارتباك ، وشيء نرغب في إصلاحه - نريد حقًا التأكد من تصحيحه هذه المرة ، لأننا لم نقم آخر مرة :)

(كما ذكر من قبل svlada ، من الشائع جدًا في أطر العمل الأخرى استخدام Transactional على مستوى أعلى)

نعم ، ولكن بقدر ما أفهم ، يتم كل ذلك عبر خطافات AOP في إطار عمل DI (مثل Spring). نظرًا لأن JDBI لا "تلتف" جميع كائنات الخدمة الخاصة بك ، فليس لدينا أي فرصة لتقديم حلول شبيهة بـ AOP للكائنات التي لا نقوم بإنشائها بأنفسنا. حتى التنفيذ القديم cglib لن يلاحظ سوى @Transactional على daos نفسها ، فلن يعمل أبدًا في فئة خدمة منفصلة.

إذا كان ذلك مفيدًا ، فيمكننا التفكير في إضافة ارتباطات الربيع و / أو Guice AOP للسماح بالمعاملات على مستوى أعلى. لن يكون هذا جزءًا من core ولكن على سبيل المثال جزء من ملحقات spring . من غير المحتمل أن يكون هذا شيئًا يقفز عليه المطورون الأساسيون ، ولكن سيتم النظر بجدية في المساهمة في التضمين. ربما هذا يحل مشكلتك بطريقة "أنظف"؟

قررنا التبديل من cglib إلى Proxy في الغالب لأسباب تتعلق بالتوافق - Proxy هو واجهة برمجة تطبيقات jdk مدعومة ، بينما cglib (أو بشكل أكثر تحديدًا asm يميل

مرحبا،

شكرا على المعلوماتstevenschlansker. أنا لا أستخدم Spring مع JDBI في الواقع لمشروعي الحالي ، فقد تم ذكره للتو للمقارنة. أنا أفهم تمامًا إعادة عناصر AOP ، فقط أحاول إيجاد طريقة للالتفاف حولها مما يعني أنه يمكنني تقسيم الكود بالطريقة التي أريدها - الفصول المجردة المستخدمة للعمل من أجلي ، ولكن من المنطقي تمامًا الابتعاد عن cglib

أفكر الآن أنني سأقوم فقط ببناء فصول الخدمة الخاصة بي عندما أحتاج إليها ، وبهذه الطريقة يمكنني تكوينها داخل معاملة.

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

أعتقد أن هذا يعني أنه يمكنني اختبار الخدمة باستخدام Mock Dao ، والحفاظ على أساليب الخدمة نهائية وعدم الاعتماد على الأساليب الافتراضية في الواجهات لإدارة المعاملات ، ولا ينبغي أن يكون للتكاليف الإضافية لإنشاء مثيل فئة الخدمة تأثير كبير.

مجرد رد سريع على مثال الرمز في تعليقك الأخير: يمكنك تخطي كتلة try والاتصال فقط بـ jdbi.useTransaction() مباشرة. تخصص هذه الطريقة مؤشرًا مؤقتًا يتم إغلاقه تلقائيًا عند عودة رد الاتصال.

مرحبًا بالجميع ، هناك PR # 1579 - اعتبارًا من jdbi 3.10.0 ، يجب أن يلعب CreateSqlObject و onDemand (أخيرًا) بشكل جيد معًا.

هل كانت هذه الصفحة مفيدة؟
0 / 5 - 0 التقييمات