Um guia para o wrapper JDBC do sql2o

Um guia para o wrapper JDBC do sql2o

1. Introdução

Neste tutorial, vamos dar uma olhada emSql2o, uma biblioteca pequena e rápida para acesso a banco de dados relacional em Java idiomático.

Vale a pena mencionar que, embora Sql2o funcione mapeando os resultados da consulta para POJOs (objetos Java simples e antigos),it’s not a complete ORM solution such as Hibernate.

2. Configuração Sql2o

Sql2o é um único arquivo jar que podemos adicionar facilmente às dependências do nosso projeto:


    org.sql2o
    sql2o
    1.6.0

Também usaremos HSQL, o banco de dados incorporado, em nossos exemplos; para acompanhar, podemos incluí-lo também:


    org.hsqldb
    hsqldb
    2.4.0
    test

O Maven Central hospeda a versão mais recente desql2oeHSQLDB.

3. Conectando ao banco de dados

Para estabelecer uma conexão, partimos de uma instância da classeSql2o:

Sql2o sql2o = new Sql2o("jdbc:hsqldb:mem:testDB", "sa", "");

Aqui, especificamos o URL, o nome de usuário e a senha da conexão como parâmetros do construtor.

O objetoSql2o é seguro para thread e podemos compartilhá-lo com o aplicativo.

3.1. Usando umDataSource

In most applications, we’ll want to use a*DataSource* ao invés de uma conexãoDriverManager bruta, talvez para alavancar um pool de conexão ou para especificar parâmetros de conexão adicionais. Não se preocupe, o Sql2o nos cobriu:

Sql2o sql2o = new Sql2o(datasource);

3.2. Trabalhando com conexões

Apenas instanciar um objetoSql2o não estabelece nenhuma conexão com o banco de dados.

Em vez disso,we use the open method to get a Connection object (observe que não é um JDBCConnection). ComoConnection éAutoCloseable,, podemos envolvê-lo em um blocotry-with-resources:

try (Connection connection = sql2o.open()) {
    // use the connection
}

4. Inserir e atualizar declarações

Agora vamos criar um banco de dados e colocar alguns dados nele. Ao longo do tutorial, usaremos uma tabela simples chamadaproject:

connection.createQuery(
    "create table project "
    + "(id integer identity, name varchar(50), url varchar(100))").executeUpdate();

executeUpdate retorna o objetoConnection para que possamos encadear várias chamadas. Então, se quisermos saber o número de linhas afetadas, usamosgetResult:

assertEquals(0, connection.getResult());

Vamos aplicar o padrão que acabamos de ver -createQueryeexecuteUpdate –for all DDL, INSERT and UPDATE statements.

4.1. Obtendo valores-chave gerados

Em alguns casos, porém,we might want to get generated key values back. Esses são os valores das colunas-chave que são calculados automaticamente (como ao usar o incremento automático em certos bancos de dados).

Fazemos isso em duas etapas. Primeiro, com um parâmetro adicional paracreateQuery:

Query query = connection.createQuery(
    "insert into project (name, url) "
    + "values ('tutorials', 'github.com/eugenp/tutorials')", true);

Então, invocandogetKey na conexão:

assertEquals(0, query.executeUpdate().getKey());

Se as chaves forem mais de uma, usamosgetKeys, que retorna uma matriz:

assertEquals(1, query.executeUpdate().getKeys()[0]);

5. Extração de dados do banco de dados

Vamos agora ao cerne da questão:SELECTqueries and the mapping of result sets to Java objects.

Primeiro, precisamos definir uma classe POJO com getters e setters para representar nossa tabela de projetos:

public class Project {
    long id;
    private String name;
    private String url;
    //Standard getters and setters
}

Então, como antes, vamos escrever nossa consulta:

Query query = connection.createQuery("select * from project order by id");

No entanto, desta vez, usaremos um novo método,executeAndFetch:

List list = query.executeAndFetch(Project.class);

Como podemos ver, o método toma a classe dos resultados como um parâmetro, para o qual o Sql2o mapeará as linhas do conjunto de resultados brutos provenientes do banco de dados.

