Improve Java/SQL ORM

- Support for custom Java types and conversion for database storage
- Enums are considered custom types
- SQLField.name is now encapsulated as encouraged by OOP principles
- SQLField.name is now auto deducted from the Java field's name in the SQLElement subclass (no more field name in the constructor)
- No need to precise the SQLType of an SQLFKField (auto deducted from the target SQLField's type) via static method SQLFKField.customFK()
- Support of Java type UUID (stored as CHAR(36) in database. No need to use custom getter and setters for SQLElements using UUID fields
This commit is contained in:
Marc Baloup 2017-09-13 01:05:10 +02:00
parent 706ae682ec
commit 0391b7a9a0
13 changed files with 1421 additions and 1358 deletions

View File

@ -1,246 +1,251 @@
package fr.pandacube.java.util.orm; package fr.pandacube.java.util.orm;
import java.sql.PreparedStatement; import java.sql.PreparedStatement;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.List; import java.util.List;
import fr.pandacube.java.util.EnumUtil; import org.javatuples.Pair;
import fr.pandacube.java.util.Log;
import fr.pandacube.java.util.orm.SQLWhereChain.SQLBoolOp; import fr.pandacube.java.util.Log;
import fr.pandacube.java.util.orm.SQLWhereComp.SQLComparator; import fr.pandacube.java.util.orm.SQLWhereChain.SQLBoolOp;
import fr.pandacube.java.util.orm.SQLWhereComp.SQLComparator;
import org.javatuples.Pair;
/**
/** * <b>ORM = Object-Relational Mapping</b>
* <b>ORM = Object-Relational Mapping</b> *
* * @author Marc Baloup
* @author Marc Baloup *
* */
*/ public final class ORM {
public final class ORM {
private static List<Class<? extends SQLElement<?>>> tables = new ArrayList<>();
private static List<Class<? extends SQLElement<?>>> tables = new ArrayList<>();
private static DBConnection connection;
private static DBConnection connection;
public static DBConnection getConnection() {
public static DBConnection getConnection() { return connection;
return connection; }
}
public synchronized static <E extends SQLElement<E>> void init(DBConnection conn) {
public synchronized static <E extends SQLElement<E>> void init(DBConnection conn) {
connection = conn;
connection = conn;
}
}
public static synchronized <E extends SQLElement<E>> void initTable(Class<E> elemClass) throws ORMInitTableException {
public static synchronized <E extends SQLElement<E>> void initTable(Class<E> elemClass) throws ORMInitTableException { if (tables.contains(elemClass)) return;
if (tables.contains(elemClass)) return; try {
try { tables.add(elemClass);
tables.add(elemClass); //Log.info("Start Init SQL table "+elemClass.getSimpleName());
//Log.info("Start Init SQL table "+elemClass.getSimpleName()); E instance = elemClass.newInstance();
E instance = elemClass.newInstance(); String tableName = instance.tableName();
String tableName = instance.tableName(); if (!tableExist(tableName)) createTable(instance);
if (!tableExist(tableName)) createTable(instance); //Log.info("End init SQL table "+elemClass.getSimpleName());
//Log.info("End init SQL table "+elemClass.getSimpleName()); } catch (Exception|ExceptionInInitializerError e) {
} catch (Exception|ExceptionInInitializerError e) { throw new ORMInitTableException(elemClass, e);
throw new ORMInitTableException(elemClass, e); }
} }
}
private static <E extends SQLElement<E>> void createTable(E elem) throws SQLException {
private static <E extends SQLElement<E>> void createTable(E elem) throws SQLException {
String sql = "CREATE TABLE IF NOT EXISTS " + elem.tableName() + " (";
String sql = "CREATE TABLE IF NOT EXISTS " + elem.tableName() + " ("; List<Object> params = new ArrayList<>();
List<Object> params = new ArrayList<>();
Collection<SQLField<E, ?>> tableFields = elem.getFields().values();
Collection<SQLField<E, ?>> tableFields = elem.getFields().values(); boolean first = true;
boolean first = true; for (SQLField<E, ?> f : tableFields) {
for (SQLField<E, ?> f : tableFields) { Pair<String, List<Object>> statementPart = f.forSQLPreparedStatement();
Pair<String, List<Object>> statementPart = f.forSQLPreparedStatement(); params.addAll(statementPart.getValue1());
params.addAll(statementPart.getValue1());
if (!first) sql += ", ";
if (!first) sql += ", "; first = false;
first = false; sql += statementPart.getValue0();
sql += statementPart.getValue0(); }
}
sql += ", PRIMARY KEY id(id))";
sql += ", PRIMARY KEY id(id))"; PreparedStatement ps = connection.getNativeConnection().prepareStatement(sql);
PreparedStatement ps = connection.getNativeConnection().prepareStatement(sql); int i = 1;
int i = 1; for (Object val : params)
for (Object val : params) ps.setObject(i++, val);
ps.setObject(i++, val); try {
try { Log.info("Creating table " + elem.tableName() + ":\n" + ps.toString());
Log.info("Creating table " + elem.tableName() + ":\n" + ps.toString()); ps.executeUpdate();
ps.executeUpdate(); } finally {
} finally { ps.close();
ps.close(); }
} }
}
private static boolean tableExist(String tableName) throws SQLException {
private static boolean tableExist(String tableName) throws SQLException { ResultSet set = null;
ResultSet set = null; boolean exist = false;
boolean exist = false; try {
try { set = connection.getNativeConnection().getMetaData().getTables(null, null, tableName, null);
set = connection.getNativeConnection().getMetaData().getTables(null, null, tableName, null); exist = set.next();
exist = set.next(); } finally {
} finally { if (set != null) set.close();
if (set != null) set.close(); }
} return exist;
return exist; }
}
@SuppressWarnings("unchecked")
@SuppressWarnings("unchecked") public static <E extends SQLElement<E>> SQLField<E, Integer> getSQLIdField(Class<E> elemClass)
public static <E extends SQLElement<E>> SQLField<E, Integer> getSQLIdField(Class<E> elemClass) throws ORMInitTableException {
throws ORMInitTableException { initTable(elemClass);
initTable(elemClass); return (SQLField<E, Integer>) SQLElement.fieldsCache.get(elemClass).get("id");
return (SQLField<E, Integer>) SQLElement.fieldsCache.get(elemClass).get("id"); }
}
public static <E extends SQLElement<E>> SQLElementList<E> getByIds(Class<E> elemClass, Collection<Integer> ids)
public static <E extends SQLElement<E>> List<E> getByIds(Class<E> elemClass, Collection<Integer> ids) throws ORMException {
throws ORMException { return getByIds(elemClass, ids.toArray(new Integer[ids.size()]));
return getByIds(elemClass, ids.toArray(new Integer[ids.size()])); }
}
public static <E extends SQLElement<E>> SQLElementList<E> getByIds(Class<E> elemClass, Integer... ids) throws ORMException {
public static <E extends SQLElement<E>> List<E> getByIds(Class<E> elemClass, Integer... ids) throws ORMException { SQLField<E, Integer> idField = getSQLIdField(elemClass);
SQLField<E, Integer> idField = getSQLIdField(elemClass); SQLWhereChain where = new SQLWhereChain(SQLBoolOp.OR);
SQLWhereChain where = new SQLWhereChain(SQLBoolOp.OR); for (Integer id : ids)
for (Integer id : ids) if (id != null) where.add(new SQLWhereComp(idField, SQLComparator.EQ, id));
if (id != null) where.add(new SQLWhereComp(idField, SQLComparator.EQ, id)); return getAll(elemClass, where, new SQLOrderBy().addField(idField), 1, null);
return getAll(elemClass, where, new SQLOrderBy().addField(idField), 1, null); }
}
public static <E extends SQLElement<E>> E getById(Class<E> elemClass, int id) throws ORMException {
public static <E extends SQLElement<E>> E getById(Class<E> elemClass, int id) throws ORMException { return getFirst(elemClass, new SQLWhereComp(getSQLIdField(elemClass), SQLComparator.EQ, id), null);
return getFirst(elemClass, new SQLWhereComp(getSQLIdField(elemClass), SQLComparator.EQ, id), null); }
}
public static <E extends SQLElement<E>> E getFirst(Class<E> elemClass, SQLWhere where, SQLOrderBy orderBy)
public static <E extends SQLElement<E>> E getFirst(Class<E> elemClass, SQLWhere where, SQLOrderBy orderBy) throws ORMException {
throws ORMException { SQLElementList<E> elts = getAll(elemClass, where, orderBy, 1, null);
SQLElementList<E> elts = getAll(elemClass, where, orderBy, 1, null); return (elts.size() == 0) ? null : elts.get(0);
return (elts.size() == 0) ? null : elts.get(0); }
}
public static <E extends SQLElement<E>> SQLElementList<E> getAll(Class<E> elemClass) throws ORMException {
public static <E extends SQLElement<E>> SQLElementList<E> getAll(Class<E> elemClass) throws ORMException { return getAll(elemClass, null, null, null, null);
return getAll(elemClass, null, null, null, null); }
}
public static <E extends SQLElement<E>> SQLElementList<E> getAll(Class<E> elemClass, SQLWhere where,
public static <E extends SQLElement<E>> SQLElementList<E> getAll(Class<E> elemClass, SQLWhere where, SQLOrderBy orderBy, Integer limit, Integer offset) throws ORMException {
SQLOrderBy orderBy, Integer limit, Integer offset) throws ORMException { initTable(elemClass);
initTable(elemClass);
try {
try { String sql = "SELECT * FROM " + elemClass.newInstance().tableName();
String sql = "SELECT * FROM " + elemClass.newInstance().tableName();
List<Object> params = new ArrayList<>();
List<Object> params = new ArrayList<>();
if (where != null) {
if (where != null) { Pair<String, List<Object>> ret = where.toSQL();
Pair<String, List<Object>> ret = where.toSQL(); sql += " WHERE " + ret.getValue0();
sql += " WHERE " + ret.getValue0(); params.addAll(ret.getValue1());
params.addAll(ret.getValue1()); }
} if (orderBy != null) sql += " ORDER BY " + orderBy.toSQL();
if (orderBy != null) sql += " ORDER BY " + orderBy.toSQL(); if (limit != null) sql += " LIMIT " + limit;
if (limit != null) sql += " LIMIT " + limit; if (offset != null) sql += " OFFSET " + offset;
if (offset != null) sql += " OFFSET " + offset; sql += ";";
sql += ";";
SQLElementList<E> elmts = new SQLElementList<>();
SQLElementList<E> elmts = new SQLElementList<>();
PreparedStatement ps = connection.getNativeConnection().prepareStatement(sql);
PreparedStatement ps = connection.getNativeConnection().prepareStatement(sql);
try {
try {
int i = 1;
int i = 1; for (Object val : params) {
for (Object val : params) { if (val instanceof Enum<?>) val = ((Enum<?>) val).name();
if (val instanceof Enum<?>) val = ((Enum<?>) val).name(); ps.setObject(i++, val);
ps.setObject(i++, val); }
} Log.debug(ps.toString());
Log.debug(ps.toString()); ResultSet set = ps.executeQuery();
ResultSet set = ps.executeQuery();
try {
try { while (set.next())
while (set.next()) elmts.add(getElementInstance(set, elemClass));
elmts.add(getElementInstance(set, elemClass)); } finally {
} finally { set.close();
set.close(); }
} } finally {
} finally { ps.close();
ps.close(); }
}
return elmts;
return elmts; } catch (ReflectiveOperationException | SQLException e) {
} catch (ReflectiveOperationException | SQLException e) { throw new ORMException(e);
throw new ORMException(e); }
}
}
}
@SuppressWarnings("unchecked")
private static <E extends SQLElement<E>> E getElementInstance(ResultSet set, Class<E> elemClass) throws ORMException { private static <E extends SQLElement<E>> E getElementInstance(ResultSet set, Class<E> elemClass) throws ORMException {
try { try {
E instance = elemClass.getConstructor(int.class).newInstance(set.getInt("id")); E instance = elemClass.getConstructor(int.class).newInstance(set.getInt("id"));
int fieldCount = set.getMetaData().getColumnCount(); int fieldCount = set.getMetaData().getColumnCount();
for (int c = 1; c <= fieldCount; c++) { for (int c = 1; c <= fieldCount; c++) {
String fieldName = set.getMetaData().getColumnLabel(c); String fieldName = set.getMetaData().getColumnLabel(c);
if (!instance.getFields().containsKey(fieldName)) continue;
// ignore when field is present in database but not handled by SQLElement instance // ignore when field is present in database but not handled by SQLElement instance
@SuppressWarnings("unchecked") if (!instance.getFields().containsKey(fieldName)) continue;
SQLField<E, Object> sqlField = (SQLField<E, Object>) instance.getFields().get(fieldName);
if (sqlField.type.getJavaType().isEnum()) { SQLField<E, Object> sqlField = (SQLField<E, Object>) instance.getFields().get(fieldName);
// JDBC ne supporte pas les enums
String enumStrValue = set.getString(c); boolean customType = sqlField.type instanceof SQLCustomType;
if (enumStrValue == null || set.wasNull()) instance.set(sqlField, null, false);
else { Object val = set.getObject(c,
Enum<?> enumValue = EnumUtil.searchUncheckedEnum(sqlField.type.getJavaType(), enumStrValue); (Class<?>)(customType ? ((SQLCustomType<?, ?>)sqlField.type).intermediateJavaType
if (enumValue == null) throw new ORMException("The enum constant '" + enumStrValue : sqlField.type.getJavaType()));
+ "' is not found in enum class " + sqlField.type.getJavaType().getName());
instance.set(sqlField, enumValue, false); if (val == null || set.wasNull()) {
} instance.set(sqlField, null, false);
} }
else { else {
Object val = set.getObject(c, sqlField.type.getJavaType()); if (customType) {
if (val == null || set.wasNull()) instance.set(sqlField, null, false); try {
else val = ((SQLCustomType<Object, Object>)sqlField.type).dbToJavaConv.apply(val);
instance.set(sqlField, val, false); } catch (Exception e) {
} throw new ORMException("Error while converting value of field '"+sqlField.getName()+"' with SQLCustomType from "+((SQLCustomType<Object, Object>)sqlField.type).intermediateJavaType
+"(jdbc source) to "+sqlField.type.getJavaType()+"(java destination). The original value is '"+val.toString()+"'", e);
// la valeur venant de la BDD est marqué comme "non modifié" }
// dans l'instance }
// car le constructeur de l'instance met tout les champs comme
// modifiés instance.set(sqlField, val, false);
instance.modifiedSinceLastSave.remove(sqlField.name); // la valeur venant de la BDD est marqué comme "non modifié"
} // dans l'instance car le constructeur de l'instance met
// tout les champs comme modifiés
if (!instance.isValidForSave()) throw new ORMException( instance.modifiedSinceLastSave.remove(sqlField.getName());
"This SQLElement representing a database entry is not valid for save : " + instance.toString());
}
return instance; }
} catch (ReflectiveOperationException | IllegalArgumentException | SecurityException | SQLException e) {
throw new ORMException("Can't instanciate " + elemClass.getName(), e); if (!instance.isValidForSave()) throw new ORMException(
} "This SQLElement representing a database entry is not valid for save : " + instance.toString());
}
return instance;
private ORM() {} // rend la classe non instanciable } catch (ReflectiveOperationException | IllegalArgumentException | SecurityException | SQLException e) {
throw new ORMException("Can't instanciate " + elemClass.getName(), e);
/* }
* public static void main(String[] args) throws Throwable { }
* ORM.init(new DBConnection("localhost", 3306, "pandacube", "pandacube",
* "pandacube")); private ORM() {} // rend la classe non instanciable
* List<SQLPlayer> players = ORM.getAll(SQLPlayer.class,
* new SQLWhereChain(SQLBoolOp.AND) /*
* .add(new SQLWhereNull(SQLPlayer.banTimeout, true)) * public static void main(String[] args) throws Throwable {
* .add(new SQLWhereChain(SQLBoolOp.OR) * ORM.init(new DBConnection("localhost", 3306, "pandacube", "pandacube",
* .add(new SQLWhereComp(SQLPlayer.bambou, SQLComparator.EQ, 0L)) * "pandacube"));
* .add(new SQLWhereComp(SQLPlayer.grade, SQLComparator.EQ, "default")) * List<SQLPlayer> players = ORM.getAll(SQLPlayer.class,
* ), * new SQLWhereChain(SQLBoolOp.AND)
* new SQLOrderBy().addField(SQLPlayer.playerDisplayName), null, null); * .add(new SQLWhereNull(SQLPlayer.banTimeout, true))
* for(SQLPlayer p : players) { * .add(new SQLWhereChain(SQLBoolOp.OR)
* System.out.println(p.get(SQLPlayer.playerDisplayName)); * .add(new SQLWhereComp(SQLPlayer.bambou, SQLComparator.EQ, 0L))
* } * .add(new SQLWhereComp(SQLPlayer.grade, SQLComparator.EQ, "default"))
* // TODO mise à jour relative d'un champ (incrément / décrément) * ),
* } * new SQLOrderBy().addField(SQLPlayer.playerDisplayName), null, null);
*/ * for(SQLPlayer p : players) {
* System.out.println(p.get(SQLPlayer.playerDisplayName));
} * }
* // TODO mise à jour relative d'un champ (incrément / décrément)
* }
*/
}

View File

@ -0,0 +1,32 @@
package fr.pandacube.java.util.orm;
import java.util.function.Function;
/**
*
* @author Marc
*
* @param <IT> intermediate type, the type of the value transmitted to the JDBC
* @param <JT> Java type
*/
public class SQLCustomType<IT, JT> extends SQLType<JT> {
public final Class<IT> intermediateJavaType;
public final Function<IT, JT> dbToJavaConv;
public final Function<JT, IT> javaToDbConv;
protected SQLCustomType(SQLType<IT> type, Class<JT> javaT, Function<IT, JT> dbToJava, Function<JT, IT> javaToDb) {
this(type.sqlDeclaration, type.getJavaType(), javaT, dbToJava, javaToDb);
}
protected SQLCustomType(String sqlD, Class<IT> intermediateJavaT, Class<JT> javaT, Function<IT, JT> dbToJava, Function<JT, IT> javaToDb) {
super(sqlD, javaT);
intermediateJavaType = intermediateJavaT;
dbToJavaConv = dbToJava;
javaToDbConv = javaToDb;
}
// tester en local
}

View File

@ -1,410 +1,415 @@
package fr.pandacube.java.util.orm; package fr.pandacube.java.util.orm;
import java.sql.Connection; import java.sql.Connection;
import java.sql.PreparedStatement; import java.sql.PreparedStatement;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import java.sql.Statement; import java.sql.Statement;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Objects; import java.util.Objects;
import java.util.Set; import java.util.Set;
import org.apache.commons.lang.builder.ToStringBuilder; import org.apache.commons.lang.builder.ToStringBuilder;
import com.google.gson.Gson; import com.google.gson.Gson;
import com.google.gson.JsonObject; import com.google.gson.JsonObject;
import fr.pandacube.java.util.Log; import fr.pandacube.java.util.Log;
import fr.pandacube.java.util.orm.SQLWhereComp.SQLComparator; import fr.pandacube.java.util.orm.SQLWhereComp.SQLComparator;
public abstract class SQLElement<E extends SQLElement<E>> { public abstract class SQLElement<E extends SQLElement<E>> {
/** cache for fields for each subclass of SQLElement */ /** cache for fields for each subclass of SQLElement */
/* package */ static final Map<Class<? extends SQLElement<?>>, SQLFieldMap<? extends SQLElement<?>>> fieldsCache = new HashMap<>(); /* package */ static final Map<Class<? extends SQLElement<?>>, SQLFieldMap<? extends SQLElement<?>>> fieldsCache = new HashMap<>();
DBConnection db = ORM.getConnection(); DBConnection db = ORM.getConnection();
private boolean stored = false; private boolean stored = false;
private int id; private int id;
private final String tableName; private final String tableName;
private final SQLFieldMap<E> fields; private final SQLFieldMap<E> fields;
private final Map<SQLField<E, ?>, Object> values; private final Map<SQLField<E, ?>, Object> values;
/* package */ final Set<String> modifiedSinceLastSave; /* package */ final Set<String> modifiedSinceLastSave;
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public SQLElement() { public SQLElement() {
tableName = tableName(); tableName = tableName();
try { try {
ORM.initTable((Class<E>)getClass()); ORM.initTable((Class<E>)getClass());
} catch (ORMInitTableException e) { } catch (ORMInitTableException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
} }
if (fieldsCache.get(getClass()) == null) { if (fieldsCache.get(getClass()) == null) {
fields = new SQLFieldMap<>((Class<E>)getClass()); fields = new SQLFieldMap<>((Class<E>)getClass());
// le champ id commun à toutes les tables // le champ id commun à toutes les tables
fields.addField(new SQLField<>("id", SQLType.INT, false, true, 0)); SQLField<E, Integer> idF = new SQLField<>(SQLType.INT, false, true, 0);
idF.setName("id");
generateFields(fields); fields.addField(idF);
fieldsCache.put((Class<E>)getClass(), fields);
} generateFields(fields);
else fieldsCache.put((Class<E>)getClass(), fields);
fields = (SQLFieldMap<E>) fieldsCache.get(getClass()); }
else
values = new LinkedHashMap<>(fields.size()); fields = (SQLFieldMap<E>) fieldsCache.get(getClass());
modifiedSinceLastSave = new HashSet<>(fields.size());
values = new LinkedHashMap<>(fields.size());
initDefaultValues(); modifiedSinceLastSave = new HashSet<>(fields.size());
} initDefaultValues();
protected SQLElement(int id) { }
this();
@SuppressWarnings("unchecked") protected SQLElement(int id) {
SQLField<E, Integer> idField = (SQLField<E, Integer>) fields.get("id"); this();
set(idField, id, false); @SuppressWarnings("unchecked")
this.id = id; SQLField<E, Integer> idField = (SQLField<E, Integer>) fields.get("id");
stored = true; set(idField, id, false);
} this.id = id;
stored = true;
/** }
* @return The name of the table in the database.
*/ /**
protected abstract String tableName(); * @return The name of the table in the database.
*/
@SuppressWarnings("unchecked") protected abstract String tableName();
private void initDefaultValues() {
// remplissage des données par défaut (si peut être null ou si valeur @SuppressWarnings("unchecked")
// par défaut existe) private void initDefaultValues() {
for (@SuppressWarnings("rawtypes") // remplissage des données par défaut (si peut être null ou si valeur
SQLField f : fields.values()) // par défaut existe)
if (f.defaultValue != null) set(f, f.defaultValue); for (@SuppressWarnings("rawtypes")
else if (f.canBeNull || (f.autoIncrement && !stored)) set(f, null); SQLField f : fields.values())
} if (f.defaultValue != null) set(f, f.defaultValue);
else if (f.canBeNull || (f.autoIncrement && !stored)) set(f, null);
protected void generateFields(SQLFieldMap<E> listToFill) { }
java.lang.reflect.Field[] declaredFields = getClass().getDeclaredFields(); @SuppressWarnings("unchecked")
for (java.lang.reflect.Field field : declaredFields) { protected void generateFields(SQLFieldMap<E> listToFill) {
if (!java.lang.reflect.Modifier.isStatic(field.getModifiers())) continue;
java.lang.reflect.Field[] declaredFields = getClass().getDeclaredFields();
try { for (java.lang.reflect.Field field : declaredFields) {
Object val = field.get(null); if (!java.lang.reflect.Modifier.isStatic(field.getModifiers())) continue;
if (val == null || !(val instanceof SQLField)) continue;
try {
listToFill.addField((SQLField<?, ?>) val); Object val = field.get(null);
} catch (IllegalArgumentException | IllegalAccessException e) { if (val == null || !(val instanceof SQLField)) continue;
Log.severe("Can't get value of static field " + field.toString(), e); SQLField<E, ?> checkedF = (SQLField<E, ?>) val;
} checkedF.setName(field.getName());
} if (listToFill.containsKey(checkedF.getName())) throw new IllegalArgumentException(
"SQLField " + checkedF.getName() + " already exist in " + getClass().getName());
} checkedF.setSQLElementType((Class<E>) getClass());
listToFill.addField((SQLField<?, ?>) val);
/* package */ Map<String, SQLField<E, ?>> getFields() { } catch (IllegalArgumentException | IllegalAccessException e) {
return Collections.unmodifiableMap(fields); Log.severe("Can't get value of static field " + field.toString(), e);
} }
}
public Map<SQLField<E, ?>, Object> getValues() {
return Collections.unmodifiableMap(values); }
}
/* package */ Map<String, SQLField<E, ?>> getFields() {
public <T> void set(SQLField<E, T> field, T value) { return Collections.unmodifiableMap(fields);
set(field, value, true); }
}
public Map<SQLField<E, ?>, Object> getValues() {
/* package */ <T> void set(SQLField<E, T> sqlField, T value, boolean setModified) { return Collections.unmodifiableMap(values);
if (sqlField == null) throw new IllegalArgumentException("sqlField can't be null"); }
if (!fields.containsValue(sqlField))
throw new IllegalArgumentException(sqlField.getSQLElementType().getName()+sqlField.name + " is not a SQLField of " + getClass().getName()); public <T> void set(SQLField<E, T> field, T value) {
set(field, value, true);
boolean modify = false; }
if (value == null) {
if (sqlField.canBeNull || (sqlField.autoIncrement && !stored)) modify = true; /* package */ <T> void set(SQLField<E, T> sqlField, T value, boolean setModified) {
else if (sqlField == null) throw new IllegalArgumentException("sqlField can't be null");
throw new IllegalArgumentException( if (!fields.containsValue(sqlField))
"SQLField '" + sqlField.name + "' of " + getClass().getName() + " is a NOT NULL field"); throw new IllegalArgumentException(sqlField.getSQLElementType().getName()+sqlField.getName() + " is not a SQLField of " + getClass().getName());
}
else if (sqlField.type.isAssignableFrom(value)) modify = true; boolean modify = false;
else if (value == null) {
throw new IllegalArgumentException("SQLField '" + sqlField.name + "' of " + getClass().getName() if (sqlField.canBeNull || (sqlField.autoIncrement && !stored)) modify = true;
+ " type is '" + sqlField.type.toString() + "' and can't accept values of type " else
+ value.getClass().getName()); throw new IllegalArgumentException(
"SQLField '" + sqlField.getName() + "' of " + getClass().getName() + " is a NOT NULL field");
if (modify) if (!values.containsKey(sqlField)) { }
values.put(sqlField, value); else if (sqlField.type.isAssignableFrom(value)) modify = true;
if (setModified) modifiedSinceLastSave.add(sqlField.name); else
} throw new IllegalArgumentException("SQLField '" + sqlField.getName() + "' of " + getClass().getName()
else { + " type is '" + sqlField.type.toString() + "' and can't accept values of type "
Object oldVal = values.get(sqlField); + value.getClass().getName());
if (!Objects.equals(oldVal, value)) {
values.put(sqlField, value); if (modify) if (!values.containsKey(sqlField)) {
if (setModified) modifiedSinceLastSave.add(sqlField.name); values.put(sqlField, value);
} if (setModified) modifiedSinceLastSave.add(sqlField.getName());
// sinon, rien n'est modifié }
} else {
Object oldVal = values.get(sqlField);
} if (!Objects.equals(oldVal, value)) {
values.put(sqlField, value);
public <T> T get(SQLField<E, T> field) { if (setModified) modifiedSinceLastSave.add(sqlField.getName());
if (field == null) throw new IllegalArgumentException("field can't be null"); }
if (values.containsKey(field)) { // sinon, rien n'est modifié
@SuppressWarnings("unchecked") }
T val = (T) values.get(field);
return val; }
}
throw new IllegalArgumentException("The field '" + field.name + "' in this instance of " + getClass().getName() public <T> T get(SQLField<E, T> field) {
+ " does not exist or is not set"); if (field == null) throw new IllegalArgumentException("field can't be null");
} if (values.containsKey(field)) {
@SuppressWarnings("unchecked")
public <T, F extends SQLElement<F>> F getForeign(SQLFKField<E, T, F> field) throws ORMException { T val = (T) values.get(field);
T fkValue = get(field); return val;
if (fkValue == null) return null; }
return ORM.getFirst(field.getForeignElementClass(), throw new IllegalArgumentException("The field '" + field.getName() + "' in this instance of " + getClass().getName()
new SQLWhereComp(field.getForeignField(), SQLComparator.EQ, fkValue), null); + " does not exist or is not set");
} }
public boolean isValidForSave() { public <T, F extends SQLElement<F>> F getForeign(SQLFKField<E, T, F> field) throws ORMException {
return values.keySet().containsAll(fields.values()); T fkValue = get(field);
} if (fkValue == null) return null;
return ORM.getFirst(field.getForeignElementClass(),
private Map<SQLField<E, ?>, Object> getOnlyModifiedValues() { new SQLWhereComp(field.getForeignField(), SQLComparator.EQ, fkValue), null);
Map<SQLField<E, ?>, Object> modifiedValues = new LinkedHashMap<>(); }
values.forEach((k, v) -> {
if (modifiedSinceLastSave.contains(k.name)) modifiedValues.put(k, v); public boolean isValidForSave() {
}); return values.keySet().containsAll(fields.values());
return modifiedValues; }
}
private Map<SQLField<E, ?>, Object> getOnlyModifiedValues() {
public boolean isModified(SQLField<E, ?> field) { Map<SQLField<E, ?>, Object> modifiedValues = new LinkedHashMap<>();
return modifiedSinceLastSave.contains(field.name); values.forEach((k, v) -> {
} if (modifiedSinceLastSave.contains(k.getName())) modifiedValues.put(k, v);
});
@SuppressWarnings("unchecked") return modifiedValues;
public void save() throws ORMException { }
if (!isValidForSave())
throw new IllegalStateException(toString() + " has at least one undefined value and can't be saved."); public boolean isModified(SQLField<E, ?> field) {
return modifiedSinceLastSave.contains(field.getName());
ORM.initTable((Class<E>)getClass()); }
String toStringStatement = "";
try { @SuppressWarnings("unchecked")
public void save() throws ORMException {
Connection conn = db.getNativeConnection(); if (!isValidForSave())
throw new IllegalStateException(toString() + " has at least one undefined value and can't be saved.");
if (stored) { // mettre à jour les valeurs dans la base
ORM.initTable((Class<E>)getClass());
// restaurer l'ID au cas il aurait été changé à la main dans String toStringStatement = "";
// values try {
SQLField<E, Integer> idField = (SQLField<E, Integer>) fields.get("id");
values.put(idField, id); Connection conn = db.getNativeConnection();
modifiedSinceLastSave.remove("id");
Map<SQLField<E, ?>, Object> modifiedValues = getOnlyModifiedValues(); if (stored) { // mettre à jour les valeurs dans la base
if (modifiedValues.isEmpty()) return; // restaurer l'ID au cas il aurait été changé à la main dans
// values
String sql = ""; SQLField<E, Integer> idField = (SQLField<E, Integer>) fields.get("id");
List<Object> psValues = new ArrayList<>(); values.put(idField, id);
modifiedSinceLastSave.remove("id");
for (Map.Entry<SQLField<E, ?>, Object> entry : modifiedValues.entrySet()) { Map<SQLField<E, ?>, Object> modifiedValues = getOnlyModifiedValues();
sql += entry.getKey().name + " = ? ,";
if (entry.getKey().type.getJavaType().isEnum()) // prise en if (modifiedValues.isEmpty()) return;
// charge
// enum (non String sql = "";
// prise en List<Object> psValues = new ArrayList<>();
// charge
// par JDBC) for (Map.Entry<SQLField<E, ?>, Object> entry : modifiedValues.entrySet()) {
psValues.add(((Enum<?>) entry.getValue()).name()); sql += entry.getKey().getName() + " = ? ,";
else addValueToSQLObjectList(psValues, entry.getKey(), entry.getValue());
psValues.add(entry.getValue()); }
}
if (sql.length() > 0) sql = sql.substring(0, sql.length() - 1);
if (sql.length() > 0) sql = sql.substring(0, sql.length() - 1);
PreparedStatement ps = conn.prepareStatement("UPDATE " + tableName + " SET " + sql + " WHERE id=" + id);
PreparedStatement ps = conn.prepareStatement("UPDATE " + tableName + " SET " + sql + " WHERE id=" + id);
try {
try {
int i = 1;
int i = 1; for (Object val : psValues)
for (Object val : psValues) ps.setObject(i++, val);
ps.setObject(i++, val);
toStringStatement = ps.toString();
toStringStatement = ps.toString(); ps.executeUpdate();
ps.executeUpdate(); } finally {
} finally { ps.close();
ps.close(); }
} }
} else { // ajouter dans la base
else { // ajouter dans la base
// restaurer l'ID au cas il aurait été changé à la main dans
// restaurer l'ID au cas il aurait été changé à la main dans // values
// values values.put(fields.get("id"), null);
values.put(fields.get("id"), null);
String concat_vals = "";
String concat_vals = ""; String concat_fields = "";
String concat_fields = ""; List<Object> psValues = new ArrayList<>();
List<Object> psValues = new ArrayList<>();
boolean first = true;
boolean first = true; for (Map.Entry<SQLField<E, ?>, Object> entry : values.entrySet()) {
for (Map.Entry<SQLField<E, ?>, Object> entry : values.entrySet()) { if (!first) {
if (!first) { concat_vals += ",";
concat_vals += ","; concat_fields += ",";
concat_fields += ","; }
} first = false;
first = false; concat_vals += " ? ";
concat_vals += " ? "; concat_fields += entry.getKey().getName();
concat_fields += entry.getKey().name; addValueToSQLObjectList(psValues, entry.getKey(), entry.getValue());
if (entry.getKey().type.getJavaType().isEnum()) // prise en }
// charge
// enum (non PreparedStatement ps = conn.prepareStatement(
// prise en "INSERT INTO " + tableName + " (" + concat_fields + ") VALUES (" + concat_vals + ")",
// charge Statement.RETURN_GENERATED_KEYS);
// par JDBC) try {
psValues.add(((Enum<?>) entry.getValue()).name());
else int i = 1;
psValues.add(entry.getValue()); for (Object val : psValues)
} ps.setObject(i++, val);
PreparedStatement ps = conn.prepareStatement( toStringStatement = ps.toString();
"INSERT INTO " + tableName + " (" + concat_fields + ") VALUES (" + concat_vals + ")", ps.executeUpdate();
Statement.RETURN_GENERATED_KEYS);
try { ResultSet rs = ps.getGeneratedKeys();
try {
int i = 1; if (rs.next()) id = rs.getInt(1);
for (Object val : psValues)
ps.setObject(i++, val); stored = true;
} finally {
toStringStatement = ps.toString(); rs.close();
ps.executeUpdate(); }
} finally {
ResultSet rs = ps.getGeneratedKeys(); ps.close();
try { }
if (rs.next()) id = rs.getInt(1);
}
stored = true;
} finally { modifiedSinceLastSave.clear();
rs.close(); } catch (SQLException e) {
} throw new ORMException("Error while executing SQL statement " + toStringStatement, e);
} finally { }
ps.close(); Log.debug(toStringStatement);
} }
}
@SuppressWarnings("rawtypes")
modifiedSinceLastSave.clear(); protected static <E extends SQLElement<E>> void addValueToSQLObjectList(List<Object> list, SQLField<E, ?> field, Object jValue) throws ORMException {
} catch (SQLException e) { if (jValue != null && field.type instanceof SQLCustomType) {
throw new ORMException("Error while executing SQL statement " + toStringStatement, e); try {
} jValue = ((SQLCustomType)field.type).javaToDbConv.apply(jValue);
Log.debug(toStringStatement); } catch (Exception e) {
} throw new ORMException("Error while converting value of field '"+field.getName()+"' with SQLCustomType from "+field.type.getJavaType()
+"(java source) to "+((SQLCustomType<?, ?>)field.type).intermediateJavaType+"(jdbc destination). The original value is '"+jValue.toString()+"'", e);
public boolean isStored() { }
return stored; }
} list.add(jValue);
}
public Integer getId() {
return (stored) ? id : null; public boolean isStored() {
} return stored;
}
@SuppressWarnings("unchecked")
public SQLField<E, Integer> getFieldId() { public Integer getId() {
return (SQLField<E, Integer>) fields.get("id"); return (stored) ? id : null;
} }
public void delete() throws ORMException { @SuppressWarnings("unchecked")
public SQLField<E, Integer> getFieldId() {
try { return (SQLField<E, Integer>) fields.get("id");
if (stored) { // supprimer la ligne de la base }
PreparedStatement st = db.getNativeConnection()
.prepareStatement("DELETE FROM " + tableName + " WHERE id=" + id); public void delete() throws ORMException {
try {
Log.debug(st.toString()); try {
st.executeUpdate(); if (stored) { // supprimer la ligne de la base
markAsNotStored(); PreparedStatement st = db.getNativeConnection()
} finally { .prepareStatement("DELETE FROM " + tableName + " WHERE id=" + id);
st.close(); try {
} Log.debug(st.toString());
} st.executeUpdate();
} catch (SQLException e) { markAsNotStored();
throw new ORMException(e); } finally {
} st.close();
}
} }
} catch (SQLException e) {
/** throw new ORMException(e);
* Méthode appelée quand l'élément courant est retirée de la base de données }
* via une requête externe
*/ }
/* package */ void markAsNotStored() {
stored = false; /**
id = 0; * Méthode appelée quand l'élément courant est retirée de la base de données
modifiedSinceLastSave.clear(); * via une requête externe
values.forEach((k, v) -> modifiedSinceLastSave.add(k.name)); */
} /* package */ void markAsNotStored() {
stored = false;
protected static class SQLFieldMap<E extends SQLElement<E>> extends LinkedHashMap<String, SQLField<E, ?>> { id = 0;
private static final long serialVersionUID = 1L; modifiedSinceLastSave.clear();
values.forEach((k, v) -> modifiedSinceLastSave.add(k.getName()));
private final Class<E> sqlElemClass; }
private SQLFieldMap(Class<E> elemClass) { protected static class SQLFieldMap<E extends SQLElement<E>> extends LinkedHashMap<String, SQLField<E, ?>> {
sqlElemClass = elemClass; private static final long serialVersionUID = 1L;
}
private final Class<E> sqlElemClass;
private void addField(SQLField<?, ?> f) {
if (f == null) return; private SQLFieldMap(Class<E> elemClass) {
if (containsKey(f.name)) throw new IllegalArgumentException( sqlElemClass = elemClass;
"SQLField " + f.name + " already exist in " + sqlElemClass.getName()); }
@SuppressWarnings("unchecked")
SQLField<E, ?> checkedF = (SQLField<E, ?>) f; private void addField(SQLField<?, ?> f) {
checkedF.setSQLElementType(sqlElemClass); if (f == null) return;
put(checkedF.name, checkedF); if (containsKey(f.getName())) throw new IllegalArgumentException(
} "SQLField " + f.getName() + " already exist in " + sqlElemClass.getName());
@SuppressWarnings("unchecked")
} SQLField<E, ?> checkedF = (SQLField<E, ?>) f;
checkedF.setSQLElementType(sqlElemClass);
@Override put(checkedF.getName(), checkedF);
public String toString() { }
ToStringBuilder b = new ToStringBuilder(this);
}
for (SQLField<E, ?> f : fields.values())
try { @Override
b.append(f.name, get(f)); public String toString() {
} catch (IllegalArgumentException e) { ToStringBuilder b = new ToStringBuilder(this);
b.append(f.name, "(Undefined)");
} for (SQLField<E, ?> f : fields.values())
try {
return b.toString(); b.append(f.getName(), get(f));
} } catch (IllegalArgumentException e) {
b.append(f.getName(), "(Undefined)");
@Override }
public boolean equals(Object o) {
if (o == null || !(getClass().isInstance(o))) return false; return b.toString();
SQLElement<?> oEl = (SQLElement<?>) o; }
if (oEl.getId() == null) return false;
return oEl.getId().equals(getId()); @Override
} public boolean equals(Object o) {
if (o == null || !(getClass().isInstance(o))) return false;
@Override SQLElement<?> oEl = (SQLElement<?>) o;
public int hashCode() { if (oEl.getId() == null) return false;
return super.hashCode(); return oEl.getId().equals(getId());
} }
@Override
public int hashCode() {
public JsonObject asJsonObject() { return super.hashCode();
JsonObject json = new JsonObject(); }
for (SQLField<E, ?> f : getFields().values()) {
json.add(f.name, new Gson().toJsonTree(get(f)));
}
return json; public JsonObject asJsonObject() {
} JsonObject json = new JsonObject();
for (SQLField<E, ?> f : getFields().values()) {
} json.add(f.getName(), new Gson().toJsonTree(get(f)));
}
return json;
}
}

View File

@ -1,207 +1,207 @@
package fr.pandacube.java.util.orm; package fr.pandacube.java.util.orm;
import java.sql.PreparedStatement; import java.sql.PreparedStatement;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet; import java.util.HashSet;
import java.util.LinkedHashMap; import java.util.LinkedHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set; import java.util.Set;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import com.google.gson.JsonArray; import com.google.gson.JsonArray;
import fr.pandacube.java.util.Log; import fr.pandacube.java.util.Log;
import fr.pandacube.java.util.orm.SQLWhereChain.SQLBoolOp; import fr.pandacube.java.util.orm.SQLWhereChain.SQLBoolOp;
import fr.pandacube.java.util.orm.SQLWhereComp.SQLComparator; import fr.pandacube.java.util.orm.SQLWhereComp.SQLComparator;
/** /**
* *
* @param <E> * @param <E>
*/ */
public class SQLElementList<E extends SQLElement<E>> extends ArrayList<E> { public class SQLElementList<E extends SQLElement<E>> extends ArrayList<E> {
private static final long serialVersionUID = 1L; private static final long serialVersionUID = 1L;
private final Map<SQLField<E, ?>, Object> modifiedValues = new LinkedHashMap<>(); private final Map<SQLField<E, ?>, Object> modifiedValues = new LinkedHashMap<>();
@Override @Override
public synchronized boolean add(E e) { public synchronized boolean add(E e) {
if (e == null || !e.isStored()) return false; if (e == null || !e.isStored()) return false;
return super.add(e); return super.add(e);
} }
/** /**
* Défini une valeur à un champ qui sera appliquée dans la base de données à * Défini une valeur à un champ qui sera appliquée dans la base de données à
* tous les * tous les
* entrées présente dans cette liste lors de l'appel à {@link #saveCommon()} * entrées présente dans cette liste lors de l'appel à {@link #saveCommon()}
* . * .
* Les valeurs stockés dans chaque élément de cette liste ne seront affectés * Les valeurs stockés dans chaque élément de cette liste ne seront affectés
* que lors de * que lors de
* l'appel à {@link #saveCommon()} * l'appel à {@link #saveCommon()}
* *
* @param <T> * @param <T>
* @param field le champs à modifier * @param field le champs à modifier
* @param value la valeur à lui appliquer * @param value la valeur à lui appliquer
*/ */
public synchronized <T> void setCommon(SQLField<E, T> field, T value) { public synchronized <T> void setCommon(SQLField<E, T> field, T value) {
if (field == null) if (field == null)
throw new IllegalArgumentException("field can't be null"); throw new IllegalArgumentException("field can't be null");
if (field.name == "id") if (field.getName() == "id")
throw new IllegalArgumentException("Can't modify id field in a SQLElementList"); throw new IllegalArgumentException("Can't modify id field in a SQLElementList");
Class<E> elemClass = field.getSQLElementType(); Class<E> elemClass = field.getSQLElementType();
try { try {
E emptyElement = elemClass.newInstance(); E emptyElement = elemClass.newInstance();
emptyElement.set(field, value, false); emptyElement.set(field, value, false);
} catch (Exception e) { } catch (Exception e) {
throw new IllegalArgumentException("Illegal field or value or can't instanciante an empty instance of " throw new IllegalArgumentException("Illegal field or value or can't instanciante an empty instance of "
+ elemClass.getName() + ". (the instance is only created to test validity of field and value)", e); + elemClass.getName() + ". (the instance is only created to test validity of field and value)", e);
} }
// ici, la valeur est bonne // ici, la valeur est bonne
modifiedValues.put(field, value); modifiedValues.put(field, value);
} }
/** /**
* Applique toutes les valeurs défini avec * Applique toutes les valeurs défini avec
* {@link #setCommon(SQLField, Object)} à toutes * {@link #setCommon(SQLField, Object)} à toutes
* les entrées dans la base de données correspondants aux entrées de cette * les entrées dans la base de données correspondants aux entrées de cette
* liste. Les nouvelles * liste. Les nouvelles
* valeurs sont aussi mises à jour dans les objets contenus dans cette * valeurs sont aussi mises à jour dans les objets contenus dans cette
* liste, si la valeur n'a pas été modifiée individuellement avec * liste, si la valeur n'a pas été modifiée individuellement avec
* {@link SQLElement#set(SQLField, Object)}.<br/> * {@link SQLElement#set(SQLField, Object)}.<br/>
* Les objets de cette liste qui n'ont pas leur données en base de données * Les objets de cette liste qui n'ont pas leur données en base de données
* sont ignorées. * sont ignorées.
* *
* @throws SQLException * @throws SQLException
*/ */
public synchronized void saveCommon() throws SQLException { public synchronized void saveCommon() throws ORMException {
List<E> storedEl = getStoredEl(); List<E> storedEl = getStoredEl();
if (storedEl.isEmpty()) return; if (storedEl.isEmpty()) return;
String sqlSet = ""; String sqlSet = "";
List<Object> psValues = new ArrayList<>(); List<Object> psValues = new ArrayList<>();
for (Map.Entry<SQLField<E, ?>, Object> entry : modifiedValues.entrySet()) { for (Map.Entry<SQLField<E, ?>, Object> entry : modifiedValues.entrySet()) {
sqlSet += entry.getKey().name + " = ? ,"; sqlSet += entry.getKey().getName() + " = ? ,";
if (entry.getKey().type.getJavaType().isEnum()) // prise en charge SQLElement.addValueToSQLObjectList(psValues, entry.getKey(), entry.getValue());
// enum (non prise if (entry.getKey().type.getJavaType().isEnum()) {
// en charge par // prise en charge enum (non prise en charge par JDBC)
// JDBC) psValues.add(((Enum<?>) entry.getValue()).name());
psValues.add(((Enum<?>) entry.getValue()).name()); }
else else
psValues.add(entry.getValue()); psValues.add(entry.getValue());
} }
if (sqlSet.length() > 0) sqlSet = sqlSet.substring(0, sqlSet.length() - 1); if (sqlSet.length() > 0) sqlSet = sqlSet.substring(0, sqlSet.length() - 1);
String sqlWhere = ""; String sqlWhere = "";
boolean first = true; boolean first = true;
for (E el : storedEl) { for (E el : storedEl) {
if (!first) sqlWhere += " OR "; if (!first) sqlWhere += " OR ";
first = false; first = false;
sqlWhere += "id = " + el.getId(); sqlWhere += "id = " + el.getId();
} }
PreparedStatement ps = ORM.getConnection().getNativeConnection() try(PreparedStatement ps = ORM.getConnection().getNativeConnection()
.prepareStatement("UPDATE " + storedEl.get(0).tableName() + " SET " + sqlSet + " WHERE " + sqlWhere); .prepareStatement("UPDATE " + storedEl.get(0).tableName() + " SET " + sqlSet + " WHERE " + sqlWhere);) {
try {
int i = 1; int i = 1;
for (Object val : psValues) for (Object val : psValues)
ps.setObject(i++, val); ps.setObject(i++, val);
Log.debug(ps.toString()); Log.debug(ps.toString());
ps.executeUpdate(); ps.executeUpdate();
applyNewValuesToElements(storedEl); applyNewValuesToElements(storedEl);
} finally { } catch (SQLException e) {
ps.close(); throw new ORMException(e);
} }
} }
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private void applyNewValuesToElements(List<E> storedEl) { private void applyNewValuesToElements(List<E> storedEl) {
// applique les valeurs dans chaques objets de la liste // applique les valeurs dans chaques objets de la liste
for (E el : storedEl) for (E el : storedEl)
for (@SuppressWarnings("rawtypes") for (@SuppressWarnings("rawtypes")
SQLField entry : modifiedValues.keySet()) SQLField entry : modifiedValues.keySet())
if (!el.isModified(entry)) el.set(entry, modifiedValues.get(entry), false); if (!el.isModified(entry)) el.set(entry, modifiedValues.get(entry), false);
} }
private List<E> getStoredEl() { private List<E> getStoredEl() {
return stream().filter(SQLElement::isStored).collect(Collectors.toCollection(() -> new ArrayList<>())); return stream().filter(SQLElement::isStored).collect(Collectors.toCollection(() -> new ArrayList<>()));
} }
public synchronized void removeFromDB() { public synchronized void removeFromDB() {
List<E> storedEl = getStoredEl(); List<E> storedEl = getStoredEl();
if (storedEl.isEmpty()) return; if (storedEl.isEmpty()) return;
try { try {
String sqlWhere = ""; String sqlWhere = "";
boolean first = true; boolean first = true;
for (E el : storedEl) { for (E el : storedEl) {
if (!first) sqlWhere += " OR "; if (!first) sqlWhere += " OR ";
first = false; first = false;
sqlWhere += "id = " + el.getId(); sqlWhere += "id = " + el.getId();
} }
PreparedStatement st = ORM.getConnection().getNativeConnection() PreparedStatement st = ORM.getConnection().getNativeConnection()
.prepareStatement("DELETE FROM " + storedEl.get(0).tableName() + " WHERE " + sqlWhere); .prepareStatement("DELETE FROM " + storedEl.get(0).tableName() + " WHERE " + sqlWhere);
try { try {
Log.debug(st.toString()); Log.debug(st.toString());
st.executeUpdate(); st.executeUpdate();
for (E el : storedEl) for (E el : storedEl)
el.markAsNotStored(); el.markAsNotStored();
} finally { } finally {
st.close(); st.close();
} }
} catch (SQLException e) { } catch (SQLException e) {
Log.severe(e); Log.severe(e);
} }
} }
public <T, F extends SQLElement<F>> Map<T, F> getAllForeign(SQLFKField<E, T, F> foreignKey) throws ORMException { public <T, F extends SQLElement<F>> Map<T, F> getAllForeign(SQLFKField<E, T, F> foreignKey) throws ORMException {
Set<T> values = new HashSet<>(); Set<T> values = new HashSet<>();
forEach(v -> { forEach(v -> {
T val = v.get(foreignKey); T val = v.get(foreignKey);
if (val != null) if (val != null)
values.add(val); values.add(val);
}); });
if (values.isEmpty()) { if (values.isEmpty()) {
return new HashMap<>(); return new HashMap<>();
} }
SQLWhereChain where = new SQLWhereChain(SQLBoolOp.OR); SQLWhereChain where = new SQLWhereChain(SQLBoolOp.OR);
values.forEach(v -> where.add(new SQLWhereComp(foreignKey.getForeignField(), SQLComparator.EQ, v))); values.forEach(v -> where.add(new SQLWhereComp(foreignKey.getForeignField(), SQLComparator.EQ, v)));
SQLElementList<F> foreignElemts = ORM.getAll(foreignKey.getForeignElementClass(), where, null, null, null); SQLElementList<F> foreignElemts = ORM.getAll(foreignKey.getForeignElementClass(), where, null, null, null);
Map<T, F> ret = new HashMap<>(); Map<T, F> ret = new HashMap<>();
foreignElemts.forEach(foreignVal -> ret.put(foreignVal.get(foreignKey.getForeignField()), foreignVal)); foreignElemts.forEach(foreignVal -> ret.put(foreignVal.get(foreignKey.getForeignField()), foreignVal));
return ret; return ret;
} }
public JsonArray asJsonArray() { public JsonArray asJsonArray() {
JsonArray json = new JsonArray(); JsonArray json = new JsonArray();
forEach(el -> json.add(el.asJsonObject())); forEach(el -> json.add(el.asJsonObject()));
return json; return json;
} }
} }

View File

@ -1,65 +1,62 @@
package fr.pandacube.java.util.orm; package fr.pandacube.java.util.orm;
import fr.pandacube.java.util.Log; import fr.pandacube.java.util.Log;
public class SQLFKField<E extends SQLElement<E>, T, F extends SQLElement<F>> extends SQLField<E, T> { public class SQLFKField<E extends SQLElement<E>, T, F extends SQLElement<F>> extends SQLField<E, T> {
private SQLField<F, T> sqlForeignKeyField; private SQLField<F, T> sqlForeignKeyField;
private Class<F> sqlForeignKeyElemClass; private Class<F> sqlForeignKeyElemClass;
public SQLFKField(String n, SQLType<T> t, boolean nul, Class<F> fkEl, SQLField<F, T> fkF) { protected SQLFKField(SQLType<T> t, boolean nul, T deflt, Class<F> fkEl, SQLField<F, T> fkF) {
super(n, t, nul); super(t, nul, deflt);
construct(fkEl, fkF); construct(fkEl, fkF);
} }
public SQLFKField(String n, SQLType<T> t, boolean nul, T deflt, Class<F> fkEl, SQLField<F, T> fkF) { public static <E extends SQLElement<E>, F extends SQLElement<F>> SQLFKField<E, Integer, F> idFK(boolean nul, Class<F> fkEl) {
super(n, t, nul, deflt); return idFK(nul, null, fkEl);
construct(fkEl, fkF); }
}
public static <E extends SQLElement<E>, F extends SQLElement<F>> SQLFKField<E, Integer, F> idFK(boolean nul, Integer deflt, Class<F> fkEl) {
public static <E extends SQLElement<E>, F extends SQLElement<F>> SQLFKField<E, Integer, F> idFK(String n, SQLType<Integer> t, boolean nul, if (fkEl == null) throw new IllegalArgumentException("foreignKeyElement can't be null");
Class<F> fkEl) { try {
if (fkEl == null) throw new IllegalArgumentException("foreignKeyElement can't be null"); SQLField<F, Integer> f = ORM.getSQLIdField(fkEl);
try { return new SQLFKField<>(f.type, nul, deflt, fkEl, f);
return new SQLFKField<>(n, t, nul, fkEl, ORM.getSQLIdField(fkEl)); } catch (ORMInitTableException e) {
} catch (ORMInitTableException e) { Log.severe("Can't create Foreign key Field targetting id field of '"+fkEl+"'", e);
Log.severe("Can't create Foreign key Field called '" + n + "'", e); return null;
return null; }
} }
}
public static <E extends SQLElement<E>, T, F extends SQLElement<F>> SQLFKField<E, T, F> customFK(boolean nul, Class<F> fkEl, SQLField<F, T> fkF) {
public static <E extends SQLElement<E>, F extends SQLElement<F>> SQLFKField<E, Integer, F> idFKField(String n, SQLType<Integer> t, boolean nul, return customFK(nul, null, fkEl, fkF);
Integer deflt, Class<F> fkEl) { }
if (fkEl == null) throw new IllegalArgumentException("foreignKeyElement can't be null");
try { public static <E extends SQLElement<E>, T, F extends SQLElement<F>> SQLFKField<E, T, F> customFK(boolean nul, T deflt, Class<F> fkEl, SQLField<F, T> fkF) {
return new SQLFKField<>(n, t, nul, deflt, fkEl, ORM.getSQLIdField(fkEl)); if (fkEl == null) throw new IllegalArgumentException("foreignKeyElement can't be null");
} catch (ORMInitTableException e) { return new SQLFKField<>(fkF.type, nul, deflt, fkEl, fkF);
Log.severe("Can't create Foreign key Field called '" + n + "'", e); }
return null;
} private void construct(Class<F> fkEl, SQLField<F, T> fkF) {
} if (fkF == null) throw new IllegalArgumentException("foreignKeyField can't be null");
try {
private void construct(Class<F> fkEl, SQLField<F, T> fkF) { ORM.initTable(fkEl);
if (fkF == null) throw new IllegalArgumentException("foreignKeyField can't be null"); } catch (ORMInitTableException e) {
try { throw new RuntimeException(e);
ORM.initTable(fkEl); }
} catch (ORMInitTableException e) { if (!fkEl.equals(fkF.getSQLElementType()))
throw new RuntimeException(e); throw new IllegalArgumentException("foreignKeyField must be from supplied foreignKeyElement");
} if (!type.equals(fkF.type))
if (!fkEl.equals(fkF.getSQLElementType())) throw new IllegalArgumentException("foreignKeyField and current Field must have the same SQLType");
throw new IllegalArgumentException("foreignKeyField must be from supplied foreignKeyElement"); sqlForeignKeyField = fkF;
if (!type.equals(fkF.type)) sqlForeignKeyElemClass = fkEl;
throw new IllegalArgumentException("foreignKeyField and current Field must have the same SQLType"); }
sqlForeignKeyField = fkF;
sqlForeignKeyElemClass = fkEl; public SQLField<F, T> getForeignField() {
} return sqlForeignKeyField;
}
public SQLField<F, T> getForeignField() {
return sqlForeignKeyField; public Class<F> getForeignElementClass() {
} return sqlForeignKeyElemClass;
}
public Class<F> getForeignElementClass() {
return sqlForeignKeyElemClass; }
}
}

View File

@ -1,80 +1,87 @@
package fr.pandacube.java.util.orm; package fr.pandacube.java.util.orm;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.javatuples.Pair; import org.javatuples.Pair;
public class SQLField<E extends SQLElement<E>, T> { public class SQLField<E extends SQLElement<E>, T> {
private Class<E> sqlElemClass; private Class<E> sqlElemClass;
public final String name; private String name = null;
public final SQLType<T> type; public final SQLType<T> type;
public final boolean canBeNull; public final boolean canBeNull;
public final boolean autoIncrement; public final boolean autoIncrement;
/* package */ final T defaultValue; /* package */ final T defaultValue;
public SQLField(String n, SQLType<T> t, boolean nul, boolean autoIncr, T deflt) { public SQLField(SQLType<T> t, boolean nul, boolean autoIncr, T deflt) {
name = n; type = t;
type = t; canBeNull = nul;
canBeNull = nul; autoIncrement = autoIncr;
autoIncrement = autoIncr; defaultValue = deflt;
defaultValue = deflt; }
}
public SQLField(SQLType<T> t, boolean nul) {
public SQLField(String n, SQLType<T> t, boolean nul) { this(t, nul, false, null);
this(n, t, nul, false, null); }
}
public SQLField(SQLType<T> t, boolean nul, boolean autoIncr) {
public SQLField(String n, SQLType<T> t, boolean nul, boolean autoIncr) { this(t, nul, autoIncr, null);
this(n, t, nul, autoIncr, null); }
}
public SQLField(SQLType<T> t, boolean nul, T deflt) {
public SQLField(String n, SQLType<T> t, boolean nul, T deflt) { this(t, nul, false, deflt);
this(n, t, nul, false, deflt); }
}
/* package */ Pair<String, List<Object>> forSQLPreparedStatement() {
/* package */ Pair<String, List<Object>> forSQLPreparedStatement() { List<Object> params = new ArrayList<>(1);
List<Object> params = new ArrayList<>(1); if (defaultValue != null && !autoIncrement) params.add(defaultValue);
if (defaultValue != null && !autoIncrement) params.add(defaultValue); return new Pair<>(getName() + " " + type.toString() + (canBeNull ? " NULL" : " NOT NULL")
return new Pair<>(name + " " + type.toString() + (canBeNull ? " NULL" : " NOT NULL") + (autoIncrement ? " AUTO_INCREMENT" : "")
+ (autoIncrement ? " AUTO_INCREMENT" : "") + ((defaultValue == null || autoIncrement) ? "" : " DEFAULT ?"), params);
+ ((defaultValue == null || autoIncrement) ? "" : " DEFAULT ?"), params); }
}
/* package */ void setSQLElementType(Class<E> elemClass) {
/* package */ void setSQLElementType(Class<E> elemClass) { sqlElemClass = elemClass;
sqlElemClass = elemClass; }
}
public Class<E> getSQLElementType() {
public Class<E> getSQLElementType() { return sqlElemClass;
return sqlElemClass; }
}
/* package */ void setName(String n) {
/** name = n;
* <b>Don't use this {@link #toString()} method in a SQL query, because }
* the default value is not escaped correctly</b>
* public String getName() {
* @see java.lang.Object#toString() return name;
*/ }
@Override
public String toString() { /**
return forSQLPreparedStatement().getValue0().replaceFirst("\\?", * <b>Don't use this {@link #toString()} method in a SQL query, because
(defaultValue != null && !autoIncrement) ? defaultValue.toString() : ""); * the default value is not escaped correctly</b>
} *
* @see java.lang.Object#toString()
@Override */
public boolean equals(Object obj) { @Override
if (obj == null) return false; public String toString() {
if (!(obj instanceof SQLField)) return false; return forSQLPreparedStatement().getValue0().replaceFirst("\\?",
SQLField<?, ?> f = (SQLField<?, ?>) obj; (defaultValue != null && !autoIncrement) ? defaultValue.toString() : "");
if (!f.name.equals(name)) return false; }
if (!f.sqlElemClass.equals(sqlElemClass)) return false;
return true; @Override
} public boolean equals(Object obj) {
if (obj == null) return false;
@Override if (!(obj instanceof SQLField)) return false;
public int hashCode() { SQLField<?, ?> f = (SQLField<?, ?>) obj;
return name.hashCode() + sqlElemClass.hashCode(); if (!f.getName().equals(getName())) return false;
} if (!f.sqlElemClass.equals(sqlElemClass)) return false;
return true;
} }
@Override
public int hashCode() {
return getName().hashCode() + sqlElemClass.hashCode();
}
}

View File

@ -1,68 +1,68 @@
package fr.pandacube.java.util.orm; package fr.pandacube.java.util.orm;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
public class SQLOrderBy { public class SQLOrderBy {
private List<OBField> orderByFields = new ArrayList<>(); private List<OBField> orderByFields = new ArrayList<>();
/** /**
* Construit une nouvelle clause ORDER BY * Construit une nouvelle clause ORDER BY
*/ */
public SQLOrderBy() {} public SQLOrderBy() {}
/** /**
* Ajoute un champ dans la clause ORDER BY en construction * Ajoute un champ dans la clause ORDER BY en construction
* *
* @param field le champ SQL à ordonner * @param field le champ SQL à ordonner
* @param d le sens de tri (croissant ASC ou décroissant DESC) * @param d le sens de tri (croissant ASC ou décroissant DESC)
* @return l'objet courant (permet de chainer les ajouts de champs) * @return l'objet courant (permet de chainer les ajouts de champs)
*/ */
public SQLOrderBy addField(SQLField<?, ?> field, Direction d) { public SQLOrderBy addField(SQLField<?, ?> field, Direction d) {
orderByFields.add(new OBField(field, d)); orderByFields.add(new OBField(field, d));
return this; return this;
} }
/** /**
* Ajoute un champ dans la clause ORDER BY en construction, * Ajoute un champ dans la clause ORDER BY en construction,
* avec comme ordre de tri croissant ASC par défaut * avec comme ordre de tri croissant ASC par défaut
* *
* @param field le champ SQL à ordonner dans l'ordre croissant ASC * @param field le champ SQL à ordonner dans l'ordre croissant ASC
* @return l'objet courant (permet de chainer les ajouts de champs) * @return l'objet courant (permet de chainer les ajouts de champs)
*/ */
public SQLOrderBy addField(SQLField<?, ?> field) { public SQLOrderBy addField(SQLField<?, ?> field) {
return addField(field, Direction.ASC); return addField(field, Direction.ASC);
} }
/* package */ String toSQL() { /* package */ String toSQL() {
String ret = ""; String ret = "";
boolean first = true; boolean first = true;
for (OBField f : orderByFields) { for (OBField f : orderByFields) {
if (!first) ret += ", "; if (!first) ret += ", ";
first = false; first = false;
ret += f.field.name + " " + f.direction.name(); ret += f.field.getName() + " " + f.direction.name();
} }
return ret; return ret;
} }
@Override @Override
public String toString() { public String toString() {
return toSQL(); return toSQL();
} }
private class OBField { private class OBField {
public final SQLField<?, ?> field; public final SQLField<?, ?> field;
public final Direction direction; public final Direction direction;
public OBField(SQLField<?, ?> f, Direction d) { public OBField(SQLField<?, ?> f, Direction d) {
field = f; field = f;
direction = d; direction = d;
} }
} }
public enum Direction { public enum Direction {
ASC, DESC; ASC, DESC;
} }
} }

View File

@ -1,90 +1,95 @@
package fr.pandacube.java.util.orm; package fr.pandacube.java.util.orm;
import java.sql.Date; import java.sql.Date;
import java.util.UUID;
public class SQLType<T> {
import fr.pandacube.java.util.EnumUtil;
private final String sqlType;
private final String sqlTypeParam; public class SQLType<T> {
private final Class<T> javaTypes;
protected final String sqlDeclaration;
public SQLType(String sqlT, String sqlP, Class<T> javaT) { private final Class<T> javaTypes;
sqlType = sqlT;
sqlTypeParam = sqlP; protected SQLType(String sqlD, Class<T> javaT) {
javaTypes = javaT; sqlDeclaration = sqlD;
} javaTypes = javaT;
}
@Override
public String toString() { @Override
return sqlType + sqlTypeParam; public String toString() {
} return sqlDeclaration;
}
public boolean isAssignableFrom(Object val) {
if (javaTypes.isInstance(val)) return true; public boolean isAssignableFrom(Object val) {
return false; if (javaTypes.isInstance(val)) return true;
} return false;
}
@Override
public int hashCode() { @Override
return toString().hashCode(); public int hashCode() {
} return toString().hashCode();
}
@Override
public boolean equals(Object obj) { @Override
if (obj == null || !(obj instanceof SQLType)) return false; public boolean equals(Object obj) {
return toString().equals(((SQLType<?>) obj).toString()); if (obj == null || !(obj instanceof SQLType)) return false;
} return toString().equals(((SQLType<?>) obj).toString());
}
public Class<T> getJavaType() {
return javaTypes; public Class<T> getJavaType() {
} return javaTypes;
}
public static final SQLType<Boolean> BOOLEAN = new SQLType<>("BOOLEAN", "", Boolean.class);
public static final SQLType<Boolean> BOOLEAN = new SQLType<>("BOOLEAN", Boolean.class);
public static final SQLType<Byte> TINYINT = new SQLType<>("TINYINT", "", Byte.class);
public static final SQLType<Byte> BYTE = TINYINT; public static final SQLType<Byte> TINYINT = new SQLType<>("TINYINT", Byte.class);
public static final SQLType<Byte> BYTE = TINYINT;
public static final SQLType<Short> SMALLINT = new SQLType<>("SMALLINT", "", Short.class);
public static final SQLType<Short> SHORT = SMALLINT; public static final SQLType<Short> SMALLINT = new SQLType<>("SMALLINT", Short.class);
public static final SQLType<Short> SHORT = SMALLINT;
public static final SQLType<Integer> INT = new SQLType<>("INT", "", Integer.class);
public static final SQLType<Integer> INTEGER = INT; public static final SQLType<Integer> INT = new SQLType<>("INT", Integer.class);
public static final SQLType<Integer> INTEGER = INT;
public static final SQLType<Long> BIGINT = new SQLType<>("BIGINT", "", Long.class);
public static final SQLType<Long> LONG = BIGINT; public static final SQLType<Long> BIGINT = new SQLType<>("BIGINT", Long.class);
public static final SQLType<Long> LONG = BIGINT;
public static final SQLType<Date> DATE = new SQLType<>("DATE", "", Date.class);
public static final SQLType<Date> DATE = new SQLType<>("DATE", Date.class);
public static final SQLType<Float> FLOAT = new SQLType<>("FLOAT", "", Float.class);
public static final SQLType<Float> FLOAT = new SQLType<>("FLOAT", Float.class);
public static final SQLType<Double> DOUBLE = new SQLType<>("DOUBLE", "", Double.class);
public static final SQLType<Double> DOUBLE = new SQLType<>("DOUBLE", Double.class);
public static final SQLType<String> CHAR(int charCount) {
if (charCount <= 0) throw new IllegalArgumentException("charCount must be positive."); @Deprecated
return new SQLType<>("CHAR", "(" + charCount + ")", String.class); public static final SQLType<String> CHAR(int charCount) {
} if (charCount <= 0) throw new IllegalArgumentException("charCount must be positive.");
return new SQLType<>("CHAR(" + charCount + ")", String.class);
public static final SQLType<String> VARCHAR(int charCount) { }
if (charCount <= 0) throw new IllegalArgumentException("charCount must be positive.");
return new SQLType<>("VARCHAR", "(" + charCount + ")", String.class); public static final SQLType<String> VARCHAR(int charCount) {
} if (charCount <= 0) throw new IllegalArgumentException("charCount must be positive.");
return new SQLType<>("VARCHAR(" + charCount + ")", String.class);
public static final SQLType<String> TEXT = new SQLType<>("TEXT", "", String.class); }
public static final SQLType<String> STRING = TEXT;
public static final SQLType<String> TEXT = new SQLType<>("TEXT", String.class);
public static final <T extends Enum<T>> SQLType<T> ENUM(Class<T> enumType) { public static final SQLType<String> STRING = TEXT;
if (enumType == null) throw new IllegalArgumentException("enumType can't be null.");
String enumStr = "'"; public static final <T extends Enum<T>> SQLType<T> ENUM(Class<T> enumType) {
boolean first = true; if (enumType == null) throw new IllegalArgumentException("enumType can't be null.");
for (T el : enumType.getEnumConstants()) { String enumStr = "'";
if (!first) enumStr += "', '"; boolean first = true;
first = false; for (T el : enumType.getEnumConstants()) {
enumStr += el.name(); if (!first) enumStr += "', '";
first = false;
} enumStr += el.name();
enumStr += "'";
}
return new SQLType<>("VARCHAR", "(" + enumStr + ")", enumType); enumStr += "'";
}
return new SQLCustomType<>("VARCHAR(" + enumStr + ")", String.class, enumType, s -> EnumUtil.searchEnum(enumType, s), Enum::name);
} }
public static final SQLType<UUID> CHAR36_UUID = new SQLCustomType<>(SQLType.CHAR(36), UUID.class, UUID::fromString, UUID::toString);
}

View File

@ -1,16 +1,23 @@
package fr.pandacube.java.util.orm; package fr.pandacube.java.util.orm;
import java.util.List; import java.util.List;
import org.javatuples.Pair; import org.javatuples.Pair;
public abstract class SQLWhere { import fr.pandacube.java.util.Log;
public abstract Pair<String, List<Object>> toSQL(); public abstract class SQLWhere {
@Override public abstract Pair<String, List<Object>> toSQL() throws ORMException;
public String toString() {
return toSQL().getValue0(); @Override
} public String toString() {
try {
} return toSQL().getValue0();
} catch (ORMException e) {
Log.warning(e);
return "[SQLWhere.toString() error (see logs)]";
}
}
}

View File

@ -1,54 +1,54 @@
package fr.pandacube.java.util.orm; package fr.pandacube.java.util.orm;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.javatuples.Pair; import org.javatuples.Pair;
public class SQLWhereChain extends SQLWhere { public class SQLWhereChain extends SQLWhere {
private SQLBoolOp operator; private SQLBoolOp operator;
private List<SQLWhere> conditions = new ArrayList<>(); private List<SQLWhere> conditions = new ArrayList<>();
public SQLWhereChain(SQLBoolOp op) { public SQLWhereChain(SQLBoolOp op) {
if (op == null) throw new IllegalArgumentException("op can't be null"); if (op == null) throw new IllegalArgumentException("op can't be null");
operator = op; operator = op;
} }
public SQLWhereChain add(SQLWhere sqlWhere) { public SQLWhereChain add(SQLWhere sqlWhere) {
if (sqlWhere == null) throw new IllegalArgumentException("sqlWhere can't be null"); if (sqlWhere == null) throw new IllegalArgumentException("sqlWhere can't be null");
conditions.add(sqlWhere); conditions.add(sqlWhere);
return this; return this;
} }
@Override @Override
public Pair<String, List<Object>> toSQL() { public Pair<String, List<Object>> toSQL() throws ORMException {
String sql = ""; String sql = "";
List<Object> params = new ArrayList<>(); List<Object> params = new ArrayList<>();
boolean first = true; boolean first = true;
for (SQLWhere w : conditions) { for (SQLWhere w : conditions) {
if (!first) sql += " " + operator.sql + " "; if (!first) sql += " " + operator.sql + " ";
first = false; first = false;
Pair<String, List<Object>> ret = w.toSQL(); Pair<String, List<Object>> ret = w.toSQL();
sql += "(" + ret.getValue0() + ")"; sql += "(" + ret.getValue0() + ")";
params.addAll(ret.getValue1()); params.addAll(ret.getValue1());
} }
return new Pair<>(sql, params); return new Pair<>(sql, params);
} }
public enum SQLBoolOp { public enum SQLBoolOp {
/** Equivalent to SQL "<code>AND</code>" */ /** Equivalent to SQL "<code>AND</code>" */
AND("AND"), /** Equivalent to SQL "<code>OR</code>" */ AND("AND"), /** Equivalent to SQL "<code>OR</code>" */
OR("OR"); OR("OR");
public final String sql; public final String sql;
private SQLBoolOp(String s) { private SQLBoolOp(String s) {
sql = s; sql = s;
} }
} }
} }

View File

@ -1,53 +1,58 @@
package fr.pandacube.java.util.orm; package fr.pandacube.java.util.orm;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.javatuples.Pair; import org.javatuples.Pair;
public class SQLWhereComp extends SQLWhere { public class SQLWhereComp extends SQLWhere {
private SQLField<?, ?> left; private SQLField<?, ?> left;
private SQLComparator comp; private SQLComparator comp;
private Object right; private Object right;
/** /**
* Compare a field with a value * Compare a field with a value
* *
* @param l the field at left of the comparison operator. Can't be null * @param l the field at left of the comparison operator. Can't be null
* @param c the comparison operator, can't be null * @param c the comparison operator, can't be null
* @param r the value at right of the comparison operator. Can't be null * @param r the value at right of the comparison operator. Can't be null
*/ */
public <T> SQLWhereComp(SQLField<?, T> l, SQLComparator c, T r) { public <T> SQLWhereComp(SQLField<?, T> l, SQLComparator c, T r) {
if (l == null || r == null || c == null) if (l == null || r == null || c == null)
throw new IllegalArgumentException("All arguments for SQLWhereComp constructor can't be null"); throw new IllegalArgumentException("All arguments for SQLWhereComp constructor can't be null");
left = l; left = l;
comp = c; comp = c;
right = r; right = r;
} }
@Override @Override
public Pair<String, List<Object>> toSQL() { public Pair<String, List<Object>> toSQL() throws ORMException {
List<Object> params = new ArrayList<>(); List<Object> params = new ArrayList<>();
params.add(right); SQLElement.addValueToSQLObjectList(params, left, right);
return new Pair<>(left.name + " " + comp.sql + " ? ", params); return new Pair<>(left.getName() + " " + comp.sql + " ? ", params);
} }
public enum SQLComparator { public enum SQLComparator {
/** Equivalent to SQL "<code>=</code>" */ /** Equivalent to SQL "<code>=</code>" */
EQ("="), /** Equivalent to SQL "<code>></code>" */ EQ("="),
GT(">"), /** Equivalent to SQL "<code>>=</code>" */ /** Equivalent to SQL "<code>></code>" */
GEQ(">="), /** Equivalent to SQL "<code>&lt;</code>" */ GT(">"),
LT("<"), /** Equivalent to SQL "<code>&lt;=</code>" */ /** Equivalent to SQL "<code>>=</code>" */
LEQ("<="), /** Equivalent to SQL "<code>!=</code>" */ GEQ(">="),
NEQ("!="); /** Equivalent to SQL "<code>&lt;</code>" */
LT("<"),
public final String sql; /** Equivalent to SQL "<code>&lt;=</code>" */
LEQ("<="),
private SQLComparator(String s) { /** Equivalent to SQL "<code>!=</code>" */
sql = s; NEQ("!=");
}
public final String sql;
}
private SQLComparator(String s) {
} sql = s;
}
}
}

View File

@ -1,33 +1,33 @@
package fr.pandacube.java.util.orm; package fr.pandacube.java.util.orm;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.javatuples.Pair; import org.javatuples.Pair;
public class SQLWhereLike extends SQLWhere { public class SQLWhereLike extends SQLWhere {
private SQLField<?, String> field; private SQLField<?, String> field;
private String likeExpr; private String likeExpr;
/** /**
* Compare a field with a value * Compare a field with a value
* *
* @param f the field at left of the LIKE keyword. Can't be null * @param f the field at left of the LIKE keyword. Can't be null
* @param like the like expression. * @param like the like expression.
*/ */
public SQLWhereLike(SQLField<?, String> f, String like) { public SQLWhereLike(SQLField<?, String> f, String like) {
if (f == null || like == null) if (f == null || like == null)
throw new IllegalArgumentException("All arguments for SQLWhereLike constructor can't be null"); throw new IllegalArgumentException("All arguments for SQLWhereLike constructor can't be null");
field = f; field = f;
likeExpr = like; likeExpr = like;
} }
@Override @Override
public Pair<String, List<Object>> toSQL() { public Pair<String, List<Object>> toSQL() {
ArrayList<Object> params = new ArrayList<>(); ArrayList<Object> params = new ArrayList<>();
params.add(likeExpr); params.add(likeExpr);
return new Pair<>(field.name + " LIKE ? ", params); return new Pair<>(field.getName() + " LIKE ? ", params);
} }
} }

View File

@ -1,36 +1,36 @@
package fr.pandacube.java.util.orm; package fr.pandacube.java.util.orm;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import java.util.logging.Level; import java.util.logging.Level;
import fr.pandacube.java.util.Log; import fr.pandacube.java.util.Log;
import org.javatuples.Pair; import org.javatuples.Pair;
public class SQLWhereNull extends SQLWhere { public class SQLWhereNull extends SQLWhere {
private SQLField<?, ?> fild; private SQLField<?, ?> fild;
private boolean nulll; private boolean nulll;
/** /**
* Init a IS NULL / IS NOT NULL expression for a SQL WHERE condition. * Init a IS NULL / IS NOT NULL expression for a SQL WHERE condition.
* *
* @param field the field to check null / not null state * @param field the field to check null / not null state
* @param isNull true if we want to ckeck if "IS NULL", or false to check if * @param isNull true if we want to ckeck if "IS NULL", or false to check if
* "IS NOT NULL" * "IS NOT NULL"
*/ */
public SQLWhereNull(SQLField<?, ?> field, boolean isNull) { public SQLWhereNull(SQLField<?, ?> field, boolean isNull) {
if (field == null) throw new IllegalArgumentException("field can't be null"); if (field == null) throw new IllegalArgumentException("field can't be null");
if (!field.canBeNull) Log.getLogger().log(Level.WARNING, if (!field.canBeNull) Log.getLogger().log(Level.WARNING,
"Useless : Trying to check IS [NOT] NULL on the field " + field.getSQLElementType().getName() + "#" "Useless : Trying to check IS [NOT] NULL on the field " + field.getSQLElementType().getName() + "#"
+ field.name + " which is declared in the ORM as 'can't be null'"); + field.getName() + " which is declared in the ORM as 'can't be null'");
fild = field; fild = field;
nulll = isNull; nulll = isNull;
} }
@Override @Override
public Pair<String, List<Object>> toSQL() { public Pair<String, List<Object>> toSQL() {
return new Pair<>(fild.name + " IS" + ((nulll) ? " NULL" : " NOT NULL"), new ArrayList<>()); return new Pair<>(fild.getName() + ((nulll) ? " IS NULL" : " IS NOT NULL"), new ArrayList<>());
} }
} }