Введение в ORMLite

1. Обзор

ORMLite - легковесная библиотека ORM для приложений Java. Он предоставляет стандартные функции инструмента ORM для наиболее распространенных случаев использования, без дополнительной сложности и накладных расходов других структур ORM.

Это основные функции:

  • определение классов сущностей с использованием аннотаций Java

  • расширяемые DAO классы

класс QueryBuilder для создания сложных запросов

  • сгенерированные классы для создания и удаления таблиц базы данных

  • поддержка транзакций

  • поддержка отношений сущностей

В следующих разделах мы рассмотрим, как мы можем настроить библиотеку, определить классы сущностей и выполнить операции с базой данных с помощью библиотеки.

2. Зависимости Maven

Чтобы начать использовать ORMLite, нам нужно добавить зависимость ormlite-jdbc в нашу pom.xml :

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

По умолчанию это также приводит к зависимости h2 , В наших примерах мы будем использовать базу данных H2 в памяти, поэтому нам не нужен другой драйвер JDBC.

Если вы хотите использовать другую базу данных, вам также понадобится соответствующая зависимость.

3. Определение классов сущностей

Чтобы настроить классы нашей модели для сохранения с ORMLite, мы можем использовать две основные аннотации:

  • @ DatabaseTable для класса сущности

  • @ DatabaseField для свойств

Давайте начнем с определения сущности Library с полем name и полем libraryId , который также является первичным ключом:

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

    @DatabaseField(generatedId = true)
    private long libraryId;

    @DatabaseField(canBeNull = false)
    private String name;

    public Library() {
    }

   //standard getters, setters
}

Аннотация DatabaseTable имеет необязательный атрибут tableName__, который указывает имя таблицы, если мы не хотим полагаться на имя класса по умолчанию.

  • Для каждого поля, которое мы хотим сохранить как столбец в таблице базы данных, мы должны добавить аннотацию @ DatabaseField . **

Свойство, которое будет служить первичным ключом для таблицы, может быть помечено с помощью атрибутов id , generatedId или generatedSequence . В нашем примере мы выбираем атрибут generatedId = true , чтобы первичный ключ автоматически увеличивался.

  • Также обратите внимание, что класс должен иметь конструктор без аргументов с видимостью не менее package-scope . **

Несколько других знакомых атрибутов, которые мы можем использовать для настройки полей: columnName , dataType , defaultValue , canBeNull , unique .

3.1. Использование JPA аннотаций

Помимо аннотаций, специфичных для ORMLite, мы также можем использовать аннотации JPA-style для определения наших сущностей

Эквивалент сущности Library , которую мы определили перед использованием стандартных аннотаций JPA :

@Entity
public class LibraryJPA {

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

    @Column
    private String name;

   //standard getters, setters
}

Хотя ORMLite распознает эти аннотации, нам все равно нужно добавить javax.persistence-api зависимость их использовать.

Полный список поддерживаемых аннотаций JPA : @ Entity , @ Id, @ Column, @ GeneratedValue, @ OneToOne , @ ManyToOne, @ JoinColumn , @ Version .

4. ConnectionSource

Для работы с определенными объектами нам нужно установить ConnectionSource .

Для этого мы можем использовать класс JdbcConnectionSource , который создает отдельное соединение, или JdbcPooledConnectionSource , который представляет простой источник соединения в пуле:

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

connectionSource.close();

Можно также использовать другой внешний источник данных с более высокой производительностью, заключив их в объект DataSourceConnectionSource

5. TableUtils Class

На основе ConnectionSource мы можем использовать статические методы из класса TableUtils для выполнения операций над схемой базы данных :

  • createTable () - создать таблицу на основе определения класса сущности

или объект DatabaseTableConfig ** createTableIfNotExists () - аналогично предыдущему методу, кроме него

создаст таблицу, только если она не существует; это работает только на базы данных, которые его поддерживают ** dropTable () - удалить таблицу

  • clearTable () - удалить данные из таблицы

Давайте посмотрим, как мы можем использовать TableUtils для создания таблицы для нашего класса Library :

TableUtils.createTableIfNotExists(connectionSource, Library.class);

6. DAO объекты

ORMLite содержит класс DaoManager , который может создавать для нас объекты DAO с функцией CRUD :

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

DaoManager не восстанавливает класс для каждого последующего вызова __createDao (), а вместо этого повторно использует его для повышения производительности.

Далее мы можем выполнить CRUD-операции над объектами 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);

DAO также является итератором, который может перебирать все записи:

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

