Melhor SQL Builder - use o jOOQ no Android

Melhor construtor de SQL. Usando o jOOQ no Android


1. Introdução


Ao desenvolver aplicativos Android, é bastante natural usar o banco de dados SQLite como armazenamento principal. Geralmente, os bancos de dados em dispositivos móveis têm esquemas muito simples e consistem em 10 a 15 tabelas. Quase todo SQL Builder, ORM ou mesmo a API SQLite simples é adequado para esses casos.


Mas, infelizmente, nem todos os desenvolvedores têm sorte e, às vezes, é nosso objetivo descrever grandes modelos de dados, usar procedimentos armazenados, configurar o trabalho com tipos de dados personalizados ou gravar 10 consultas internas a uma entidade muito espessa. Portanto, seu servo leal não teve sorte, do qual o material deste artigo apareceu. Bem, tempos difíceis exigem medidas duras. Então, role o jOOQ no Android.


Tudo ficaria bem, mas


Mas há dois fatos com os quais será necessário lidar. O primeiro deles está à espera no início do trabalho com o jOOQ: no estágio ideológico. Para iniciar o processo de geração de código, você precisa, de fato, obter o banco de dados ao qual o plug-in jooq se conectará. Esse problema é facilmente resolvido. Criamos um projeto de modelo com uma descrição da tarefa gradle para geração, após o qual criamos um banco de dados localmente, prescrevemos caminhos nas configurações, ativamos o plug-in e copiamos as fontes recebidas em nosso projeto.


Em seguida, digamos que geramos todas as classes necessárias. Apenas não podemos copiá-los em um projeto Android - serão necessárias dependências adicionais, a primeira das quais em anotações em javax. Duas opções, ambas comuns. Adicione a biblioteca (org.glassfish: javax.annotation) ou - use a maravilhosa ferramenta - encontre e substitua no escopo.


E parece que está tudo bem, todas as predefinições são feitas, as classes são copiadas e importadas para o projeto. Talvez você possa até iniciar o aplicativo, e há uma chance de que ele funcione. Se você precisa oferecer suporte ao nível da API do Android <24 - não se deixe enganar, este não é o fim de nossa jornada. O fato é que, no momento, o jOOQ na versão de código aberto usa o Java 8 de várias maneiras, o que, como você sabe, é amigo do Android de maneira muito condicional. Esse problema também pode ser resolvido de duas maneiras: compre o jOOQ, escreva para o suporte e solicite chorosa uma versão em Java 6 ou Java 7 (eles têm, a julgar pelos artigos da rede), ou se você, como o meu, não tiver uma pergunta difícil a necessidade de possuir todas as inovações mais recentes da biblioteca, bem como o desejo de pagar, ou seja, o segundo caminho. O jOOQ começou a migrar para o Java 8 não faz muito tempo. A última versão antes da migração é 3.6.0, o que significa que podemos usar o gerador com o parâmetro groovy version = '3.6.0' e oferecer suporte a versões mais antigas de dispositivos.


E a última coisa que aguarda entusiastas que caminharam por esse caminho de desespero. No Android, em princípio, não há JDBC, o que significa que é hora de cruzar os dedos para procurar soluções de terceiros. Felizmente, existe uma biblioteca semelhante - SQLDroid.


Só isso. As principais etapas e ações sobre eles são pintadas com fluência. Agora vamos ao código, tudo é bastante lógico em geral, mas, para reduzir o seu tempo, darei exemplos de meu próprio projeto.


Geração de código


A configuração do plugin jOOQ terá a seguinte aparência:


 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


Dependências necessárias:


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

E agora o código fonte da classe wrapper para trabalhar com o jOOQ através do SQLiteOpenHelper. Em geral, alguém poderia ficar sem ele, mas seria muito mais conveniente (na minha opinião) usar com segurança as APIs, uma e a segunda.


 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 adicionado mode = LazyThreadSafetyMode.NONE à inicialização lenta, obrigado konstantin_berkow


Em vez de uma conclusão


Como se viu, configurar o jOOQ no Android não é um processo tão complicado. Basta fazer isso uma vez e, em seguida, você pode copiar e colar com segurança de projetos antigos.


E um pequeno bônus que dá jOOQ para quem o usa. Como você pode ver no exemplo, ao abrir uma conexão, o modo em cache é usado. O que é tsimes? A API SQLite do SDK do Android não fornece a capacidade de trabalhar com o banco de dados nesse modo, limitando-nos bastante na organização da comunicação entre processos nos aplicativos. Agora - você pode usar esse modo com segurança, que por si só pode servir como o motivo da transição para esse maravilhoso quadro.

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


All Articles