5.1. Mapeamento de Coluna

Sql2o maps columns to JavaBean properties by name, não diferencia maiúsculas de minúsculas.

No entanto, as convenções de nomenclatura diferem entre os bancos de dados Java e os relacionais. Suponha que adicionemos uma propriedade de data de criação aos nossos projetos:

public class Project {
    long id;
    private String name;
    private String url;
    private Date creationDate;
    //Standard getters and setters
}

No esquema do banco de dados, provavelmente chamaremos a mesma propriedade decreation_date.

Obviamente, podemos usar o alias em nossas consultas:

Query query = connection.createQuery(
    "select name, url, creation_date as creationDate from project");

No entanto, é tedioso e perdemos a possibilidade de usarselect *.

Outra opção é instruir Sql2o a mapearcreation_date paracreationDate.. Ou seja, podemos informar a consulta sobre o mapeamento:

connection.createQuery("select * from project")
    .addColumnMapping("creation_date", "creationDate");

Isso é bom se usarmoscreationDate moderadamente, em um punhado de consultas; entretanto, quando usado extensivamente em um projeto maior, torna-se tedioso e sujeito a erros repetir o mesmo fato continuamente.

Felizmente, também podemosspecify mappings globally:

Map mappings = new HashMap<>();
mappings.put("CREATION_DATE", "creationDate");
sql2o.setDefaultColumnMappings(mappings);

Claro, isso fará com que cada instância decreation_date seja mapeada paracreationDate, então esse é outro motivo para nos esforçarmos para manter os nomes consistentes nas definições de nossos dados.

5.2. Resultados escalares

Às vezes, queremos extrair um único resultado escalar de uma consulta. Por exemplo, quando precisamos contar o número de registros.

Nesses casos, definir uma classe e iterar sobre uma lista que sabemos conter um único elemento é um exagero. Assim,Sql2o includes the executeScalar method:

Query query = connection.createQuery(
    "select count(*) from project");
assertEquals(2, query.executeScalar(Integer.class));

Aqui, estamos especificando o tipo de retorno comoInteger. No entanto, isso é opcional e podemos deixar o driver JDBC subjacente decidir.

5.3. Resultados Complexos

Às vezes, consultas complexas (como relatórios) podem não ser facilmente mapeadas para um objeto Java. Também podemos decidir que não queremos codificar uma classe Java para usar apenas em uma única consulta.

Assim,Sql2o also allows a lower-level, dynamic mapping to tabular data structures. obtemos acesso a isso usando o métodoexecuteAndFetchTable:

Query query = connection.createQuery(
    "select * from project order by id");
Table table = query.executeAndFetchTable();

Em seguida, podemos extrair uma lista de mapas:

List> list = table.asList();
assertEquals("tutorials", list.get(0).get("name"));

Como alternativa, podemos mapear os dados em uma lista de objetosRow, que são mapeamentos de nomes de colunas para valores, semelhantes aResultSets:

List rows = table.rows();
assertEquals("tutorials", rows.get(0).getString("name"));

6. Parâmetros de consulta de ligação

Many SQL queries have a fixed structure with a few parameterized portions. Podemos escrever ingenuamente essas consultas parcialmente dinâmicas com concatenação de strings.

No entanto, o Sql2o permite consultas parametrizadas, de modo que:

  • EvitamosSQL injection attacks

  • Permitimos que o banco de dados armazene em cache as consultas mais usadas e obtenha desempenho

  • Por fim, somos poupados da necessidade de codificar tipos complexos, como datas e horas

Portanto, podemos usar parâmetros nomeados com o Sql2o para obter todos os itens acima. Introduzimos parâmetros com dois pontos e os associamos com o métodoaddParameter:

Query query = connection.createQuery(
    "insert into project (name, url) values (:name, :url)")
    .addParameter("name", "REST with Spring")
    .addParameter("url", "github.com/eugenp/REST-With-Spring");
assertEquals(1, query.executeUpdate().getResult());

6.1. Vinculação de um POJO

