Desde a versão GA do N1QL, recebemos muitas perguntas sobre a transferência de conteúdo de um banco de dados SQL para o Couchbase. Há muitas maneiras diferentes de fazer isso. Hoje, escolhi a que provavelmente é a mais simples. Transformarei cada linha de cada tabela em um JsonDocument e o armazenarei no Couchbase. Farei meu teste com o Postgres e seu conjunto de dados de amostra inspirado no MySQL Amostra de Sakila. Usarei Java, mas as diretrizes apresentadas aqui são aplicáveis a outras linguagens.
Conexão a um banco de dados SQL em execução
Como estou usando Java, implementarei o Spring Boot e seu pacote JDBC, que cuida da conexão com o banco de dados para mim. Tudo o que preciso fazer é definir as dependências e propriedades corretas para configurar o JdbcTemplate. Esse objeto facilita a execução de uma consulta SQL.
Dependências
Para garantir que tudo esteja configurado de forma organizada e automática, você precisa das seguintes dependências:
|
1 2 3 4 5 6 7 |
dependências { compilar "org.springframework.boot:spring-boot-starter", "org.springframework.boot:spring-boot-starter-data-jpa", "org.postgresql:postgresql:9.4-1206-jdbc4" } |
Estou testando com o Postgres, mas você pode adicionar qualquer outro driver compatível com o Spring JDBC. O spring-boot-starter-data-jpa permitirá que eu injete o JdbcTemplate pré-configurado.
Configuração
Para garantir que a estrutura do Spring encontre seu banco de dados, adicione as seguintes propriedades ao seu arquivo de configuração (por exemplo, src/main/resources/application.properties).
|
1 2 3 4 5 6 7 8 9 10 |
mola.jpa.banco de dados=POSTGRESQL mola.fonte de dados.plataforma=postgres mola.jpa.show-sql=verdadeiro mola.jpa.hibernar.ddl-automático=criar-queda mola.banco de dados.driverClassName=org.postgresql.Motorista mola.fonte de dados.url=jdbc:postgresql://192.168.99.100:5432/dvdrental mola.fonte de dados.nome de usuário=postgres mola.fonte de dados.senha=senha |
É claro que você precisará ajustar isso de acordo com o banco de dados que estiver usando. Aqui estou usando o Postgres em execução em 192.168.99.100 com a porta padrão 5432. O nome do banco de dados que quero usar é dvdrental.
Código
Se tudo estiver configurado corretamente, você poderá injetar o JdbcTemplate e começar a consultar o banco de dados SQL.
|
1 2 3 4 5 6 7 8 9 10 |
@Com fio automático JdbcTemplate jdbcTemplate; @Substituir público vazio doStuff() lançamentos Exceção { Cordas sql = "SELECT id FROM table" (selecionar id da tabela); Longo id = jdbcTemplate.queryForObject(sql, Longo.classe); } |
Conexão com o Couchbase
Meu objetivo é mover o conteúdo de um banco de dados SQL para o Couchbase, portanto, também precisamos de uma conexão com o Couchbase.
Dependências
Para trabalhar com o Couchbase em seu projeto Java, é necessário adicionar a seguinte dependência:
|
1 2 3 4 5 |
dependências { compilar "com.couchbase.client:java-client:2.2.3" } |
Isso lhe dará acesso ao Couchbase Java SDK.
Configuração
Uma configuração básica do Couchbase requer basicamente três propriedades: um endereço IP do servidor, um nome de bucket e uma senha de bucket. Fazer isso no Spring Boot seria parecido com o seguinte:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 |
@Configuração público classe Banco de dados { @Valor("${hostname}") privado Cordas nome do host; @Valor("${bucket}") privado Cordas balde; @Valor("${senha}") privado Cordas senha; público @Feijão Aglomerado agrupamento() { retorno CouchbaseCluster.criar(nome do host); } público @Feijão Balde balde() { retorno agrupamento().openBucket(balde, senha); } } |
As propriedades nome do host, bucket e senha podem ser adicionadas diretamente ao arquivo de propriedades do aplicativo.
|
1 2 3 4 5 6 7 8 |
# Nomes de host, lista separada por vírgulas do IP ou nome de host do nó do Couchbase nomes de host: localhost,127.0.0.1 # Nome da caçamba balde: padrão Senha da caçamba # senha: |
Código
Com o Couchbase, o nível de granularidade equivalente de um banco de dados seria um bucket, que é onde você armazena documentos. Com a configuração anterior, você pode simplesmente injetar um bucket e começar a brincar.
|
1 2 3 4 5 6 7 8 9 |
@Com fio automático Balde balde; @Substituir público vazio doStuff() lançamentos Exceção { JsonDocument doc = balde.obter("chave"); } |
Tabelas
Neste ponto, você tem uma conexão com um banco de dados SQL e com o Couchbase. Agora podemos começar a mover as coisas. A maneira mais fácil de mover dados é considerar cada linha de cada tabela como um documento.
Obtendo o esquema SQL
Vamos começar obtendo o esquema do banco de dados automaticamente usando o JdbcTemplate. O objeto interessante aqui é DatabaseMetaDataque pode nos fornecer a estrutura completa do banco de dados. A API não é a mais fácil de usar, mas pelo menos está documentada.
Vou mapear o resultado da consulta DatabaseMetaData para uma lista de tabelas e colunas. Para isso, criei a seguinte classe Java:
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 |
público classe Tabela { privado Cordas nome; privado Lista<Coluna> colunas = novo ArrayList<Coluna>(); privado Cordas PrimaryKey; público Tabela(Cordas tableName) { este.nome = tableName; } público vazio setPrimaryKey(Cordas PrimaryKey) { este.PrimaryKey = PrimaryKey; } público vazio addColumn(Cordas nome, int tipo) { colunas.adicionar(novo Coluna(nome, tipo)); } público Cordas getName() { retorno nome; } público Lista<Coluna> getColumns() { retorno colunas; } público Cordas getPrimaryKey() { retorno PrimaryKey; } público JsonObject toJsonObject() { JsonObject obj = JsonObject.criar(); JsonArray jsonColumns = JsonArray.criar(); para (Coluna col : colunas) { jsonColumns.adicionar(col.toJsonObject()); } obj.colocar("tableName", nome); obj.colocar("primaryKey", PrimaryKey); obj.colocar("colunas", jsonColumns); retorno obj; } } público classe Coluna { privado Cordas nome; privado int tipo; público Coluna(Cordas nome, int tipo) { este.nome = nome; este.tipo = tipo; } público Cordas getName() { retorno nome; } público int getType() { retorno tipo; } público JsonObject toJsonObject() { JsonObject obj = JsonObject.criar(); obj.colocar("name" (nome), nome); obj.colocar("tipo", tipo); retorno obj; } } |
Definitivamente, não é o código mais empolgante de se escrever, mas no final você obtém uma representação JSON das tabelas do seu banco de dados SQL.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 |
público vazio getDatabaseSchema() lançamentos Exceção { // obter objetos Medatadata do banco de dados para recuperar o esquema de tabelas DatabaseMetaData databaseMetadata = jdbcTemplate.getDataSource().getConnection().getMetaData(); Lista<Cordas> tableNames = novo ArrayList<Cordas>(); // Obter nomes de tabelas ResultSet resultado = databaseMetadata.getTables(catálogo, schemaPattern, tableNamePattern, tipos); enquanto (resultado.próxima()) { Cordas nome da tabela = resultado.getString(3); Cordas tableType = resultado.getString(4); // certifique-se de que importamos apenas tabelas (em vez de visualizações, contadores etc.) se (!nome da tabela.isEmpty() && "TABLE".iguais(tableType)) { tableNames.adicionar(nome da tabela); registro.depurar("Importará a tabela " + nome da tabela); } } // Mapear o esquema de tabelas para objetos de tabela Mapa<Cordas, Tabela> tabelas = novo HashMap<Cordas, Tabela>(); JsonObject tablesSchema = JsonObject.criar(); para (Cordas tableName : tableNames) { resultado = databaseMetadata.getColumns(catálogo, schemaPattern, tableName, columnNamePattern); Tabela tabela = novo Tabela(tableName); enquanto (resultado.próxima()) { Cordas columnName = resultado.getString(4); // Mapeia para o enum JDBCType int columnType = resultado.getInt(5); tabela.addColumn(columnName, columnType); } resultado = databaseMetadata.getPrimaryKeys(catálogo, schemaPattern, tableName); enquanto (resultado.próxima()) { Cordas columnName = resultado.getString(4); tabela.setPrimaryKey(columnName); } tabelas.colocar(tableName, tabela); tablesSchema.colocar(tableName, tabela.toJsonObject()); } JsonDocument schemaDoc = JsonDocument.criar(tablesSchemaId, tablesSchema); JsonDocument doc = balde.upsert(schemaDoc); } |
Conteúdo
Aqui está a parte divertida. É aqui que começamos a mapear uma linha de tabela para um JsonDocument. A seção anterior nos coloca em um estado em que podemos recuperar o nome de todas as tabelas. A partir de um nome de tabela, podemos criar uma consulta SQL que retorna todas as linhas da tabela.
O Spring tem um mecanismo que permite que você defina um RowMapper. Para cada linha retornada pela consulta, você pode retornar o objeto que desejar. Como estou usando o Couchbase, quero um JsonDocument.
A seguir, um exemplo de implementação. Esse RowMapper precisa de um objeto Table previamente definido; portanto, temos de implementar o método mapRow. Há várias coisas que precisamos fazer aqui.
A primeira tarefa é criar uma chave exclusiva. Como o escopo das linhas é definido por tabelas, alguns IDs podem ser exatamente os mesmos para linhas em tabelas diferentes. Mas os documentos têm escopo por bucket, portanto, precisamos criar uma chave de documento exclusiva que reflita o ID da linha e o nome da tabela. Para manter o controle da origem do documento, também adicionarei um campo _tableName para o nome da tabela.
Em seguida, a etapa interessante vem do mapeamento de tipos. O JSON é compatível com menos tipos do que um banco de dados SQL, portanto, temos que fazer algumas conversões aqui. É isso que o método getJsonTypedValue faz. Ele garante que a maioria dos tipos JDBC possa ser convertida em um tipo JSON nativo (String, número, booleano, matriz, objeto, nulo). No final, temos um JsonDocument que pode ser salvo no Couchbase.
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
público classe JSONRowMapper implementa Mapeador de linhas<Documento> { Tabela tabela; público JSONRowMapper(Tabela tabela) { este.tabela = tabela; } público JsonDocument mapRow(ResultSet rs, int rowNum) lançamentos SQLException { Cordas id = tabela.getName() + "::" + rs.getString(tabela.getPrimaryKey()); JsonObject obj = JsonObject.criar(); obj.colocar("_tableName", tabela.getName()); para (Coluna col : tabela.getColumns()) { Objeto valor = getJsonTypedValue(col.tipo, rs.getObject(col.nome), col.nome); obj.colocar(col.nome, valor); } retorno JsonDocument.criar(id, obj); } público Objeto getJsonTypedValue(int tipo, Objeto valor, Cordas nome) lançamentos SQLException { se (valor == nulo) { retorno nulo; } Tipo JDBCT atual = Tipo JDBCT.valueOf(tipo); interruptor (atual) { caso TEMPO: Carimbo de data/hora carimbo de data/hora = (Carimbo de data/hora) valor; retorno carimbo de data/hora.getTime(); caso TIMESTAMP_WITH_TIMEZONE: Carimbo de data/hora ts = (Carimbo de data/hora) valor; JsonObject tsWithTz = JsonObject.criar(); tsWithTz.colocar("timestamp" (registro de data e hora), ts.getTime()); tsWithTz.colocar("timezone", ts.getTimezoneOffset()); retorno tsWithTz; caso DATA: Data sqlDate = (Data) valor; retorno sqlDate.getTime(); caso DECIMAL: caso NUMÉRICO: BigDecimal bigDecimal = (BigDecimal) valor; retorno bigDecimal.doubleValue(); caso ARRAY: Matriz matriz = (Matriz) valor; Objeto[] objetos = (Objeto[]) matriz.getArray(); retorno JsonArray.de(objetos); caso BINÁRIO: caso BLOB: caso LONGVARBINARY: retorno Base64.getEncoder().encodeToString((byte[]) valor); caso OUTROS: caso JAVA_OBJECT: // específico do banco de dados, padrão para o valor String retorno valor.toString(); padrão: retorno valor; } } } |
Com esse RowMapper, as coisas ficam muito fáceis. Podemos fazer um loop pelo nome da tabela, executar a consulta e salvar os resultados no Couchbase. Fazer isso de forma síncrona seria assim:
|
1 2 3 4 5 6 7 8 9 10 11 12 |
para (Cordas tableName : tableNames) { Cordas sql = "select * from " + tableName + ";"; Lista<JsonDocument> rs = jdbcTemplate.consulta(sql, novo JSONRowMapper(tabelas.obter(tableName))); se (!rs.isEmpty()) { para (JsonDocument doc : rs) { balde.upsert(doc); } } } balde.upsert(schemaDoc); |
Mas eu prefiro a versão assíncrona:
|
1 2 3 4 5 6 7 8 |
Observável.de(tableNames).flatMap(s -> { Cordas sql = Cordas.formato("Select * from %s;", s); retorno Observável.de(jdbcTemplate.consulta(sql, novo JSONRowMapper(tabelas.obter(s)))); }) // comece com um jsonDocument contendo as tabelas a serem importadas. .começar com(schemaDoc).mapa plano(doc -> asyncBucket.upsert(doc)); |
Aqui não estou usando todo o potencial do Rx; dê uma olhada em esta função que grava um documento no Couchbase e lida com o tempo limite e o gerenciamento de erros.
Por conveniência, agrupei todas as etapas implementadas e mostradas anteriormente em um arquivo projeto único. Tudo o que você precisa fazer é verificar se o arquivo de propriedades está configurado corretamente e executar o importador:
|
1 2 3 |
$ ./caixa/couchbase-java-importador myConfiguration.propriedades |
Dê uma olhada no LEIAME para obter mais informações.
Conclusão
Hoje, aprendemos a mover o conteúdo SQL para o Couchbase, mas ainda há algum trabalho a ser feito. Na próxima vez, falarei sobre como mover a lógica de negócios do SQL para a camada de aplicativos.