Introdução ao ORMLite

Introdução ao ORMLite

*1. Visão geral *

ORMLite é uma biblioteca ORM leve para aplicativos Java. Ele fornece recursos padrão de uma ferramenta ORM para os casos de uso mais comuns,* sem a complexidade e sobrecarga adicionais de outras estruturas ORM. *

Suas principais características são:

  • definindo classes de entidade usando anotações Java

  • classes DAO extensíveis

  • uma classe QueryBuilder para criar consultas complexas

  • classes geradas para criar e eliminar tabelas de banco de dados

  • suporte para transações *suporte para relacionamentos de entidade

Nas próximas seções, veremos como podemos configurar a biblioteca, definir classes de entidade e executar operações no banco de dados usando a biblioteca.

===* 2. Dependências do Maven *

Para começar a usar o ORMLite, precisamos adicionar a dependência https://search.maven.org/classic/#search%7Cga%7C1%7Ca%3A%22ormlite-jdbc%22 [ormlite-jdbc] _ à nossa _pom.xml :

<dependency>
    <groupId>com.j256.ormlite</groupId>
    <artifactId>ormlite-jdbc</artifactId>
    <version>5.0</version>
</dependency>

Por padrão, isso também traz a dependência h2 . Em nossos exemplos, usaremos um banco de dados na memória H2, para que não precisemos de outro driver JDBC.

Se você deseja usar um banco de dados diferente, também precisará da dependência correspondente.

===* 3. Definindo classes de entidade *

Para configurar nossas classes de modelo para persistência com ORMLite, há duas anotações principais que podemos usar:

  • _ @ DatabaseTable_ para a classe de entidade

  • _ @ DatabaseField_ para as propriedades

Vamos começar definindo uma entidade Library com um campo name e um campo libraryId que também é uma chave primária:

@DatabaseTable(tableName = "libraries")
public class Library {

    @DatabaseField(generatedId = true)
    private long libraryId;

    @DatabaseField(canBeNull = false)
    private String name;

    public Library() {
    }

   //standard getters, setters
}

A anotação _ @ DatabaseTable_ possui um atributo tableName opcional que especifica o nome da tabela se não queremos confiar em um nome de classe padrão.

*Para cada campo que queremos persistir como uma coluna na tabela do banco de dados, precisamos adicionar a anotação _ @ DatabaseField_.

A propriedade que servirá como chave primária para a tabela pode ser marcada com os atributos id, generatedId ou generatedSequence. Em nosso exemplo, escolhemos o atributo generatedId = true para que a chave primária seja incrementada automaticamente.

Observe também que a classe precisa ter um construtor sem argumento com pelo menos package-scope visible.

Alguns outros atributos familiares que podemos usar para configurar os campos são columnName, dataType, defaultValue, canBeNull, unique.

====* 3.1 Usando anotações JPA *

Além das anotações específicas do ORMLite,* também podemos usar as anotações JPA-style para definir nossas entidades *.

O equivalente da entidade Library que definimos antes de usar as anotações padrão JPA seria:

@Entity
public class LibraryJPA {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private long libraryId;

    @Column
    private String name;

   //standard getters, setters
}

Embora o ORMLite reconheça essas anotações, ainda precisamos adicionar os _https://search.maven.org/classic/#search%7Cga%7C1%7Ca%3A%22javax.persistence-api%22 [javax.persistence-api] _ dependência de usá-los.

A lista completa de anotações JPA suportadas é @Entity, _ @ Id, _ _ @ Column, _ _ @ GeneratedValue, _ @OneToOne, _ @ ManyToOne, _ @JoinColumn, @Version.

*4. ConnectionSource *

Para trabalhar com os objetos definidos,* precisamos configurar um ConnectionSource *.

Para isso, podemos usar a classe JdbcConnectionSource, que cria uma única conexão, ou a JdbcPooledConnectionSource, que representa uma fonte de conexão em pool simples:

JdbcPooledConnectionSource connectionSource
  = new JdbcPooledConnectionSource("jdbc:h2:mem:myDb");

//work with the connectionSource

connectionSource.close();

Outra fonte de dados externa com melhor desempenho também pode ser usada, envolvendo-as em um objeto DataSourceConnectionSource.

*5. TableUtils Class *

