7.8 KiB
Persistence & Database
Packages: me.bobert.netneutrality.db (session/migration),
me.bobert.netneutrality.db (entities, repositories, services).
Resources: hibernate.cfg.xml, db/migration/V1__initial_schema.sql,
db/migration/V2__task_type_id.sql.
Stack
| Concern | Technology |
|---|---|
| ORM | Hibernate 7.1.9 (Jakarta JPA) |
| Engine | MariaDB 11 (per docker-compose.yml), MySQL Connector/J 9.7.0 driver, InnoDB |
| Pooling | HikariCP (via hibernate-hikaricp) |
| Migrations | Flyway 11.20.3 (flyway-core + flyway-mysql) |
Configuration is externalized via Settings (env vars / bot.properties):
MYSQL_HOST, MYSQL_PORT, MYSQL_USER, MYSQL_PASSWORD, MYSQL_DATABASE.
Session & migration lifecycle
Both are wired in at startup:
-
SessionManager.getSessionFactory()— lazily builds the HibernateSessionFactory. It reads the static config fromhibernate.cfg.xml(dialectMySQLDialect, drivercom.mysql.cj.jdbc.Driver, the Hikari connection provider, and the mapped entity list) and overrides the JDBC url/user/password from environment variables:settings.put(Environment.JAKARTA_JDBC_URL, String.format("jdbc:mysql://%s:%s/%s", dbHost, dbPort, dbName)); settings.put(Environment.JAKARTA_JDBC_USER, dbUser); settings.put(Environment.JAKARTA_JDBC_PASSWORD, dbPass); -
DatabaseMigration.migrate()— runsflyway.migrate()against the same database, applying the versioned scripts undersrc/main/resources/db/migration/(starting withV1__initial_schema.sql). The Flyway Maven plugin (flyway-dev.sh/.ps1) is available for local development, usingFLYWAY_URL/FLYWAY_USER/FLYWAY_PASSWORD. -
SessionManager.shutdown()— destroys the service registry / releases the pool on shutdown.
Repositories obtain sessions through the factory; transactional access uses helpers in
JpaUtils (open session, run in a transaction, close).
Entity catalog
Entities live in me.bobert.netneutrality.db.entities and are prefixed with O. OGuild is the hub
of a star-shaped model; most other tables reference guild_id with ON DELETE CASCADE.
| Entity | Table | Key | Relationships / notes |
|---|---|---|---|
OGuild |
guilds |
guild_id (String) |
Central entity; one-to-many with settings, blacklist, log messages, logging. |
OGuildSetting |
guild_settings |
id (auto int) |
Many-to-one OGuild; key/value guild settings. |
OBlacklist |
blacklist |
id (auto int) |
Many-to-one OGuild; blacklisted words. |
OLogging |
logging |
OLoggingId (composite) |
Composite key guild_id + channel_id; @MapsId to OGuild; debug/error log channel config. |
OLoggingId |
— (embeddable) | — | @Embeddable/@IdClass for the composite key. |
OLogMessage |
log_message |
id (auto int) |
Many-to-one OGuild; message logging history. |
OTask |
tasks |
id (auto int) |
Persistable task state; unique task_id, task_class holds the logical typeId, JSON payload. |
OWebUser |
web_users |
id (String) |
Web dashboard user (tokens/expiry). Present in schema & code but not listed in hibernate.cfg.xml. |
ER overview
┌───────────────┐
│ OGuild │ (guilds.guild_id)
└───────┬───────┘
┌───────────┬───────┼────────────┬─────────────┐
▼ ▼ ▼ ▼ ▼
OGuildSetting OBlacklist OLogging OLogMessage (…)
guild_settings blacklist logging log_message
(FK guild) (FK guild) (PK guild+ (FK guild)
channel)
OTask (tasks) OWebUser (web_users) ── standalone
OTask and OWebUser are not tied to the guild graph.
Repository layer
A lightweight generic repository pattern is used.
Repository<O>(interface) — generic CRUD:findAll(),findById(String),save(O)(viasession.merge()),deleteBy(String).BlacklistRepository— find-by-guild and case-insensitive word lookups.GuildRepository— CRUD for guilds.GuildSettingRepository— set/get setting values; removes rows that equal the default value.MessageRepository— message logging / edit history.TaskRepository— CRUD forOTaskrows (used by the task persistence flow).
Service layer
Services wrap repositories with business logic and caching.
GuildService— gateway for guild operations; caches oneGuildServiceper guild id (ConcurrentHashMap);findOrCreateguarantees a guild row exists;deleteGuild()on leave;isDebugEnabled()/ debug-channel helpers used byEventsHandlerandLogHandler.BlacklistService— maintains ablacklistCache(guild id → set of words) for fast message filtering.GuildSettingService— reads/writes guild settings backed byGSettings; caches values insettingsCacheand reloads on change; used by the web API settings endpoints.TaskService— serializesPersistableTaskinstances to JSON for thetaskstable, keyed by the task's stabletypeId()intask_class, and reconstructs them at startup viaPersistableTaskRegistry. Rows with an unknowntask_classare logged and skipped.
GSettings
GSettings is an enum of the available per-guild configuration keys and their defaults:
| Setting | Default |
|---|---|
MUSIC_ROLE |
(none) |
MUSIC_CHANNEL |
(none) |
MUSIC_VOLUME |
30 |
MUSIC_MAXPLAYLISTSIZE |
20 |
GuildSettingService looks up an OGuildSetting row by the enum name(); if no row exists for a
guild/setting, the enum's defaultValue is used. This is how MusicCommand obtains the guild's music
role/channel and how ConfigureCommand persists changes.
Schema (V1__initial_schema.sql)
The initial migration creates the tables backing the entities above (guilds, guild_settings,
blacklist, logging, log_message, tasks, web_users), with guild_id foreign keys and
ON DELETE CASCADE from the guild-scoped tables to guilds. Subsequent schema changes should be
added as new V3__…, V4__… Flyway scripts in the same directory.
V2__task_type_id.sql rewrites tasks.task_class from the old fully-qualified class names to the
stable logical typeId (e.g. emoji-reaction-role), so previously persisted rows remain loadable.