Перейти к основному содержимому

Введение в ORMLite

· 9 мин. чтения

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 с полем имени и полем 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 , generateId или generateSequence . В нашем примере мы выбираем атрибут generateId=true , чтобы первичный ключ автоматически увеличивался.

Кроме того, обратите внимание, что класс должен иметь конструктор без аргументов с видимостью по крайней мере в области пакета .

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

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

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

Эквивалент объекта библиотеки , который мы определили перед использованием стандартных аннотаций 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 .

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

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

// work with the connectionSource

connectionSource.close();

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

5. Класс TableUtils

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

  • createTable() — для создания таблицы на основе определения класса сущностей или объекта DatabaseTableConfig .
  • createTableIfNotExists() — аналогичен предыдущему методу, за исключением того, что он создаст таблицу только в том случае, если она не существует; это работает только с базами данных, которые его поддерживают
  • dropTable() — удалить таблицу
  • clearTable () — удалить данные из таблицы

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

TableUtils.createTableIfNotExists(connectionSource, Library.class);

6. Объекты ДАО

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

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

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

Далее мы можем выполнять операции CRUD над объектами библиотеки :

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 только в том случае, если цикл дойдет до конца. Исключение или оператор return могут вызвать утечку ресурсов в вашем коде.

По этой причине документация 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, нам нужно добавить имя класса в определение класса библиотеки :

@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. Поля посторонних объектов

Мы можем создать однонаправленную связь «один к одному» между двумя классами сущностей, используя атрибут external =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 в наш класс библиотеки , которое помечено как иностранное :

@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 с полем адреса , внешний объект также будет сохранен, при условии, что его id не равен нулю и имеет атрибут generateId=true .

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

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

Давайте добавим новый объект 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 , чтобы убедиться, что адрес также был сохранен:

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

7.2. Зарубежные коллекции

Для многих сторон отношения мы можем использовать типы 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"));

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

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

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

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

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

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

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

8. Построитель запросов

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

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

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

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

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

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

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