Com base no ConnectionSource,* podemos usar métodos estáticos da classe TableUtils para executar operações no esquema do banco de dados *:

  • createTable () _ - para criar uma tabela com base em uma definição de classe de entidade ou em um objeto _DatabaseTableConfig

  • _createTableIfNotExists () _ - semelhante ao método anterior, exceto que ele só criará a tabela se não existir; isso funciona apenas em bancos de dados que o suportam

  • dropTable () _ - para excluir uma tabela *_clearTable () - para excluir os dados de uma tabela

Vamos ver como podemos usar TableUtils para criar a tabela para nossa classe Library:

TableUtils.createTableIfNotExists(connectionSource, Library.class);

===* 6. Objetos DAO *

ORMLite contém* uma classe DaoManager que pode criar objetos DAO para nós com a funcionalidade CRUD *:

Dao<Library, Long> libraryDao
  = DaoManager.createDao(connectionSource, Library.class);

O DaoManager não gera novamente a classe para cada chamada subsequente de _createDao (), _ mas, em vez disso, a reutiliza para obter melhor desempenho.

Em seguida, podemos executar operações CRUD em objetos Library:

Library library = new Library();
library.setName("My Library");
libraryDao.create(library);

Library result = libraryDao.queryForId(1L);

library.setName("My Other Library");
libraryDao.update(library);

libraryDao.delete(library);

O DAO também é um iterador que pode percorrer todos os registros:

libraryDao.forEach(lib -> {
    System.out.println(lib.getName());
});

No entanto, o ORMLite fechará apenas a instrução SQL subjacente se o loop for até o final. Uma exceção ou uma declaração de retorno pode causar um vazamento de recurso no seu código.

Por esse motivo, a documentação do ORMLite recomenda o uso do iterador diretamente:

try (CloseableWrappedIterable<Library> wrappedIterable
  = libraryDao.getWrappedIterable()) {
    wrappedIterable.forEach(lib -> {
        System.out.println(lib.getName());
    });
 }

Dessa forma, podemos fechar o iterador usando um bloco try-with-resources ou finally e evitar qualquer vazamento de recurso.

6.1 Classe DAO personalizada

Se queremos estender o comportamento dos objetos DAO fornecidos, podemos criar uma nova interface que estenda o tipo Dao:

public interface LibraryDao extends Dao<Library, Long> {
    public List<Library> findByName(String name) throws SQLException;
}

Então, vamos adicionar uma classe que implementa essa interface e estende a classe BaseDaoImpl:

public class LibraryDaoImpl extends BaseDaoImpl<Library, Long>
  implements LibraryDao {
    public LibraryDaoImpl(ConnectionSource connectionSource) throws SQLException {
        super(connectionSource, Library.class);
    }

    @Override
    public List<Library> findByName(String name) throws SQLException {
        return super.queryForEq("name", name);
    }
}
*Observe que precisamos ter um construtor deste formulário.*

Por fim, para usar nosso DAO personalizado, _ precisamos adicionar o nome da classe à definição de classe _Library:

@DatabaseTable(tableName = "libraries", daoClass = LibraryDaoImpl.class)
public class Library {
   //...
}

Isso nos permite usar o DaoManager para criar uma instância da nossa classe personalizada:

LibraryDao customLibraryDao
  = DaoManager.createDao(connectionSource, Library.class);

Em seguida, podemos usar todos os métodos da classe DAO padrão, bem como nosso método personalizado:

Library library = new Library();
library.setName("My Library");

customLibraryDao.create(library);
assertEquals(
  1, customLibraryDao.findByName("My Library").size());

*7. Definindo relacionamentos de entidade *

O ORMLite usa o conceito de objetos ou coleções "estrangeiros" para definir relacionamentos entre entidades para persistência.

Vamos dar uma olhada em como podemos definir cada tipo de campo.

====* 7.1 Campos de objetos estranhos *

Podemos criar um relacionamento individual unidirecional entre duas classes de entidade usando o atributo foreign = true em um campo anotado com _ @ DatabaseField_. O campo deve ser de um tipo que também persista no banco de dados.

Primeiro, vamos definir uma nova classe de entidade chamada Address:

@DatabaseTable(tableName="addresses")
public class Address {
    @DatabaseField(generatedId = true)
    private long addressId;

    @DatabaseField(canBeNull = false)
    private String addressLine;

   //standard getters, setters
}

Em seguida, podemos adicionar um campo do tipo Address à nossa classe Library que está marcada como foreign:

