Meilleur constructeur SQL - utilisez jOOQ sur Android

Meilleur constructeur SQL. Utiliser jOOQ sur Android


Présentation


Lors du développement d'applications Android, il est tout à fait naturel d'utiliser la base de données SQLite comme stockage principal. Habituellement, les bases de données sur les appareils mobiles ont des schémas très simples et se composent de 10 à 15 tableaux. Presque n'importe quel SQL Builder, ORM ou même l'API SQLite nue convient à de tels cas.


Mais, hélas, tous les développeurs n'ont pas de chance, et parfois c'est notre lot de décrire de grands modèles de données, d'utiliser des procédures stockées, de configurer le travail avec des types de données personnalisés ou d'écrire 10 INNER JOIN dans une requête pour une entité très épaisse. Votre fidèle serviteur n'a donc pas eu de chance, d'où le matériel de cet article est apparu. Eh bien, les temps difficiles nécessitent des mesures sévères. Alors, lancez jOOQ sur Android.


Tout irait bien, mais


Mais il y a deux faits auxquels il faudra faire face. Le premier d'entre eux est en attente au tout début du travail avec jOOQ: au stade idéologique. Afin de lancer le processus de génération de code, vous devez en fait mettre la main sur la base de données à laquelle le plugin jooq se connectera. Ce problème est facilement résolu, nous créons un projet de modèle avec une description de la tâche gradle pour la génération, après quoi nous créons une base de données localement, prescrivons des chemins dans les configurations, lançons le plug-in et copions les sources reçues dans notre projet.


Ensuite, disons que nous avons généré toutes les classes nécessaires. Nous ne pourrons tout simplement pas les copier dans un projet Android - des dépendances supplémentaires seront nécessaires, dont la première concerne les annotations javax. Deux options, toutes deux courantes. Soit ajoutez la bibliothèque (org.glassfish: javax.annotation), ou - utilisez le merveilleux outil - trouvez et remplacez dans la portée.


Et il semblerait que tout va bien, tous les préréglages sont effectués, les classes sont copiées et importées dans le projet. Vous pouvez peut-être même lancer l'application, et il y a une chance qu'elle fonctionne. Si vous devez prendre en charge Android API Level <24 - ne vous laissez pas berner, ce n'est pas la fin de notre voyage. Le fait est que jOOQ en ce moment dans la version open-source utilise Java 8 à bien des égards, qui, comme vous le savez, est très conditionnellement amis sur Android. Ce problème peut également être résolu de deux manières: soit acheter jOOQ, écrire pour prendre en charge et demander en larmes une version en Java 6 ou Java 7 (ils l'ont, à en juger par les articles sur le réseau), ou si vous, comme le mien, n'en avez pas de difficile la nécessité de posséder toutes les dernières innovations de la bibliothèque, ainsi que l'envie de payer, c'est-à-dire la seconde voie. jOOQ a commencé à migrer vers Java 8 il n'y a pas si longtemps. La dernière version avant la migration est la 3.6.0, ce qui signifie que nous pouvons utiliser le générateur avec le paramètre groovy version = '3.6.0' et prendre en charge les anciennes versions des appareils.


Et la dernière chose qui attend les passionnés qui ont parcouru ce chemin du désespoir. Dans Android, en principe, il n'y a pas de JDBC, ce qui signifie qu'il est temps de croiser les doigts pour rechercher des solutions tierces. Heureusement, il existe une bibliothèque similaire - SQLDroid.


C’est tout. Les principales étapes et les actions sur eux sont parfaitement peintes. Passons maintenant au code, tout est assez logique en général, mais afin de réduire votre temps, je vais donner des exemples de mon propre projet.


Génération de code


La configuration du plugin jOOQ ressemblera à ceci:


 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


Dépendances requises:


 implementation "org.jooq:jooq:$jooq_version" implementation "org.sqldroid:sqldroid:$sqldroid_version" implementation "org.glassfish:javax.annotation:$javax_annotations_version" 

Et maintenant, le code source de la classe wrapper pour travailler avec jOOQ via SQLiteOpenHelper. En général, on pourrait s'en passer, mais il serait beaucoup plus pratique (à mon avis) d'utiliser en toute sécurité à la fois la première et la seconde API.


 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 ajouté mode = LazyThreadSafetyMode.NONE à l'initialisation paresseuse, merci konstantin_berkow


Au lieu d'une conclusion


Il s'est avéré que la configuration de jOOQ dans Android n'est pas un processus si compliqué. Il suffit de le faire une fois, puis vous pouvez faire du copier-coller en toute sécurité à partir d'anciens projets.


Et un petit bonus qui donne jOOQ à ceux qui l'utilisent. Comme vous pouvez le voir dans l'exemple, lors de l'ouverture d'une connexion, le mode mis en cache est utilisé. Qu'est-ce que les tsimes? L'API Android SDK SQLite ne permet pas de travailler avec la base de données dans ce mode, ce qui nous limite fortement dans l'organisation de la communication interprocessus dans les applications. Maintenant, vous pouvez utiliser ce mode en toute sécurité, ce qui en soi peut être la raison de la transition vers ce merveilleux cadre.

Source: https://habr.com/ru/post/fr422303/


All Articles