Sql2o oferece uma forma alternativa de parâmetros de ligação: ou seja,by using POJOs como a fonte. Essa técnica é particularmente adequada quando uma consulta possui muitos parâmetros e todos eles se referem à mesma entidade. Então, vamos apresentarthe bind method:

Project project = new Project();
project.setName("REST with Spring");
project.setUrl("github.com/eugenp/REST-With-Spring");
connection.createQuery(
    "insert into project (name, url) values (:name, :url)")
    .bind(project)
    .executeUpdate();
assertEquals(1, connection.getResult());

7. Transações e consultas em lote

With a transaction, we can issue multiple SQL statements as a single operation that is atomic. Ou seja, é bem-sucedido ou falha em massa, sem resultados intermediários. De fato, as transações são um dos principais recursos dos bancos de dados relacionais.

Para abrir uma transação, usamos o métodobeginTransaction em vez do métodoopen que usamos até agora:

try (Connection connection = sql2o.beginTransaction()) {
    // here, the transaction is active
}

Quando a execução sai do bloco,Sql2o automatically rolls back the transaction se ainda estiver ativo.

7.1. Commit e reversão manual

No entanto,we can explicitly commit or rollback the transaction with the appropriate methods:

try (Connection connection = sql2o.beginTransaction()) {
    boolean transactionSuccessful = false;
    // perform some operations
    if(transactionSuccessful) {
        connection.commit();
    } else {
        connection.rollback();
    }
}

Observe que as instruções subsequentes deboth commit and rollback end the transaction. serão executadas sem uma transação, portanto, não serão revertidas automaticamente no final do bloco.

No entanto, podemos confirmar ou reverter a transação sem finalizá-la:

try (Connection connection = sql2o.beginTransaction()) {
    List list = connection.createQuery("select * from project")
        .executeAndFetchTable()
        .asList();
    assertEquals(0, list.size());
    // insert or update some data
    connection.rollback(false);
    // perform some other insert or update queries
}
// implicit rollback
try (Connection connection = sql2o.beginTransaction()) {
    List list = connection.createQuery("select * from project")
        .executeAndFetchTable()
        .asList();
    assertEquals(0, list.size());
}

7.2. Operações em lote

Quando precisamosissue the same statement many times with different parameters, executá-los em lote oferece um grande benefício de desempenho.

Felizmente, ao combinar duas das técnicas que descrevemos até agora - consultas e transações parametrizadas - é fácil executá-las em lote:

  • Primeiro, criamos a consulta apenas uma vez

  • Em seguida, vinculamos os parâmetros e chamamosaddToBatch para cada instância da consulta

  • Finalmente, chamamosexecuteBatch:

try (Connection connection = sql2o.beginTransaction()) {
    Query query = connection.createQuery(
        "insert into project (name, url) " +
        "values (:name, :url)");
    for (int i = 0; i < 1000; i++) {
        query.addParameter("name", "tutorials" + i);
        query.addParameter("url", "https://github.com/eugenp/tutorials" + i);
        query.addToBatch();
    }
    query.executeBatch();
    connection.commit();
}
try (Connection connection = sql2o.beginTransaction()) {
    assertEquals(
        1000L,
        connection.createQuery("select count(*) from project").executeScalar());
}

7.3. Lazy Fetch

Por outro lado,when a single query returns a great number of results, converting them all and storing them in a list is heavy on memory.

Portanto, o Sql2o suporta um modo lento, onde as linhas são retornadas e mapeadas uma de cada vez:

Query query = connection.createQuery("select * from project");
try (ResultSetIterable projects = query.executeAndFetchLazy(Project.class)) {
    for(Project p : projects) {
        // do something with the project
    }
}

Observe queResultSetIterable éAutoCloseable e deve ser usado comtry-with-resources para fechar oResultSet subjacente quando terminar.

8. Conclusões

Neste tutorial, apresentamos uma visão geral da biblioteca Sql2o e seus padrões de uso mais comuns. Mais informações podem ser encontradas emSql20 wiki on GitHub.

Além disso, a implementação de todos esses exemplos e fragmentos de código pode ser encontrada emthe GitHub project, que é um projeto Maven, portanto, deve ser fácil de importar e executar como está.