@DatabaseTable(tableName = "libraries")
public class Library {
   //...

    @DatabaseField(foreign=true, foreignAutoCreate = true,
      foreignAutoRefresh = true)
    private Address address;

   //standard getters, setters
}

Observe que também adicionamos mais dois atributos à anotação _ @ DatabaseField_: foreignAutoCreate e foreignAutoRefresh, ambos configurados para true.

O atributo foreignAutoCreate = true significa que, quando salvarmos um objeto Library com um campo address, o objeto externo também será salvo, desde que seu id não seja nulo e possua um atributo generatedId = true.

Se definirmos foreignAutoCreate como false, que é o valor padrão, precisaremos persistir explicitamente o objeto externo antes de salvar o objeto Library que o referencia.

Da mesma forma, o atributo foreignAutoRefresh = _ true_ especifica que, ao recuperar um objeto Library, o objeto externo associado também será recuperado. Caso contrário, precisaríamos atualizá-lo manualmente.

Vamos adicionar um novo objeto Library com um campo Address e chamar o libraryDao para persistir:

Library library = new Library();
library.setName("My Library");
library.setAddress(new Address("Main Street nr 20"));

Dao<Library, Long> libraryDao
  = DaoManager.createDao(connectionSource, Library.class);
libraryDao.create(library);

Em seguida, podemos chamar o addressDao para verificar se o Address também foi salvo:

Dao<Address, Long> addressDao
  = DaoManager.createDao(connectionSource, Address.class);
assertEquals(1,
  addressDao.queryForEq("addressLine", "Main Street nr 20")
  .size());

====* 7.2 Coleções estrangeiras *

Para o lado _ muitos de um relacionamento, podemos usar os tipos ForeignCollection <T> _ ou _Collection <T> _ com uma anotação _ @ ForeignCollectionField.

Vamos criar uma nova entidade Book como as acima, e depois adicionar um relacionamento um para muitos na classe Library:

@DatabaseTable(tableName = "libraries")
public class Library {
   //...

    @ForeignCollectionField(eager=false)
    private ForeignCollection<Book> books;

   //standard getters, setters
}

Além disso, é necessário adicionar um campo do tipo Library na classe Book:

@DatabaseTable
public class Book {
   //...
    @DatabaseField(foreign = true, foreignAutoRefresh = true)
    private Library library;

   //standard getters, setters
}
*O _ForeignCollection_ possui os métodos _add () _ e _remove () _* que operam nos registros do tipo _Book: _
Library library = new Library();
library.setName("My Library");
libraryDao.create(library);

libraryDao.refresh(library);

library.getBooks().add(new Book("1984"));

Aqui, criamos um objeto library e adicionamos um novo objeto Book ao campo books, que também o persiste no banco de dados.

*Observe que, como nossa coleção é marcada como carregada lentamente _ (ansioso = false), _ precisamos chamar o método _refresh () _* antes de poder usar o campo de livro.

Também podemos criar o relacionamento definindo o campo library na classe Book:

Book book = new Book("It");
book.setLibrary(library);
bookDao.create(book);

Para verificar se os dois objetos Book foram adicionados à library, podemos usar o método queryForEq () _ para encontrar todos os registros _Book com o _library_id especificado: _

assertEquals(2, bookDao.queryForEq("library_id", library).size());

Aqui, library_id é o nome padrão da coluna da chave estrangeira e a chave primária é inferida a partir do objeto library.

*8. QueryBuilder *

Cada DAO pode ser usado para obter um objeto QueryBuilder que podemos usar para criar consultas mais poderosas.

Esta classe contém métodos que correspondem a operações comuns usadas em uma consulta SQL, como: _selectColumns (), where (), groupBy (), _ _having (), countOf (), distinct (), orderBy (), join (). _

Vamos ver um exemplo de como podemos encontrar todos os registros de Library que têm mais de um Book associado:

List<Library> libraries = libraryDao.queryBuilder()
  .where()
  .in("libraryId", bookDao.queryBuilder()
    .selectColumns("library_id")
    .groupBy("library_id")
    .having("count(*) > 1"))
  .query();

9. Conclusão

Neste artigo, vimos como podemos definir entidades usando ORMLite, bem como os principais recursos da biblioteca que podemos usar para manipular objetos e seus bancos de dados relacionais associados.

O código fonte completo do exemplo pode ser encontrado over no GitHub.