Database

1. Overview

FT System Metrics connects to an existing FTACS database in read-only mode. The service does not create or modify tables (ddl-auto: none).

Supported databases:

  • MySQL 8.0+ — primary, used by default

  • Oracle 21c+ — alternative, activated via the oracle profile

2. Database Schema

The service reads data from the following FTACS tables:

2.1. Entity Relationship

Diagram

2.2. Tables Description

Table Description

cpe

CPE device registry. Contains protocol, serial number, online status, and tenant binding (location_id)

isp

ISP/tenant directory. Contains ID and name

cpe_pending_task

Tasks in pending status. repeats > 0 indicates a "Sent" status

cpe_completed_task

Successfully completed tasks

cpe_failed_task

Tasks that ended with an error

cpe_rejected_task

Rejected tasks

cpe_log

CPE device event log

cpe_log_event_name

CPE event type directory

cpe_next_session_time

Next session time for TR069 devices. Used to determine online status for TR069

2.3. Protocol Enum

ID Protocol

0

TR069

1

OMA

2

LWM2M

3

MQTT

4

UNKNOWN

5

USP

6

MQTT_SN

7

COAP

3. Repository Layer

3.1. CpeRepository

JPA repository for the cpe table with native queries:

// Total devices by protocol and tenant
@Query(value = """
    SELECT c.protocol_id AS protocolId,
           COUNT(*) AS count,
           c.location_id AS ispId
    FROM cpe c
    GROUP BY c.protocol_id, c.location_id
    """, nativeQuery = true)
List<ProtocolCountProjection> countByProtocol();

// Online status for non-TR069 devices (is_online = 1)
List<OnlineStatusProjection> countOnlineStatusByProtocolNonTR();

// Online status for TR069 via cpe_next_session_time
List<OnlineStatusProjection> countOnlineStatusTR(LocalDateTime endTime);

3.2. CpeTaskQueryRepository

Interface for task queries. MySQL and Oracle implementations use UNION ALL across four tables:

// Task count by status and date (previous 1-2 days)
List<TaskStatusProjection> countTasksByStatusAndDate(
    LocalDateTime startTime, LocalDateTime endTime);

// Task count by type and hour (previous 24 hours)
List<TaskTypeProjection> countTasksByTypeAndHour(
    LocalDateTime startTime, LocalDateTime endTime);

Example UNION query (MySQL):

SELECT 'Completed' AS status, COUNT(*) AS count,
       DATE_FORMAT(created, '%Y-%m-%d') AS day, c.location_id AS ispId
FROM cpe_completed_task t JOIN cpe c ON t.cpe_id = c.id
WHERE t.created BETWEEN :start AND :end
GROUP BY day, c.location_id
UNION ALL
SELECT 'Failed' AS status, COUNT(*) AS count, ...
UNION ALL
SELECT 'Rejected' AS status, COUNT(*) AS count, ...
UNION ALL
SELECT 'Pending' AS status, COUNT(*) AS count, ...  -- repeats = 0
UNION ALL
SELECT 'Sent' AS status, COUNT(*) AS count, ...     -- repeats > 0

3.3. CpeLogQueryRepository

Interface for event queries:

// Event count by type and hour
List<EventTypeProjection> countEventsByTypeAndHour(
    LocalDateTime startTime, LocalDateTime endTime);

// Total event count by tenant
List<EventCountProjection> countEventsInTimeRange(
    LocalDateTime startTime, LocalDateTime endTime);

3.4. IspRepository

JPA repository for the tenant directory:

List<Isp> findAllByIdIn(List<Integer> ids);

4. Projections

Projection interfaces map native SQL query results to Java objects:

Projection Fields

BaseTenantProjection

getIspId() — tenant ID (base interface)

ProtocolCountProjection

getProtocolId(), getCount(), getIspId()

OnlineStatusProjection

getProtocolId(), getCount(), getIspId()

TaskStatusProjection

getStatus(), getCount(), getDay(), getIspId()

TaskTypeProjection

getTypeId(), getCount(), getHour(), getIspId()

EventCountProjection

getCount(), getIspId()

EventTypeProjection

getEventType(), getCount(), getHour(), getIspId()

Record implementations (TaskStatusProjectionImpl, TaskTypeProjectionImpl, EventCountProjectionImpl, EventTypeProjectionImpl) are used in database-specific query repositories.

5. Multi-Database Query Strategy

Queries containing database-specific SQL functions are placed in separate repositories selected via Spring Profile:

src/main/java/com/friendly/repository/
+-- CpeTaskQueryRepository.java          # Interface
+-- CpeLogQueryRepository.java           # Interface
+-- mysql/
|   +-- MysqlCpeTaskQueryRepository.java # @Profile("mysql")
|   +-- MysqlCpeLogQueryRepository.java  # @Profile("mysql")
+-- oracle/
    +-- OracleCpeTaskQueryRepository.java # @Profile("oracle")
    +-- OracleCpeLogQueryRepository.java  # @Profile("oracle")

Metrics collectors depend only on interfaces (CpeTaskQueryRepository, CpeLogQueryRepository) and are unaware of the specific database engine.

6. Best Practices

6.1. Database Access

  • Read-only access — the service uses ddl-auto: none and does not modify data

  • Native queries — complex aggregations (UNION ALL, GROUP BY) are executed via native SQL for maximum performance

  • Projection pattern — results are mapped to lightweight interfaces/records instead of full entities

  • Database abstraction — database-specific SQL is isolated in separate implementations

6.2. Performance Considerations

  • Connection pooling — HikariCP with a configurable pool size (default max=10, min=2)

  • Prepared statement caching — enabled by default (cachePrepStmts=true, cache size=512)

  • Time-bounded queries — all queries are limited to time windows (previous hour/day)

  • Lazy loading — JoinFetch is used only for required associations

6.3. Database Security

  • Parameterized queries — all parameters are passed via JPA named parameters

  • Minimal privileges — the database user only needs SELECT access to the listed tables

  • No schema modifications — the service never alters the database structure