Refactor repository layer using Jakarta Data
Replaced custom repository logic with Jakarta Data interfaces to reduce boilerplate and enhance compile-time query validation. Removed `AbstractRepository` and introduced `TransactionRunner` for consistent transactional support. Updated all repository facades to delegate query operations to generated interfaces while retaining the same API, ensuring backward compatibility. Added dependencies for `jakarta.data-api` and Hibernate's `hibernate-processor` for annotation-based query generation. Verified functional parity and improved testability across the repository layer.
This commit is contained in:
1 parent
677b3f8013
commit
bceab4b2b9
16 files changed
+604
-230
No files matched your search
@@ -0,0 +1,117 @@
|
|||||||
|
---
|
||||||
|
sessionId: session-260925-095225-4rh7
|
||||||
|
---
|
||||||
|
|
||||||
|
# Requirements
|
||||||
|
|
||||||
|
### Overview & Goals
|
||||||
|
Swap the hand-written HQL in `db/repository/*` for **Jakarta Data 1.0** repository interfaces. Hibernate's `hibernate-processor` generates the implementations at compile time. Queries become compile-time checked and the boilerplate goes away. The public repository API used by `App`, services and tests stays the same.
|
||||||
|
|
||||||
|
### Scope
|
||||||
|
**In scope**
|
||||||
|
- Build setup: `jakarta.data-api` plus `hibernate-processor` as an annotation processor.
|
||||||
|
- Generated query interfaces for `OGuild`, `OGuildSetting`, `OTask`, `OBlacklist` and `OLogMessage`.
|
||||||
|
- The existing repository classes become thin transactional facades over the generated code.
|
||||||
|
- `AbstractRepository` is removed.
|
||||||
|
|
||||||
|
**Out of scope**
|
||||||
|
- Moving to Spring or CDI.
|
||||||
|
- Schema or Flyway changes.
|
||||||
|
- Changing any service behaviour.
|
||||||
|
|
||||||
|
### Functional Requirements
|
||||||
|
- Every current public method keeps the same signature and behaviour. This includes `findOrCreate`, `upsertOrDelete`, `upsert`, `enableWord`, `deleteWord`, `deleteByTaskId`, `findEnabledWords`, `findAll`, `findById`, `save` and `deleteById`.
|
||||||
|
- Each read-modify-write operation still runs in a single transaction. On error the transaction rolls back and the exception is rethrown.
|
||||||
|
- An invalid query must fail `mvn compile`.
|
||||||
|
|
||||||
|
# Technical Design
|
||||||
|
|
||||||
|
### Current Implementation
|
||||||
|
- `AbstractRepository<T,ID>` gives each repository `read`/`write`/`writeVoid` helpers over a stateful `Session`, using `SessionFactory.fromSession`/`fromTransaction`.
|
||||||
|
- The repositories build HQL inline (for example `TaskRepository.findByTaskId`) or use named queries (`settings_findByName`).
|
||||||
|
- They are wired in `App.java` (lines 122-124 and 138) as `new XRepository(sessions)`. Tests construct them the same way.
|
||||||
|
|
||||||
|
### Key Decisions
|
||||||
|
1. **Generated interfaces run on a `StatelessSession`.** This is the Hibernate Data default: the generated `*_` class takes a `StatelessSession` in its constructor. There's no container, so transactions stay our responsibility.
|
||||||
|
2. **The facade pattern keeps the public API.** Classes like `GuildSettingRepository` stay as the public API. Each call opens a stateless transaction and creates the generated DAO. `App`, the services and the tests don't change.
|
||||||
|
3. **No dirty checking, so writes are explicit.** Code that changes a loaded entity must call `@Update`. New rows are written with `@Insert`, and removals use `@Delete` or a `@Query("delete ...")`. `session.getReference(OGuild…)` becomes a detached `OGuild` with only its id set, which is enough for the foreign key on insert.
|
||||||
|
|
||||||
|
### Proposed Changes
|
||||||
|
- `db/TransactionRunner.java` (new) replaces `AbstractRepository`. It exposes `<R> R inTx(Function<StatelessSession,R>)` using `SessionFactory.fromStatelessTransaction`, and `read(...)` using `fromStatelessSession`.
|
||||||
|
- `db/data/*Queries.java` (new) holds the `@Repository` interfaces, for example:
|
||||||
|
```java
|
||||||
|
@Repository
|
||||||
|
public interface TaskQueries extends CrudRepository<OTask,Integer> {
|
||||||
|
@Find Optional<OTask> byTaskId(String taskId);
|
||||||
|
@Query("delete from OTask where taskId = :taskId") int deleteByTaskId(String taskId);
|
||||||
|
}
|
||||||
|
@Repository
|
||||||
|
public interface GuildSettingQueries extends CrudRepository<OGuildSetting,Integer> {
|
||||||
|
@Query("where guild.guildId = :guildId") List<OGuildSetting> byGuild(String guildId);
|
||||||
|
@Query("where guild.guildId = :id and name = :name") List<OGuildSetting> byName(String id, String name);
|
||||||
|
}
|
||||||
|
```
|
||||||
|
- Repository facades, for example `upsertOrDelete`:
|
||||||
|
```java
|
||||||
|
tx.inTx(ss -> { var q = new GuildSettingQueries_(ss); var ex = q.byName(id, s.getName());
|
||||||
|
if (reset) { q.deleteAll(ex); return null; }
|
||||||
|
if (ex.isEmpty()) q.insert(newSetting(id, s, value)); else { ex.forEach(e -> e.setValue(value)); q.updateAll(ex); }
|
||||||
|
return null; });
|
||||||
|
```
|
||||||
|
- `BlacklistQueries` covers `byGuild` (ordered by id), `enabledWords` (a `select o.word` projection) and `findWord` (case-insensitive `upper()`).
|
||||||
|
- `MessageQueries` holds the same kind of migration for `MessageRepository`.
|
||||||
|
- `pom.xml` gets:
|
||||||
|
- `jakarta.data:jakarta.data-api:1.0.1`
|
||||||
|
- `maven-compiler-plugin` with `annotationProcessorPaths` pointing to `org.hibernate.orm:hibernate-processor:7.1.9.Final`
|
||||||
|
- The shade plugin keeps working unchanged.
|
||||||
|
|
||||||
|
### Risks
|
||||||
|
- **Lazy associations:** stateless sessions don't lazy-load outside the session. The facades must return fully initialised data, which the current code already assumes since its sessions are closed anyway.
|
||||||
|
- **`save()` semantics:** `merge` becomes `upsert`, available on `CrudRepository`.
|
||||||
|
- **Named query `settings_findByName`:** it's replaced by `@Query`. The `@NamedQuery` on `OGuildSetting` can be removed.
|
||||||
|
|
||||||
|
# Testing
|
||||||
|
|
||||||
|
### Validation Approach
|
||||||
|
Run the existing suites unchanged with `./mvnw test`: `AbstractRepositoryTest`, `*RepositoryTest`, `GuildServiceTest` and `BlacklistServiceTest`. They cover the public repository behaviour.
|
||||||
|
|
||||||
|
### Key Scenarios
|
||||||
|
- `upsertOrDelete`: insert, update and reset-to-default delete.
|
||||||
|
- `TaskRepository.upsert` followed by `deleteByTaskId`.
|
||||||
|
- `enableWord` returns `CREATED`, then `UNCHANGED`, and `ENABLED` after the word has been disabled.
|
||||||
|
- `findOrCreate` is idempotent.
|
||||||
|
|
||||||
|
### Edge Cases
|
||||||
|
- A failing write rolls back.
|
||||||
|
- An HQL typo breaks `mvn compile`, which confirms the processor is active.
|
||||||
|
|
||||||
|
### Test Changes
|
||||||
|
- Rename `AbstractRepositoryTest` to `TransactionRunnerTest`, or rework it so it no longer depends on the removed base class.
|
||||||
|
|
||||||
|
# Delivery Steps
|
||||||
|
|
||||||
|
### ✓ Step 1: Add Jakarta Data and hibernate-processor to the build
|
||||||
|
The project compiles with the annotation processor generating the metamodel and repository classes.
|
||||||
|
- Add the `jakarta.data-api` dependency to `pom.xml`.
|
||||||
|
- Configure `maven-compiler-plugin` with `annotationProcessorPaths` pointing to `hibernate-processor` 7.1.9.Final.
|
||||||
|
- Add `db/TransactionRunner` with stateless read and transaction helpers.
|
||||||
|
- Check that `./mvnw compile` produces classes like `OGuild_`.
|
||||||
|
|
||||||
|
### ✓ Step 2: Migrate Guild and Task repositories
|
||||||
|
`GuildRepository` and `TaskRepository` delegate to the generated `GuildQueries_` and `TaskQueries_`.
|
||||||
|
- Create `db/data/GuildQueries` and `db/data/TaskQueries`.
|
||||||
|
- Rewrite `findOrCreate`, `upsert`, `findByTaskId` and `deleteByTaskId` using explicit `@Insert`/`@Update`.
|
||||||
|
- Run `GuildServiceTest` and `TaskRepositoryTest`.
|
||||||
|
|
||||||
|
### ✓ Step 3: Migrate GuildSetting, Blacklist and Message repositories
|
||||||
|
All remaining repositories use the generated queries, with transactional logic kept in the facades.
|
||||||
|
- Create `GuildSettingQueries`, `BlacklistQueries` and `MessageQueries`.
|
||||||
|
- Port `upsertOrDelete`, `enableWord` and `deleteWord` to explicit writes.
|
||||||
|
- Remove the `settings_findByName` named query.
|
||||||
|
- Run the matching repository and service tests.
|
||||||
|
|
||||||
|
### ✓ Step 4: Remove AbstractRepository and finish
|
||||||
|
`AbstractRepository` is gone and the full test suite passes.
|
||||||
|
- Delete `db/AbstractRepository.java` and move the generic `findAll`/`findById`/`save`/`deleteById` onto `CrudRepository`.
|
||||||
|
- Rework `AbstractRepositoryTest` into `TransactionRunnerTest`.
|
||||||
|
- Run `./mvnw verify`, including checkstyle and the shaded jar.
|
||||||
@@ -30,6 +30,22 @@
|
|||||||
</resources>
|
</resources>
|
||||||
|
|
||||||
<plugins>
|
<plugins>
|
||||||
|
<plugin>
|
||||||
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
|
<artifactId>maven-compiler-plugin</artifactId>
|
||||||
|
<version>3.14.1</version>
|
||||||
|
<configuration>
|
||||||
|
<release>${java.version}</release>
|
||||||
|
<proc>full</proc>
|
||||||
|
<annotationProcessorPaths>
|
||||||
|
<path>
|
||||||
|
<groupId>org.hibernate.orm</groupId>
|
||||||
|
<artifactId>hibernate-processor</artifactId>
|
||||||
|
<version>7.1.9.Final</version>
|
||||||
|
</path>
|
||||||
|
</annotationProcessorPaths>
|
||||||
|
</configuration>
|
||||||
|
</plugin>
|
||||||
<plugin>
|
<plugin>
|
||||||
<groupId>org.apache.maven.plugins</groupId>
|
<groupId>org.apache.maven.plugins</groupId>
|
||||||
<artifactId>maven-surefire-plugin</artifactId>
|
<artifactId>maven-surefire-plugin</artifactId>
|
||||||
@@ -176,6 +192,11 @@
|
|||||||
<artifactId>hibernate-hikaricp</artifactId>
|
<artifactId>hibernate-hikaricp</artifactId>
|
||||||
<version>7.1.9.Final</version>
|
<version>7.1.9.Final</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>jakarta.data</groupId>
|
||||||
|
<artifactId>jakarta.data-api</artifactId>
|
||||||
|
<version>1.0.1</version>
|
||||||
|
</dependency>
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>com.mysql</groupId>
|
<groupId>com.mysql</groupId>
|
||||||
<artifactId>mysql-connector-j</artifactId>
|
<artifactId>mysql-connector-j</artifactId>
|
||||||
|
|||||||
@@ -1,95 +0,0 @@
|
|||||||
package me.bobert.netneutrality.db;
|
|
||||||
|
|
||||||
/*
|
|
||||||
* Copyright (c) anthorob
|
|
||||||
*
|
|
||||||
* https://discord.gg/qUa6zNFZsd
|
|
||||||
*/
|
|
||||||
|
|
||||||
import jakarta.persistence.criteria.CriteriaQuery;
|
|
||||||
import org.hibernate.Session;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Objects;
|
|
||||||
import java.util.Optional;
|
|
||||||
import java.util.function.Consumer;
|
|
||||||
import java.util.function.Function;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Generic, constructor-injected Hibernate repository.
|
|
||||||
*
|
|
||||||
* <p>All work runs through Hibernate's native {@code fromSession}/{@code fromTransaction}/
|
|
||||||
* {@code inTransaction} helpers, which open and close the session and commit or roll back the
|
|
||||||
* transaction. Exceptions are never swallowed: a failed write is rolled back and rethrown.
|
|
||||||
*
|
|
||||||
* @param <T> the entity type
|
|
||||||
* @param <ID> the entity identifier type
|
|
||||||
*/
|
|
||||||
public abstract class AbstractRepository<T, ID> {
|
|
||||||
|
|
||||||
protected final SessionProvider sessions;
|
|
||||||
private final Class<T> type;
|
|
||||||
|
|
||||||
protected AbstractRepository(SessionProvider sessions, Class<T> type) {
|
|
||||||
this.sessions = Objects.requireNonNull(sessions, "sessions");
|
|
||||||
this.type = Objects.requireNonNull(type, "type");
|
|
||||||
}
|
|
||||||
|
|
||||||
protected Class<T> type() {
|
|
||||||
return type;
|
|
||||||
}
|
|
||||||
|
|
||||||
public List<T> findAll() {
|
|
||||||
return read(session -> {
|
|
||||||
CriteriaQuery<T> query = session.getCriteriaBuilder().createQuery(type);
|
|
||||||
query.from(type);
|
|
||||||
return session.createSelectionQuery(query).getResultList();
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
public Optional<T> findById(ID id) {
|
|
||||||
Objects.requireNonNull(id, "id");
|
|
||||||
return read(session -> Optional.ofNullable(session.find(type, id)));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Inserts or updates the entity.
|
|
||||||
*
|
|
||||||
* @param entity the entity to save
|
|
||||||
* @return the persistent (merged) state, which callers should use from then on
|
|
||||||
*/
|
|
||||||
public T save(T entity) {
|
|
||||||
Objects.requireNonNull(entity, "entity");
|
|
||||||
return write(session -> session.merge(entity));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Deletes the entity with the given id, if it exists.
|
|
||||||
*
|
|
||||||
* @param id the identifier
|
|
||||||
* @return {@code true} if a row was deleted
|
|
||||||
*/
|
|
||||||
public boolean deleteById(ID id) {
|
|
||||||
Objects.requireNonNull(id, "id");
|
|
||||||
return write(session -> {
|
|
||||||
T entity = session.find(type, id);
|
|
||||||
if (entity == null) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
session.remove(entity);
|
|
||||||
return true;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
protected <R> R read(Function<Session, R> work) {
|
|
||||||
return sessions.getSessionFactory().fromSession(work);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected <R> R write(Function<Session, R> work) {
|
|
||||||
return sessions.getSessionFactory().fromTransaction(work);
|
|
||||||
}
|
|
||||||
|
|
||||||
protected void writeVoid(Consumer<Session> work) {
|
|
||||||
sessions.getSessionFactory().inTransaction(work);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -0,0 +1,35 @@
|
|||||||
|
package me.bobert.netneutrality.db;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (c) anthorob
|
||||||
|
*
|
||||||
|
* https://discord.gg/qUa6zNFZsd
|
||||||
|
*/
|
||||||
|
|
||||||
|
import org.hibernate.StatelessSession;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.function.Function;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs work on a Hibernate {@link StatelessSession}, the session type used by the generated
|
||||||
|
* Jakarta Data repositories.
|
||||||
|
*
|
||||||
|
* <p>{@link #inTx(Function)} commits on success and rolls back and rethrows on failure.
|
||||||
|
*/
|
||||||
|
public class TransactionRunner {
|
||||||
|
|
||||||
|
private final SessionProvider sessions;
|
||||||
|
|
||||||
|
public TransactionRunner(SessionProvider sessions) {
|
||||||
|
this.sessions = Objects.requireNonNull(sessions, "sessions");
|
||||||
|
}
|
||||||
|
|
||||||
|
public <R> R read(Function<StatelessSession, R> work) {
|
||||||
|
return sessions.getSessionFactory().fromStatelessSession(work);
|
||||||
|
}
|
||||||
|
|
||||||
|
public <R> R inTx(Function<StatelessSession, R> work) {
|
||||||
|
return sessions.getSessionFactory().fromStatelessTransaction(work);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package me.bobert.netneutrality.db.data;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (c) anthorob
|
||||||
|
*
|
||||||
|
* https://discord.gg/qUa6zNFZsd
|
||||||
|
*/
|
||||||
|
|
||||||
|
import jakarta.data.repository.CrudRepository;
|
||||||
|
import jakarta.data.repository.Query;
|
||||||
|
import jakarta.data.repository.Repository;
|
||||||
|
import me.bobert.netneutrality.db.entities.OBlacklist;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
public interface BlacklistQueries extends CrudRepository<OBlacklist, Integer> {
|
||||||
|
|
||||||
|
@Query("from OBlacklist where guild.guildId = :guildId order by id")
|
||||||
|
List<OBlacklist> byGuild(String guildId);
|
||||||
|
|
||||||
|
@Query("select word from OBlacklist where guild.guildId = :guildId and enabled = true")
|
||||||
|
List<String> enabledWords(String guildId);
|
||||||
|
|
||||||
|
@Query("from OBlacklist where upper(word) = upper(:word) and guild.guildId = :guildId")
|
||||||
|
List<OBlacklist> findWord(String guildId, String word);
|
||||||
|
}
|
||||||
@@ -0,0 +1,15 @@
|
|||||||
|
package me.bobert.netneutrality.db.data;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (c) anthorob
|
||||||
|
*
|
||||||
|
* https://discord.gg/qUa6zNFZsd
|
||||||
|
*/
|
||||||
|
|
||||||
|
import jakarta.data.repository.CrudRepository;
|
||||||
|
import jakarta.data.repository.Repository;
|
||||||
|
import me.bobert.netneutrality.db.entities.OGuild;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
public interface GuildQueries extends CrudRepository<OGuild, String> {
|
||||||
|
}
|
||||||
@@ -0,0 +1,24 @@
|
|||||||
|
package me.bobert.netneutrality.db.data;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (c) anthorob
|
||||||
|
*
|
||||||
|
* https://discord.gg/qUa6zNFZsd
|
||||||
|
*/
|
||||||
|
|
||||||
|
import jakarta.data.repository.CrudRepository;
|
||||||
|
import jakarta.data.repository.Query;
|
||||||
|
import jakarta.data.repository.Repository;
|
||||||
|
import me.bobert.netneutrality.db.entities.OGuildSetting;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
public interface GuildSettingQueries extends CrudRepository<OGuildSetting, Integer> {
|
||||||
|
|
||||||
|
@Query("from OGuildSetting where guild.guildId = :guildId")
|
||||||
|
List<OGuildSetting> byGuild(String guildId);
|
||||||
|
|
||||||
|
@Query("from OGuildSetting where guild.guildId = :guildId and upper(name) = upper(:name)")
|
||||||
|
List<OGuildSetting> byName(String guildId, String name);
|
||||||
|
}
|
||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package me.bobert.netneutrality.db.data;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (c) anthorob
|
||||||
|
*
|
||||||
|
* https://discord.gg/qUa6zNFZsd
|
||||||
|
*/
|
||||||
|
|
||||||
|
import jakarta.data.Limit;
|
||||||
|
import jakarta.data.repository.CrudRepository;
|
||||||
|
import jakarta.data.repository.Find;
|
||||||
|
import jakarta.data.repository.Query;
|
||||||
|
import jakarta.data.repository.Repository;
|
||||||
|
import me.bobert.netneutrality.db.entities.OLogMessage;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
public interface MessageQueries extends CrudRepository<OLogMessage, Integer> {
|
||||||
|
|
||||||
|
@Find
|
||||||
|
Optional<OLogMessage> byMessageId(String messageId);
|
||||||
|
|
||||||
|
@Query("delete from OLogMessage where messageId = :messageId")
|
||||||
|
int deleteByMessageId(String messageId);
|
||||||
|
|
||||||
|
@Query("from OLogMessage where guild.guildId = :guildId and userId = :userId order by dateSent desc")
|
||||||
|
List<OLogMessage> byUser(String userId, String guildId, Limit limit);
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
package me.bobert.netneutrality.db.data;
|
||||||
|
|
||||||
|
/*
|
||||||
|
* Copyright (c) anthorob
|
||||||
|
*
|
||||||
|
* https://discord.gg/qUa6zNFZsd
|
||||||
|
*/
|
||||||
|
|
||||||
|
import jakarta.data.repository.CrudRepository;
|
||||||
|
import jakarta.data.repository.Find;
|
||||||
|
import jakarta.data.repository.Query;
|
||||||
|
import jakarta.data.repository.Repository;
|
||||||
|
import me.bobert.netneutrality.db.entities.OTask;
|
||||||
|
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
@Repository
|
||||||
|
public interface TaskQueries extends CrudRepository<OTask, Integer> {
|
||||||
|
|
||||||
|
@Find
|
||||||
|
Optional<OTask> byTaskId(String taskId);
|
||||||
|
|
||||||
|
@Query("delete from OTask where taskId = :taskId")
|
||||||
|
int deleteByTaskId(String taskId);
|
||||||
|
}
|
||||||
@@ -14,8 +14,6 @@ import jakarta.persistence.GenerationType;
|
|||||||
import jakarta.persistence.Id;
|
import jakarta.persistence.Id;
|
||||||
import jakarta.persistence.JoinColumn;
|
import jakarta.persistence.JoinColumn;
|
||||||
import jakarta.persistence.ManyToOne;
|
import jakarta.persistence.ManyToOne;
|
||||||
import jakarta.persistence.NamedQueries;
|
|
||||||
import jakarta.persistence.NamedQuery;
|
|
||||||
import jakarta.persistence.Table;
|
import jakarta.persistence.Table;
|
||||||
import org.hibernate.Hibernate;
|
import org.hibernate.Hibernate;
|
||||||
|
|
||||||
@@ -23,9 +21,6 @@ import java.util.Objects;
|
|||||||
|
|
||||||
@Entity
|
@Entity
|
||||||
@Table(name = "guild_settings")
|
@Table(name = "guild_settings")
|
||||||
@NamedQueries({
|
|
||||||
@NamedQuery(name = "settings_findByName", query = "select o from OGuildSetting o where o.guild.id = :id and upper(o.name) = upper(:name)")
|
|
||||||
})
|
|
||||||
public class OGuildSetting {
|
public class OGuildSetting {
|
||||||
@Id
|
@Id
|
||||||
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
@GeneratedValue(strategy = GenerationType.IDENTITY)
|
||||||
|
|||||||
@@ -6,17 +6,19 @@ package me.bobert.netneutrality.db.repository;
|
|||||||
* https://discord.gg/qUa6zNFZsd
|
* https://discord.gg/qUa6zNFZsd
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import me.bobert.netneutrality.db.AbstractRepository;
|
|
||||||
import me.bobert.netneutrality.db.SessionProvider;
|
import me.bobert.netneutrality.db.SessionProvider;
|
||||||
|
import me.bobert.netneutrality.db.TransactionRunner;
|
||||||
|
import me.bobert.netneutrality.db.data.BlacklistQueries_;
|
||||||
import me.bobert.netneutrality.db.entities.OBlacklist;
|
import me.bobert.netneutrality.db.entities.OBlacklist;
|
||||||
import me.bobert.netneutrality.db.entities.OGuild;
|
import me.bobert.netneutrality.db.entities.OGuild;
|
||||||
import org.hibernate.Session;
|
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Optional;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.stream.Collectors;
|
|
||||||
|
|
||||||
public class BlacklistRepository extends AbstractRepository<OBlacklist, Integer> {
|
public class BlacklistRepository {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Outcome of {@link #enableWord(String, String, String)}.
|
* Outcome of {@link #enableWord(String, String, String)}.
|
||||||
@@ -30,23 +32,18 @@ public class BlacklistRepository extends AbstractRepository<OBlacklist, Integer>
|
|||||||
UNCHANGED
|
UNCHANGED
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private final TransactionRunner tx;
|
||||||
|
|
||||||
public BlacklistRepository(SessionProvider sessions) {
|
public BlacklistRepository(SessionProvider sessions) {
|
||||||
super(sessions, OBlacklist.class);
|
this.tx = new TransactionRunner(sessions);
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<OBlacklist> findByGuildId(String guildId) {
|
public List<OBlacklist> findByGuildId(String guildId) {
|
||||||
return read(session -> session
|
return tx.read(session -> new BlacklistQueries_(session).byGuild(guildId));
|
||||||
.createSelectionQuery("FROM OBlacklist o WHERE o.guild.guildId = :guildId ORDER BY o.id", OBlacklist.class)
|
|
||||||
.setParameter("guildId", guildId)
|
|
||||||
.getResultList());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public Set<String> findEnabledWords(String guildId) {
|
public Set<String> findEnabledWords(String guildId) {
|
||||||
return read(session -> session
|
return tx.read(session -> Set.copyOf(new BlacklistQueries_(session).enabledWords(guildId)));
|
||||||
.createSelectionQuery("SELECT o.word FROM OBlacklist o WHERE o.guild.guildId = :guildId AND o.enabled = true", String.class)
|
|
||||||
.setParameter("guildId", guildId)
|
|
||||||
.getResultStream()
|
|
||||||
.collect(Collectors.toUnmodifiableSet()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -59,26 +56,33 @@ public class BlacklistRepository extends AbstractRepository<OBlacklist, Integer>
|
|||||||
* @return what changed
|
* @return what changed
|
||||||
*/
|
*/
|
||||||
public EnableOutcome enableWord(String guildId, String creatorId, String word) {
|
public EnableOutcome enableWord(String guildId, String creatorId, String word) {
|
||||||
return write(session -> {
|
return tx.inTx(session -> {
|
||||||
List<OBlacklist> existing = findWord(session, guildId, word);
|
BlacklistQueries_ queries = new BlacklistQueries_(session);
|
||||||
|
List<OBlacklist> existing = queries.findWord(guildId, word);
|
||||||
if (existing.isEmpty()) {
|
if (existing.isEmpty()) {
|
||||||
|
OGuild guild = new OGuild();
|
||||||
|
guild.setGuildId(guildId);
|
||||||
OBlacklist blacklist = new OBlacklist();
|
OBlacklist blacklist = new OBlacklist();
|
||||||
blacklist.setGuild(session.getReference(OGuild.class, guildId));
|
blacklist.setGuild(guild);
|
||||||
blacklist.setCreatorId(creatorId);
|
blacklist.setCreatorId(creatorId);
|
||||||
blacklist.setWord(word);
|
blacklist.setWord(word);
|
||||||
blacklist.setEnabled(true);
|
blacklist.setEnabled(true);
|
||||||
session.persist(blacklist);
|
queries.insert(blacklist);
|
||||||
return EnableOutcome.CREATED;
|
return EnableOutcome.CREATED;
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean changed = false;
|
List<OBlacklist> changed = new ArrayList<>();
|
||||||
for (OBlacklist blacklist : existing) {
|
for (OBlacklist blacklist : existing) {
|
||||||
if (!Boolean.TRUE.equals(blacklist.getEnabled())) {
|
if (!Boolean.TRUE.equals(blacklist.getEnabled())) {
|
||||||
blacklist.setEnabled(true);
|
blacklist.setEnabled(true);
|
||||||
changed = true;
|
changed.add(blacklist);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return changed ? EnableOutcome.ENABLED : EnableOutcome.UNCHANGED;
|
if (changed.isEmpty()) {
|
||||||
|
return EnableOutcome.UNCHANGED;
|
||||||
|
}
|
||||||
|
queries.updateAll(changed);
|
||||||
|
return EnableOutcome.ENABLED;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -90,18 +94,50 @@ public class BlacklistRepository extends AbstractRepository<OBlacklist, Integer>
|
|||||||
* @return {@code true} if at least one row was deleted
|
* @return {@code true} if at least one row was deleted
|
||||||
*/
|
*/
|
||||||
public boolean deleteWord(String guildId, String word) {
|
public boolean deleteWord(String guildId, String word) {
|
||||||
return write(session -> {
|
return tx.inTx(session -> {
|
||||||
List<OBlacklist> existing = findWord(session, guildId, word);
|
BlacklistQueries_ queries = new BlacklistQueries_(session);
|
||||||
existing.forEach(session::remove);
|
List<OBlacklist> existing = queries.findWord(guildId, word);
|
||||||
|
queries.deleteAll(existing);
|
||||||
return !existing.isEmpty();
|
return !existing.isEmpty();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private static List<OBlacklist> findWord(Session session, String guildId, String word) {
|
public List<OBlacklist> findAll() {
|
||||||
return session
|
return tx.read(session -> new BlacklistQueries_(session).findAll().toList());
|
||||||
.createSelectionQuery("FROM OBlacklist o WHERE upper(o.word) = upper(:word) AND o.guild.guildId = :guildId", OBlacklist.class)
|
}
|
||||||
.setParameter("guildId", guildId)
|
|
||||||
.setParameter("word", word)
|
public Optional<OBlacklist> findById(Integer id) {
|
||||||
.getResultList();
|
Objects.requireNonNull(id, "id");
|
||||||
|
return tx.read(session -> new BlacklistQueries_(session).findById(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inserts or updates the entity.
|
||||||
|
*
|
||||||
|
* @param entity the entity to save
|
||||||
|
* @return the saved state, which callers should use from then on
|
||||||
|
*/
|
||||||
|
public OBlacklist save(OBlacklist entity) {
|
||||||
|
Objects.requireNonNull(entity, "entity");
|
||||||
|
return tx.inTx(session -> new BlacklistQueries_(session).save(entity));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes the entity with the given id, if it exists.
|
||||||
|
*
|
||||||
|
* @param id the identifier
|
||||||
|
* @return {@code true} if a row was deleted
|
||||||
|
*/
|
||||||
|
public boolean deleteById(Integer id) {
|
||||||
|
Objects.requireNonNull(id, "id");
|
||||||
|
return tx.inTx(session -> {
|
||||||
|
BlacklistQueries_ queries = new BlacklistQueries_(session);
|
||||||
|
return queries.findById(id)
|
||||||
|
.map(entity -> {
|
||||||
|
queries.delete(entity);
|
||||||
|
return true;
|
||||||
|
})
|
||||||
|
.orElse(false);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6,14 +6,21 @@ package me.bobert.netneutrality.db.repository;
|
|||||||
* https://discord.gg/qUa6zNFZsd
|
* https://discord.gg/qUa6zNFZsd
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import me.bobert.netneutrality.db.AbstractRepository;
|
|
||||||
import me.bobert.netneutrality.db.SessionProvider;
|
import me.bobert.netneutrality.db.SessionProvider;
|
||||||
|
import me.bobert.netneutrality.db.TransactionRunner;
|
||||||
|
import me.bobert.netneutrality.db.data.GuildQueries_;
|
||||||
import me.bobert.netneutrality.db.entities.OGuild;
|
import me.bobert.netneutrality.db.entities.OGuild;
|
||||||
|
|
||||||
public class GuildRepository extends AbstractRepository<OGuild, String> {
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
|
public class GuildRepository {
|
||||||
|
|
||||||
|
private final TransactionRunner tx;
|
||||||
|
|
||||||
public GuildRepository(SessionProvider sessions) {
|
public GuildRepository(SessionProvider sessions) {
|
||||||
super(sessions, OGuild.class);
|
this.tx = new TransactionRunner(sessions);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -23,14 +30,52 @@ public class GuildRepository extends AbstractRepository<OGuild, String> {
|
|||||||
* @return the persistent guild
|
* @return the persistent guild
|
||||||
*/
|
*/
|
||||||
public OGuild findOrCreate(String guildId) {
|
public OGuild findOrCreate(String guildId) {
|
||||||
return write(session -> {
|
return tx.inTx(session -> {
|
||||||
OGuild guild = session.find(OGuild.class, guildId);
|
GuildQueries_ queries = new GuildQueries_(session);
|
||||||
if (guild == null) {
|
return queries.findById(guildId).orElseGet(() -> {
|
||||||
guild = new OGuild();
|
OGuild guild = new OGuild();
|
||||||
guild.setGuildId(guildId);
|
guild.setGuildId(guildId);
|
||||||
session.persist(guild);
|
return queries.insert(guild);
|
||||||
}
|
});
|
||||||
return guild;
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<OGuild> findAll() {
|
||||||
|
return tx.read(session -> new GuildQueries_(session).findAll().toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<OGuild> findById(String id) {
|
||||||
|
Objects.requireNonNull(id, "id");
|
||||||
|
return tx.read(session -> new GuildQueries_(session).findById(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inserts or updates the entity.
|
||||||
|
*
|
||||||
|
* @param entity the entity to save
|
||||||
|
* @return the saved state, which callers should use from then on
|
||||||
|
*/
|
||||||
|
public OGuild save(OGuild entity) {
|
||||||
|
Objects.requireNonNull(entity, "entity");
|
||||||
|
return tx.inTx(session -> new GuildQueries_(session).save(entity));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes the entity with the given id, if it exists.
|
||||||
|
*
|
||||||
|
* @param id the identifier
|
||||||
|
* @return {@code true} if a row was deleted
|
||||||
|
*/
|
||||||
|
public boolean deleteById(String id) {
|
||||||
|
Objects.requireNonNull(id, "id");
|
||||||
|
return tx.inTx(session -> {
|
||||||
|
GuildQueries_ queries = new GuildQueries_(session);
|
||||||
|
return queries.findById(id)
|
||||||
|
.map(entity -> {
|
||||||
|
queries.delete(entity);
|
||||||
|
return true;
|
||||||
|
})
|
||||||
|
.orElse(false);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6,25 +6,27 @@ package me.bobert.netneutrality.db.repository;
|
|||||||
* https://discord.gg/qUa6zNFZsd
|
* https://discord.gg/qUa6zNFZsd
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import me.bobert.netneutrality.db.AbstractRepository;
|
|
||||||
import me.bobert.netneutrality.db.GSettings;
|
import me.bobert.netneutrality.db.GSettings;
|
||||||
import me.bobert.netneutrality.db.SessionProvider;
|
import me.bobert.netneutrality.db.SessionProvider;
|
||||||
|
import me.bobert.netneutrality.db.TransactionRunner;
|
||||||
|
import me.bobert.netneutrality.db.data.GuildSettingQueries_;
|
||||||
import me.bobert.netneutrality.db.entities.OGuild;
|
import me.bobert.netneutrality.db.entities.OGuild;
|
||||||
import me.bobert.netneutrality.db.entities.OGuildSetting;
|
import me.bobert.netneutrality.db.entities.OGuildSetting;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.Optional;
|
||||||
|
|
||||||
public class GuildSettingRepository extends AbstractRepository<OGuildSetting, Integer> {
|
public class GuildSettingRepository {
|
||||||
|
|
||||||
|
private final TransactionRunner tx;
|
||||||
|
|
||||||
public GuildSettingRepository(SessionProvider sessions) {
|
public GuildSettingRepository(SessionProvider sessions) {
|
||||||
super(sessions, OGuildSetting.class);
|
this.tx = new TransactionRunner(sessions);
|
||||||
}
|
}
|
||||||
|
|
||||||
public List<OGuildSetting> findByGuild(String guildId) {
|
public List<OGuildSetting> findByGuild(String guildId) {
|
||||||
return read(session -> session
|
return tx.read(session -> new GuildSettingQueries_(session).byGuild(guildId));
|
||||||
.createSelectionQuery("FROM OGuildSetting o WHERE o.guild.guildId = :guildId", OGuildSetting.class)
|
|
||||||
.setParameter("guildId", guildId)
|
|
||||||
.getResultList());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -36,27 +38,67 @@ public class GuildSettingRepository extends AbstractRepository<OGuildSetting, In
|
|||||||
* @param value the new value, or {@code null} to reset
|
* @param value the new value, or {@code null} to reset
|
||||||
*/
|
*/
|
||||||
public void upsertOrDelete(String guildId, GSettings setting, String value) {
|
public void upsertOrDelete(String guildId, GSettings setting, String value) {
|
||||||
writeVoid(session -> {
|
tx.inTx(session -> {
|
||||||
List<OGuildSetting> existing = session
|
GuildSettingQueries_ queries = new GuildSettingQueries_(session);
|
||||||
.createNamedSelectionQuery("settings_findByName", OGuildSetting.class)
|
List<OGuildSetting> existing = queries.byName(guildId, setting.getName());
|
||||||
.setParameter("id", guildId)
|
|
||||||
.setParameter("name", setting.getName())
|
|
||||||
.getResultList();
|
|
||||||
|
|
||||||
if (value == null || setting.getDefaultValue().equals(value)) {
|
if (value == null || setting.getDefaultValue().equals(value)) {
|
||||||
existing.forEach(session::remove);
|
queries.deleteAll(existing);
|
||||||
return;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (existing.isEmpty()) {
|
if (existing.isEmpty()) {
|
||||||
|
OGuild guild = new OGuild();
|
||||||
|
guild.setGuildId(guildId);
|
||||||
OGuildSetting guildSetting = new OGuildSetting();
|
OGuildSetting guildSetting = new OGuildSetting();
|
||||||
guildSetting.setGuild(session.getReference(OGuild.class, guildId));
|
guildSetting.setGuild(guild);
|
||||||
guildSetting.setName(setting.getName());
|
guildSetting.setName(setting.getName());
|
||||||
guildSetting.setValue(value);
|
guildSetting.setValue(value);
|
||||||
session.persist(guildSetting);
|
queries.insert(guildSetting);
|
||||||
} else {
|
} else {
|
||||||
existing.forEach(guildSetting -> guildSetting.setValue(value));
|
existing.forEach(guildSetting -> guildSetting.setValue(value));
|
||||||
|
queries.updateAll(existing);
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public List<OGuildSetting> findAll() {
|
||||||
|
return tx.read(session -> new GuildSettingQueries_(session).findAll().toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
public Optional<OGuildSetting> findById(Integer id) {
|
||||||
|
Objects.requireNonNull(id, "id");
|
||||||
|
return tx.read(session -> new GuildSettingQueries_(session).findById(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inserts or updates the entity.
|
||||||
|
*
|
||||||
|
* @param entity the entity to save
|
||||||
|
* @return the saved state, which callers should use from then on
|
||||||
|
*/
|
||||||
|
public OGuildSetting save(OGuildSetting entity) {
|
||||||
|
Objects.requireNonNull(entity, "entity");
|
||||||
|
return tx.inTx(session -> new GuildSettingQueries_(session).save(entity));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes the entity with the given id, if it exists.
|
||||||
|
*
|
||||||
|
* @param id the identifier
|
||||||
|
* @return {@code true} if a row was deleted
|
||||||
|
*/
|
||||||
|
public boolean deleteById(Integer id) {
|
||||||
|
Objects.requireNonNull(id, "id");
|
||||||
|
return tx.inTx(session -> {
|
||||||
|
GuildSettingQueries_ queries = new GuildSettingQueries_(session);
|
||||||
|
return queries.findById(id)
|
||||||
|
.map(entity -> {
|
||||||
|
queries.delete(entity);
|
||||||
|
return true;
|
||||||
|
})
|
||||||
|
.orElse(false);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6,20 +6,24 @@ package me.bobert.netneutrality.db.repository;
|
|||||||
* https://discord.gg/qUa6zNFZsd
|
* https://discord.gg/qUa6zNFZsd
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import me.bobert.netneutrality.db.AbstractRepository;
|
import jakarta.data.Limit;
|
||||||
import me.bobert.netneutrality.db.SessionProvider;
|
import me.bobert.netneutrality.db.SessionProvider;
|
||||||
|
import me.bobert.netneutrality.db.TransactionRunner;
|
||||||
|
import me.bobert.netneutrality.db.data.MessageQueries_;
|
||||||
import me.bobert.netneutrality.db.entities.OGuild;
|
import me.bobert.netneutrality.db.entities.OGuild;
|
||||||
import me.bobert.netneutrality.db.entities.OLogMessage;
|
import me.bobert.netneutrality.db.entities.OLogMessage;
|
||||||
import org.hibernate.Session;
|
|
||||||
|
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
public class MessageRepository extends AbstractRepository<OLogMessage, Integer> {
|
public class MessageRepository {
|
||||||
|
|
||||||
|
private final TransactionRunner tx;
|
||||||
|
|
||||||
public MessageRepository(SessionProvider sessions) {
|
public MessageRepository(SessionProvider sessions) {
|
||||||
super(sessions, OLogMessage.class);
|
this.tx = new TransactionRunner(sessions);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -34,21 +38,22 @@ public class MessageRepository extends AbstractRepository<OLogMessage, Integer>
|
|||||||
* @return the persistent log entry
|
* @return the persistent log entry
|
||||||
*/
|
*/
|
||||||
public OLogMessage log(String guildId, String channelId, String userId, String messageId, String content, Instant sentAt) {
|
public OLogMessage log(String guildId, String channelId, String userId, String messageId, String content, Instant sentAt) {
|
||||||
return write(session -> {
|
return tx.inTx(session -> {
|
||||||
|
OGuild guild = new OGuild();
|
||||||
|
guild.setGuildId(guildId);
|
||||||
OLogMessage logMessage = new OLogMessage();
|
OLogMessage logMessage = new OLogMessage();
|
||||||
logMessage.setGuild(session.getReference(OGuild.class, guildId));
|
logMessage.setGuild(guild);
|
||||||
logMessage.setChannelId(channelId);
|
logMessage.setChannelId(channelId);
|
||||||
logMessage.setUserId(userId);
|
logMessage.setUserId(userId);
|
||||||
logMessage.setMessageId(messageId);
|
logMessage.setMessageId(messageId);
|
||||||
logMessage.setMessage(content == null ? null : content.trim());
|
logMessage.setMessage(content == null ? null : content.trim());
|
||||||
logMessage.setDateSent(sentAt);
|
logMessage.setDateSent(sentAt);
|
||||||
session.persist(logMessage);
|
return new MessageQueries_(session).insert(logMessage);
|
||||||
return logMessage;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public Optional<OLogMessage> findByMessageId(String messageId) {
|
public Optional<OLogMessage> findByMessageId(String messageId) {
|
||||||
return read(session -> findByMessageId(session, messageId));
|
return tx.read(session -> new MessageQueries_(session).byMessageId(messageId));
|
||||||
}
|
}
|
||||||
|
|
||||||
public Optional<String> findMessageContent(String messageId) {
|
public Optional<String> findMessageContent(String messageId) {
|
||||||
@@ -63,19 +68,20 @@ public class MessageRepository extends AbstractRepository<OLogMessage, Integer>
|
|||||||
* @return {@code true} if the message was found and updated
|
* @return {@code true} if the message was found and updated
|
||||||
*/
|
*/
|
||||||
public boolean updateEditedMessage(String messageId, String newMessage) {
|
public boolean updateEditedMessage(String messageId, String newMessage) {
|
||||||
return write(session -> findByMessageId(session, messageId)
|
return tx.inTx(session -> {
|
||||||
.map(message -> {
|
MessageQueries_ queries = new MessageQueries_(session);
|
||||||
message.setEditedMessage(newMessage);
|
return queries.byMessageId(messageId)
|
||||||
return true;
|
.map(message -> {
|
||||||
})
|
message.setEditedMessage(newMessage);
|
||||||
.orElse(false));
|
queries.update(message);
|
||||||
|
return true;
|
||||||
|
})
|
||||||
|
.orElse(false);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean deleteByMessageId(String messageId) {
|
public boolean deleteByMessageId(String messageId) {
|
||||||
return write(session -> session
|
return tx.inTx(session -> new MessageQueries_(session).deleteByMessageId(messageId) > 0);
|
||||||
.createMutationQuery("DELETE FROM OLogMessage o WHERE o.messageId = :messageId")
|
|
||||||
.setParameter("messageId", messageId)
|
|
||||||
.executeUpdate() > 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -87,18 +93,45 @@ public class MessageRepository extends AbstractRepository<OLogMessage, Integer>
|
|||||||
* @return the messages
|
* @return the messages
|
||||||
*/
|
*/
|
||||||
public List<OLogMessage> findByUser(String userId, String guildId, int max) {
|
public List<OLogMessage> findByUser(String userId, String guildId, int max) {
|
||||||
return read(session -> session
|
return tx.read(session -> new MessageQueries_(session).byUser(userId, guildId, Limit.of(max)));
|
||||||
.createSelectionQuery("FROM OLogMessage o WHERE o.guild.guildId = :guildId AND o.userId = :userId ORDER BY o.dateSent DESC", OLogMessage.class)
|
|
||||||
.setParameter("guildId", guildId)
|
|
||||||
.setParameter("userId", userId)
|
|
||||||
.setMaxResults(max)
|
|
||||||
.getResultList());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Optional<OLogMessage> findByMessageId(Session session, String messageId) {
|
public List<OLogMessage> findAll() {
|
||||||
return session
|
return tx.read(session -> new MessageQueries_(session).findAll().toList());
|
||||||
.createSelectionQuery("FROM OLogMessage o WHERE o.messageId = :messageId", OLogMessage.class)
|
}
|
||||||
.setParameter("messageId", messageId)
|
|
||||||
.uniqueResultOptional();
|
public Optional<OLogMessage> findById(Integer id) {
|
||||||
|
Objects.requireNonNull(id, "id");
|
||||||
|
return tx.read(session -> new MessageQueries_(session).findById(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inserts or updates the entity.
|
||||||
|
*
|
||||||
|
* @param entity the entity to save
|
||||||
|
* @return the saved state, which callers should use from then on
|
||||||
|
*/
|
||||||
|
public OLogMessage save(OLogMessage entity) {
|
||||||
|
Objects.requireNonNull(entity, "entity");
|
||||||
|
return tx.inTx(session -> new MessageQueries_(session).save(entity));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes the entity with the given id, if it exists.
|
||||||
|
*
|
||||||
|
* @param id the identifier
|
||||||
|
* @return {@code true} if a row was deleted
|
||||||
|
*/
|
||||||
|
public boolean deleteById(Integer id) {
|
||||||
|
Objects.requireNonNull(id, "id");
|
||||||
|
return tx.inTx(session -> {
|
||||||
|
MessageQueries_ queries = new MessageQueries_(session);
|
||||||
|
return queries.findById(id)
|
||||||
|
.map(entity -> {
|
||||||
|
queries.delete(entity);
|
||||||
|
return true;
|
||||||
|
})
|
||||||
|
.orElse(false);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -6,21 +6,25 @@ package me.bobert.netneutrality.db.repository;
|
|||||||
* https://discord.gg/qUa6zNFZsd
|
* https://discord.gg/qUa6zNFZsd
|
||||||
*/
|
*/
|
||||||
|
|
||||||
import me.bobert.netneutrality.db.AbstractRepository;
|
|
||||||
import me.bobert.netneutrality.db.SessionProvider;
|
import me.bobert.netneutrality.db.SessionProvider;
|
||||||
|
import me.bobert.netneutrality.db.TransactionRunner;
|
||||||
|
import me.bobert.netneutrality.db.data.TaskQueries_;
|
||||||
import me.bobert.netneutrality.db.entities.OTask;
|
import me.bobert.netneutrality.db.entities.OTask;
|
||||||
import org.hibernate.Session;
|
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
public class TaskRepository extends AbstractRepository<OTask, Integer> {
|
public class TaskRepository {
|
||||||
|
|
||||||
|
private final TransactionRunner tx;
|
||||||
|
|
||||||
public TaskRepository(SessionProvider sessions) {
|
public TaskRepository(SessionProvider sessions) {
|
||||||
super(sessions, OTask.class);
|
this.tx = new TransactionRunner(sessions);
|
||||||
}
|
}
|
||||||
|
|
||||||
public Optional<OTask> findByTaskId(String taskId) {
|
public Optional<OTask> findByTaskId(String taskId) {
|
||||||
return read(session -> findByTaskId(session, taskId));
|
return tx.read(session -> new TaskQueries_(session).byTaskId(taskId));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -32,32 +36,62 @@ public class TaskRepository extends AbstractRepository<OTask, Integer> {
|
|||||||
* @return the persistent task
|
* @return the persistent task
|
||||||
*/
|
*/
|
||||||
public OTask upsert(String taskId, String typeId, String payload) {
|
public OTask upsert(String taskId, String typeId, String payload) {
|
||||||
return write(session -> {
|
return tx.inTx(session -> {
|
||||||
OTask task = findByTaskId(session, taskId).orElse(null);
|
TaskQueries_ queries = new TaskQueries_(session);
|
||||||
if (task == null) {
|
Optional<OTask> existing = queries.byTaskId(taskId);
|
||||||
task = new OTask();
|
if (existing.isPresent()) {
|
||||||
task.setTaskId(taskId);
|
OTask task = existing.get();
|
||||||
task.setTaskClass(typeId);
|
|
||||||
task.setPayload(payload);
|
|
||||||
session.persist(task);
|
|
||||||
} else {
|
|
||||||
task.setPayload(payload);
|
task.setPayload(payload);
|
||||||
|
return queries.update(task);
|
||||||
}
|
}
|
||||||
return task;
|
OTask task = new OTask();
|
||||||
|
task.setTaskId(taskId);
|
||||||
|
task.setTaskClass(typeId);
|
||||||
|
task.setPayload(payload);
|
||||||
|
return queries.insert(task);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean deleteByTaskId(String taskId) {
|
public boolean deleteByTaskId(String taskId) {
|
||||||
return write(session -> session
|
return tx.inTx(session -> new TaskQueries_(session).deleteByTaskId(taskId) > 0);
|
||||||
.createMutationQuery("DELETE FROM OTask o WHERE o.taskId = :taskId")
|
|
||||||
.setParameter("taskId", taskId)
|
|
||||||
.executeUpdate() > 0);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static Optional<OTask> findByTaskId(Session session, String taskId) {
|
public List<OTask> findAll() {
|
||||||
return session
|
return tx.read(session -> new TaskQueries_(session).findAll().toList());
|
||||||
.createSelectionQuery("FROM OTask o WHERE o.taskId = :taskId", OTask.class)
|
}
|
||||||
.setParameter("taskId", taskId)
|
|
||||||
.uniqueResultOptional();
|
public Optional<OTask> findById(Integer id) {
|
||||||
|
Objects.requireNonNull(id, "id");
|
||||||
|
return tx.read(session -> new TaskQueries_(session).findById(id));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Inserts or updates the entity.
|
||||||
|
*
|
||||||
|
* @param entity the entity to save
|
||||||
|
* @return the saved state, which callers should use from then on
|
||||||
|
*/
|
||||||
|
public OTask save(OTask entity) {
|
||||||
|
Objects.requireNonNull(entity, "entity");
|
||||||
|
return tx.inTx(session -> new TaskQueries_(session).save(entity));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes the entity with the given id, if it exists.
|
||||||
|
*
|
||||||
|
* @param id the identifier
|
||||||
|
* @return {@code true} if a row was deleted
|
||||||
|
*/
|
||||||
|
public boolean deleteById(Integer id) {
|
||||||
|
Objects.requireNonNull(id, "id");
|
||||||
|
return tx.inTx(session -> {
|
||||||
|
TaskQueries_ queries = new TaskQueries_(session);
|
||||||
|
return queries.findById(id)
|
||||||
|
.map(entity -> {
|
||||||
|
queries.delete(entity);
|
||||||
|
return true;
|
||||||
|
})
|
||||||
|
.orElse(false);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
+9
-19
@@ -6,6 +6,7 @@ package me.bobert.netneutrality.db;
|
|||||||
* https://discord.gg/qUa6zNFZsd
|
* https://discord.gg/qUa6zNFZsd
|
||||||
*/
|
*/
|
||||||
|
|
||||||
|
import me.bobert.netneutrality.db.data.GuildQueries_;
|
||||||
import me.bobert.netneutrality.db.entities.OBlacklist;
|
import me.bobert.netneutrality.db.entities.OBlacklist;
|
||||||
import me.bobert.netneutrality.db.entities.OGuild;
|
import me.bobert.netneutrality.db.entities.OGuild;
|
||||||
import me.bobert.netneutrality.db.repository.BlacklistRepository;
|
import me.bobert.netneutrality.db.repository.BlacklistRepository;
|
||||||
@@ -18,7 +19,7 @@ import static org.junit.jupiter.api.Assertions.assertFalse;
|
|||||||
import static org.junit.jupiter.api.Assertions.assertThrows;
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
|
|
||||||
class AbstractRepositoryTest extends MySqlTestBase {
|
class TransactionRunnerTest extends MySqlTestBase {
|
||||||
|
|
||||||
private GuildRepository guilds;
|
private GuildRepository guilds;
|
||||||
private BlacklistRepository blacklist;
|
private BlacklistRepository blacklist;
|
||||||
@@ -65,9 +66,14 @@ class AbstractRepositoryTest extends MySqlTestBase {
|
|||||||
@Test
|
@Test
|
||||||
void failingWriteThrowsAndRollsBack() {
|
void failingWriteThrowsAndRollsBack() {
|
||||||
RuntimeException failure = new IllegalStateException("boom");
|
RuntimeException failure = new IllegalStateException("boom");
|
||||||
FailingGuildRepository failing = new FailingGuildRepository(sessions());
|
TransactionRunner tx = new TransactionRunner(sessions());
|
||||||
|
|
||||||
RuntimeException thrown = assertThrows(RuntimeException.class, () -> failing.insertThenFail("guild-1", failure));
|
RuntimeException thrown = assertThrows(RuntimeException.class, () -> tx.inTx(session -> {
|
||||||
|
OGuild guild = new OGuild();
|
||||||
|
guild.setGuildId("guild-1");
|
||||||
|
new GuildQueries_(session).insert(guild);
|
||||||
|
throw failure;
|
||||||
|
}));
|
||||||
|
|
||||||
assertEquals(failure, thrown);
|
assertEquals(failure, thrown);
|
||||||
assertTrue(guilds.findById("guild-1").isEmpty());
|
assertTrue(guilds.findById("guild-1").isEmpty());
|
||||||
@@ -86,20 +92,4 @@ class AbstractRepositoryTest extends MySqlTestBase {
|
|||||||
assertThrows(RuntimeException.class, () -> blacklist.save(orphan));
|
assertThrows(RuntimeException.class, () -> blacklist.save(orphan));
|
||||||
assertTrue(blacklist.findAll().isEmpty());
|
assertTrue(blacklist.findAll().isEmpty());
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final class FailingGuildRepository extends AbstractRepository<OGuild, String> {
|
|
||||||
FailingGuildRepository(SessionProvider sessions) {
|
|
||||||
super(sessions, OGuild.class);
|
|
||||||
}
|
|
||||||
|
|
||||||
void insertThenFail(String guildId, RuntimeException failure) {
|
|
||||||
writeVoid(session -> {
|
|
||||||
OGuild guild = new OGuild();
|
|
||||||
guild.setGuildId(guildId);
|
|
||||||
session.persist(guild);
|
|
||||||
session.flush();
|
|
||||||
throw failure;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Reference in new issue
Block a user