Introduction à ORMLite

1. Vue d’ensemble

ORMLite est une bibliothèque ORM légère pour les applications Java. Il fournit les fonctionnalités standard d’un outil ORM pour les cas d’utilisation les plus courants , sans la complexité et les frais généraux supplémentaires des autres frameworks ORM.

Ses principales caractéristiques sont:

  • définition de classes d’entités à l’aide d’annotations Java

  • classes DAO extensibles

  • une classe QueryBuilder pour la création de requêtes complexes

  • classes générées pour la création et la suppression de tables de base de données

  • support pour les transactions

  • support pour les relations d’entité

Dans les sections suivantes, nous verrons comment configurer la bibliothèque, définir des classes d’entités et effectuer des opérations sur la base de données à l’aide de la bibliothèque.

2. Dépendances Maven

Pour commencer à utiliser ORMLite, nous devons ajouter la dépendance ormlite-jdbc de notre pom.xml :

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

Par défaut, cela introduit également la dépendance https://search.maven.org/classic/#search%7Cga%7C1%7Ca%3A%22h2%22%20and%20g%3A%22com.h2database%22&amp ; h2 ]. Dans nos exemples, nous utiliserons une base de données H2 en mémoire, nous n’avons donc pas besoin d’un autre pilote JDBC.

Si vous souhaitez utiliser une base de données différente, vous aurez également besoin de la dépendance correspondante.

3. Définition des classes d’entité

Pour configurer nos classes de modèle pour la persistance avec ORMLite, vous pouvez utiliser deux annotations principales:

  • @ DatabaseTable pour la classe d’entité

  • @ DatabaseField pour les propriétés

Commençons par définir une entité Library avec un champ name et un champ libraryId qui est également une clé primaire:

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

    @DatabaseField(generatedId = true)
    private long libraryId;

    @DatabaseField(canBeNull = false)
    private String name;

    public Library() {
    }

   //standard getters, setters
}

L’annotation @ DatabaseTable a un attribut facultatif tableName qui spécifie le nom de la table si nous ne voulons pas nous fier à un nom de classe par défaut.

  • Pour chaque champ que nous voulons conserver en tant que colonne dans la table de base de données, nous devons ajouter l’annotation @ DatabaseField .

La propriété qui servira de clé primaire pour la table peut être marquée avec les attributs id , generatedId ou generatedSequence . Dans notre exemple, nous choisissons l’attribut generatedId = true pour que la clé primaire soit automatiquement incrémentée.

  • Notez également que la classe doit avoir un constructeur sans argument avec au moins une visibilité de package-scope . **

Quelques autres attributs familiers que nous pouvons utiliser pour configurer les champs sont columnName , dataType , defaultValue , canBeNull , unique .

3.1. Utiliser JPA Annotations

En plus des annotations spécifiques à ORMLite, nous pouvons également utiliser les annotations JPA-style pour définir nos entités .

L’équivalent de l’entité Library que nous avons définie avant d’utiliser les annotations standard JPA serait:

@Entity
public class LibraryJPA {

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

    @Column
    private String name;

   //standard getters, setters
}

Bien qu’ORMLite reconnaisse ces annotations, il est toujours nécessaire d’ajouter javax.persistence-api dépendance à les utiliser.

La liste complète des annotations JPA supportées est @ Entity , @ Id, @ Column, @ GeneratedValue, @ OneToOne , @ ManyToOne, @ JoinColumn , @ Version .

4. ConnectionSource

Pour travailler avec les objets définis, nous devons configurer un ConnectionSource .

Pour cela, nous pouvons utiliser la classe JdbcConnectionSource qui crée une connexion unique ou la JdbcPooledConnectionSource qui représente une source de connexion en pool simple:

JdbcPooledConnectionSource connectionSource
  = new JdbcPooledConnectionSource("jdbc:h2:mem:myDb");
//work with the connectionSource

connectionSource.close();

Vous pouvez également utiliser d’autres sources de données externes offrant de meilleures performances, en les enveloppant dans un objet DataSourceConnectionSource .

5. TableUtils Class

Sur la base de ConnectionSource , nous pouvons utiliser des méthodes statiques de la classe TableUtils pour effectuer des opérations sur le schéma de base de données :

  • createTable () - pour créer une table basée sur une définition de classe d’entité

ou un objet DatabaseTableConfig ** createTableIfNotExists () - similaire à la méthode précédente, sauf qu’elle

ne créera la table que si elle n’existe pas; cela ne fonctionne que sur bases de données qui le supportent ** dropTable () - pour supprimer une table

  • clearTable () - pour supprimer les données d’une table

Voyons comment utiliser TableUtils pour créer la table de notre classe Library :

TableUtils.createTableIfNotExists(connectionSource, Library.class);

6. DAO Objets

ORMLite contient une classe DaoManager pouvant créer pour nous des objets DAO avec la fonctionnalité CRUD :

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

DaoManager ne régénère pas la classe à chaque appel ultérieur de createDao (), mais le réutilise pour de meilleures performances.

Ensuite, nous pouvons effectuer des opérations CRUD sur les objets 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);

