[DEV] try to fix the ManyToMany link table insertion and update to simplify API

This commit is contained in:
Edouard DUPIN 2023-12-31 09:34:46 +01:00
parent 554e2493aa
commit 66796d9591
9 changed files with 257 additions and 65 deletions

View File

@ -28,6 +28,7 @@ import org.kar.archidata.dataAccess.addOn.AddOnSQLTableExternalForeinKeyAsList;
import org.kar.archidata.dataAccess.options.CheckFunction;
import org.kar.archidata.dataAccess.options.Condition;
import org.kar.archidata.dataAccess.options.FilterValue;
import org.kar.archidata.dataAccess.options.TransmitKey;
import org.kar.archidata.db.DBEntry;
import org.kar.archidata.exception.DataAccessException;
import org.kar.archidata.tools.ConfigBaseVariable;
@ -460,6 +461,7 @@ public class DataAccess {
return null;
}
// TODO: manage insert batch...
public static <T> List<T> insertMultiple(final List<T> data, final QueryOption... options) throws Exception {
final List<T> out = new ArrayList<>();
for (final T elem : data) {
@ -480,6 +482,7 @@ public class DataAccess {
}
DBEntry entry = DBEntry.createInterface(GlobalConfiguration.dbConfig);
final List<Field> needAsyncInsert = new ArrayList<>();
// real add in the BDD:
try {
final String tableName = AnnotationTools.getTableName(clazz, options);
@ -501,6 +504,9 @@ public class DataAccess {
}
final DataAccessAddOn addOn = findAddOnforField(elem);
if (addOn != null && !addOn.canInsert(elem)) {
if (addOn.isInsertAsync(elem)) {
needAsyncInsert.add(elem);
}
continue;
}
final boolean createTime = elem.getDeclaredAnnotationsByType(CreationTimestamp.class).length != 0;
@ -613,6 +619,18 @@ public class DataAccess {
}
}
// ps.execute();
if (needAsyncInsert.size() != 0) {
LOGGER.error("Need to insert async !!!!");
LOGGER.error("Need to insert async !!!!");
LOGGER.error("Need to insert async !!!!");
LOGGER.error("Need to insert async !!!!");
LOGGER.error("Need to insert async !!!!");
LOGGER.error("Need to insert async !!!!");
LOGGER.error("Need to insert async !!!!");
LOGGER.error("Need to insert async !!!!");
LOGGER.error("Need to insert async !!!!");
}
} catch (final SQLException ex) {
ex.printStackTrace();
} finally {
@ -638,7 +656,13 @@ public class DataAccess {
}
// check the compatibility of the id and the declared ID
final Class<?> typeClass = idField.getType();
if (idKey == null) {
throw new DataAccessException("Try to identify the ID type and object wa null.");
}
if (idKey.getClass() != typeClass) {
if (idKey.getClass() == Condition.class) {
throw new DataAccessException("Try to identify the ID type on a condition 'close' internal API error use xxxWhere(...) instead.");
}
throw new DataAccessException("Request update with the wrong type ...");
}
return new QueryCondition(AnnotationTools.getFieldName(idField), "=", idKey);
@ -660,6 +684,7 @@ public class DataAccess {
throw new DataAccessException("request a updateWithJson with a condition");
}
options.add(new Condition(getTableIdCondition(clazz, id)));
options.add(new TransmitKey(id));
return updateWhereWithJson(clazz, jsonData, options.getAllArray());
}
@ -692,8 +717,7 @@ public class DataAccess {
* @return the affected rows.
* @throws Exception */
public static <T, ID_TYPE> int update(final T data, final ID_TYPE id, final List<String> updateColomn) throws Exception {
final QueryOptions options = new QueryOptions(new Condition(getTableIdCondition(data.getClass(), id)), new FilterValue(updateColomn));
return updateWhere(data, options.getAllArray());
return updateWhere(data, new Condition(getTableIdCondition(data.getClass(), id)), new FilterValue(updateColomn), new TransmitKey(id));
}
// il y avait: final List<String> filterValue
@ -708,9 +732,6 @@ public class DataAccess {
if (filter == null) {
throw new DataAccessException("request a gets without any filter values");
}
// public static NodeSmall createNode(String typeInNode, String name, String description, Long parentId) {
// External checker of data:
if (options != null) {
final CheckFunction check = options.get(CheckFunction.class);
@ -718,7 +739,7 @@ public class DataAccess {
check.getChecker().check("", data, filter.getValues());
}
}
final List<LazyGetter> asyncActions = new ArrayList<>();
DBEntry entry = DBEntry.createInterface(GlobalConfiguration.dbConfig);
// real add in the BDD:
try {
@ -743,6 +764,13 @@ public class DataAccess {
}
final DataAccessAddOn addOn = findAddOnforField(field);
if (addOn != null && !addOn.canInsert(field)) {
if (addOn.isInsertAsync(field)) {
final TransmitKey transmitKey = options.get(TransmitKey.class);
if (transmitKey == null) {
throw new DataAccessException("Fail to transmit Key to update the async update...");
}
addOn.asyncUpdate(tableName, transmitKey.getKey(), field, field.get(data), asyncActions);
}
continue;
}
if (!field.getClass().isPrimitive()) {
@ -768,41 +796,47 @@ public class DataAccess {
query.append(" ");
final String deletedFieldName = AnnotationTools.getDeletedFieldName(clazz);
condition.whereAppendQuery(query, tableName, null, deletedFieldName);
firstField = true;
LOGGER.debug("generate the query: '{}'", query.toString());
// prepare the request:
final PreparedStatement ps = entry.connection.prepareStatement(query.toString(), Statement.RETURN_GENERATED_KEYS);
final CountInOut iii = new CountInOut(1);
for (final Field field : clazz.getFields()) {
// static field is only for internal global declaration ==> remove it ..
if (java.lang.reflect.Modifier.isStatic(field.getModifiers())) {
continue;
}
final String name = AnnotationTools.getFieldName(field);
if (!filter.getValues().contains(name)) {
continue;
} else if (AnnotationTools.isGenericField(field)) {
continue;
}
final DataAccessAddOn addOn = findAddOnforField(field);
if (addOn != null && !addOn.canInsert(field)) {
continue;
}
if (addOn == null) {
final Class<?> type = field.getType();
if (!type.isPrimitive()) {
final Object tmp = field.get(data);
if (tmp == null && field.getDeclaredAnnotationsByType(DataDefault.class).length != 0) {
continue;
}
// If the first field is not set, then nothing to update n the main base:
if (!firstField) {
LOGGER.debug("generate the query: '{}'", query.toString());
// prepare the request:
final PreparedStatement ps = entry.connection.prepareStatement(query.toString(), Statement.RETURN_GENERATED_KEYS);
final CountInOut iii = new CountInOut(1);
for (final Field field : clazz.getFields()) {
// static field is only for internal global declaration ==> remove it ..
if (java.lang.reflect.Modifier.isStatic(field.getModifiers())) {
continue;
}
final String name = AnnotationTools.getFieldName(field);
if (!filter.getValues().contains(name)) {
continue;
} else if (AnnotationTools.isGenericField(field)) {
continue;
}
final DataAccessAddOn addOn = findAddOnforField(field);
if (addOn != null && !addOn.canInsert(field)) {
continue;
}
if (addOn == null) {
final Class<?> type = field.getType();
if (!type.isPrimitive()) {
final Object tmp = field.get(data);
if (tmp == null && field.getDeclaredAnnotationsByType(DataDefault.class).length != 0) {
continue;
}
}
setValuedb(type, data, iii, field, ps);
} else {
addOn.insertData(ps, field, data, iii);
}
setValuedb(type, data, iii, field, ps);
} else {
addOn.insertData(ps, field, data, iii);
}
condition.injectQuerry(ps, iii);
return ps.executeUpdate();
}
for (final LazyGetter action : asyncActions) {
action.doRequest();
}
condition.injectQuerry(ps, iii);
return ps.executeUpdate();
} catch (final SQLException ex) {
ex.printStackTrace();
} finally {
@ -1038,7 +1072,7 @@ public class DataAccess {
try {
final StringBuilder query = new StringBuilder();
final String tableName = AnnotationTools.getTableName(clazz, options);
query.append("SELECT COUNT(*) FROM `");
query.append("SELECT COUNT(*) AS count FROM `");
query.append(tableName);
query.append("` ");
if (condition != null) {

View File

@ -31,11 +31,19 @@ public interface DataAccessAddOn {
* @throws SQLException */
void insertData(PreparedStatement ps, final Field field, Object data, CountInOut iii) throws Exception, SQLException, IllegalArgumentException, IllegalAccessException;
// Element can insert in the single request
boolean canInsert(final Field field);
/** Element can insert in the single request
* @param field
* @return */
default boolean canInsert(final Field field) {
return false;
}
// Element can be retrieve with the specific mode
boolean canRetrieve(final Field field);
/** Element can be retrieve with the specific mode
* @param field
* @return */
default boolean canRetrieve(final Field field) {
return false;
}
void generateQuerry(@NotNull String tableName, @NotNull Field field, @NotNull final StringBuilder querySelect, @NotNull final StringBuilder query, @NotNull String name, @NotNull CountInOut count,
QueryOptions options) throws Exception;
@ -56,4 +64,38 @@ public interface DataAccessAddOn {
void createTables(String tableName, Field field, StringBuilder mainTableBuilder, List<String> preActionList, List<String> postActionList, boolean createIfNotExist, boolean createDrop, int fieldId)
throws Exception;
/** Some action must be done asynchronously for update or remove element
* @param field
* @return */
default boolean isInsertAsync(final Field field) throws Exception {
return false;
}
/** When insert is mark async, this function permit to create or update the data
* @param tableName Name of the Table.
* @param localId Local ID of the current table
* @param field Field that is updated.
* @param data Data that might be inserted.
* @param actions Asynchronous action to do after main request. */
default void asyncInsert(final String tableName, final Object localId, final Field field, final Object data, final List<LazyGetter> actions) throws Exception {
}
/** Some action must be done asynchronously for update or remove element
* @param field
* @return */
default boolean isUpdateAsync(final Field field) throws Exception {
return false;
}
/** When insert is mark async, this function permit to create or update the data
* @param tableName Name of the Table.
* @param localId Local ID of the current table
* @param field Field that is updated.
* @param data Data that might be inserted.
* @param actions Asynchronous action to do after main request. */
default void asyncUpdate(final String tableName, final Object localId, final Field field, final Object data, final List<LazyGetter> actions) throws Exception {
}
}

View File

@ -58,6 +58,11 @@ public class AddOnDataJson implements DataAccessAddOn {
return true;
}
@Override
public boolean isInsertAsync(final Field field) throws Exception {
return false;
}
@Override
public boolean canRetrieve(final Field field) {
return true;

View File

@ -143,6 +143,7 @@ public class AddOnManyToMany implements DataAccessAddOn {
@Override
public void fillFromQuerry(final ResultSet rs, final Field field, final Object data, final CountInOut count, final QueryOptions options, final List<LazyGetter> lazyCall) throws Exception {
if (field.getType() != List.class) {
LOGGER.error("Can not ManyToMany with other than List Model: {}", field.getType().getCanonicalName());
return;
}
final Class<?> objectClass = (Class<?>) ((ParameterizedType) field.getGenericType()).getActualTypeArguments()[0];
@ -150,6 +151,9 @@ public class AddOnManyToMany implements DataAccessAddOn {
final List<Long> idList = DataAccess.getListOfIds(rs, count.value, SEPARATOR);
field.set(data, idList);
count.inc();
} else {
LOGGER.error("Can not ManyToMany with other than List<Long> Model: List<{}>", objectClass.getCanonicalName());
return;
}
final ManyToMany decorators = field.getDeclaredAnnotation(ManyToMany.class);
if (decorators == null) {
@ -181,6 +185,69 @@ public class AddOnManyToMany implements DataAccessAddOn {
}
}
@Override
public boolean isUpdateAsync(final Field field) {
return true;
}
@Override
public void asyncUpdate(final String tableName, final Object localKey, final Field field, final Object data, final List<LazyGetter> actions) throws Exception {
if (field.getType() != List.class) {
LOGGER.error("Can not ManyToMany with other than List Model: {}", field.getType().getCanonicalName());
return;
}
final String columnName = AnnotationTools.getFieldName(field);
final String linkTableName = generateLinkTableName(tableName, columnName);
actions.add(() -> {
DataAccess.deleteWhere(LinkTable.class, new OverrideTableName(linkTableName), new Condition(new QueryCondition("object1Id", "=", localKey)));
});
asyncInsert(tableName, localKey, field, data, actions);
}
@Override
public boolean isInsertAsync(final Field field) {
return true;
}
@Override
public void asyncInsert(final String tableName, final Object localKey, final Field field, final Object data, final List<LazyGetter> actions) throws Exception {
if (data == null) {
return;
}
if (field.getType() != List.class) {
LOGGER.error("Can not ManyToMany with other than List Model: {}", field.getType().getCanonicalName());
return;
}
final String columnName = AnnotationTools.getFieldName(field);
final String linkTableName = generateLinkTableName(tableName, columnName);
final Class<?> objectClass = (Class<?>) ((ParameterizedType) field.getGenericType()).getActualTypeArguments()[0];
if (objectClass != Long.class) {
LOGGER.error("Can not ManyToMany with other than List<Long> Model: List<{}>", objectClass.getCanonicalName());
return;
}
@SuppressWarnings("unchecked")
final List<Long> dataCasted = (List<Long>) data;
if (dataCasted.size() == 0) {
return;
}
final List<LinkTable> insertElements = new ArrayList<>();
for (final Long remoteKey : dataCasted) {
if (remoteKey == null) {
continue;
}
if (localKey instanceof final Long localKeyLong) {
insertElements.add(new LinkTable(localKeyLong, remoteKey));
} else {
throw new DataAccessException("Not manage access of remte key like ManyToMany other than Long: " + localKey.getClass().getCanonicalName());
}
}
if (insertElements.size() == 0) {
LOGGER.warn("Insert multiple link without any value (may have null in the list): {}", dataCasted);
return;
}
DataAccess.insertMultiple(insertElements, new OverrideTableName(linkTableName));
}
public static void addLink(final Class<?> clazz, final long localKey, final String column, final long remoteKey) throws Exception {
final String tableName = AnnotationTools.getTableName(clazz);
final String linkTableName = generateLinkTableName(tableName, column);
@ -192,9 +259,8 @@ public class AddOnManyToMany implements DataAccessAddOn {
public static int removeLink(final Class<?> clazz, final long localKey, final String column, final long remoteKey) throws Exception {
final String tableName = AnnotationTools.getTableName(clazz);
final String linkTableName = generateLinkTableName(tableName, column);
final QueryOptions options = new QueryOptions(new OverrideTableName(linkTableName));
options.add(new Condition(new QueryAnd(new QueryCondition("object1Id", "=", localKey), new QueryCondition("object2Id", "=", remoteKey))));
return DataAccess.deleteWhere(LinkTable.class, options.getAllArray());
return DataAccess.deleteWhere(LinkTable.class, new OverrideTableName(linkTableName),
new Condition(new QueryAnd(new QueryCondition("object1Id", "=", localKey), new QueryCondition("object2Id", "=", remoteKey))));
}
@Override

View File

@ -81,6 +81,11 @@ public class AddOnManyToOne implements DataAccessAddOn {
return false;
}
@Override
public boolean isInsertAsync(final Field field) throws Exception {
return false;
}
@Override
public boolean canRetrieve(final Field field) {
if (field.getType() == Long.class) {

View File

@ -92,6 +92,11 @@ public class AddOnOneToMany implements DataAccessAddOn {
return false;
}
@Override
public boolean isInsertAsync(final Field field) throws Exception {
return false;
}
@Override
public boolean canRetrieve(final Field field) {
return false;

View File

@ -22,6 +22,7 @@ import org.slf4j.LoggerFactory;
import jakarta.validation.constraints.NotNull;
// TODO: maybe deprecated ==> use DataJson instead...
public class AddOnSQLTableExternalForeinKeyAsList implements DataAccessAddOn {
static final Logger LOGGER = LoggerFactory.getLogger(AddOnManyToMany.class);
static final String SEPARATOR = "-";
@ -75,6 +76,11 @@ public class AddOnSQLTableExternalForeinKeyAsList implements DataAccessAddOn {
return false;
}
@Override
public boolean isInsertAsync(final Field field) throws Exception {
return false;
}
@Override
public boolean canRetrieve(final Field field) {
return false;

View File

@ -63,7 +63,7 @@ public class CheckJPA<T> implements CheckFunctionInterface {
// create Table:
final List<String> primaryKeys = new ArrayList<>();
for (final Field field : this.clazz.getFields()) {
final String fieldName = AnnotationTools.getFieldName(field);
final String fieldName = field.getName(); // AnnotationTools.getFieldName(field);
if (AnnotationTools.isPrimaryKey(field)) {
add(fieldName, (final String baseName, final T data) -> {
throw new InputException(baseName + fieldName, "This is a '@Id' (primaryKey) ==> can not be change");
@ -97,7 +97,7 @@ public class CheckJPA<T> implements CheckFunctionInterface {
}
});
}
final Long minValue = AnnotationTools.getConstraintsMax(field);
final Long minValue = AnnotationTools.getConstraintsMin(field);
if (minValue != null) {
add(fieldName, (final String baseName, final T data) -> {
final Object elem = field.get(data);
@ -117,7 +117,7 @@ public class CheckJPA<T> implements CheckFunctionInterface {
if (elem == null) {
return;
}
long count = DataAccess.count(annotationManyToOne.targetEntity(), elem);
final long count = DataAccess.count(annotationManyToOne.targetEntity(), elem);
if (count == 0) {
throw new InputException(baseName + fieldName, "Foreign element does not exist in the DB:" + elem);
}
@ -128,58 +128,71 @@ public class CheckJPA<T> implements CheckFunctionInterface {
final Long maxValueRoot = AnnotationTools.getConstraintsMax(field);
if (maxValueRoot != null) {
int maxValue = maxValueRoot.intValue();
final int maxValue = maxValueRoot.intValue();
add(fieldName, (final String baseName, final T data) -> {
final Object elem = field.get(data);
if (elem == null) {
return;
}
final Long elemTyped = (Long) elem;
final Integer elemTyped = (Integer) elem;
if (elemTyped > maxValue) {
throw new InputException(baseName + fieldName, "Value too height max: " + maxValue);
}
});
}
final Long minValueRoot = AnnotationTools.getConstraintsMax(field);
final Long minValueRoot = AnnotationTools.getConstraintsMin(field);
if (minValueRoot != null) {
int minValue = minValueRoot.intValue();
final int minValue = minValueRoot.intValue();
add(fieldName, (final String baseName, final T data) -> {
final Object elem = field.get(data);
if (elem == null) {
return;
}
final Long elemTyped = (Long) elem;
final Integer elemTyped = (Integer) elem;
if (elemTyped < minValue) {
throw new InputException(baseName + fieldName, "Value too Low min: " + minValue);
}
});
}
final ManyToOne annotationManyToOne = AnnotationTools.getManyToOne(field);
if (annotationManyToOne != null && annotationManyToOne.targetEntity() != null) {
add(fieldName, (final String baseName, final T data) -> {
final Object elem = field.get(data);
if (elem == null) {
return;
}
final long count = DataAccess.count(annotationManyToOne.targetEntity(), elem);
if (count == 0) {
throw new InputException(baseName + fieldName, "Foreign element does not exist in the DB:" + elem);
}
});
}
} 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) {
float maxValue = maxValueRoot.floatValue();
final float maxValue = maxValueRoot.floatValue();
add(fieldName, (final String baseName, final T data) -> {
final Object elem = field.get(data);
if (elem == null) {
return;
}
final Long elemTyped = (Long) elem;
final Float elemTyped = (Float) elem;
if (elemTyped > maxValue) {
throw new InputException(baseName + fieldName, "Value too height max: " + maxValue);
}
});
}
final Long minValueRoot = AnnotationTools.getConstraintsMax(field);
final Long minValueRoot = AnnotationTools.getConstraintsMin(field);
if (minValueRoot != null) {
float minValue = minValueRoot.floatValue();
final float minValue = minValueRoot.floatValue();
add(fieldName, (final String baseName, final T data) -> {
final Object elem = field.get(data);
if (elem == null) {
return;
}
final Long elemTyped = (Long) elem;
final Float elemTyped = (Float) elem;
if (elemTyped < minValue) {
throw new InputException(baseName + fieldName, "Value too Low min: " + minValue);
}
@ -188,27 +201,27 @@ public class CheckJPA<T> implements CheckFunctionInterface {
} else if (type == Double.class || type == double.class) {
final Long maxValueRoot = AnnotationTools.getConstraintsMax(field);
if (maxValueRoot != null) {
double maxValue = maxValueRoot.doubleValue();
final double maxValue = maxValueRoot.doubleValue();
add(fieldName, (final String baseName, final T data) -> {
final Object elem = field.get(data);
if (elem == null) {
return;
}
final Long elemTyped = (Long) elem;
final Double elemTyped = (Double) elem;
if (elemTyped > maxValue) {
throw new InputException(baseName + fieldName, "Value too height max: " + maxValue);
}
});
}
final Long minValueRoot = AnnotationTools.getConstraintsMax(field);
final Long minValueRoot = AnnotationTools.getConstraintsMin(field);
if (minValueRoot != null) {
double minValue = minValueRoot.doubleValue();
final double minValue = minValueRoot.doubleValue();
add(fieldName, (final String baseName, final T data) -> {
final Object elem = field.get(data);
if (elem == null) {
return;
}
final Long elemTyped = (Long) elem;
final Double elemTyped = (Double) elem;
if (elemTyped < minValue) {
throw new InputException(baseName + fieldName, "Value too Low min: " + minValue);
}
@ -268,7 +281,7 @@ public class CheckJPA<T> implements CheckFunctionInterface {
final DataJson jsonAnnotation = AnnotationTools.getDataJson(field);
if (jsonAnnotation != null && jsonAnnotation.checker() != CheckFunctionVoid.class) {
// Here if we have an error it crash at start and no new instance after creation...
CheckFunctionInterface instance = jsonAnnotation.checker().getDeclaredConstructor().newInstance();
final CheckFunctionInterface instance = jsonAnnotation.checker().getDeclaredConstructor().newInstance();
add(fieldName, (final String baseName, final T data) -> {
instance.checkAll(baseName + fieldName + ".", field.get(data));
});

View File

@ -0,0 +1,16 @@
package org.kar.archidata.dataAccess.options;
import org.kar.archidata.dataAccess.QueryOption;
/** Internal option that permit to transmit the Key when updating the ManyToMany values (first step). */
public class TransmitKey extends QueryOption {
private final Object key;
public TransmitKey(final Object key) {
this.key = key;
}
public Object getKey() {
return this.key;
}
}