Bester SQL Builder - Verwenden Sie jOOQ unter Android

Bester SQL Builder. Verwenden von jOOQ unter Android


Einführung


Bei der Entwicklung von Android-Anwendungen ist es ganz natürlich, die SQLite-Datenbank als Hauptspeicher zu verwenden. Normalerweise haben Datenbanken auf Mobilgeräten sehr einfache Schemata und bestehen aus 10-15 Tabellen. Fast jeder SQL Builder, ORM oder sogar die reine SQLite-API ist für solche Fälle geeignet.


Leider haben nicht alle Entwickler Glück, und manchmal ist es unser Los, große Datenmodelle zu beschreiben, gespeicherte Prozeduren zu verwenden, die Arbeit mit benutzerdefinierten Datentypen zu konfigurieren oder 10 INNER JOIN in eine Abfrage für eine sehr dicke Entität zu schreiben. Ihr treuer Diener hatte also kein Glück, aus dem das Material für diesen Artikel hervorging. Nun, harte Zeiten erfordern harte Maßnahmen. Also, rollen Sie jOOQ auf Android.


Alles wäre gut, aber


Es gibt jedoch zwei Tatsachen, mit denen man sich auseinandersetzen muss. Der erste von ihnen wartet zu Beginn der Arbeit mit jOOQ: auf der ideologischen Stufe. Um den Codegenerierungsprozess zu starten, müssen Sie tatsächlich die Datenbank abrufen, mit der das jooq-Plugin eine Verbindung herstellen wird. Dieses Problem lässt sich leicht lösen. Wir erstellen ein Vorlagenprojekt mit einer Beschreibung der Gradle-Aufgabe für die Generierung. Anschließend erstellen wir lokal eine Datenbank, schreiben Pfade in den Konfigurationen vor, starten das Plug-In und kopieren die empfangenen Quellen in unser Projekt.


Nehmen wir als nächstes an, wir haben alle erforderlichen Klassen generiert. Wir können sie nur nicht in ein Android-Projekt kopieren. Es sind zusätzliche Abhängigkeiten erforderlich, von denen sich die erste auf Javax-Anmerkungen bezieht. Zwei Optionen, beide alltäglich. Fügen Sie entweder die Bibliothek hinzu (org.glassfish: javax.annotation) oder - verwenden Sie das wunderbare Tool - suchen und ersetzen Sie sie im Bereich.


Und es scheint, dass alles in Ordnung ist, alle Voreinstellungen erledigt sind, die Klassen kopiert und in das Projekt importiert werden. Vielleicht können Sie die Anwendung sogar starten, und es besteht die Möglichkeit, dass sie funktioniert. Wenn Sie die Android-API-Stufe <24 unterstützen müssen - lassen Sie sich nicht täuschen, ist dies nicht das Ende unserer Reise. Tatsache ist, dass jOOQ derzeit in der Open-Source-Version Java 8 auf vielfältige Weise verwendet, was, wie Sie wissen, unter Android sehr bedingt befreundet ist. Dieses Problem kann auch auf zwei Arten gelöst werden: entweder jOOQ kaufen, an den Support schreiben und unter Tränen nach einer Version in Java 6 oder Java 7 fragen (nach den Artikeln im Netzwerk), oder wenn Sie, wie meine, keine schwierige haben die Notwendigkeit, die neuesten Innovationen der Bibliothek zu besitzen, sowie der Wunsch zu zahlen, das heißt der zweite Weg. jOOQ hat vor nicht allzu langer Zeit mit der Migration auf Java 8 begonnen. Die letzte Version vor der Migration ist 3.6.0, was bedeutet, dass wir den Generator mit dem Parameter groovy version = '3.6.0' und ältere Versionen von Geräten unterstützen können.


Und das Letzte, was Enthusiasten erwartet, die diesen Weg der Verzweiflung gegangen sind. In Android gibt es im Prinzip kein JDBC, was bedeutet, dass es Zeit ist, die Daumen zu drücken, um nach Lösungen von Drittanbietern zu suchen. Glücklicherweise gibt es eine ähnliche Bibliothek - SQLDroid.


Das ist alles. Die Hauptbühnen und Aktionen auf ihnen sind fließend gemalt. Kommen wir nun zum Code, alles ist im Allgemeinen ziemlich logisch, aber um Ihre Zeit zu verkürzen, werde ich Beispiele aus meinem eigenen Projekt geben.


Codegenerierung


Das Setup des jOOQ-Plugins sieht folgendermaßen aus:


 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


Erforderliche Abhängigkeiten:


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

Und jetzt der Quellcode der Wrapper-Klasse für die Arbeit mit jOOQ über SQLiteOpenHelper. Im Allgemeinen könnte man darauf verzichten, aber es wäre (meiner Meinung nach) viel bequemer, sowohl die eine als auch die zweite API sicher zu verwenden.


 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 zur verzögerten Initialisierung hinzugefügt, danke konstantin_berkow


Anstelle einer Schlussfolgerung


Wie sich herausstellte, ist das Einrichten von jOOQ in Android kein so komplizierter Prozess. Es reicht aus, dies einmal zu tun, und dann können Sie sicher aus alten Projekten kopieren und einfügen.


Und ein kleiner Bonus, der denjenigen, die ihn nutzen, jOOQ gibt. Wie Sie dem Beispiel entnehmen können, wird beim Öffnen einer Verbindung der zwischengespeicherte Modus verwendet. Was ist Tsimes? Die Android SDK SQLite-API bietet in diesem Modus keine Möglichkeit, mit der Datenbank zu arbeiten, was uns bei der Organisation der Interprozesskommunikation in Anwendungen stark einschränkt. Jetzt können Sie diesen Modus sicher verwenden, der an sich als Grund für den Übergang zu diesem wunderbaren Framework dienen kann.

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


All Articles