Le DAO est également un itérateur qui peut parcourir tous les enregistrements:

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

Cependant, ORMLite ne fermera l’instruction SQL sous-jacente que si la boucle va jusqu’au bout. Une exception ou une instruction return peut provoquer une fuite de ressource dans votre code.

Pour cette raison, la documentation ORMLite recommande d’utiliser directement l’itérateur:

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

De cette façon, nous pouvons fermer l’itérateur en utilisant un bloc try-with-resources ou finally et éviter toute fuite de ressource.

6.1. Classe DAO personnalisée

Si nous voulons étendre le comportement des objets DAO fournis, nous pouvons créer une nouvelle interface qui étend le type Dao :

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

Ajoutons ensuite une classe qui implémente cette interface et étend la 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);
    }
}
  • Notez que nous avons besoin d’un constructeur de cette forme. **

Enfin, pour utiliser notre DAO personnalisé, nous devons ajouter le nom de la classe à la définition de la classe Library :

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

Cela nous permet d’utiliser le DaoManager pour créer une instance de notre classe personnalisée:

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

Nous pouvons ensuite utiliser toutes les méthodes de la classe standard DAO , ainsi que notre méthode personnalisée:

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

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

7. Définir les relations entre entités

ORMLite utilise le concept d’objets ou de collections «étrangers» pour définir les relations entre entités pour la persistance.

Voyons comment définir chaque type de champ.

7.1. Champs d’objet étranger

Nous pouvons créer une relation unidirectionnelle un-à-un entre deux classes d’entités en utilisant l’attribut foreign = true sur un champ annoté avec @ DatabaseField . Le champ doit être d’un type qui a également persisté dans la base de données.

Commençons par définir une nouvelle classe d’entités appelée Address :

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

    @DatabaseField(canBeNull = false)
    private String addressLine;

   //standard getters, setters
}

Ensuite, nous pouvons ajouter un champ de type Address à notre classe Library qui est marqué comme foreign :

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

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

   //standard getters, setters
}

Notez que nous avons également ajouté deux attributs supplémentaires à l’annotation @ DatabaseField : foreignAutoCreate et foreignAutoRefresh , tous deux définis sur true. .

L’attribut foreignAutoCreate = true signifie que, lorsque nous sauvegardons un objet Library avec un champ address , l’objet étranger sera également enregistré, à condition que son id ne soit pas nul et qu’il ait un attribut generatedId = true .

Si nous définissons foreignAutoCreate sur false , qui est la valeur par défaut, il ne sera pas nécessaire de conserver explicitement l’objet étranger avant de sauvegarder l’objet Library qui le référence.

De même, l’attribut _foreignAutoRefresh = true indique que, lors de la récupération d’un objet Library_ , l’objet étranger associé est également récupéré. Sinon, nous aurions besoin de l’actualiser manuellement.

Ajoutons un nouvel objet Library avec un champ Address et appelons le libraryDao pour les conserver:

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);

Ensuite, nous pouvons appeler addressDao pour vérifier que l’adresse adresse a bien été enregistrée

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

7.2. Collections étrangères

Pour le côté multiple d’une relation, nous pouvons utiliser les types ForeignCollection <T> ou Collection <T> avec une annotation @ ForeignCollectionField .

Créons une nouvelle entité Book comme celle ci-dessus, puis ajoutons une relation un à plusieurs dans la classe Library :

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

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

   //standard getters, setters
}

De plus, il est nécessaire d’ajouter un champ de type Library dans la classe Book :

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

   //standard getters, setters
}
  • Le ForeignCollection a add () et remove () méthodes ** qui fonctionnent sur les enregistrements de type Book:

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

libraryDao.refresh(library);

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

Nous avons créé un objet library , puis ajouté un nouvel objet Book au champ books , qui le conserve également dans la base de données.

  • Notez que puisque notre collection est marquée comme étant chargé paresseusement (eager = false), nous devons appeler la méthode refresh () avant de pouvoir utiliser le champ book.

Nous pouvons également créer la relation en définissant le champ library dans la classe Book :

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

Pour vérifier que les deux objets Book sont ajoutés à la bibliothèque , nous pouvons utiliser la méthode queryForEq () pour rechercher tous les enregistrements Book avec le library id:

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

Library id est ici le nom par défaut de la colonne de clé étrangère et la clé primaire est déduite de l’objet library__.

8. QueryBuilder

Chaque DAO peut être utilisé pour obtenir un objet QueryBuilder que nous pouvons ensuite utiliser pour créer des requêtes plus puissantes.

Cette classe contient des méthodes correspondant aux opérations courantes utilisées dans une requête SQL, telles que: selectColumns (), where (), groupBy (), having (), countOf (), distinct (), orderBy (), join ().

Voyons un exemple de la façon dont nous pouvons trouver tous les enregistrements Library auxquels plusieurs Book sont associés:

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

9. Conclusion

Dans cet article, nous avons vu comment définir des entités avec ORMLite, ainsi que les principales fonctionnalités de la bibliothèque que nous pouvons utiliser pour manipuler des objets et leurs bases de données relationnelles associées.

Le code source complet de l’exemple peut être trouvé à over sur GitHub .