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 la surcharge supplémentaires d'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 créer des requêtes complexes
  • classes générées pour créer et supprimer des tables de base de données
  • support pour les transactions
  • prise en charge des relations d'entité

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

2. Dépendances de Maven

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

 com.j256.ormlite ormlite-jdbc 5.0 

Par défaut, cela introduit également la dépendance 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 autre base de données, vous aurez également besoin de la dépendance correspondante.

3. Définition des classes d'entités

Pour configurer nos classes de modèle pour la persistance avec ORMLite, nous pouvons 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 tableName facultatif 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 afin 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 la portée du package .

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

3.1. Utilisation des annotations JPA

En plus des annotations spécifiques à ORMLite, nous pouvons également utiliser des annotations de style JPA 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, nous devons encore ajouter la dépendance javax.persistence-api pour les utiliser.

La liste complète des annotations JPA prises en charge est @ Entity , @Id, @Column, @GeneratedValue, @ OneToOne , @ManyToOne, @ JoinColumn , @ Version .

4. ConnectionSource

Pour travailler avec les objets définis, nous devons configurer une 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 groupée simple:

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

D'autres sources de données externes avec de meilleures performances peuvent également être utilisées, en les encapsulant dans un objet DataSourceConnectionSource .

5. Classe TableUtils

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 les bases de données qui le prennent en charge
  • dropTable () - pour supprimer une table
  • clearTable () - pour supprimer les données d'une table

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

TableUtils.createTableIfNotExists(connectionSource, Library.class);

6. Objets DAO

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

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

The DaoManager doesn't regenerate the class for each subsequent call of createDao(), but instead reuses it for better performance.

Next, we can perform CRUD operations on Library objects:

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

The DAO is also an iterator that can loop through all the records:

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

However, ORMLite will only close the underlying SQL statement if the loop goes all the way to the end. An exception or a return statement may cause a resource leak in your code.

For that reason, the ORMLite documentation recommends we use the iterator directly:

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

This way, we can close the iterator using a try-with-resources or a finally block and avoid any resource leak.

6.1. Custom DAO Class

If we want to extend the behavior of the DAO objects provided, we can create a new interface which extends the Dao type:

public interface LibraryDao extends Dao { public List findByName(String name) throws SQLException; }

Then, let's add a class that implements this interface and extends the BaseDaoImpl class:

public class LibraryDaoImpl extends BaseDaoImpl implements LibraryDao { public LibraryDaoImpl(ConnectionSource connectionSource) throws SQLException { super(connectionSource, Library.class); } @Override public List findByName(String name) throws SQLException { return super.queryForEq("name", name); } }

Note that we need to have a constructor of this form.

Finally, to use our custom DAO, we need to add the class name to the Library class definition:

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

This enables us to use the DaoManager to create an instance of our custom class:

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

Then we can use all the methods from the standard DAO class, as well as our custom method:

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

7. Defining Entity Relationships

ORMLite uses the concept of “foreign” objects or collections to define relationships between entities for persistence.

Let's take a look at how we can define each type of field.

7.1. Foreign Object Fields

We can create a unidirectional one-to-one relationship between two entity classes by using the foreign=true attribute on a field annotated with @DatabaseField. The field must be of a type that's also persisted in the database.

First, let's define a new entity class called Address:

@DatabaseTable(tableName="addresses") public class Address { @DatabaseField(generatedId = true) private long addressId; @DatabaseField(canBeNull = false) private String addressLine; // standard getters, setters }

Next, we can add a field of type Address to our Library class which is marked as foreign:

@DatabaseTable(tableName = "libraries") public class Library { //... @DatabaseField(foreign=true, foreignAutoCreate = true, foreignAutoRefresh = true) private Address address; // standard getters, setters }

Notice that we've also added two more attributes to the @DatabaseField annotation: foreignAutoCreate and foreignAutoRefresh, both set to true.

The foreignAutoCreate=true attribute means that when we save a Library object with an address field, the foreign object will also be saved, provided its id is not null and has a generatedId=true attribute.

If we set foreignAutoCreate to false, which is the default value, then we'd need to persist the foreign object explicitly before saving the Library object that references it.

Similarly, the foreignAutoRefresh=true attribute specifies that when retrieving a Library object, the associated foreign object will also be retrieved. Otherwise, we'd need to refresh it manually.

Let's add a new Library object with an Address field and call the libraryDao to persist both:

Library library = new Library(); library.setName("My Library"); library.setAddress(new Address("Main Street nr 20")); Dao libraryDao = DaoManager.createDao(connectionSource, Library.class); libraryDao.create(library);

Then, we can call the addressDao to verify that the Address has also been saved:

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

7.2. Foreign Collections

For the many side of a relationship, we can use the types ForeignCollection or Collection with a @ForeignCollectionField annotation.

Let's create a new Book entity like the ones above, then add a one-to-many relationship in the Library class:

@DatabaseTable(tableName = "libraries") public class Library { // ... @ForeignCollectionField(eager=false) private ForeignCollection books; // standard getters, setters }

In addition to this, it's required that we add a field of type Library in the Book class:

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

The ForeignCollection has add() and remove() methods that operate on the records of type Book:

Library library = new Library(); library.setName("My Library"); libraryDao.create(library); libraryDao.refresh(library); library.getBooks().add(new Book("1984"));

Here, we've created a library object, then added a new Book object to the books field, which also persists it to the database.

Note that since our collection is marked as lazily loaded (eager=false), we need to call the refresh() method before being able to use the book field.

We can also create the relationship by setting the library field in the Book class:

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

To verify that both Book objects are added to the library we can use the queryForEq() method to find all the Book records with the given library_id:

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

Here, the library_id is the default name of the foreign key column, and the primary key is inferred from the library object.

8. QueryBuilder

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

Cette classe contient des méthodes qui correspondent 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 de la bibliothèque auxquels plusieurs livres sont associés:

List 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 nous pouvons définir des entités à l'aide d'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 est disponible à l'adresse over sur GitHub.