لقد كنت أتصفح ملف javadoc ومستند الموقع بالكامل ولكن لا يمكنني العثور على آلية بديلة لـ SQLLog. أستطيع أن أرى أن StatementContext بها بعض أساليب get * Sql ، ولكن لا يوجد بديل لـ DBI.setSQLLog. يتلقى TimingCollector العبارة statementcontext بحيث يمكن إجراؤها تقنيًا هناك ، ولكن يبدو أن هذا حل بديل نظرًا لاسم الواجهة.
أعتقد أننا أزلنا تجريد التسجيل هذا في JDBI3. إذا كنت ترغب في تمكين تسجيل SQL ، فأنت بحاجة إلى ذلك في تكوين تنفيذ SLF4J الخاص بك.
في SqlStatement
LOG.trace("Execute SQL \"{}\" in {}ms", sql, elapsedTime / 1000000L);
المشكلة هي أن هذا لا يترك لي الحرية في أن أقرر _كيف _ أريد أن يتم تسجيل استفساراتي: المستوى ، الصياغة ، الخام / المقدمة / المحللة sql ، الشروط ...
يقول موقع المستند أن TimingCollector لم يتم تجسيده بالكامل حتى الآن على أي حال ، لذا فإن الأفكار مرحب بها ، لذلك أعتقد أنني أقترح بموجب هذا إما إنشاء واجهة شقيقة لتسجيل الاستعلامات بدلاً من إضاعة الوقت ، أو إنشاء TimingCollector ليكون مخصصًا أيضًا لاستعلامات التسجيل.
نعم ، ليس لدينا واجهة رائعة لهذا الآن. ترك SQLLog
القديم الكثير مما هو مرغوب فيه ، لذا قمنا بإزالته بينما سنحت لنا الفرصة ، لصالح بناء شيء أفضل.
بعد أن أمضيت بضع ساعات في تحديث مشروعي إلى jdbi3 الآن ، أجد TimingCollector حلاً ليس نصف سيئ في مشروعي ، مع 3 نقاط بسيطة فقط: إنه خطأ لغويًا ، يتم تنفيذه _ بعد_ الاستعلام بدلاً من قبل (لذلك إذا كان الاستعلام يتسبب في استثناء لن يكون هناك تسجيل له) ، وأنا غير قادر على الحصول على الحجج المسماة / الموضعية من ربط statementcontext.
-
لا علاقة له بهذه المشكلة تمامًا ولكن فيما يتعلق بموضوع قضاء بعض الوقت في التحديث إلى jdbi3 ، أريد فقط أن أقول إنني أحبه. الطريقة التي تعمل بها فئة Jdbi كمستودع تكوين يحتفظ بالمكونات الإضافية رائعة. كان مشروعي حتى الآن مبنيًا على استخدام jdbi بطريقة محددة جدًا ، وعادةً ما عملت ولكن لم تكن jdbi تعمل بطريقة jdbi. نظرًا لأن jdbi3 أجبرني على القيام بكل شيء بطريقة jdbi3 عن طريق إزالة بعض الحريات ، فقد تعلمت بعض الآليات البديلة مثل الحجج ، والبناء على العمود بدلاً من تعيين الصفوف ، وتحسين واجهة برمجة التطبيقات بطلاقة ، وهو مجرد عالم جديد تمامًا من الملاءمة بمجرد الانتهاء من إعداد كل شيء. لم أدرك حتى مدى الإحساس الذي يجعله نموذجك تقنيًا وعمليًا حتى الآن. رفاق عمل رائع ، شكرًا على صنع هذا :)
شكرا لردود الفعل اللطيفة! هل تمانع في تجميع الطرق التي تريد استخدام SqlLog / TimingCollector بها في هذه التذكرة كبداية لمستند تصميم؟ سيكون من الجيد توضيح بعض حالات الاستخدام الفعلية ، حتى نتمكن من التأكد من حل المشكلات الفعلية.
رسم سريع للشفرة الزائفة جافا:
public interface Jdbi {
void registerSqlLogger(SqlLogger logger);
void registerSqlLoggerFactory(SqlLoggerFactory factory);
}
public interface SqlLogger {
void logBeforeExecution(StatementContext context);
void logAfterExecution(StatementContext context, long nanos);
void logException(StatementContext context, Exception ex);
}
public interface SqlLoggerFactory {
// the sqlObject, if any
Optional<SqlLogger> build(<strong i="6">@Nullable</strong> SqlObject extension, <strong i="7">@Nullable</strong> Class extensionClass);
}
public interface StatementContext {
// "select * from x where foo = 'bar'"
String getNormalizedSql();
// "select * from x where foo = ?"
// "select * from x where foo = :bar"
String getParamaterizedSql();
List<Object> getPositionalArgs();
Map<String, Object> getNamedArgs();
ParamsType getParamsType();
enum ParamsType {
NONE, POSITIONAL, NAMED
}
}
public class Main {
public static void main(String[] args) {
Jdbi jdbi = Jdbi.create();
jdbi.registerSqlLoggerFactory((sqlObject, sqlObjectClass) -> new SqlLogger() {
private final Logger logger = LoggerFactory.getLogger(sqlObject != null? sqlObjectClass: MyApp.class);
<strong i="10">@Override</strong>
void before(StatementContext context) {
String params;
switch (context.getParamsType()) {
case NONE:
params = "N/A";
case POSITIONAL:
params = context.getPositionalArgs().toString();
case NAMED:
params = toKeyValueString(context.getNamedArgs());
}
logger.debug(Markers.SQL, "query: {}, params: {}", context.getParameterizedSql(), params);
}
<strong i="11">@Override</strong>
void after(StatementContext context, long nanos) {
logger.trace(Markers.SQL, "query {} took {}ns", context.getNormalizedSql, nanos);
}
<strong i="12">@Override</strong>
void exception(StatementContext context, Exception ex) {
logger.error(Markers.SQL, context.getNormalizedSql(), e);
}
});
}
}
لما يستحق ، يسمح لك SqlStatementCustomizerFactory البسيط بتنفيذ ذلك من خلال التعليقات التوضيحية ، إما على طرق فردية أو واجهات كاملة. نعم ، إنها عرضة للنسيان ، وهذه مشكلة. لا أحتاج حاليًا إلى تسجيل دقيق للغاية للاستعلامات لأنها في الغالب مخصصة لتصحيح الأخطاء ، ويتم تشغيلها عندما أحتاج إلى ذلك ، ولكن يمكنك تجاوز قبل التنفيذ وبعد التنفيذ للتوقيت أيضًا. ليس من الصعب جدًا تمديده لأخذ فصل دراسي لإنشاء مثيل له بدلاً من ذلك.
<strong i="6">@NameBinding</strong>
@Retention(AnnotationRetention.RUNTIME)
@Target(AnnotationTarget.CLASS)
@SqlStatementCustomizingAnnotation(WriteQueryToLogFactory::class)
annotation class WriteQueryToLog
class WriteQueryToLogFactory : SqlStatementCustomizerFactory {
val log: Logger = LoggerFactory.getLogger("api.sql-statements")
override fun createForType(annotation: Annotation?, sqlObjectType: Class<*>?): SqlStatementCustomizer {
return SqlStatementCustomizer( { q -> q.addCustomizer(object: StatementCustomizer {
override fun beforeExecution(stmt: PreparedStatement, ctx: StatementContext) {
log.trace("Context: ${ctx.extensionMethod.type}.${ctx.extensionMethod.method.name}")
log.trace("Statement: ${stmt.toString()}")
}
})})
}
}
نعم ، هذا يعني أنه يمكنك الوصول إلى البيان بالكامل ويمكنك إفساده ، لكن هل هي مشكلة حقًا؟
pikzen يؤدي ذلك إلى تحمل العبء الأكبر ، ولكن اقتراحي كان حول جعل بيانات الاستعلام أكثر سهولة وإضافة طريقة onException أيضًا. لقد جربت أحد تلك التعليقات التوضيحية المخصصة في البداية أيضًا ، لكنني وجدت أنها محدودة للغاية بالنسبة لما أردت القيام به ، ولهذا السبب اقترحت كل هذا.
لقد ألقيت للتو نظرة أخرى ويبدو أن ParsedParameters و ParsedSql في منتصف الطريق إلى ما أود رؤيته. ParsedParameters يفتقد فقط طريقة للحصول على قيمة المعلمة من الاسم / الفهرس المقدم. أعتقد أن هذا سيجعل الصورة كاملة إلى حد كبير.
هل ألقيت نظرة على كائن StatementContext الذي تم تمريره إلى أداة التخصيص؟ يتيح لك الوصول إلى parsedSql
rawSql
و renderedSql
لـ SQL ، بالإضافة إلى attributes
يحتوي على معلمات الاستعلام.
أوافق على أن الحصول على شيء ما على استثناء سيكون أمرًا عمليًا ، لكن أليس من الأفضل تقديمه ببساطة من خلال تحسين واجهة StatementCustomizer الحالية وتكييف النواة؟
بالإضافة إلى
attributes
يحتوي على معلمات الاستعلام
هممم ، لا؟
المكان الوحيد للحصول على قيم معلمات الاستعلام AFAIK هو من خلال الانعكاس على الرابط:
Reflect.on(context.getBinding()).<HashMap<Integer, Argument>>get("named")
في الواقع ، سيئتي ، كنت أعني binding
، كنت أنظر إليه فقط من خلال مصحح الأخطاء ولم أدرك أنه لم يكن متاحًا من الخارج.
هناك عدد قليل من الأماكن التي تتوفر فيها ، نعم ، مواقع ملزمة / مسمى ، بيان.المعلمات القيم / المعلمات أنواع (على الرغم من أن هذا ليس بالضبط الأكثر عملية) وهي متاحة فقط من خلال التفكير. أنا
المعلمات المربوطة موجودة في getBinding()
. attributes
على سمات محددة من خلال define()
هي السمات المضمنة في علامات <attr>
في SQL.
التعليق الأكثر فائدة
بعد أن أمضيت بضع ساعات في تحديث مشروعي إلى jdbi3 الآن ، أجد TimingCollector حلاً ليس نصف سيئ في مشروعي ، مع 3 نقاط بسيطة فقط: إنه خطأ لغويًا ، يتم تنفيذه _ بعد_ الاستعلام بدلاً من قبل (لذلك إذا كان الاستعلام يتسبب في استثناء لن يكون هناك تسجيل له) ، وأنا غير قادر على الحصول على الحجج المسماة / الموضعية من ربط statementcontext.
-
لا علاقة له بهذه المشكلة تمامًا ولكن فيما يتعلق بموضوع قضاء بعض الوقت في التحديث إلى jdbi3 ، أريد فقط أن أقول إنني أحبه. الطريقة التي تعمل بها فئة Jdbi كمستودع تكوين يحتفظ بالمكونات الإضافية رائعة. كان مشروعي حتى الآن مبنيًا على استخدام jdbi بطريقة محددة جدًا ، وعادةً ما عملت ولكن لم تكن jdbi تعمل بطريقة jdbi. نظرًا لأن jdbi3 أجبرني على القيام بكل شيء بطريقة jdbi3 عن طريق إزالة بعض الحريات ، فقد تعلمت بعض الآليات البديلة مثل الحجج ، والبناء على العمود بدلاً من تعيين الصفوف ، وتحسين واجهة برمجة التطبيقات بطلاقة ، وهو مجرد عالم جديد تمامًا من الملاءمة بمجرد الانتهاء من إعداد كل شيء. لم أدرك حتى مدى الإحساس الذي يجعله نموذجك تقنيًا وعمليًا حتى الآن. رفاق عمل رائع ، شكرًا على صنع هذا :)