[DEV] start full refacto of SQL interface

This commit is contained in:
Edouard DUPIN 2024-10-05 12:28:49 +02:00
parent 6b0c392ff3
commit c144cd378e
21 changed files with 3897 additions and 930 deletions

View File

@ -11,6 +11,7 @@ import org.kar.archidata.exception.DataAccessException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import dev.morphia.annotations.Entity;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.annotation.Nullable;
import jakarta.persistence.Column;
@ -31,6 +32,7 @@ import jakarta.ws.rs.DefaultValue;
public class AnnotationTools {
static final Logger LOGGER = LoggerFactory.getLogger(AnnotationTools.class);
// For SQL declaration table Name
public static String getTableName(final Class<?> clazz, final QueryOptions options) throws DataAccessException {
if (options != null) {
final List<OverrideTableName> data = options.get(OverrideTableName.class);
@ -41,16 +43,13 @@ public class AnnotationTools {
return AnnotationTools.getTableName(clazz);
}
public static String getTableName(final Class<?> element) throws DataAccessException {
// For SQL declaration table Name
public static String getTableName(final Class<?> element) {
final Annotation[] annotation = element.getDeclaredAnnotationsByType(Table.class);
if (annotation.length == 0) {
// when no annotation is detected, then the table name is the class name
return element.getSimpleName();
}
if (annotation.length > 1) {
throw new DataAccessException(
"Must not have more than 1 element @Table on " + element.getClass().getCanonicalName());
}
final String tmp = ((Table) annotation[0]).name();
if (tmp == null) {
return element.getSimpleName();
@ -58,6 +57,31 @@ public class AnnotationTools {
return tmp;
}
public static String getCollectionName(final Class<?> clazz, final QueryOptions options) {
if (options != null) {
// TODO: maybe change OverrideTableName with OverrideCollectionName
final List<OverrideTableName> data = options.get(OverrideTableName.class);
if (data.size() == 1) {
return data.get(0).getName();
}
}
return AnnotationTools.getCollectionName(clazz);
}
// For No-SQL Table/Collection Name
public static String getCollectionName(final Class<?> clazz) {
final Annotation[] annotation = clazz.getDeclaredAnnotationsByType(Entity.class);
if (annotation.length == 0) {
// when no annotation is detected, then the table name is the class name
return clazz.getSimpleName();
}
final String tmp = ((Entity) annotation[0]).value();
if (tmp == null) {
return clazz.getSimpleName();
}
return tmp;
}
public static boolean getSchemaReadOnly(final Field element) throws DataAccessException {
final Annotation[] annotation = element.getDeclaredAnnotationsByType(Schema.class);
if (annotation.length == 0) {

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@ -5,22 +5,26 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import org.bson.conversions.Bson;
import com.mongodb.client.model.Filters;
public class QueryAnd implements QueryItem {
protected final List<QueryItem> childs;
public QueryAnd(final List<QueryItem> child) {
this.childs = child;
}
public QueryAnd(final QueryItem... child) {
this.childs = new ArrayList<>();
Collections.addAll(this.childs, child);
}
public void add(final QueryItem... child) {
Collections.addAll(this.childs, child);
}
@Override
public void generateQuery(final StringBuilder query, final String tableName) {
if (this.childs.size() >= 1) {
@ -39,16 +43,25 @@ public class QueryAnd implements QueryItem {
query.append(")");
}
}
@Override
public void injectQuery(final PreparedStatement ps, final CountInOut iii) throws Exception {
for (final QueryItem elem : this.childs) {
elem.injectQuery(ps, iii);
}
}
public int size() {
return this.childs.size();
}
@Override
public void generateFilter(final List<Bson> filters) {
final List<Bson> filtersLocal = new ArrayList<>();
for (final QueryItem elem : this.childs) {
elem.generateFilter(filtersLocal);
}
filters.add(Filters.and(filtersLocal.toArray(new Bson[0])));
}
}

View File

@ -1,12 +1,20 @@
package org.kar.archidata.dataAccess;
import java.sql.PreparedStatement;
import java.util.List;
import org.bson.conversions.Bson;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.mongodb.client.model.Filters;
public class QueryCondition implements QueryItem {
static final Logger LOGGER = LoggerFactory.getLogger(DataAccess.class);
private final String key;
private final String comparator;
private final Object value;
/**
* Simple DB comparison element. Note the injected object is injected in the statement and not in the query directly.
* @param key Field to check (the Model property name)
@ -18,7 +26,7 @@ public class QueryCondition implements QueryItem {
this.comparator = comparator;
this.value = value;
}
@Override
public void generateQuery(final StringBuilder query, final String tableName) {
if (tableName != null) {
@ -30,10 +38,30 @@ public class QueryCondition implements QueryItem {
query.append(this.comparator);
query.append(" ?");
}
@Override
public void injectQuery(final PreparedStatement ps, final CountInOut iii) throws Exception {
DataAccess.addElement(ps, this.value, iii);
iii.inc();
}
@Override
public void generateFilter(final List<Bson> filters) {
if ("=".equals(this.comparator)) {
filters.add(Filters.eq(this.key, this.value));
} else if ("!=".equals(this.comparator)) {
filters.add(Filters.ne(this.key, this.value));
} else if (">".equals(this.comparator)) {
filters.add(Filters.gt(this.key, this.value));
} else if (">=".equals(this.comparator)) {
filters.add(Filters.gte(this.key, this.value));
} else if ("<".equals(this.comparator)) {
filters.add(Filters.lt(this.key, this.value));
} else if ("<=".equals(this.comparator)) {
filters.add(Filters.lte(this.key, this.value));
} else {
LOGGER.error("Not manage comparison: '{}'", this.key);
}
}
}

View File

@ -3,6 +3,10 @@ package org.kar.archidata.dataAccess;
import java.sql.PreparedStatement;
import java.util.List;
import org.bson.conversions.Bson;
import com.mongodb.client.model.Filters;
public class QueryInList<T> implements QueryItem {
protected final String key;
protected final String comparator;
@ -50,4 +54,9 @@ public class QueryInList<T> implements QueryItem {
iii.inc();
}
}
@Override
public void generateFilter(final List<Bson> filters) {
filters.add(Filters.in(this.key, this.value));
}
}

View File

@ -1,9 +1,17 @@
package org.kar.archidata.dataAccess;
import java.sql.PreparedStatement;
import java.util.List;
import org.bson.conversions.Bson;
public interface QueryItem {
// For SQL mode query construction
void generateQuery(StringBuilder query, String tableName);
// For SQL mode query injection
void injectQuery(PreparedStatement ps, CountInOut iii) throws Exception;
// For No-SQL mode filter creation
void generateFilter(List<Bson> filters);
}

View File

@ -1,6 +1,11 @@
package org.kar.archidata.dataAccess;
import java.sql.PreparedStatement;
import java.util.List;
import org.bson.conversions.Bson;
import com.mongodb.client.model.Filters;
public class QueryNotNull implements QueryItem {
private final String key;
@ -21,4 +26,9 @@ public class QueryNotNull implements QueryItem {
@Override
public void injectQuery(final PreparedStatement ps, final CountInOut iii) throws Exception {}
@Override
public void generateFilter(final List<Bson> filters) {
filters.add(Filters.exists(this.key));
}
}

View File

@ -1,14 +1,19 @@
package org.kar.archidata.dataAccess;
import java.sql.PreparedStatement;
import java.util.List;
import org.bson.conversions.Bson;
import com.mongodb.client.model.Filters;
public class QueryNull implements QueryItem {
private final String key;
public QueryNull(final String key) {
this.key = key;
}
@Override
public void generateQuery(final StringBuilder query, final String tableName) {
if (tableName != null) {
@ -18,7 +23,13 @@ public class QueryNull implements QueryItem {
query.append(this.key);
query.append(" IS NULL");
}
@Override
public void injectQuery(final PreparedStatement ps, final CountInOut iii) throws Exception {}
@Override
public void generateFilter(final List<Bson> filters) {
// Not sure of the result ... maybe check it ...
filters.add(Filters.eq(this.key, null));
}
}

View File

@ -1,19 +1,24 @@
package org.kar.archidata.dataAccess;
import java.sql.PreparedStatement;
import java.util.ArrayList;
import java.util.List;
import org.bson.conversions.Bson;
import com.mongodb.client.model.Filters;
public class QueryOr implements QueryItem {
protected final List<QueryItem> childs;
public QueryOr(final List<QueryItem> childs) {
this.childs = childs;
}
public QueryOr(final QueryItem... childs) {
this.childs = List.of(childs);
}
@Override
public void generateQuery(final StringBuilder query, final String tableName) {
if (this.childs.size() >= 1) {
@ -32,11 +37,20 @@ public class QueryOr implements QueryItem {
query.append(")");
}
}
@Override
public void injectQuery(final PreparedStatement ps, final CountInOut iii) throws Exception {
for (final QueryItem elem : this.childs) {
elem.injectQuery(ps, iii);
}
}
@Override
public void generateFilter(final List<Bson> filters) {
final List<Bson> filtersLocal = new ArrayList<>();
for (final QueryItem elem : this.childs) {
elem.generateFilter(filtersLocal);
}
filters.add(Filters.or(filtersLocal.toArray(new Bson[0])));
}
}

View File

@ -28,10 +28,10 @@ import jakarta.persistence.ManyToOne;
import jakarta.validation.constraints.Size;
public class CheckJPA<T> implements CheckFunctionInterface {
private static final Logger LOGGER = LoggerFactory.getLogger(CheckJPA.class);
private final Class<?> clazz;
/** By default some element are not read like createAt and UpdatedAt. This option permit to read it. */
public interface CheckInterface<K> {
/** This function implementation is design to check if the updated class is valid of not for insertion
@ -40,9 +40,9 @@ public class CheckJPA<T> implements CheckFunctionInterface {
* @throws Exception Exception is generate if the data are incorrect. */
void check(final String baseName, final K data, final QueryOptions options) throws Exception;
}
protected Map<String, List<CheckInterface<T>>> checking = null;
protected void add(final String field, final CheckInterface<T> checkFunction) {
List<CheckInterface<T>> actions = this.checking.get(field);
if (actions == null) {
@ -51,11 +51,11 @@ public class CheckJPA<T> implements CheckFunctionInterface {
}
actions.add(checkFunction);
}
public CheckJPA(final Class<T> clazz) {
this.clazz = clazz;
}
public void initialize() throws Exception {
if (this.checking != null) {
return;
@ -84,7 +84,7 @@ public class CheckJPA<T> implements CheckFunctionInterface {
throw new InputException(baseName + fieldName, "It is forbidden to change this field");
});
}
final Class<?> type = field.getType();
if (type == Long.class || type == long.class) {
final Long maxValue = AnnotationTools.getConstraintsMax(field);
@ -131,7 +131,7 @@ public class CheckJPA<T> implements CheckFunctionInterface {
}
});
}
} else if (type == Integer.class || type == int.class) {
final Long maxValueRoot = AnnotationTools.getConstraintsMax(field);
if (maxValueRoot != null) {
@ -191,7 +191,7 @@ public class CheckJPA<T> implements CheckFunctionInterface {
});
}
} else if (type == Boolean.class || type == boolean.class) {
} else if (type == Float.class || type == float.class) {
final Long maxValueRoot = AnnotationTools.getConstraintsMax(field);
if (maxValueRoot != null) {
@ -251,11 +251,11 @@ public class CheckJPA<T> implements CheckFunctionInterface {
});
}
} else if (type == Date.class || type == Timestamp.class) {
} else if (type == LocalDate.class) {
} else if (type == LocalTime.class) {
} else if (type == String.class) {
final int maxSizeString = AnnotationTools.getLimitSize(field);
if (maxSizeString > 0) {
@ -337,14 +337,22 @@ public class CheckJPA<T> implements CheckFunctionInterface {
}
});
}
}
} catch (final Exception ex) {
this.checking = null;
throw ex;
}
}
public void check(final String baseName, final Object data) throws Exception {
check(baseName, data, null, null);
}
public void check(final String baseName, final Object data, final List<String> filterValue) throws Exception {
check(baseName, data, filterValue, null);
}
@Override
public void check(
final String baseName,
@ -370,7 +378,7 @@ public class CheckJPA<T> implements CheckFunctionInterface {
}
checkTyped(dataCasted, filterValue, options);
}
public void checkTyped(final T data, final List<String> filterValue, final QueryOptions options) throws Exception {
// nothing to do ...
}

View File

@ -1,35 +1,40 @@
package org.kar.archidata.dataAccess.options;
import java.sql.PreparedStatement;
import java.util.ArrayList;
import java.util.List;
import org.bson.conversions.Bson;
import org.kar.archidata.dataAccess.CountInOut;
import org.kar.archidata.dataAccess.QueryItem;
import org.kar.archidata.dataAccess.QueryOptions;
import com.mongodb.client.model.Filters;
/** By default some element are not read like createAt and UpdatedAt. This option permit to read it. */
public class Condition extends QueryOption {
public final QueryItem condition;
public Condition(final QueryItem items) {
this.condition = items;
}
public Condition() {
this.condition = null;
}
public void generateQuery(final StringBuilder query, final String tableName) {
if (this.condition != null) {
this.condition.generateQuery(query, tableName);
}
}
public void injectQuery(final PreparedStatement ps, final CountInOut iii) throws Exception {
if (this.condition != null) {
this.condition.injectQuery(ps, iii);
}
}
public void whereAppendQuery(
final StringBuilder query,
final String tableName,
@ -62,4 +67,26 @@ public class Condition extends QueryOption {
}
query.append("\n");
}
public Bson getFilter(final String collectionName, final QueryOptions options, final String deletedFieldName) {
boolean exclude_deleted = true;
if (options != null) {
exclude_deleted = !options.exist(AccessDeletedItems.class);
}
final List<Bson> filter = new ArrayList<>();
if (exclude_deleted && deletedFieldName != null) {
filter.add(Filters.ne(deletedFieldName, false));
}
// Check if we have a condition to generate
if (this.condition != null) {
this.condition.generateFilter(filter);
}
if (filter.size() == 0) {
return null;
}
if (filter.size() == 1) {
return filter.get(0);
}
return Filters.and(filter.toArray(new Bson[0]));
}
}

View File

@ -7,17 +7,21 @@ import org.kar.archidata.dataAccess.DataAccess;
public class Limit extends QueryOption {
protected final long limit;
public Limit(final long limit) {
this.limit = limit;
}
public void generateQuery(final StringBuilder query, final String tableName) {
query.append(" LIMIT ? \n");
}
public void injectQuery(final PreparedStatement ps, final CountInOut iii) throws Exception {
DataAccess.addElement(ps, this.limit, iii);
iii.inc();
}
public long getValue() {
return this.limit;
}
}

View File

@ -3,19 +3,21 @@ package org.kar.archidata.dataAccess.options;
import java.sql.PreparedStatement;
import java.util.List;
import org.bson.Document;
import org.kar.archidata.dataAccess.CountInOut;
import org.kar.archidata.dataAccess.options.OrderItem.Order;
public class OrderBy extends QueryOption {
protected final List<OrderItem> childs;
public OrderBy(final List<OrderItem> childs) {
this.childs = childs;
}
public OrderBy(final OrderItem... childs) {
this.childs = List.of(childs);
}
public void generateQuery(final StringBuilder query, final String tableName) {
if (this.childs.size() == 0) {
return;
@ -36,8 +38,14 @@ public class OrderBy extends QueryOption {
}
query.append("\n");
}
public void injectQuery(final PreparedStatement ps, final CountInOut iii) throws Exception {
// nothing to add.
}
public void generateSort(final Document data) {
for (final OrderItem elem : this.childs) {
data.append(elem.value, elem.order == Order.ASC ? 1 : -1);
}
}
}

View File

@ -1,6 +1,7 @@
package org.kar.archidata.db;
import org.kar.archidata.dataAccess.DataAccess;
import org.kar.archidata.exception.DataAccessException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
@ -15,10 +16,13 @@ public class DBConfig {
private final boolean keepConnected;
public DBConfig(final String type, final String hostname, final Integer port, final String login,
final String password, final String dbName, final boolean keepConnected) {
final String password, final String dbName, final boolean keepConnected) throws DataAccessException {
if (type == null) {
this.type = "mysql";
} else {
if (!"mysql".equals(type) && !"sqlite".equals(type) && !"mongo".equals(type)) {
throw new DataAccessException("unexpected DB type: '" + type + "'");
}
this.type = type;
}
if (hostname == null) {
@ -27,7 +31,11 @@ public class DBConfig {
this.hostname = hostname;
}
if (port == null) {
this.port = 3306;
if ("mysql".equals(this.type)) {
this.port = 3306;
} else {
this.port = 27017;
}
} else {
this.port = port;
}
@ -35,6 +43,7 @@ public class DBConfig {
this.password = password;
this.dbName = dbName;
this.keepConnected = keepConnected;
}
@Override
@ -82,11 +91,17 @@ public class DBConfig {
}
return "jdbc:sqlite:" + this.hostname + ".db";
}
if (isRoot) {
return "jdbc:" + this.type + "://" + this.hostname + ":" + this.port
+ "/?allowPublicKeyRetrieval=true&useSSL=false&serverTimezone=UTC";
if ("mongo".equals(this.type)) {
return "mongodb:" + getLogin() + ":" + getPassword() + "//" + this.hostname + ":" + this.port;
}
return "jdbc:" + this.type + "://" + this.hostname + ":" + this.port + "/" + this.dbName
+ "?allowPublicKeyRetrieval=true&useSSL=false&serverTimezone=UTC";
if ("mysql".equals(this.type)) {
if (isRoot) {
return "jdbc:" + this.type + "://" + this.hostname + ":" + this.port
+ "/?allowPublicKeyRetrieval=true&useSSL=false&serverTimezone=UTC";
}
return "jdbc:" + this.type + "://" + this.hostname + ":" + this.port + "/" + this.dbName
+ "?allowPublicKeyRetrieval=true&useSSL=false&serverTimezone=UTC";
}
return "dead_code";
}
}

View File

@ -1,95 +0,0 @@
package org.kar.archidata.db;
import java.io.Closeable;
import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.List;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class DBEntry implements Closeable {
final static Logger LOGGER = LoggerFactory.getLogger(DBEntry.class);
public DBConfig config;
public Connection connection;
private static List<DBEntry> stored = new ArrayList<>();
private DBEntry(final DBConfig config, final boolean root) throws IOException {
this.config = config;
if (root) {
connectRoot();
} else {
connect();
}
}
public static DBEntry createInterface(final DBConfig config) throws IOException {
return createInterface(config, false);
}
public static DBEntry createInterface(final DBConfig config, final boolean root) throws IOException {
if (config.getKeepConnected()) {
for (final DBEntry elem : stored) {
if (elem == null) {
continue;
}
if (elem.config.getUrl().equals(config.getUrl())) {
return elem;
}
}
final DBEntry tmp = new DBEntry(config, root);
stored.add(tmp);
return tmp;
} else {
return new DBEntry(config, root);
}
}
public void connectRoot() throws IOException {
try {
this.connection = DriverManager.getConnection(this.config.getUrl(true), this.config.getLogin(),
this.config.getPassword());
} catch (final SQLException ex) {
throw new IOException("Connection db fail: " + ex.getMessage() + " On URL: " + this.config.getUrl(true));
}
}
public void connect() throws IOException {
try {
this.connection = DriverManager.getConnection(this.config.getUrl(), this.config.getLogin(),
this.config.getPassword());
} catch (final SQLException ex) {
LOGGER.error("Connection db fail: " + ex.getMessage() + " On URL: " + this.config.getUrl(true));
throw new IOException("Connection db fail: " + ex.getMessage() + " On URL: " + this.config.getUrl(true));
}
}
@Override
public void close() throws IOException {
if (this.config.getKeepConnected()) {
return;
}
closeForce();
}
public void closeForce() throws IOException {
try {
// connection.commit();
this.connection.close();
} catch (final SQLException ex) {
throw new IOException("Dis-connection db fail: " + ex.getMessage());
}
}
public static void closeAllForceMode() throws IOException {
for (final DBEntry entry : stored) {
entry.closeForce();
}
stored = new ArrayList<>();
}
}

View File

@ -0,0 +1,3 @@
package org.kar.archidata.db;
public class DbInterface {}

View File

@ -0,0 +1,68 @@
package org.kar.archidata.db;
import java.io.Closeable;
import java.io.IOException;
import org.bson.UuidRepresentation;
import org.bson.codecs.configuration.CodecRegistries;
import org.bson.codecs.configuration.CodecRegistry;
import org.bson.codecs.pojo.PojoCodecProvider;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.mongodb.ConnectionString;
import com.mongodb.MongoClientSettings;
import com.mongodb.client.MongoClient;
import com.mongodb.client.MongoClients;
import dev.morphia.Datastore;
import dev.morphia.Morphia;
public class DbInterfaceMorphia extends DbInterface implements Closeable {
final static Logger LOGGER = LoggerFactory.getLogger(DbInterfaceMorphia.class);
private final MongoClient mongoClient;
private final Datastore datastore;
public DbInterfaceMorphia(final String dbUrl, final String dbName, final Class<?>... classes) {
// Connect to MongoDB (simple form):
// final MongoClient mongoClient = MongoClients.create(dbUrl);
// Connect to MongoDB (complex form):
final ConnectionString connectionString = new ConnectionString(dbUrl);
// Créer un CodecRegistry pour UUID
//final CodecRegistry uuidCodecRegistry = CodecRegistries.fromCodecs(new UUIDCodec());
// Créer un CodecRegistry pour POJOs
final CodecRegistry pojoCodecRegistry = CodecRegistries
.fromProviders(PojoCodecProvider.builder().automatic(true).build());
// Ajouter le CodecRegistry par défaut, le codec UUID et celui pour POJOs
//final CodecRegistry codecRegistry = CodecRegistries.fromRegistries(
// MongoClientSettings.getDefaultCodecRegistry(), /*uuidCodecRegistry, */ pojoCodecRegistry);
final CodecRegistry codecRegistry = CodecRegistries.fromRegistries(
MongoClientSettings.getDefaultCodecRegistry(),
CodecRegistries.fromCodecs(new org.bson.codecs.UuidCodec(UuidRepresentation.STANDARD)),
pojoCodecRegistry);
// Configurer MongoClientSettings
final MongoClientSettings clientSettings = MongoClientSettings.builder() //
.applyConnectionString(connectionString)//
.codecRegistry(codecRegistry) //
.uuidRepresentation(UuidRepresentation.STANDARD)//
.build();
this.mongoClient = MongoClients.create(clientSettings);
this.datastore = Morphia.createDatastore(this.mongoClient, "karusic");
// Map entities
this.datastore.getMapper().map(classes);
// Ensure indexes
this.datastore.ensureIndexes();
}
public Datastore getDatastore() {
return this.datastore;
}
@Override
public void close() throws IOException {
this.mongoClient.close();
}
}

View File

@ -0,0 +1,42 @@
package org.kar.archidata.db;
import java.io.Closeable;
import java.io.IOException;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.SQLException;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
public class DbInterfaceSQL extends DbInterface implements Closeable {
final static Logger LOGGER = LoggerFactory.getLogger(DbInterfaceSQL.class);
private final Connection connection;
public DbInterfaceSQL(final DBConfig config, final String dbName, final Class<?>... classes) throws IOException {
this(config.getUrl(), config.getLogin(), config.getPassword());
}
public DbInterfaceSQL(final String dbUrl, final String login, final String password) throws IOException {
try {
this.connection = DriverManager.getConnection(dbUrl, login, password);
} catch (final SQLException ex) {
LOGGER.error("Connection db fail: " + ex.getMessage() + " On URL: " + dbUrl);
throw new IOException("Connection db fail: " + ex.getMessage() + " On URL: " + dbUrl);
}
}
public Connection getConnection() {
return this.connection;
}
@Override
public void close() throws IOException {
try {
this.connection.close();
} catch (final SQLException ex) {
throw new IOException("Dis-connection db fail: " + ex.getMessage());
}
}
}

View File

@ -10,6 +10,7 @@ import jakarta.ws.rs.DefaultValue;
public class UUIDGenericData extends GenericTiming {
@Id
@DefaultValue("(UUID_TO_BIN(UUID(), TRUE))")
@Column(nullable = false, unique = true)
@Schema(description = "Unique UUID of the object", required = false, readOnly = true, example = "e6b33c1c-d24d-11ee-b616-02420a030102")