أفضل منشئ SQL. استخدام jOOQ على Android
مقدمة
عند تطوير تطبيقات Android ، من الطبيعي جدًا استخدام قاعدة بيانات SQLite كمخزن رئيسي. عادة ، تحتوي قواعد البيانات على الأجهزة المحمولة على مخططات بسيطة للغاية وتتكون من 10-15 جدولًا. تقريبا أي منشئ SQL ، ORM ، أو حتى API SQLite العارية مناسبة لمثل هذه الحالات.
ولكن ، للأسف ، ليس كل المطورين محظوظين ، وأحيانًا يكون من دواعي سرورنا وصف نماذج البيانات الكبيرة ، أو استخدام الإجراءات المخزنة ، أو تكوين العمل مع أنواع البيانات المخصصة أو كتابة 10 INNER JOIN في استعلام عن كيان سميك جدًا. لذلك لم يكن خادمك المخلص محظوظًا ، حيث ظهرت منه مادة هذا المقال. حسنًا ، تتطلب الأوقات العصيبة إجراءات قاسية. لذا ، لف jOOQ على Android.
كل شيء سيكون على ما يرام ، ولكن
ولكن هناك حقيقتان سيكون من الضروري التعامل معها. أولها ينتظر في بداية العمل مع jOOQ: في المرحلة الأيديولوجية. من أجل البدء في عملية إنشاء التعليمات البرمجية ، تحتاج ، في الواقع ، إلى الحصول على قاعدة البيانات التي سيتصل بها البرنامج المساعد jooq. يتم حل هذه المشكلة بسهولة ، نقوم بإنشاء مشروع قالب مع وصف مهمة التدرج للإنشاء ، وبعد ذلك نقوم بإنشاء قاعدة بيانات محليًا ، ونصف المسارات في التكوينات ، ونطلق المكون الإضافي وننسخ المصادر المستلمة إلى مشروعنا.
بعد ذلك ، لنفترض أننا أنشأنا جميع الفئات اللازمة. لن نتمكن من نسخها إلى مشروع Android - ستحتاج إلى تبعيات إضافية ، أولها على تعليقات جافا سكريبت. خياران ، كلاهما شائع. قم بإضافة المكتبة (org.glassfish: javax.annotation) ، أو - استخدم الأداة الرائعة - ابحث واستبدل في النطاق.
ويبدو أن كل شيء على ما يرام ، ويتم تنفيذ جميع الإعدادات المسبقة ، ويتم نسخ الفئات واستيرادها في المشروع. ربما يمكنك حتى تشغيل التطبيق ، وهناك احتمال أن يعمل. إذا طُلب منك دعم Android API Level <24 - فلا تنخدع ، فهذه ليست نهاية رحلتنا. والحقيقة هي أن jOOQ في الوقت الحالي في إصدار مفتوح المصدر يستخدم Java 8 بطرق عديدة ، كما تعلمون ، هو أصدقاء على Android بشكل مشروط للغاية. يمكن أيضًا حل هذه المشكلة بطريقتين: إما شراء jOOQ ، والكتابة للدعم ، وطلب إصدار دموع في Java 6 أو Java 7 (لديهم ، بناءً على المقالات على الشبكة) ، أو إذا لم يكن لديك ، مثل ، خاصتي صعبة. الحاجة إلى امتلاك أحدث ابتكارات المكتبة ، بالإضافة إلى الرغبة في الدفع ، أي الطريقة الثانية. بدأ jOOQ بالترحيل إلى Java 8 منذ وقت ليس ببعيد. الإصدار الأخير قبل الترحيل هو 3.6.0 ، مما يعني أنه يمكننا استخدام المولد مع المعلمة groovy version = '3.6.0'
ودعم الإصدارات القديمة من الأجهزة.
وآخر شيء ينتظر المتحمسين الذين سلكوا طريق اليأس هذا. في Android ، من حيث المبدأ ، لا يوجد JDBC ، مما يعني أن الوقت قد حان لتجاوز أصابعك للبحث عن حلول خارجية. لحسن الحظ ، هناك مكتبة مماثلة - SQLDroid.
هذا كل شيء. يتم رسم المراحل والإجراءات الرئيسية عليها بطلاقة. الآن دعنا ننتقل إلى الرمز ، كل شيء منطقي بشكل عام بشكل عام ، ولكن لتقليل وقتك ، سأقدم أمثلة من مشروعي الخاص.
إنشاء التعليمات البرمجية
سيبدو إعداد المكون الإضافي jOOQ كما يلي:
buildScript { repositories { mavenCentral() } dependencies { classpath "nu.studer:gradle-jooq-plugin:$jooq_plugin_version" } } apply plugin: 'nu.studer.jooq' dependencies { jooqRuntime "org.xerial:sqlite-jdbc:$xerial_version" } jooq { version = '3.6.0' edition = 'OSS' dev(sourceSets.main) { jdbc { driver = 'org.sqlite.JDBC' url = 'jdbc:sqlite:/Path/To/Database/database.db3' } generator { name = 'org.jooq.util.DefaultGenerator' strategy { name = 'org.jooq.util.DefaultGeneratorStrategy' } database { name = 'org.jooq.util.sqlite.SQLiteDatabase' } generate { relations = true deprecated = false records = true immutablePojos = true fluentSetters = true } target { packageName = 'com.example.mypackage.data.database' } } } }
Android
التبعيات المطلوبة:
implementation "org.jooq:jooq:$jooq_version" implementation "org.sqldroid:sqldroid:$sqldroid_version" implementation "org.glassfish:javax.annotation:$javax_annotations_version"
والآن الكود المصدر لفئة الغلاف للعمل مع jOOQ من خلال SQLiteOpenHelper. بشكل عام ، يمكن للمرء الاستغناء عنها ، ولكن سيكون أكثر ملاءمة (في رأيي) استخدام كل من واجهات برمجة التطبيقات الواحدة والثانية بأمان.
class DatabaseAdapter(private val context: Context) : SQLiteOpenHelper(context, DATABASE_NAME, null, DATABASE_VERSION) { companion object { private const val DATABASE_NAME = "database" private const val DATABASE_VERSION = 1 @JvmStatic private val OPEN_OPTIONS = mapOf( "cache" to "shared", "journal_mode" to "WAL", "synchronous" to "ON", "foreign_keys" to "ON") } val connectionLock: ReentrantLock = ReentrantLock(true) val configuration: Configuration by lazy(mode = LazyThreadSafetyMode.NONE) { connectionLock.withLock { // ensure the database exists, // all upgrades are performed, // and connection is ready to be set val database = context.openOrCreateDatabase( DATABASE_NAME, Context.MODE_PRIVATE, null) if (database.isOpen) { database.close() } // register SQLDroid driver to be used for establishing connections // with our database DriverManager.registerDriver( Class.forName("org.sqldroid.SQLDroidDriver") .newInstance() as Driver) DefaultConfiguration() .set(SQLiteSource( context, OPEN_OPTIONS, "database", arrayOf("databases"))) .set(SQLDialect.SQLITE) } } override fun onCreate(db: SQLiteDatabase) { // acquire monitor until the database connection is created // this is important as otherwise transactions might be tryingg to run // concurrently that will lead to crashes connectionLock.withLock { // TODO: Create tables } } override fun onOpen(db: SQLiteDatabase) { // acquire monitor until the database connection is established // this is important as otherwise transactions might be tryingg to run // concurrently that will lead to crashes connectionLock.withLock { super.onOpen(db) } } override fun onUpgrade(db: SQLiteDatabase, oldVersion: Int, newVersion: Int) { // acquire monitor until the database is upgraded // this is important as otherwise transactions might be tryingg to run // concurrently that will lead to crashes connectionLock.withLock { } } infix inline fun <reified T> transaction(noinline f: (Configuration) -> T): Observable<T> = Observable.create { emitter -> val tryResult = Try { connectionLock.withLock { DSL.using(configuration).transactionResult(f) } } when (tryResult) { is Try.Success -> { emitter.onNext(tryResult.value) emitter.onComplete() } is Try.Failure -> { emitter.onError(tryResult.exception) } } } fun invalidate() { connectionLock.withLock { // TODO: Drop tables, vacuum and create tables } } private class SQLiteSource(val context: Context, val options: Map<String, String>, val database: String, val fragments: Array<out String>): DroidDataSource() { override fun getConnection(): Connection = openConnection(options) private fun openConnection(options: Map<String, String> = emptyMap()): Connection { return DriverManager.getConnection(StringBuilder().apply { append("jdbc:sqldroid:") append(context.applicationInfo.dataDir) append("/") append(buildFragments(fragments)) append(database) append("?") append(buildOptions(options)) }.toString()) } private fun buildFragments(fragments: Array<out String>) = when (fragments.isEmpty()) { true -> "" false -> "${fragments.joinToString("/")}/" } private fun buildOptions(options: Map<String, String>) = options.mapTo(mutableListOf<String>()) { entry -> "${entry.key}=${entry.value}" } .joinToString(separator = "&") } }
UPD: mode = LazyThreadSafetyMode.NONE
مضاف mode = LazyThreadSafetyMode.NONE
إلى التهيئة البطيئة ، بفضل konstantin_berkow
بدلا من الاستنتاج
كما اتضح ، فإن إعداد jOOQ في Android ليس بهذه العملية المعقدة. يكفي القيام بذلك مرة واحدة ، وبعد ذلك يمكنك القيام بالنسخ واللصق بأمان من المشاريع القديمة.
ومكافأة صغيرة تعطي JOOQ لأولئك الذين يستخدمونها. كما ترى من المثال ، عند فتح اتصال ، يتم استخدام وضع التخزين المؤقت. ما هو tsimes؟ لا توفر واجهة برمجة تطبيقات Android SDK SQLite API القدرة على العمل مع قاعدة البيانات في هذا الوضع ، مما يقيدنا كثيرًا في تنظيم عملية الاتصال بين العمليات في التطبيقات. الآن - يمكنك استخدام هذا الوضع بأمان ، والذي يمكن أن يكون في حد ذاته بمثابة سبب للانتقال إلى هذا الإطار الرائع.