Что такое Active Objects и как с ними работать?
Active Objects (AO) — ORM-фреймворк для плагинов Jira Data Center, позволяющий плагинам хранить данные в БД Jira без создания таблиц вручную. По концепции похож на JPA, но значительно легковеснее.
Аналогия из жизни: Active Objects — это как встроенный шкаф в съёмной квартире. Вы не строите мебель сами (не создаёте таблицы SQL), а описываете, что хотите хранить (интерфейсы), и фреймворк сам организует пространство. При выезде (удалении плагина) шкаф убирается автоматически.
Ключевые особенности
- Таблицы создаются автоматически из Java-интерфейсов
- Префикс таблиц — уникальный для каждого плагина (избежание конфликтов)
- Поддержка миграций (upgrade tasks)
- Работает с БД Jira (PostgreSQL, MySQL, Oracle, MS SQL)
Определение сущности (entity)
Пример
@Table("TASK_CONFIG")
@Preload // загружать все поля при выборке (оптимизация)
public interface TaskConfig extends Entity {
// Entity предоставляет int getID() автоматически
@StringLength(255)
@NotNull
String getProjectKey();
void setProjectKey(String projectKey);
@StringLength(StringLength.UNLIMITED)
String getConfiguration();
void setConfiguration(String configuration);
boolean isEnabled();
void setEnabled(boolean enabled);
@Default("0")
int getPriority();
void setPriority(int priority);
// Связь один-ко-многим
@OneToMany(reverse = "getTaskConfig")
TaskExecution[] getExecutions();
}
@Table("TASK_EXEC")
public interface TaskExecution extends Entity {
@NotNull
TaskConfig getTaskConfig();
void setTaskConfig(TaskConfig config);
@StringLength(50)
String getStatus();
void setStatus(String status);
long getStartedAt();
void setStartedAt(long startedAt);
long getFinishedAt();
void setFinishedAt(long finishedAt);
@StringLength(StringLength.UNLIMITED)
String getErrorMessage();
void setErrorMessage(String message);
}
Регистрация AO в atlassian-plugin.xml
Пример
<ao key="ao-module">
<description>Active Objects модуль</description>
<entity>com.example.plugin.ao.TaskConfig</entity>
<entity>com.example.plugin.ao.TaskExecution</entity>
</ao>
CRUD-операции
Код TaskConfigService
@Named
public class TaskConfigService {
private final ActiveObjects ao;
@Inject
public TaskConfigService(@ComponentImport ActiveObjects ao) {
this.ao = ao;
}
// CREATE
public TaskConfig create(String projectKey, String config) {
return ao.executeInTransaction(() -> {
TaskConfig entity = ao.create(TaskConfig.class);
entity.setProjectKey(projectKey);
entity.setConfiguration(config);
entity.setEnabled(true);
entity.setPriority(0);
entity.save();
return entity;
});
}
// READ — по ID
public TaskConfig getById(int id) {
return ao.get(TaskConfig.class, id);
}
// READ — поиск по условию
public TaskConfig[] findByProject(String projectKey) {
return ao.find(TaskConfig.class,
Query.select()
.where("PROJECT_KEY = ?", projectKey)
.order("PRIORITY DESC")
.limit(100));
}
// UPDATE
public void update(int id, String newConfig) {
ao.executeInTransaction(() -> {
TaskConfig entity = ao.get(TaskConfig.class, id);
if (entity != null) {
entity.setConfiguration(newConfig);
entity.save();
}
return null;
});
}
// DELETE
public void delete(int id) {
ao.executeInTransaction(() -> {
TaskConfig entity = ao.get(TaskConfig.class, id);
if (entity != null) {
for (TaskExecution exec : entity.getExecutions()) {
ao.delete(exec);
}
ao.delete(entity);
}
return null;
});
}
}
Миграция (Upgrade Task)
Пример
public class UpgradeTask001 implements ActiveObjectsUpgradeTask {
@Override
public ModelVersion getModelVersion() {
return ModelVersion.valueOf("1");
}
@Override
public void upgrade(ModelVersion currentVersion, ActiveObjects ao) {
ao.migrate(TaskConfig.class);
}
}
Пример
<ao key="ao-module">
<entity>com.example.plugin.ao.TaskConfig</entity>
<entity>com.example.plugin.ao.TaskExecution</entity>
<upgradeTask>com.example.plugin.upgrade.UpgradeTask001</upgradeTask>
</ao>
Частые ошибки
- Забыть
@Preload— N+1 проблема, каждый доступ к полю = отдельный SELECT - Использование Java-имён полей в Query вместо SQL-имён:
"projectKey"вместо"PROJECT_KEY" - Не обернуть запись в транзакцию — данные могут быть в inconsistent-состоянии
- Отсутствие upgrade tasks — при первом деплое таблицы не создаются
- Хранение больших данных без
@StringLength(UNLIMITED)— дефолтная длина строки 255 символов
Как используется в 2026
- Active Objects остаётся стандартом для DC-плагинов
- Нет альтернатив (JPA/Hibernate недоступны из-за OSGi-изоляции)
- Для Cloud (Forge) аналог — Forge Storage API и Entity Storage
- При планировании миграции DC -> Cloud нужно продумать перенос данных из AO в Cloud storage
На собеседовании: подчеркните, что AO — единственный поддерживаемый способ хранения данных плагина в БД Jira DC. Обязательно упомяните
@Preload(без него — N+1), UPPERCASE-имена колонок в Query иexecuteInTransaction()для записи. Это практические знания, которые показывают реальный опыт.