Best SQL Builder: use jOOQ en Android

Mejor constructor de SQL. Usando jOOQ en Android


Introduccion


Al desarrollar aplicaciones de Android, es bastante natural utilizar la base de datos SQLite como almacenamiento principal. Por lo general, las bases de datos en dispositivos móviles tienen esquemas muy simples y consisten en 10-15 tablas. Casi cualquier SQL Builder, ORM, o incluso la API SQLite simple es adecuada para tales casos.


Pero, por desgracia, no todos los desarrolladores tienen suerte, y a veces nos corresponde describir modelos de datos de gran tamaño, usar procedimientos almacenados, configurar el trabajo con tipos de datos personalizados o escribir 10 INNER JOIN en una consulta para una entidad muy gruesa. Entonces su fiel servidor no tuvo suerte, de donde apareció el material para este artículo. Bueno, los tiempos difíciles requieren medidas duras. Entonces, rodar jOOQ en Android.


Todo estaría bien, pero


Pero hay dos hechos con los que será necesario hacer frente. El primero de ellos yace al comienzo del trabajo con jOOQ: en la etapa ideológica. Para iniciar el proceso de generación de código, necesita, de hecho, obtener la base de datos a la que se conectará el complemento jooq. Este problema se resuelve fácilmente, creamos un proyecto de plantilla con una descripción de la tarea gradle para la generación, después de lo cual creamos una base de datos localmente, prescribimos rutas en las configuraciones, iniciamos el complemento y copiamos las fuentes recibidas a nuestro proyecto.


A continuación, digamos que generamos todas las clases necesarias. Simplemente no podremos copiarlos en un proyecto de Android; se requerirán dependencias adicionales, la primera de las cuales está en las anotaciones de Java. Dos opciones, ambas comunes. Agregue la biblioteca (org.glassfish: javax.annotation) o, use la herramienta maravillosa, encuentre y reemplace en el alcance.


Y parece que todo está bien, todos los ajustes preestablecidos están hechos, las clases se copian e importan al proyecto. Quizás incluso pueda iniciar la aplicación, y existe la posibilidad de que funcione. Si debe admitir el nivel de API de Android <24, no se deje engañar, este no es el final de nuestro viaje. El hecho es que jOOQ en este momento en la versión de código abierto usa Java 8 de muchas maneras, que, como saben, son amigos en Android de manera muy condicional. Este problema también se puede resolver de dos maneras: ya sea comprar jOOQ, escribir para apoyar y pedir con lágrimas una versión en Java 6 o Java 7 (lo tienen, a juzgar por los artículos en la red), o si usted, como el mío, no tiene una difícil. la necesidad de poseer todas las últimas innovaciones de la biblioteca, así como el deseo de pagar, es decir, la segunda forma. jOOQ comenzó a migrar a Java 8 no hace mucho tiempo. La última versión antes de la migración es 3.6.0, lo que significa que podemos usar el generador con el parámetro groovy version = '3.6.0' y admitir versiones anteriores de dispositivos.


Y lo último que les espera a los entusiastas que han recorrido este camino de desesperación. En Android, en principio, no hay JDBC, lo que significa que es hora de cruzar los dedos para buscar soluciones de terceros. Afortunadamente, hay una biblioteca similar: SQLDroid.


Eso es todo. Las principales etapas y acciones en ellos están pintadas con fluidez. Ahora pasemos al código, todo es bastante lógico en general, pero para reducir su tiempo, daré ejemplos de mi propio proyecto.


Generación de código


La configuración del complemento jOOQ se verá así:


 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


Dependencias requeridas:


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

Y ahora el código fuente de la clase wrapper para trabajar con jOOQ a través de SQLiteOpenHelper. En general, uno podría prescindir de él, pero sería mucho más conveniente (en mi opinión) utilizar de forma segura tanto la primera como la segunda 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 agregado mode = LazyThreadSafetyMode.NONE a la inicialización diferida, gracias konstantin_berkow


En lugar de una conclusión


Al final resultó que, configurar jOOQ en Android no es un proceso tan complicado. Es suficiente hacerlo una vez, y luego puede copiar y pegar de manera segura desde proyectos antiguos.


Y una pequeña bonificación que otorga jOOQ a quienes lo usan. Como puede ver en el ejemplo, al abrir una conexión, se utiliza el modo en caché. ¿Qué es tsimes? La API de Android SDK SQLite no proporciona la capacidad de trabajar con la base de datos en este modo, limitando en gran medida la organización de la comunicación entre procesos en las aplicaciones. Ahora, puede usar este modo de forma segura, que en sí mismo puede servir como la razón para la transición a este maravilloso marco.

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


All Articles