Однако ORMLite закроет базовый оператор SQL только в том случае, если цикл проходит до конца. Исключение или оператор возврата могут вызвать утечку ресурсов в вашем коде.

По этой причине документация ORMLite рекомендует использовать итератор напрямую:

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

Таким образом, мы можем закрыть итератор с помощью блока try-with-resources или finally и избежать утечки ресурсов.

6.1. Пользовательский класс DAO

Если мы хотим расширить поведение предоставленных объектов DAO , мы можем создать новый интерфейс, который расширяет тип Dao :

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

Затем давайте добавим класс, который реализует этот интерфейс и расширяет класс 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);
    }
}

Обратите внимание, что нам нужен конструктор этой формы.

Наконец, чтобы использовать наш собственный DAO, нам нужно добавить имя класса в определение класса Library__:

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

Это позволяет нам использовать DaoManager для создания экземпляра нашего пользовательского класса:

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

Затем мы можем использовать все методы из стандартного класса DAO , а также наш пользовательский метод:

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

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

7. Определение сущностных отношений

ORMLite использует понятие «чужие» объекты или коллекции для определения отношений между сущностями для сохранения.

Давайте посмотрим, как мы можем определить каждый тип поля.

7.1. Поля посторонних объектов

Мы можем создать однонаправленное отношение один к одному между двумя классами сущностей, используя атрибут foreign = true в поле, помеченном @ DatabaseField . Поле должно иметь тип, который также сохраняется в базе данных.

Во-первых, давайте определим новый класс сущности с именем Address :

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

    @DatabaseField(canBeNull = false)
    private String addressLine;

   //standard getters, setters
}

Затем мы можем добавить поле типа Address в наш класс Library , помеченное как foreign :

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

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

   //standard getters, setters
}

Обратите внимание, что мы также добавили еще два атрибута к аннотации @ DatabaseField : foreignAutoCreate и foreignAutoRefresh , оба из которых имеют значение true.

Атрибут foreignAutoCreate = true означает, что при сохранении объекта Library с полем address внешний объект также будет сохранен при условии, что его id не равен нулю и имеет атрибут generatedId = true .

Если мы установим для foreignAutoCreate значение false , которое является значением по умолчанию, то нам потребуется явно сохранить внешний объект перед сохранением объекта Library , который ссылается на него.

Точно так же атрибут _foreignAutoRefresh = true указывает, что при извлечении объекта Library_ также будет извлечен связанный сторонний объект. В противном случае нам нужно обновить его вручную.

Давайте добавим новый объект Library с полем Address и вызовем libraryDao для сохранения обоих:

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

Затем мы можем вызвать addressDao , чтобы убедиться, что Address также был сохранен:

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

7.2. Иностранные Коллекции

Для many стороны отношения мы можем использовать типы ForeignCollection <T> или Collection <T> с аннотацией @ ForeignCollectionField .

Давайте создадим новую сущность Book , подобную приведенной выше, а затем добавим отношение «один ко многим» в классе Library :

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

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

   //standard getters, setters
}

В дополнение к этому необходимо добавить поле типа Library в класс Book :

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

   //standard getters, setters
}
  • ForeignCollection имеет методы add () и remove () ** , которые работают с записями типа Book:

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

libraryDao.refresh(library);

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

Здесь мы создали объект library , затем добавили новый объект Book в поле books , который также сохраняет его в базе данных.

  • Обратите внимание, что поскольку наша коллекция помечена как лениво загруженная (eager = false), нам нужно вызвать метод refresh () ** , прежде чем можно будет использовать поле книги.

Мы также можем создать отношения, установив поле library в классе Book :

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

Чтобы убедиться, что оба объекта Book добавлены в library , мы можем использовать метод queryForEq () , чтобы найти все записи Book с заданным library id: __

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

Здесь library id - это имя столбца внешнего ключа по умолчанию, а первичный ключ выводится из объекта library__.

8. QueryBuilder

Каждый DAO может использоваться для получения объекта QueryBuilder , который мы затем можем использовать для создания более мощных запросов.

Этот класс содержит методы, которые соответствуют общим операциям, используемым в запросе SQL, таким как: selectColumns (), where (), groupBy (), having (), countOf (), Different (), orderBy (), join ().

Давайте рассмотрим пример того, как мы можем найти все Library записи, которые имеют более одной Book связанной:

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

9. Заключение

В этой статье мы увидели, как мы можем определять сущности, используя ORMLite, а также основные функции библиотеки, которые мы можем использовать для управления объектами и связанными с ними реляционными базами данных.

Полный исходный код этого примера можно найти по адресу over на GitHub .