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:
parent
706ae682ec
commit
0391b7a9a0
@ -1,246 +1,251 @@
|
||||
package fr.pandacube.java.util.orm;
|
||||
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import fr.pandacube.java.util.EnumUtil;
|
||||
import fr.pandacube.java.util.Log;
|
||||
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>
|
||||
*
|
||||
* @author Marc Baloup
|
||||
*
|
||||
*/
|
||||
public final class ORM {
|
||||
|
||||
private static List<Class<? extends SQLElement<?>>> tables = new ArrayList<>();
|
||||
|
||||
private static DBConnection connection;
|
||||
|
||||
public static DBConnection getConnection() {
|
||||
return connection;
|
||||
}
|
||||
|
||||
public synchronized static <E extends SQLElement<E>> void init(DBConnection conn) {
|
||||
|
||||
connection = conn;
|
||||
|
||||
|
||||
}
|
||||
|
||||
public static synchronized <E extends SQLElement<E>> void initTable(Class<E> elemClass) throws ORMInitTableException {
|
||||
if (tables.contains(elemClass)) return;
|
||||
try {
|
||||
tables.add(elemClass);
|
||||
//Log.info("Start Init SQL table "+elemClass.getSimpleName());
|
||||
E instance = elemClass.newInstance();
|
||||
String tableName = instance.tableName();
|
||||
if (!tableExist(tableName)) createTable(instance);
|
||||
//Log.info("End init SQL table "+elemClass.getSimpleName());
|
||||
} catch (Exception|ExceptionInInitializerError e) {
|
||||
throw new ORMInitTableException(elemClass, e);
|
||||
}
|
||||
}
|
||||
|
||||
private static <E extends SQLElement<E>> void createTable(E elem) throws SQLException {
|
||||
|
||||
String sql = "CREATE TABLE IF NOT EXISTS " + elem.tableName() + " (";
|
||||
List<Object> params = new ArrayList<>();
|
||||
|
||||
Collection<SQLField<E, ?>> tableFields = elem.getFields().values();
|
||||
boolean first = true;
|
||||
for (SQLField<E, ?> f : tableFields) {
|
||||
Pair<String, List<Object>> statementPart = f.forSQLPreparedStatement();
|
||||
params.addAll(statementPart.getValue1());
|
||||
|
||||
if (!first) sql += ", ";
|
||||
first = false;
|
||||
sql += statementPart.getValue0();
|
||||
}
|
||||
|
||||
sql += ", PRIMARY KEY id(id))";
|
||||
PreparedStatement ps = connection.getNativeConnection().prepareStatement(sql);
|
||||
int i = 1;
|
||||
for (Object val : params)
|
||||
ps.setObject(i++, val);
|
||||
try {
|
||||
Log.info("Creating table " + elem.tableName() + ":\n" + ps.toString());
|
||||
ps.executeUpdate();
|
||||
} finally {
|
||||
ps.close();
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean tableExist(String tableName) throws SQLException {
|
||||
ResultSet set = null;
|
||||
boolean exist = false;
|
||||
try {
|
||||
set = connection.getNativeConnection().getMetaData().getTables(null, null, tableName, null);
|
||||
exist = set.next();
|
||||
} finally {
|
||||
if (set != null) set.close();
|
||||
}
|
||||
return exist;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <E extends SQLElement<E>> SQLField<E, Integer> getSQLIdField(Class<E> elemClass)
|
||||
throws ORMInitTableException {
|
||||
initTable(elemClass);
|
||||
return (SQLField<E, Integer>) SQLElement.fieldsCache.get(elemClass).get("id");
|
||||
}
|
||||
|
||||
public static <E extends SQLElement<E>> List<E> getByIds(Class<E> elemClass, Collection<Integer> ids)
|
||||
throws ORMException {
|
||||
return getByIds(elemClass, ids.toArray(new Integer[ids.size()]));
|
||||
}
|
||||
|
||||
public static <E extends SQLElement<E>> List<E> getByIds(Class<E> elemClass, Integer... ids) throws ORMException {
|
||||
SQLField<E, Integer> idField = getSQLIdField(elemClass);
|
||||
SQLWhereChain where = new SQLWhereChain(SQLBoolOp.OR);
|
||||
for (Integer id : ids)
|
||||
if (id != null) where.add(new SQLWhereComp(idField, SQLComparator.EQ, id));
|
||||
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 {
|
||||
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)
|
||||
throws ORMException {
|
||||
SQLElementList<E> elts = getAll(elemClass, where, orderBy, 1, null);
|
||||
return (elts.size() == 0) ? null : elts.get(0);
|
||||
}
|
||||
|
||||
public static <E extends SQLElement<E>> SQLElementList<E> getAll(Class<E> elemClass) throws ORMException {
|
||||
return getAll(elemClass, null, null, null, null);
|
||||
}
|
||||
|
||||
public static <E extends SQLElement<E>> SQLElementList<E> getAll(Class<E> elemClass, SQLWhere where,
|
||||
SQLOrderBy orderBy, Integer limit, Integer offset) throws ORMException {
|
||||
initTable(elemClass);
|
||||
|
||||
try {
|
||||
String sql = "SELECT * FROM " + elemClass.newInstance().tableName();
|
||||
|
||||
List<Object> params = new ArrayList<>();
|
||||
|
||||
if (where != null) {
|
||||
Pair<String, List<Object>> ret = where.toSQL();
|
||||
sql += " WHERE " + ret.getValue0();
|
||||
params.addAll(ret.getValue1());
|
||||
}
|
||||
if (orderBy != null) sql += " ORDER BY " + orderBy.toSQL();
|
||||
if (limit != null) sql += " LIMIT " + limit;
|
||||
if (offset != null) sql += " OFFSET " + offset;
|
||||
sql += ";";
|
||||
|
||||
SQLElementList<E> elmts = new SQLElementList<>();
|
||||
|
||||
PreparedStatement ps = connection.getNativeConnection().prepareStatement(sql);
|
||||
|
||||
try {
|
||||
|
||||
int i = 1;
|
||||
for (Object val : params) {
|
||||
if (val instanceof Enum<?>) val = ((Enum<?>) val).name();
|
||||
ps.setObject(i++, val);
|
||||
}
|
||||
Log.debug(ps.toString());
|
||||
ResultSet set = ps.executeQuery();
|
||||
|
||||
try {
|
||||
while (set.next())
|
||||
elmts.add(getElementInstance(set, elemClass));
|
||||
} finally {
|
||||
set.close();
|
||||
}
|
||||
} finally {
|
||||
ps.close();
|
||||
}
|
||||
|
||||
return elmts;
|
||||
} catch (ReflectiveOperationException | SQLException e) {
|
||||
throw new ORMException(e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private static <E extends SQLElement<E>> E getElementInstance(ResultSet set, Class<E> elemClass) throws ORMException {
|
||||
try {
|
||||
E instance = elemClass.getConstructor(int.class).newInstance(set.getInt("id"));
|
||||
|
||||
int fieldCount = set.getMetaData().getColumnCount();
|
||||
|
||||
for (int c = 1; c <= fieldCount; 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
|
||||
@SuppressWarnings("unchecked")
|
||||
SQLField<E, Object> sqlField = (SQLField<E, Object>) instance.getFields().get(fieldName);
|
||||
if (sqlField.type.getJavaType().isEnum()) {
|
||||
// JDBC ne supporte pas les enums
|
||||
String enumStrValue = set.getString(c);
|
||||
if (enumStrValue == null || set.wasNull()) instance.set(sqlField, null, false);
|
||||
else {
|
||||
Enum<?> enumValue = EnumUtil.searchUncheckedEnum(sqlField.type.getJavaType(), enumStrValue);
|
||||
if (enumValue == null) throw new ORMException("The enum constant '" + enumStrValue
|
||||
+ "' is not found in enum class " + sqlField.type.getJavaType().getName());
|
||||
instance.set(sqlField, enumValue, false);
|
||||
}
|
||||
}
|
||||
else {
|
||||
Object val = set.getObject(c, sqlField.type.getJavaType());
|
||||
if (val == null || set.wasNull()) instance.set(sqlField, null, false);
|
||||
else
|
||||
instance.set(sqlField, val, false);
|
||||
}
|
||||
|
||||
// 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.modifiedSinceLastSave.remove(sqlField.name);
|
||||
}
|
||||
|
||||
if (!instance.isValidForSave()) throw new ORMException(
|
||||
"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);
|
||||
}
|
||||
}
|
||||
|
||||
private ORM() {} // rend la classe non instanciable
|
||||
|
||||
/*
|
||||
* public static void main(String[] args) throws Throwable {
|
||||
* ORM.init(new DBConnection("localhost", 3306, "pandacube", "pandacube",
|
||||
* "pandacube"));
|
||||
* List<SQLPlayer> players = ORM.getAll(SQLPlayer.class,
|
||||
* new SQLWhereChain(SQLBoolOp.AND)
|
||||
* .add(new SQLWhereNull(SQLPlayer.banTimeout, true))
|
||||
* .add(new SQLWhereChain(SQLBoolOp.OR)
|
||||
* .add(new SQLWhereComp(SQLPlayer.bambou, SQLComparator.EQ, 0L))
|
||||
* .add(new SQLWhereComp(SQLPlayer.grade, SQLComparator.EQ, "default"))
|
||||
* ),
|
||||
* 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)
|
||||
* }
|
||||
*/
|
||||
|
||||
}
|
||||
package fr.pandacube.java.util.orm;
|
||||
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.List;
|
||||
|
||||
import org.javatuples.Pair;
|
||||
|
||||
import fr.pandacube.java.util.Log;
|
||||
import fr.pandacube.java.util.orm.SQLWhereChain.SQLBoolOp;
|
||||
import fr.pandacube.java.util.orm.SQLWhereComp.SQLComparator;
|
||||
|
||||
/**
|
||||
* <b>ORM = Object-Relational Mapping</b>
|
||||
*
|
||||
* @author Marc Baloup
|
||||
*
|
||||
*/
|
||||
public final class ORM {
|
||||
|
||||
private static List<Class<? extends SQLElement<?>>> tables = new ArrayList<>();
|
||||
|
||||
private static DBConnection connection;
|
||||
|
||||
public static DBConnection getConnection() {
|
||||
return connection;
|
||||
}
|
||||
|
||||
public synchronized static <E extends SQLElement<E>> void init(DBConnection conn) {
|
||||
|
||||
connection = conn;
|
||||
|
||||
|
||||
}
|
||||
|
||||
public static synchronized <E extends SQLElement<E>> void initTable(Class<E> elemClass) throws ORMInitTableException {
|
||||
if (tables.contains(elemClass)) return;
|
||||
try {
|
||||
tables.add(elemClass);
|
||||
//Log.info("Start Init SQL table "+elemClass.getSimpleName());
|
||||
E instance = elemClass.newInstance();
|
||||
String tableName = instance.tableName();
|
||||
if (!tableExist(tableName)) createTable(instance);
|
||||
//Log.info("End init SQL table "+elemClass.getSimpleName());
|
||||
} catch (Exception|ExceptionInInitializerError e) {
|
||||
throw new ORMInitTableException(elemClass, e);
|
||||
}
|
||||
}
|
||||
|
||||
private static <E extends SQLElement<E>> void createTable(E elem) throws SQLException {
|
||||
|
||||
String sql = "CREATE TABLE IF NOT EXISTS " + elem.tableName() + " (";
|
||||
List<Object> params = new ArrayList<>();
|
||||
|
||||
Collection<SQLField<E, ?>> tableFields = elem.getFields().values();
|
||||
boolean first = true;
|
||||
for (SQLField<E, ?> f : tableFields) {
|
||||
Pair<String, List<Object>> statementPart = f.forSQLPreparedStatement();
|
||||
params.addAll(statementPart.getValue1());
|
||||
|
||||
if (!first) sql += ", ";
|
||||
first = false;
|
||||
sql += statementPart.getValue0();
|
||||
}
|
||||
|
||||
sql += ", PRIMARY KEY id(id))";
|
||||
PreparedStatement ps = connection.getNativeConnection().prepareStatement(sql);
|
||||
int i = 1;
|
||||
for (Object val : params)
|
||||
ps.setObject(i++, val);
|
||||
try {
|
||||
Log.info("Creating table " + elem.tableName() + ":\n" + ps.toString());
|
||||
ps.executeUpdate();
|
||||
} finally {
|
||||
ps.close();
|
||||
}
|
||||
}
|
||||
|
||||
private static boolean tableExist(String tableName) throws SQLException {
|
||||
ResultSet set = null;
|
||||
boolean exist = false;
|
||||
try {
|
||||
set = connection.getNativeConnection().getMetaData().getTables(null, null, tableName, null);
|
||||
exist = set.next();
|
||||
} finally {
|
||||
if (set != null) set.close();
|
||||
}
|
||||
return exist;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public static <E extends SQLElement<E>> SQLField<E, Integer> getSQLIdField(Class<E> elemClass)
|
||||
throws ORMInitTableException {
|
||||
initTable(elemClass);
|
||||
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)
|
||||
throws ORMException {
|
||||
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 {
|
||||
SQLField<E, Integer> idField = getSQLIdField(elemClass);
|
||||
SQLWhereChain where = new SQLWhereChain(SQLBoolOp.OR);
|
||||
for (Integer id : ids)
|
||||
if (id != null) where.add(new SQLWhereComp(idField, SQLComparator.EQ, id));
|
||||
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 {
|
||||
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)
|
||||
throws ORMException {
|
||||
SQLElementList<E> elts = getAll(elemClass, where, orderBy, 1, null);
|
||||
return (elts.size() == 0) ? null : elts.get(0);
|
||||
}
|
||||
|
||||
public static <E extends SQLElement<E>> SQLElementList<E> getAll(Class<E> elemClass) throws ORMException {
|
||||
return getAll(elemClass, null, null, null, null);
|
||||
}
|
||||
|
||||
public static <E extends SQLElement<E>> SQLElementList<E> getAll(Class<E> elemClass, SQLWhere where,
|
||||
SQLOrderBy orderBy, Integer limit, Integer offset) throws ORMException {
|
||||
initTable(elemClass);
|
||||
|
||||
try {
|
||||
String sql = "SELECT * FROM " + elemClass.newInstance().tableName();
|
||||
|
||||
List<Object> params = new ArrayList<>();
|
||||
|
||||
if (where != null) {
|
||||
Pair<String, List<Object>> ret = where.toSQL();
|
||||
sql += " WHERE " + ret.getValue0();
|
||||
params.addAll(ret.getValue1());
|
||||
}
|
||||
if (orderBy != null) sql += " ORDER BY " + orderBy.toSQL();
|
||||
if (limit != null) sql += " LIMIT " + limit;
|
||||
if (offset != null) sql += " OFFSET " + offset;
|
||||
sql += ";";
|
||||
|
||||
SQLElementList<E> elmts = new SQLElementList<>();
|
||||
|
||||
PreparedStatement ps = connection.getNativeConnection().prepareStatement(sql);
|
||||
|
||||
try {
|
||||
|
||||
int i = 1;
|
||||
for (Object val : params) {
|
||||
if (val instanceof Enum<?>) val = ((Enum<?>) val).name();
|
||||
ps.setObject(i++, val);
|
||||
}
|
||||
Log.debug(ps.toString());
|
||||
ResultSet set = ps.executeQuery();
|
||||
|
||||
try {
|
||||
while (set.next())
|
||||
elmts.add(getElementInstance(set, elemClass));
|
||||
} finally {
|
||||
set.close();
|
||||
}
|
||||
} finally {
|
||||
ps.close();
|
||||
}
|
||||
|
||||
return elmts;
|
||||
} catch (ReflectiveOperationException | SQLException e) {
|
||||
throw new ORMException(e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private static <E extends SQLElement<E>> E getElementInstance(ResultSet set, Class<E> elemClass) throws ORMException {
|
||||
try {
|
||||
E instance = elemClass.getConstructor(int.class).newInstance(set.getInt("id"));
|
||||
|
||||
int fieldCount = set.getMetaData().getColumnCount();
|
||||
|
||||
for (int c = 1; c <= fieldCount; c++) {
|
||||
String fieldName = set.getMetaData().getColumnLabel(c);
|
||||
|
||||
// ignore when field is present in database but not handled by SQLElement instance
|
||||
if (!instance.getFields().containsKey(fieldName)) continue;
|
||||
|
||||
SQLField<E, Object> sqlField = (SQLField<E, Object>) instance.getFields().get(fieldName);
|
||||
|
||||
boolean customType = sqlField.type instanceof SQLCustomType;
|
||||
|
||||
Object val = set.getObject(c,
|
||||
(Class<?>)(customType ? ((SQLCustomType<?, ?>)sqlField.type).intermediateJavaType
|
||||
: sqlField.type.getJavaType()));
|
||||
|
||||
if (val == null || set.wasNull()) {
|
||||
instance.set(sqlField, null, false);
|
||||
}
|
||||
else {
|
||||
if (customType) {
|
||||
try {
|
||||
val = ((SQLCustomType<Object, Object>)sqlField.type).dbToJavaConv.apply(val);
|
||||
} 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);
|
||||
}
|
||||
}
|
||||
|
||||
instance.set(sqlField, val, false);
|
||||
// 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.modifiedSinceLastSave.remove(sqlField.getName());
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
if (!instance.isValidForSave()) throw new ORMException(
|
||||
"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);
|
||||
}
|
||||
}
|
||||
|
||||
private ORM() {} // rend la classe non instanciable
|
||||
|
||||
/*
|
||||
* public static void main(String[] args) throws Throwable {
|
||||
* ORM.init(new DBConnection("localhost", 3306, "pandacube", "pandacube",
|
||||
* "pandacube"));
|
||||
* List<SQLPlayer> players = ORM.getAll(SQLPlayer.class,
|
||||
* new SQLWhereChain(SQLBoolOp.AND)
|
||||
* .add(new SQLWhereNull(SQLPlayer.banTimeout, true))
|
||||
* .add(new SQLWhereChain(SQLBoolOp.OR)
|
||||
* .add(new SQLWhereComp(SQLPlayer.bambou, SQLComparator.EQ, 0L))
|
||||
* .add(new SQLWhereComp(SQLPlayer.grade, SQLComparator.EQ, "default"))
|
||||
* ),
|
||||
* 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)
|
||||
* }
|
||||
*/
|
||||
|
||||
}
|
||||
|
32
src/main/java/fr/pandacube/java/util/orm/SQLCustomType.java
Normal file
32
src/main/java/fr/pandacube/java/util/orm/SQLCustomType.java
Normal 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
|
||||
|
||||
}
|
@ -1,410 +1,415 @@
|
||||
package fr.pandacube.java.util.orm;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.commons.lang.builder.ToStringBuilder;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
import fr.pandacube.java.util.Log;
|
||||
import fr.pandacube.java.util.orm.SQLWhereComp.SQLComparator;
|
||||
|
||||
public abstract class SQLElement<E extends SQLElement<E>> {
|
||||
/** cache for fields for each subclass of SQLElement */
|
||||
/* package */ static final Map<Class<? extends SQLElement<?>>, SQLFieldMap<? extends SQLElement<?>>> fieldsCache = new HashMap<>();
|
||||
|
||||
DBConnection db = ORM.getConnection();
|
||||
|
||||
private boolean stored = false;
|
||||
private int id;
|
||||
|
||||
private final String tableName;
|
||||
private final SQLFieldMap<E> fields;
|
||||
|
||||
private final Map<SQLField<E, ?>, Object> values;
|
||||
/* package */ final Set<String> modifiedSinceLastSave;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public SQLElement() {
|
||||
tableName = tableName();
|
||||
|
||||
try {
|
||||
ORM.initTable((Class<E>)getClass());
|
||||
} catch (ORMInitTableException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
if (fieldsCache.get(getClass()) == null) {
|
||||
fields = new SQLFieldMap<>((Class<E>)getClass());
|
||||
|
||||
// le champ id commun à toutes les tables
|
||||
fields.addField(new SQLField<>("id", SQLType.INT, false, true, 0));
|
||||
|
||||
generateFields(fields);
|
||||
fieldsCache.put((Class<E>)getClass(), fields);
|
||||
}
|
||||
else
|
||||
fields = (SQLFieldMap<E>) fieldsCache.get(getClass());
|
||||
|
||||
values = new LinkedHashMap<>(fields.size());
|
||||
modifiedSinceLastSave = new HashSet<>(fields.size());
|
||||
|
||||
initDefaultValues();
|
||||
|
||||
}
|
||||
|
||||
protected SQLElement(int id) {
|
||||
this();
|
||||
@SuppressWarnings("unchecked")
|
||||
SQLField<E, Integer> idField = (SQLField<E, Integer>) fields.get("id");
|
||||
set(idField, id, false);
|
||||
this.id = id;
|
||||
stored = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The name of the table in the database.
|
||||
*/
|
||||
protected abstract String tableName();
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void initDefaultValues() {
|
||||
// remplissage des données par défaut (si peut être null ou si valeur
|
||||
// par défaut existe)
|
||||
for (@SuppressWarnings("rawtypes")
|
||||
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();
|
||||
for (java.lang.reflect.Field field : declaredFields) {
|
||||
if (!java.lang.reflect.Modifier.isStatic(field.getModifiers())) continue;
|
||||
|
||||
try {
|
||||
Object val = field.get(null);
|
||||
if (val == null || !(val instanceof SQLField)) continue;
|
||||
|
||||
listToFill.addField((SQLField<?, ?>) val);
|
||||
} catch (IllegalArgumentException | IllegalAccessException e) {
|
||||
Log.severe("Can't get value of static field " + field.toString(), e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* package */ Map<String, SQLField<E, ?>> getFields() {
|
||||
return Collections.unmodifiableMap(fields);
|
||||
}
|
||||
|
||||
public Map<SQLField<E, ?>, Object> getValues() {
|
||||
return Collections.unmodifiableMap(values);
|
||||
}
|
||||
|
||||
public <T> void set(SQLField<E, T> field, T value) {
|
||||
set(field, value, true);
|
||||
}
|
||||
|
||||
/* package */ <T> void set(SQLField<E, T> sqlField, T value, boolean setModified) {
|
||||
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());
|
||||
|
||||
boolean modify = false;
|
||||
if (value == null) {
|
||||
if (sqlField.canBeNull || (sqlField.autoIncrement && !stored)) modify = true;
|
||||
else
|
||||
throw new IllegalArgumentException(
|
||||
"SQLField '" + sqlField.name + "' of " + getClass().getName() + " is a NOT NULL field");
|
||||
}
|
||||
else if (sqlField.type.isAssignableFrom(value)) modify = true;
|
||||
else
|
||||
throw new IllegalArgumentException("SQLField '" + sqlField.name + "' of " + getClass().getName()
|
||||
+ " type is '" + sqlField.type.toString() + "' and can't accept values of type "
|
||||
+ value.getClass().getName());
|
||||
|
||||
if (modify) if (!values.containsKey(sqlField)) {
|
||||
values.put(sqlField, value);
|
||||
if (setModified) modifiedSinceLastSave.add(sqlField.name);
|
||||
}
|
||||
else {
|
||||
Object oldVal = values.get(sqlField);
|
||||
if (!Objects.equals(oldVal, value)) {
|
||||
values.put(sqlField, value);
|
||||
if (setModified) modifiedSinceLastSave.add(sqlField.name);
|
||||
}
|
||||
// sinon, rien n'est modifié
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public <T> T get(SQLField<E, T> field) {
|
||||
if (field == null) throw new IllegalArgumentException("field can't be null");
|
||||
if (values.containsKey(field)) {
|
||||
@SuppressWarnings("unchecked")
|
||||
T val = (T) values.get(field);
|
||||
return val;
|
||||
}
|
||||
throw new IllegalArgumentException("The field '" + field.name + "' in this instance of " + getClass().getName()
|
||||
+ " does not exist or is not set");
|
||||
}
|
||||
|
||||
public <T, F extends SQLElement<F>> F getForeign(SQLFKField<E, T, F> field) throws ORMException {
|
||||
T fkValue = get(field);
|
||||
if (fkValue == null) return null;
|
||||
return ORM.getFirst(field.getForeignElementClass(),
|
||||
new SQLWhereComp(field.getForeignField(), SQLComparator.EQ, fkValue), null);
|
||||
}
|
||||
|
||||
public boolean isValidForSave() {
|
||||
return values.keySet().containsAll(fields.values());
|
||||
}
|
||||
|
||||
private Map<SQLField<E, ?>, Object> getOnlyModifiedValues() {
|
||||
Map<SQLField<E, ?>, Object> modifiedValues = new LinkedHashMap<>();
|
||||
values.forEach((k, v) -> {
|
||||
if (modifiedSinceLastSave.contains(k.name)) modifiedValues.put(k, v);
|
||||
});
|
||||
return modifiedValues;
|
||||
}
|
||||
|
||||
public boolean isModified(SQLField<E, ?> field) {
|
||||
return modifiedSinceLastSave.contains(field.name);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public void save() throws ORMException {
|
||||
if (!isValidForSave())
|
||||
throw new IllegalStateException(toString() + " has at least one undefined value and can't be saved.");
|
||||
|
||||
ORM.initTable((Class<E>)getClass());
|
||||
String toStringStatement = "";
|
||||
try {
|
||||
|
||||
Connection conn = db.getNativeConnection();
|
||||
|
||||
if (stored) { // mettre à jour les valeurs dans la base
|
||||
|
||||
// restaurer l'ID au cas il aurait été changé à la main dans
|
||||
// values
|
||||
SQLField<E, Integer> idField = (SQLField<E, Integer>) fields.get("id");
|
||||
values.put(idField, id);
|
||||
modifiedSinceLastSave.remove("id");
|
||||
Map<SQLField<E, ?>, Object> modifiedValues = getOnlyModifiedValues();
|
||||
|
||||
if (modifiedValues.isEmpty()) return;
|
||||
|
||||
String sql = "";
|
||||
List<Object> psValues = new ArrayList<>();
|
||||
|
||||
for (Map.Entry<SQLField<E, ?>, Object> entry : modifiedValues.entrySet()) {
|
||||
sql += entry.getKey().name + " = ? ,";
|
||||
if (entry.getKey().type.getJavaType().isEnum()) // prise en
|
||||
// charge
|
||||
// enum (non
|
||||
// prise en
|
||||
// charge
|
||||
// par JDBC)
|
||||
psValues.add(((Enum<?>) entry.getValue()).name());
|
||||
else
|
||||
psValues.add(entry.getValue());
|
||||
}
|
||||
|
||||
if (sql.length() > 0) sql = sql.substring(0, sql.length() - 1);
|
||||
|
||||
PreparedStatement ps = conn.prepareStatement("UPDATE " + tableName + " SET " + sql + " WHERE id=" + id);
|
||||
|
||||
try {
|
||||
|
||||
int i = 1;
|
||||
for (Object val : psValues)
|
||||
ps.setObject(i++, val);
|
||||
|
||||
toStringStatement = ps.toString();
|
||||
ps.executeUpdate();
|
||||
} finally {
|
||||
ps.close();
|
||||
}
|
||||
}
|
||||
else { // ajouter dans la base
|
||||
|
||||
// restaurer l'ID au cas il aurait été changé à la main dans
|
||||
// values
|
||||
values.put(fields.get("id"), null);
|
||||
|
||||
String concat_vals = "";
|
||||
String concat_fields = "";
|
||||
List<Object> psValues = new ArrayList<>();
|
||||
|
||||
boolean first = true;
|
||||
for (Map.Entry<SQLField<E, ?>, Object> entry : values.entrySet()) {
|
||||
if (!first) {
|
||||
concat_vals += ",";
|
||||
concat_fields += ",";
|
||||
}
|
||||
first = false;
|
||||
concat_vals += " ? ";
|
||||
concat_fields += entry.getKey().name;
|
||||
if (entry.getKey().type.getJavaType().isEnum()) // prise en
|
||||
// charge
|
||||
// enum (non
|
||||
// prise en
|
||||
// charge
|
||||
// par JDBC)
|
||||
psValues.add(((Enum<?>) entry.getValue()).name());
|
||||
else
|
||||
psValues.add(entry.getValue());
|
||||
}
|
||||
|
||||
PreparedStatement ps = conn.prepareStatement(
|
||||
"INSERT INTO " + tableName + " (" + concat_fields + ") VALUES (" + concat_vals + ")",
|
||||
Statement.RETURN_GENERATED_KEYS);
|
||||
try {
|
||||
|
||||
int i = 1;
|
||||
for (Object val : psValues)
|
||||
ps.setObject(i++, val);
|
||||
|
||||
toStringStatement = ps.toString();
|
||||
ps.executeUpdate();
|
||||
|
||||
ResultSet rs = ps.getGeneratedKeys();
|
||||
try {
|
||||
if (rs.next()) id = rs.getInt(1);
|
||||
|
||||
stored = true;
|
||||
} finally {
|
||||
rs.close();
|
||||
}
|
||||
} finally {
|
||||
ps.close();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
modifiedSinceLastSave.clear();
|
||||
} catch (SQLException e) {
|
||||
throw new ORMException("Error while executing SQL statement " + toStringStatement, e);
|
||||
}
|
||||
Log.debug(toStringStatement);
|
||||
}
|
||||
|
||||
public boolean isStored() {
|
||||
return stored;
|
||||
}
|
||||
|
||||
public Integer getId() {
|
||||
return (stored) ? id : null;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public SQLField<E, Integer> getFieldId() {
|
||||
return (SQLField<E, Integer>) fields.get("id");
|
||||
}
|
||||
|
||||
public void delete() throws ORMException {
|
||||
|
||||
try {
|
||||
if (stored) { // supprimer la ligne de la base
|
||||
PreparedStatement st = db.getNativeConnection()
|
||||
.prepareStatement("DELETE FROM " + tableName + " WHERE id=" + id);
|
||||
try {
|
||||
Log.debug(st.toString());
|
||||
st.executeUpdate();
|
||||
markAsNotStored();
|
||||
} 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;
|
||||
modifiedSinceLastSave.clear();
|
||||
values.forEach((k, v) -> modifiedSinceLastSave.add(k.name));
|
||||
}
|
||||
|
||||
protected static class SQLFieldMap<E extends SQLElement<E>> extends LinkedHashMap<String, SQLField<E, ?>> {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final Class<E> sqlElemClass;
|
||||
|
||||
private SQLFieldMap(Class<E> elemClass) {
|
||||
sqlElemClass = elemClass;
|
||||
}
|
||||
|
||||
private void addField(SQLField<?, ?> f) {
|
||||
if (f == null) return;
|
||||
if (containsKey(f.name)) throw new IllegalArgumentException(
|
||||
"SQLField " + f.name + " already exist in " + sqlElemClass.getName());
|
||||
@SuppressWarnings("unchecked")
|
||||
SQLField<E, ?> checkedF = (SQLField<E, ?>) f;
|
||||
checkedF.setSQLElementType(sqlElemClass);
|
||||
put(checkedF.name, checkedF);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
ToStringBuilder b = new ToStringBuilder(this);
|
||||
|
||||
for (SQLField<E, ?> f : fields.values())
|
||||
try {
|
||||
b.append(f.name, get(f));
|
||||
} catch (IllegalArgumentException e) {
|
||||
b.append(f.name, "(Undefined)");
|
||||
}
|
||||
|
||||
return b.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (o == null || !(getClass().isInstance(o))) return false;
|
||||
SQLElement<?> oEl = (SQLElement<?>) o;
|
||||
if (oEl.getId() == null) return false;
|
||||
return oEl.getId().equals(getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return super.hashCode();
|
||||
}
|
||||
|
||||
|
||||
|
||||
public JsonObject asJsonObject() {
|
||||
JsonObject json = new JsonObject();
|
||||
for (SQLField<E, ?> f : getFields().values()) {
|
||||
json.add(f.name, new Gson().toJsonTree(get(f)));
|
||||
}
|
||||
return json;
|
||||
}
|
||||
|
||||
}
|
||||
package fr.pandacube.java.util.orm;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Objects;
|
||||
import java.util.Set;
|
||||
|
||||
import org.apache.commons.lang.builder.ToStringBuilder;
|
||||
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.JsonObject;
|
||||
|
||||
import fr.pandacube.java.util.Log;
|
||||
import fr.pandacube.java.util.orm.SQLWhereComp.SQLComparator;
|
||||
|
||||
public abstract class SQLElement<E extends SQLElement<E>> {
|
||||
/** cache for fields for each subclass of SQLElement */
|
||||
/* package */ static final Map<Class<? extends SQLElement<?>>, SQLFieldMap<? extends SQLElement<?>>> fieldsCache = new HashMap<>();
|
||||
|
||||
DBConnection db = ORM.getConnection();
|
||||
|
||||
private boolean stored = false;
|
||||
private int id;
|
||||
|
||||
private final String tableName;
|
||||
private final SQLFieldMap<E> fields;
|
||||
|
||||
private final Map<SQLField<E, ?>, Object> values;
|
||||
/* package */ final Set<String> modifiedSinceLastSave;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public SQLElement() {
|
||||
tableName = tableName();
|
||||
|
||||
try {
|
||||
ORM.initTable((Class<E>)getClass());
|
||||
} catch (ORMInitTableException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
if (fieldsCache.get(getClass()) == null) {
|
||||
fields = new SQLFieldMap<>((Class<E>)getClass());
|
||||
|
||||
// le champ id commun à toutes les tables
|
||||
SQLField<E, Integer> idF = new SQLField<>(SQLType.INT, false, true, 0);
|
||||
idF.setName("id");
|
||||
fields.addField(idF);
|
||||
|
||||
generateFields(fields);
|
||||
fieldsCache.put((Class<E>)getClass(), fields);
|
||||
}
|
||||
else
|
||||
fields = (SQLFieldMap<E>) fieldsCache.get(getClass());
|
||||
|
||||
values = new LinkedHashMap<>(fields.size());
|
||||
modifiedSinceLastSave = new HashSet<>(fields.size());
|
||||
|
||||
initDefaultValues();
|
||||
|
||||
}
|
||||
|
||||
protected SQLElement(int id) {
|
||||
this();
|
||||
@SuppressWarnings("unchecked")
|
||||
SQLField<E, Integer> idField = (SQLField<E, Integer>) fields.get("id");
|
||||
set(idField, id, false);
|
||||
this.id = id;
|
||||
stored = true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return The name of the table in the database.
|
||||
*/
|
||||
protected abstract String tableName();
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void initDefaultValues() {
|
||||
// remplissage des données par défaut (si peut être null ou si valeur
|
||||
// par défaut existe)
|
||||
for (@SuppressWarnings("rawtypes")
|
||||
SQLField f : fields.values())
|
||||
if (f.defaultValue != null) set(f, f.defaultValue);
|
||||
else if (f.canBeNull || (f.autoIncrement && !stored)) set(f, null);
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
protected void generateFields(SQLFieldMap<E> listToFill) {
|
||||
|
||||
java.lang.reflect.Field[] declaredFields = getClass().getDeclaredFields();
|
||||
for (java.lang.reflect.Field field : declaredFields) {
|
||||
if (!java.lang.reflect.Modifier.isStatic(field.getModifiers())) continue;
|
||||
|
||||
try {
|
||||
Object val = field.get(null);
|
||||
if (val == null || !(val instanceof SQLField)) continue;
|
||||
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);
|
||||
} catch (IllegalArgumentException | IllegalAccessException e) {
|
||||
Log.severe("Can't get value of static field " + field.toString(), e);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/* package */ Map<String, SQLField<E, ?>> getFields() {
|
||||
return Collections.unmodifiableMap(fields);
|
||||
}
|
||||
|
||||
public Map<SQLField<E, ?>, Object> getValues() {
|
||||
return Collections.unmodifiableMap(values);
|
||||
}
|
||||
|
||||
public <T> void set(SQLField<E, T> field, T value) {
|
||||
set(field, value, true);
|
||||
}
|
||||
|
||||
/* package */ <T> void set(SQLField<E, T> sqlField, T value, boolean setModified) {
|
||||
if (sqlField == null) throw new IllegalArgumentException("sqlField can't be null");
|
||||
if (!fields.containsValue(sqlField))
|
||||
throw new IllegalArgumentException(sqlField.getSQLElementType().getName()+sqlField.getName() + " is not a SQLField of " + getClass().getName());
|
||||
|
||||
boolean modify = false;
|
||||
if (value == null) {
|
||||
if (sqlField.canBeNull || (sqlField.autoIncrement && !stored)) modify = true;
|
||||
else
|
||||
throw new IllegalArgumentException(
|
||||
"SQLField '" + sqlField.getName() + "' of " + getClass().getName() + " is a NOT NULL field");
|
||||
}
|
||||
else if (sqlField.type.isAssignableFrom(value)) modify = true;
|
||||
else
|
||||
throw new IllegalArgumentException("SQLField '" + sqlField.getName() + "' of " + getClass().getName()
|
||||
+ " type is '" + sqlField.type.toString() + "' and can't accept values of type "
|
||||
+ value.getClass().getName());
|
||||
|
||||
if (modify) if (!values.containsKey(sqlField)) {
|
||||
values.put(sqlField, value);
|
||||
if (setModified) modifiedSinceLastSave.add(sqlField.getName());
|
||||
}
|
||||
else {
|
||||
Object oldVal = values.get(sqlField);
|
||||
if (!Objects.equals(oldVal, value)) {
|
||||
values.put(sqlField, value);
|
||||
if (setModified) modifiedSinceLastSave.add(sqlField.getName());
|
||||
}
|
||||
// sinon, rien n'est modifié
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public <T> T get(SQLField<E, T> field) {
|
||||
if (field == null) throw new IllegalArgumentException("field can't be null");
|
||||
if (values.containsKey(field)) {
|
||||
@SuppressWarnings("unchecked")
|
||||
T val = (T) values.get(field);
|
||||
return val;
|
||||
}
|
||||
throw new IllegalArgumentException("The field '" + field.getName() + "' in this instance of " + getClass().getName()
|
||||
+ " does not exist or is not set");
|
||||
}
|
||||
|
||||
public <T, F extends SQLElement<F>> F getForeign(SQLFKField<E, T, F> field) throws ORMException {
|
||||
T fkValue = get(field);
|
||||
if (fkValue == null) return null;
|
||||
return ORM.getFirst(field.getForeignElementClass(),
|
||||
new SQLWhereComp(field.getForeignField(), SQLComparator.EQ, fkValue), null);
|
||||
}
|
||||
|
||||
public boolean isValidForSave() {
|
||||
return values.keySet().containsAll(fields.values());
|
||||
}
|
||||
|
||||
private Map<SQLField<E, ?>, Object> getOnlyModifiedValues() {
|
||||
Map<SQLField<E, ?>, Object> modifiedValues = new LinkedHashMap<>();
|
||||
values.forEach((k, v) -> {
|
||||
if (modifiedSinceLastSave.contains(k.getName())) modifiedValues.put(k, v);
|
||||
});
|
||||
return modifiedValues;
|
||||
}
|
||||
|
||||
public boolean isModified(SQLField<E, ?> field) {
|
||||
return modifiedSinceLastSave.contains(field.getName());
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public void save() throws ORMException {
|
||||
if (!isValidForSave())
|
||||
throw new IllegalStateException(toString() + " has at least one undefined value and can't be saved.");
|
||||
|
||||
ORM.initTable((Class<E>)getClass());
|
||||
String toStringStatement = "";
|
||||
try {
|
||||
|
||||
Connection conn = db.getNativeConnection();
|
||||
|
||||
if (stored) { // mettre à jour les valeurs dans la base
|
||||
|
||||
// restaurer l'ID au cas il aurait été changé à la main dans
|
||||
// values
|
||||
SQLField<E, Integer> idField = (SQLField<E, Integer>) fields.get("id");
|
||||
values.put(idField, id);
|
||||
modifiedSinceLastSave.remove("id");
|
||||
Map<SQLField<E, ?>, Object> modifiedValues = getOnlyModifiedValues();
|
||||
|
||||
if (modifiedValues.isEmpty()) return;
|
||||
|
||||
String sql = "";
|
||||
List<Object> psValues = new ArrayList<>();
|
||||
|
||||
for (Map.Entry<SQLField<E, ?>, Object> entry : modifiedValues.entrySet()) {
|
||||
sql += entry.getKey().getName() + " = ? ,";
|
||||
addValueToSQLObjectList(psValues, entry.getKey(), entry.getValue());
|
||||
}
|
||||
|
||||
if (sql.length() > 0) sql = sql.substring(0, sql.length() - 1);
|
||||
|
||||
PreparedStatement ps = conn.prepareStatement("UPDATE " + tableName + " SET " + sql + " WHERE id=" + id);
|
||||
|
||||
try {
|
||||
|
||||
int i = 1;
|
||||
for (Object val : psValues)
|
||||
ps.setObject(i++, val);
|
||||
|
||||
toStringStatement = ps.toString();
|
||||
ps.executeUpdate();
|
||||
} finally {
|
||||
ps.close();
|
||||
}
|
||||
}
|
||||
else { // ajouter dans la base
|
||||
|
||||
// restaurer l'ID au cas il aurait été changé à la main dans
|
||||
// values
|
||||
values.put(fields.get("id"), null);
|
||||
|
||||
String concat_vals = "";
|
||||
String concat_fields = "";
|
||||
List<Object> psValues = new ArrayList<>();
|
||||
|
||||
boolean first = true;
|
||||
for (Map.Entry<SQLField<E, ?>, Object> entry : values.entrySet()) {
|
||||
if (!first) {
|
||||
concat_vals += ",";
|
||||
concat_fields += ",";
|
||||
}
|
||||
first = false;
|
||||
concat_vals += " ? ";
|
||||
concat_fields += entry.getKey().getName();
|
||||
addValueToSQLObjectList(psValues, entry.getKey(), entry.getValue());
|
||||
}
|
||||
|
||||
PreparedStatement ps = conn.prepareStatement(
|
||||
"INSERT INTO " + tableName + " (" + concat_fields + ") VALUES (" + concat_vals + ")",
|
||||
Statement.RETURN_GENERATED_KEYS);
|
||||
try {
|
||||
|
||||
int i = 1;
|
||||
for (Object val : psValues)
|
||||
ps.setObject(i++, val);
|
||||
|
||||
toStringStatement = ps.toString();
|
||||
ps.executeUpdate();
|
||||
|
||||
ResultSet rs = ps.getGeneratedKeys();
|
||||
try {
|
||||
if (rs.next()) id = rs.getInt(1);
|
||||
|
||||
stored = true;
|
||||
} finally {
|
||||
rs.close();
|
||||
}
|
||||
} finally {
|
||||
ps.close();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
modifiedSinceLastSave.clear();
|
||||
} catch (SQLException e) {
|
||||
throw new ORMException("Error while executing SQL statement " + toStringStatement, e);
|
||||
}
|
||||
Log.debug(toStringStatement);
|
||||
}
|
||||
|
||||
|
||||
@SuppressWarnings("rawtypes")
|
||||
protected static <E extends SQLElement<E>> void addValueToSQLObjectList(List<Object> list, SQLField<E, ?> field, Object jValue) throws ORMException {
|
||||
if (jValue != null && field.type instanceof SQLCustomType) {
|
||||
try {
|
||||
jValue = ((SQLCustomType)field.type).javaToDbConv.apply(jValue);
|
||||
} 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);
|
||||
}
|
||||
}
|
||||
list.add(jValue);
|
||||
}
|
||||
|
||||
public boolean isStored() {
|
||||
return stored;
|
||||
}
|
||||
|
||||
public Integer getId() {
|
||||
return (stored) ? id : null;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public SQLField<E, Integer> getFieldId() {
|
||||
return (SQLField<E, Integer>) fields.get("id");
|
||||
}
|
||||
|
||||
public void delete() throws ORMException {
|
||||
|
||||
try {
|
||||
if (stored) { // supprimer la ligne de la base
|
||||
PreparedStatement st = db.getNativeConnection()
|
||||
.prepareStatement("DELETE FROM " + tableName + " WHERE id=" + id);
|
||||
try {
|
||||
Log.debug(st.toString());
|
||||
st.executeUpdate();
|
||||
markAsNotStored();
|
||||
} 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;
|
||||
modifiedSinceLastSave.clear();
|
||||
values.forEach((k, v) -> modifiedSinceLastSave.add(k.getName()));
|
||||
}
|
||||
|
||||
protected static class SQLFieldMap<E extends SQLElement<E>> extends LinkedHashMap<String, SQLField<E, ?>> {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final Class<E> sqlElemClass;
|
||||
|
||||
private SQLFieldMap(Class<E> elemClass) {
|
||||
sqlElemClass = elemClass;
|
||||
}
|
||||
|
||||
private void addField(SQLField<?, ?> f) {
|
||||
if (f == null) return;
|
||||
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);
|
||||
put(checkedF.getName(), checkedF);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
ToStringBuilder b = new ToStringBuilder(this);
|
||||
|
||||
for (SQLField<E, ?> f : fields.values())
|
||||
try {
|
||||
b.append(f.getName(), get(f));
|
||||
} catch (IllegalArgumentException e) {
|
||||
b.append(f.getName(), "(Undefined)");
|
||||
}
|
||||
|
||||
return b.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (o == null || !(getClass().isInstance(o))) return false;
|
||||
SQLElement<?> oEl = (SQLElement<?>) o;
|
||||
if (oEl.getId() == null) return false;
|
||||
return oEl.getId().equals(getId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return super.hashCode();
|
||||
}
|
||||
|
||||
|
||||
|
||||
public JsonObject asJsonObject() {
|
||||
JsonObject json = new JsonObject();
|
||||
for (SQLField<E, ?> f : getFields().values()) {
|
||||
json.add(f.getName(), new Gson().toJsonTree(get(f)));
|
||||
}
|
||||
return json;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,207 +1,207 @@
|
||||
package fr.pandacube.java.util.orm;
|
||||
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
|
||||
import fr.pandacube.java.util.Log;
|
||||
import fr.pandacube.java.util.orm.SQLWhereChain.SQLBoolOp;
|
||||
import fr.pandacube.java.util.orm.SQLWhereComp.SQLComparator;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param <E>
|
||||
*/
|
||||
public class SQLElementList<E extends SQLElement<E>> extends ArrayList<E> {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final Map<SQLField<E, ?>, Object> modifiedValues = new LinkedHashMap<>();
|
||||
|
||||
@Override
|
||||
public synchronized boolean add(E e) {
|
||||
if (e == null || !e.isStored()) return false;
|
||||
return super.add(e);
|
||||
}
|
||||
|
||||
/**
|
||||
* Défini une valeur à un champ qui sera appliquée dans la base de données à
|
||||
* tous les
|
||||
* 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
|
||||
* que lors de
|
||||
* l'appel à {@link #saveCommon()}
|
||||
*
|
||||
* @param <T>
|
||||
* @param field le champs à modifier
|
||||
* @param value la valeur à lui appliquer
|
||||
*/
|
||||
public synchronized <T> void setCommon(SQLField<E, T> field, T value) {
|
||||
if (field == null)
|
||||
throw new IllegalArgumentException("field can't be null");
|
||||
if (field.name == "id")
|
||||
throw new IllegalArgumentException("Can't modify id field in a SQLElementList");
|
||||
|
||||
Class<E> elemClass = field.getSQLElementType();
|
||||
try {
|
||||
E emptyElement = elemClass.newInstance();
|
||||
emptyElement.set(field, value, false);
|
||||
} catch (Exception e) {
|
||||
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);
|
||||
}
|
||||
|
||||
// ici, la valeur est bonne
|
||||
modifiedValues.put(field, value);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Applique toutes les valeurs défini avec
|
||||
* {@link #setCommon(SQLField, Object)} à toutes
|
||||
* les entrées dans la base de données correspondants aux entrées de cette
|
||||
* liste. Les nouvelles
|
||||
* valeurs sont aussi mises à jour dans les objets contenus dans cette
|
||||
* liste, si la valeur n'a pas été modifiée individuellement avec
|
||||
* {@link SQLElement#set(SQLField, Object)}.<br/>
|
||||
* Les objets de cette liste qui n'ont pas leur données en base de données
|
||||
* sont ignorées.
|
||||
*
|
||||
* @throws SQLException
|
||||
*/
|
||||
public synchronized void saveCommon() throws SQLException {
|
||||
List<E> storedEl = getStoredEl();
|
||||
if (storedEl.isEmpty()) return;
|
||||
|
||||
String sqlSet = "";
|
||||
List<Object> psValues = new ArrayList<>();
|
||||
|
||||
for (Map.Entry<SQLField<E, ?>, Object> entry : modifiedValues.entrySet()) {
|
||||
sqlSet += entry.getKey().name + " = ? ,";
|
||||
if (entry.getKey().type.getJavaType().isEnum()) // prise en charge
|
||||
// enum (non prise
|
||||
// en charge par
|
||||
// JDBC)
|
||||
psValues.add(((Enum<?>) entry.getValue()).name());
|
||||
else
|
||||
psValues.add(entry.getValue());
|
||||
}
|
||||
|
||||
if (sqlSet.length() > 0) sqlSet = sqlSet.substring(0, sqlSet.length() - 1);
|
||||
|
||||
String sqlWhere = "";
|
||||
boolean first = true;
|
||||
for (E el : storedEl) {
|
||||
if (!first) sqlWhere += " OR ";
|
||||
first = false;
|
||||
sqlWhere += "id = " + el.getId();
|
||||
}
|
||||
|
||||
PreparedStatement ps = ORM.getConnection().getNativeConnection()
|
||||
.prepareStatement("UPDATE " + storedEl.get(0).tableName() + " SET " + sqlSet + " WHERE " + sqlWhere);
|
||||
try {
|
||||
|
||||
int i = 1;
|
||||
for (Object val : psValues)
|
||||
ps.setObject(i++, val);
|
||||
|
||||
Log.debug(ps.toString());
|
||||
ps.executeUpdate();
|
||||
|
||||
applyNewValuesToElements(storedEl);
|
||||
} finally {
|
||||
ps.close();
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void applyNewValuesToElements(List<E> storedEl) {
|
||||
// applique les valeurs dans chaques objets de la liste
|
||||
for (E el : storedEl)
|
||||
for (@SuppressWarnings("rawtypes")
|
||||
SQLField entry : modifiedValues.keySet())
|
||||
if (!el.isModified(entry)) el.set(entry, modifiedValues.get(entry), false);
|
||||
}
|
||||
|
||||
private List<E> getStoredEl() {
|
||||
return stream().filter(SQLElement::isStored).collect(Collectors.toCollection(() -> new ArrayList<>()));
|
||||
}
|
||||
|
||||
public synchronized void removeFromDB() {
|
||||
List<E> storedEl = getStoredEl();
|
||||
if (storedEl.isEmpty()) return;
|
||||
|
||||
try {
|
||||
|
||||
String sqlWhere = "";
|
||||
boolean first = true;
|
||||
for (E el : storedEl) {
|
||||
if (!first) sqlWhere += " OR ";
|
||||
first = false;
|
||||
sqlWhere += "id = " + el.getId();
|
||||
}
|
||||
|
||||
PreparedStatement st = ORM.getConnection().getNativeConnection()
|
||||
.prepareStatement("DELETE FROM " + storedEl.get(0).tableName() + " WHERE " + sqlWhere);
|
||||
try {
|
||||
Log.debug(st.toString());
|
||||
st.executeUpdate();
|
||||
|
||||
for (E el : storedEl)
|
||||
el.markAsNotStored();
|
||||
|
||||
} finally {
|
||||
st.close();
|
||||
}
|
||||
|
||||
} catch (SQLException e) {
|
||||
Log.severe(e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
public <T, F extends SQLElement<F>> Map<T, F> getAllForeign(SQLFKField<E, T, F> foreignKey) throws ORMException {
|
||||
Set<T> values = new HashSet<>();
|
||||
forEach(v -> {
|
||||
T val = v.get(foreignKey);
|
||||
if (val != null)
|
||||
values.add(val);
|
||||
});
|
||||
|
||||
if (values.isEmpty()) {
|
||||
return new HashMap<>();
|
||||
}
|
||||
|
||||
SQLWhereChain where = new SQLWhereChain(SQLBoolOp.OR);
|
||||
values.forEach(v -> where.add(new SQLWhereComp(foreignKey.getForeignField(), SQLComparator.EQ, v)));
|
||||
|
||||
|
||||
SQLElementList<F> foreignElemts = ORM.getAll(foreignKey.getForeignElementClass(), where, null, null, null);
|
||||
|
||||
Map<T, F> ret = new HashMap<>();
|
||||
foreignElemts.forEach(foreignVal -> ret.put(foreignVal.get(foreignKey.getForeignField()), foreignVal));
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public JsonArray asJsonArray() {
|
||||
JsonArray json = new JsonArray();
|
||||
forEach(el -> json.add(el.asJsonObject()));
|
||||
return json;
|
||||
}
|
||||
|
||||
}
|
||||
package fr.pandacube.java.util.orm;
|
||||
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.SQLException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import com.google.gson.JsonArray;
|
||||
|
||||
import fr.pandacube.java.util.Log;
|
||||
import fr.pandacube.java.util.orm.SQLWhereChain.SQLBoolOp;
|
||||
import fr.pandacube.java.util.orm.SQLWhereComp.SQLComparator;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param <E>
|
||||
*/
|
||||
public class SQLElementList<E extends SQLElement<E>> extends ArrayList<E> {
|
||||
private static final long serialVersionUID = 1L;
|
||||
|
||||
private final Map<SQLField<E, ?>, Object> modifiedValues = new LinkedHashMap<>();
|
||||
|
||||
@Override
|
||||
public synchronized boolean add(E e) {
|
||||
if (e == null || !e.isStored()) return false;
|
||||
return super.add(e);
|
||||
}
|
||||
|
||||
/**
|
||||
* Défini une valeur à un champ qui sera appliquée dans la base de données à
|
||||
* tous les
|
||||
* 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
|
||||
* que lors de
|
||||
* l'appel à {@link #saveCommon()}
|
||||
*
|
||||
* @param <T>
|
||||
* @param field le champs à modifier
|
||||
* @param value la valeur à lui appliquer
|
||||
*/
|
||||
public synchronized <T> void setCommon(SQLField<E, T> field, T value) {
|
||||
if (field == null)
|
||||
throw new IllegalArgumentException("field can't be null");
|
||||
if (field.getName() == "id")
|
||||
throw new IllegalArgumentException("Can't modify id field in a SQLElementList");
|
||||
|
||||
Class<E> elemClass = field.getSQLElementType();
|
||||
try {
|
||||
E emptyElement = elemClass.newInstance();
|
||||
emptyElement.set(field, value, false);
|
||||
} catch (Exception e) {
|
||||
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);
|
||||
}
|
||||
|
||||
// ici, la valeur est bonne
|
||||
modifiedValues.put(field, value);
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Applique toutes les valeurs défini avec
|
||||
* {@link #setCommon(SQLField, Object)} à toutes
|
||||
* les entrées dans la base de données correspondants aux entrées de cette
|
||||
* liste. Les nouvelles
|
||||
* valeurs sont aussi mises à jour dans les objets contenus dans cette
|
||||
* liste, si la valeur n'a pas été modifiée individuellement avec
|
||||
* {@link SQLElement#set(SQLField, Object)}.<br/>
|
||||
* Les objets de cette liste qui n'ont pas leur données en base de données
|
||||
* sont ignorées.
|
||||
*
|
||||
* @throws SQLException
|
||||
*/
|
||||
public synchronized void saveCommon() throws ORMException {
|
||||
List<E> storedEl = getStoredEl();
|
||||
if (storedEl.isEmpty()) return;
|
||||
|
||||
String sqlSet = "";
|
||||
List<Object> psValues = new ArrayList<>();
|
||||
|
||||
for (Map.Entry<SQLField<E, ?>, Object> entry : modifiedValues.entrySet()) {
|
||||
sqlSet += entry.getKey().getName() + " = ? ,";
|
||||
SQLElement.addValueToSQLObjectList(psValues, entry.getKey(), entry.getValue());
|
||||
if (entry.getKey().type.getJavaType().isEnum()) {
|
||||
// prise en charge enum (non prise en charge par JDBC)
|
||||
psValues.add(((Enum<?>) entry.getValue()).name());
|
||||
}
|
||||
else
|
||||
psValues.add(entry.getValue());
|
||||
}
|
||||
|
||||
if (sqlSet.length() > 0) sqlSet = sqlSet.substring(0, sqlSet.length() - 1);
|
||||
|
||||
String sqlWhere = "";
|
||||
boolean first = true;
|
||||
for (E el : storedEl) {
|
||||
if (!first) sqlWhere += " OR ";
|
||||
first = false;
|
||||
sqlWhere += "id = " + el.getId();
|
||||
}
|
||||
|
||||
try(PreparedStatement ps = ORM.getConnection().getNativeConnection()
|
||||
.prepareStatement("UPDATE " + storedEl.get(0).tableName() + " SET " + sqlSet + " WHERE " + sqlWhere);) {
|
||||
|
||||
|
||||
int i = 1;
|
||||
for (Object val : psValues)
|
||||
ps.setObject(i++, val);
|
||||
|
||||
Log.debug(ps.toString());
|
||||
ps.executeUpdate();
|
||||
|
||||
applyNewValuesToElements(storedEl);
|
||||
} catch (SQLException e) {
|
||||
throw new ORMException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private void applyNewValuesToElements(List<E> storedEl) {
|
||||
// applique les valeurs dans chaques objets de la liste
|
||||
for (E el : storedEl)
|
||||
for (@SuppressWarnings("rawtypes")
|
||||
SQLField entry : modifiedValues.keySet())
|
||||
if (!el.isModified(entry)) el.set(entry, modifiedValues.get(entry), false);
|
||||
}
|
||||
|
||||
private List<E> getStoredEl() {
|
||||
return stream().filter(SQLElement::isStored).collect(Collectors.toCollection(() -> new ArrayList<>()));
|
||||
}
|
||||
|
||||
public synchronized void removeFromDB() {
|
||||
List<E> storedEl = getStoredEl();
|
||||
if (storedEl.isEmpty()) return;
|
||||
|
||||
try {
|
||||
|
||||
String sqlWhere = "";
|
||||
boolean first = true;
|
||||
for (E el : storedEl) {
|
||||
if (!first) sqlWhere += " OR ";
|
||||
first = false;
|
||||
sqlWhere += "id = " + el.getId();
|
||||
}
|
||||
|
||||
PreparedStatement st = ORM.getConnection().getNativeConnection()
|
||||
.prepareStatement("DELETE FROM " + storedEl.get(0).tableName() + " WHERE " + sqlWhere);
|
||||
try {
|
||||
Log.debug(st.toString());
|
||||
st.executeUpdate();
|
||||
|
||||
for (E el : storedEl)
|
||||
el.markAsNotStored();
|
||||
|
||||
} finally {
|
||||
st.close();
|
||||
}
|
||||
|
||||
} catch (SQLException e) {
|
||||
Log.severe(e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
public <T, F extends SQLElement<F>> Map<T, F> getAllForeign(SQLFKField<E, T, F> foreignKey) throws ORMException {
|
||||
Set<T> values = new HashSet<>();
|
||||
forEach(v -> {
|
||||
T val = v.get(foreignKey);
|
||||
if (val != null)
|
||||
values.add(val);
|
||||
});
|
||||
|
||||
if (values.isEmpty()) {
|
||||
return new HashMap<>();
|
||||
}
|
||||
|
||||
SQLWhereChain where = new SQLWhereChain(SQLBoolOp.OR);
|
||||
values.forEach(v -> where.add(new SQLWhereComp(foreignKey.getForeignField(), SQLComparator.EQ, v)));
|
||||
|
||||
|
||||
SQLElementList<F> foreignElemts = ORM.getAll(foreignKey.getForeignElementClass(), where, null, null, null);
|
||||
|
||||
Map<T, F> ret = new HashMap<>();
|
||||
foreignElemts.forEach(foreignVal -> ret.put(foreignVal.get(foreignKey.getForeignField()), foreignVal));
|
||||
return ret;
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
||||
|
||||
public JsonArray asJsonArray() {
|
||||
JsonArray json = new JsonArray();
|
||||
forEach(el -> json.add(el.asJsonObject()));
|
||||
return json;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,65 +1,62 @@
|
||||
package fr.pandacube.java.util.orm;
|
||||
|
||||
import fr.pandacube.java.util.Log;
|
||||
|
||||
public class SQLFKField<E extends SQLElement<E>, T, F extends SQLElement<F>> extends SQLField<E, T> {
|
||||
|
||||
private SQLField<F, T> sqlForeignKeyField;
|
||||
private Class<F> sqlForeignKeyElemClass;
|
||||
|
||||
public SQLFKField(String n, SQLType<T> t, boolean nul, Class<F> fkEl, SQLField<F, T> fkF) {
|
||||
super(n, t, nul);
|
||||
construct(fkEl, fkF);
|
||||
}
|
||||
|
||||
public SQLFKField(String n, SQLType<T> t, boolean nul, T deflt, Class<F> fkEl, SQLField<F, T> fkF) {
|
||||
super(n, t, nul, deflt);
|
||||
construct(fkEl, fkF);
|
||||
}
|
||||
|
||||
public static <E extends SQLElement<E>, F extends SQLElement<F>> SQLFKField<E, Integer, F> idFK(String n, SQLType<Integer> t, boolean nul,
|
||||
Class<F> fkEl) {
|
||||
if (fkEl == null) throw new IllegalArgumentException("foreignKeyElement can't be null");
|
||||
try {
|
||||
return new SQLFKField<>(n, t, nul, fkEl, ORM.getSQLIdField(fkEl));
|
||||
} catch (ORMInitTableException e) {
|
||||
Log.severe("Can't create Foreign key Field called '" + n + "'", e);
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static <E extends SQLElement<E>, F extends SQLElement<F>> SQLFKField<E, Integer, F> idFKField(String n, SQLType<Integer> t, boolean nul,
|
||||
Integer deflt, Class<F> fkEl) {
|
||||
if (fkEl == null) throw new IllegalArgumentException("foreignKeyElement can't be null");
|
||||
try {
|
||||
return new SQLFKField<>(n, t, nul, deflt, fkEl, ORM.getSQLIdField(fkEl));
|
||||
} catch (ORMInitTableException e) {
|
||||
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 {
|
||||
ORM.initTable(fkEl);
|
||||
} catch (ORMInitTableException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
if (!fkEl.equals(fkF.getSQLElementType()))
|
||||
throw new IllegalArgumentException("foreignKeyField must be from supplied foreignKeyElement");
|
||||
if (!type.equals(fkF.type))
|
||||
throw new IllegalArgumentException("foreignKeyField and current Field must have the same SQLType");
|
||||
sqlForeignKeyField = fkF;
|
||||
sqlForeignKeyElemClass = fkEl;
|
||||
}
|
||||
|
||||
public SQLField<F, T> getForeignField() {
|
||||
return sqlForeignKeyField;
|
||||
}
|
||||
|
||||
public Class<F> getForeignElementClass() {
|
||||
return sqlForeignKeyElemClass;
|
||||
}
|
||||
|
||||
}
|
||||
package fr.pandacube.java.util.orm;
|
||||
|
||||
import fr.pandacube.java.util.Log;
|
||||
|
||||
public class SQLFKField<E extends SQLElement<E>, T, F extends SQLElement<F>> extends SQLField<E, T> {
|
||||
|
||||
private SQLField<F, T> sqlForeignKeyField;
|
||||
private Class<F> sqlForeignKeyElemClass;
|
||||
|
||||
protected SQLFKField(SQLType<T> t, boolean nul, T deflt, Class<F> fkEl, SQLField<F, T> fkF) {
|
||||
super(t, nul, deflt);
|
||||
construct(fkEl, fkF);
|
||||
}
|
||||
|
||||
public static <E extends SQLElement<E>, F extends SQLElement<F>> SQLFKField<E, Integer, F> idFK(boolean nul, Class<F> fkEl) {
|
||||
return idFK(nul, null, fkEl);
|
||||
}
|
||||
|
||||
public static <E extends SQLElement<E>, F extends SQLElement<F>> SQLFKField<E, Integer, F> idFK(boolean nul, Integer deflt, Class<F> fkEl) {
|
||||
if (fkEl == null) throw new IllegalArgumentException("foreignKeyElement can't be null");
|
||||
try {
|
||||
SQLField<F, Integer> f = ORM.getSQLIdField(fkEl);
|
||||
return new SQLFKField<>(f.type, nul, deflt, fkEl, f);
|
||||
} catch (ORMInitTableException e) {
|
||||
Log.severe("Can't create Foreign key Field targetting id field of '"+fkEl+"'", e);
|
||||
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) {
|
||||
return customFK(nul, null, fkEl, fkF);
|
||||
}
|
||||
|
||||
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) {
|
||||
if (fkEl == null) throw new IllegalArgumentException("foreignKeyElement can't be null");
|
||||
return new SQLFKField<>(fkF.type, nul, deflt, fkEl, fkF);
|
||||
}
|
||||
|
||||
private void construct(Class<F> fkEl, SQLField<F, T> fkF) {
|
||||
if (fkF == null) throw new IllegalArgumentException("foreignKeyField can't be null");
|
||||
try {
|
||||
ORM.initTable(fkEl);
|
||||
} catch (ORMInitTableException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
if (!fkEl.equals(fkF.getSQLElementType()))
|
||||
throw new IllegalArgumentException("foreignKeyField must be from supplied foreignKeyElement");
|
||||
if (!type.equals(fkF.type))
|
||||
throw new IllegalArgumentException("foreignKeyField and current Field must have the same SQLType");
|
||||
sqlForeignKeyField = fkF;
|
||||
sqlForeignKeyElemClass = fkEl;
|
||||
}
|
||||
|
||||
public SQLField<F, T> getForeignField() {
|
||||
return sqlForeignKeyField;
|
||||
}
|
||||
|
||||
public Class<F> getForeignElementClass() {
|
||||
return sqlForeignKeyElemClass;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,80 +1,87 @@
|
||||
package fr.pandacube.java.util.orm;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.javatuples.Pair;
|
||||
|
||||
public class SQLField<E extends SQLElement<E>, T> {
|
||||
|
||||
private Class<E> sqlElemClass;
|
||||
public final String name;
|
||||
public final SQLType<T> type;
|
||||
public final boolean canBeNull;
|
||||
public final boolean autoIncrement;
|
||||
/* package */ final T defaultValue;
|
||||
|
||||
public SQLField(String n, SQLType<T> t, boolean nul, boolean autoIncr, T deflt) {
|
||||
name = n;
|
||||
type = t;
|
||||
canBeNull = nul;
|
||||
autoIncrement = autoIncr;
|
||||
defaultValue = deflt;
|
||||
}
|
||||
|
||||
public SQLField(String n, SQLType<T> t, boolean nul) {
|
||||
this(n, t, nul, false, null);
|
||||
}
|
||||
|
||||
public SQLField(String n, SQLType<T> t, boolean nul, boolean autoIncr) {
|
||||
this(n, t, nul, autoIncr, null);
|
||||
}
|
||||
|
||||
public SQLField(String n, SQLType<T> t, boolean nul, T deflt) {
|
||||
this(n, t, nul, false, deflt);
|
||||
}
|
||||
|
||||
/* package */ Pair<String, List<Object>> forSQLPreparedStatement() {
|
||||
List<Object> params = new ArrayList<>(1);
|
||||
if (defaultValue != null && !autoIncrement) params.add(defaultValue);
|
||||
return new Pair<>(name + " " + type.toString() + (canBeNull ? " NULL" : " NOT NULL")
|
||||
+ (autoIncrement ? " AUTO_INCREMENT" : "")
|
||||
+ ((defaultValue == null || autoIncrement) ? "" : " DEFAULT ?"), params);
|
||||
}
|
||||
|
||||
/* package */ void setSQLElementType(Class<E> elemClass) {
|
||||
sqlElemClass = elemClass;
|
||||
}
|
||||
|
||||
public Class<E> getSQLElementType() {
|
||||
return sqlElemClass;
|
||||
}
|
||||
|
||||
/**
|
||||
* <b>Don't use this {@link #toString()} method in a SQL query, because
|
||||
* the default value is not escaped correctly</b>
|
||||
*
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return forSQLPreparedStatement().getValue0().replaceFirst("\\?",
|
||||
(defaultValue != null && !autoIncrement) ? defaultValue.toString() : "");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == null) return false;
|
||||
if (!(obj instanceof SQLField)) return false;
|
||||
SQLField<?, ?> f = (SQLField<?, ?>) obj;
|
||||
if (!f.name.equals(name)) return false;
|
||||
if (!f.sqlElemClass.equals(sqlElemClass)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return name.hashCode() + sqlElemClass.hashCode();
|
||||
}
|
||||
|
||||
}
|
||||
package fr.pandacube.java.util.orm;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.javatuples.Pair;
|
||||
|
||||
public class SQLField<E extends SQLElement<E>, T> {
|
||||
|
||||
private Class<E> sqlElemClass;
|
||||
private String name = null;
|
||||
public final SQLType<T> type;
|
||||
public final boolean canBeNull;
|
||||
public final boolean autoIncrement;
|
||||
/* package */ final T defaultValue;
|
||||
|
||||
public SQLField(SQLType<T> t, boolean nul, boolean autoIncr, T deflt) {
|
||||
type = t;
|
||||
canBeNull = nul;
|
||||
autoIncrement = autoIncr;
|
||||
defaultValue = deflt;
|
||||
}
|
||||
|
||||
public SQLField(SQLType<T> t, boolean nul) {
|
||||
this(t, nul, false, null);
|
||||
}
|
||||
|
||||
public SQLField(SQLType<T> t, boolean nul, boolean autoIncr) {
|
||||
this(t, nul, autoIncr, null);
|
||||
}
|
||||
|
||||
public SQLField(SQLType<T> t, boolean nul, T deflt) {
|
||||
this(t, nul, false, deflt);
|
||||
}
|
||||
|
||||
/* package */ Pair<String, List<Object>> forSQLPreparedStatement() {
|
||||
List<Object> params = new ArrayList<>(1);
|
||||
if (defaultValue != null && !autoIncrement) params.add(defaultValue);
|
||||
return new Pair<>(getName() + " " + type.toString() + (canBeNull ? " NULL" : " NOT NULL")
|
||||
+ (autoIncrement ? " AUTO_INCREMENT" : "")
|
||||
+ ((defaultValue == null || autoIncrement) ? "" : " DEFAULT ?"), params);
|
||||
}
|
||||
|
||||
/* package */ void setSQLElementType(Class<E> elemClass) {
|
||||
sqlElemClass = elemClass;
|
||||
}
|
||||
|
||||
public Class<E> getSQLElementType() {
|
||||
return sqlElemClass;
|
||||
}
|
||||
|
||||
/* package */ void setName(String n) {
|
||||
name = n;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
/**
|
||||
* <b>Don't use this {@link #toString()} method in a SQL query, because
|
||||
* the default value is not escaped correctly</b>
|
||||
*
|
||||
* @see java.lang.Object#toString()
|
||||
*/
|
||||
@Override
|
||||
public String toString() {
|
||||
return forSQLPreparedStatement().getValue0().replaceFirst("\\?",
|
||||
(defaultValue != null && !autoIncrement) ? defaultValue.toString() : "");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == null) return false;
|
||||
if (!(obj instanceof SQLField)) return false;
|
||||
SQLField<?, ?> f = (SQLField<?, ?>) obj;
|
||||
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();
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,68 +1,68 @@
|
||||
package fr.pandacube.java.util.orm;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class SQLOrderBy {
|
||||
|
||||
private List<OBField> orderByFields = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Construit une nouvelle clause ORDER BY
|
||||
*/
|
||||
public SQLOrderBy() {}
|
||||
|
||||
/**
|
||||
* Ajoute un champ dans la clause ORDER BY en construction
|
||||
*
|
||||
* @param field le champ SQL à ordonner
|
||||
* @param d le sens de tri (croissant ASC ou décroissant DESC)
|
||||
* @return l'objet courant (permet de chainer les ajouts de champs)
|
||||
*/
|
||||
public SQLOrderBy addField(SQLField<?, ?> field, Direction d) {
|
||||
orderByFields.add(new OBField(field, d));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ajoute un champ dans la clause ORDER BY en construction,
|
||||
* avec comme ordre de tri croissant ASC par défaut
|
||||
*
|
||||
* @param field le champ SQL à ordonner dans l'ordre croissant ASC
|
||||
* @return l'objet courant (permet de chainer les ajouts de champs)
|
||||
*/
|
||||
public SQLOrderBy addField(SQLField<?, ?> field) {
|
||||
return addField(field, Direction.ASC);
|
||||
}
|
||||
|
||||
/* package */ String toSQL() {
|
||||
String ret = "";
|
||||
boolean first = true;
|
||||
for (OBField f : orderByFields) {
|
||||
if (!first) ret += ", ";
|
||||
first = false;
|
||||
ret += f.field.name + " " + f.direction.name();
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return toSQL();
|
||||
}
|
||||
|
||||
private class OBField {
|
||||
public final SQLField<?, ?> field;
|
||||
public final Direction direction;
|
||||
|
||||
public OBField(SQLField<?, ?> f, Direction d) {
|
||||
field = f;
|
||||
direction = d;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public enum Direction {
|
||||
ASC, DESC;
|
||||
}
|
||||
}
|
||||
package fr.pandacube.java.util.orm;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
public class SQLOrderBy {
|
||||
|
||||
private List<OBField> orderByFields = new ArrayList<>();
|
||||
|
||||
/**
|
||||
* Construit une nouvelle clause ORDER BY
|
||||
*/
|
||||
public SQLOrderBy() {}
|
||||
|
||||
/**
|
||||
* Ajoute un champ dans la clause ORDER BY en construction
|
||||
*
|
||||
* @param field le champ SQL à ordonner
|
||||
* @param d le sens de tri (croissant ASC ou décroissant DESC)
|
||||
* @return l'objet courant (permet de chainer les ajouts de champs)
|
||||
*/
|
||||
public SQLOrderBy addField(SQLField<?, ?> field, Direction d) {
|
||||
orderByFields.add(new OBField(field, d));
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Ajoute un champ dans la clause ORDER BY en construction,
|
||||
* avec comme ordre de tri croissant ASC par défaut
|
||||
*
|
||||
* @param field le champ SQL à ordonner dans l'ordre croissant ASC
|
||||
* @return l'objet courant (permet de chainer les ajouts de champs)
|
||||
*/
|
||||
public SQLOrderBy addField(SQLField<?, ?> field) {
|
||||
return addField(field, Direction.ASC);
|
||||
}
|
||||
|
||||
/* package */ String toSQL() {
|
||||
String ret = "";
|
||||
boolean first = true;
|
||||
for (OBField f : orderByFields) {
|
||||
if (!first) ret += ", ";
|
||||
first = false;
|
||||
ret += f.field.getName() + " " + f.direction.name();
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return toSQL();
|
||||
}
|
||||
|
||||
private class OBField {
|
||||
public final SQLField<?, ?> field;
|
||||
public final Direction direction;
|
||||
|
||||
public OBField(SQLField<?, ?> f, Direction d) {
|
||||
field = f;
|
||||
direction = d;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
public enum Direction {
|
||||
ASC, DESC;
|
||||
}
|
||||
}
|
||||
|
@ -1,90 +1,95 @@
|
||||
package fr.pandacube.java.util.orm;
|
||||
|
||||
import java.sql.Date;
|
||||
|
||||
public class SQLType<T> {
|
||||
|
||||
private final String sqlType;
|
||||
private final String sqlTypeParam;
|
||||
private final Class<T> javaTypes;
|
||||
|
||||
public SQLType(String sqlT, String sqlP, Class<T> javaT) {
|
||||
sqlType = sqlT;
|
||||
sqlTypeParam = sqlP;
|
||||
javaTypes = javaT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return sqlType + sqlTypeParam;
|
||||
}
|
||||
|
||||
public boolean isAssignableFrom(Object val) {
|
||||
if (javaTypes.isInstance(val)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return toString().hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == null || !(obj instanceof SQLType)) return false;
|
||||
return toString().equals(((SQLType<?>) obj).toString());
|
||||
}
|
||||
|
||||
public Class<T> getJavaType() {
|
||||
return javaTypes;
|
||||
}
|
||||
|
||||
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<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<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<Float> FLOAT = new SQLType<>("FLOAT", "", Float.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.");
|
||||
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> TEXT = new SQLType<>("TEXT", "", String.class);
|
||||
public static final SQLType<String> STRING = TEXT;
|
||||
|
||||
public static final <T extends Enum<T>> SQLType<T> ENUM(Class<T> enumType) {
|
||||
if (enumType == null) throw new IllegalArgumentException("enumType can't be null.");
|
||||
String enumStr = "'";
|
||||
boolean first = true;
|
||||
for (T el : enumType.getEnumConstants()) {
|
||||
if (!first) enumStr += "', '";
|
||||
first = false;
|
||||
enumStr += el.name();
|
||||
|
||||
}
|
||||
enumStr += "'";
|
||||
|
||||
return new SQLType<>("VARCHAR", "(" + enumStr + ")", enumType);
|
||||
}
|
||||
|
||||
}
|
||||
package fr.pandacube.java.util.orm;
|
||||
|
||||
import java.sql.Date;
|
||||
import java.util.UUID;
|
||||
|
||||
import fr.pandacube.java.util.EnumUtil;
|
||||
|
||||
public class SQLType<T> {
|
||||
|
||||
protected final String sqlDeclaration;
|
||||
private final Class<T> javaTypes;
|
||||
|
||||
protected SQLType(String sqlD, Class<T> javaT) {
|
||||
sqlDeclaration = sqlD;
|
||||
javaTypes = javaT;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return sqlDeclaration;
|
||||
}
|
||||
|
||||
public boolean isAssignableFrom(Object val) {
|
||||
if (javaTypes.isInstance(val)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return toString().hashCode();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj == null || !(obj instanceof SQLType)) return false;
|
||||
return toString().equals(((SQLType<?>) obj).toString());
|
||||
}
|
||||
|
||||
public Class<T> getJavaType() {
|
||||
return javaTypes;
|
||||
}
|
||||
|
||||
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<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<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<Float> FLOAT = new SQLType<>("FLOAT", Float.class);
|
||||
|
||||
public static final SQLType<Double> DOUBLE = new SQLType<>("DOUBLE", Double.class);
|
||||
|
||||
@Deprecated
|
||||
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> TEXT = new SQLType<>("TEXT", String.class);
|
||||
public static final SQLType<String> STRING = TEXT;
|
||||
|
||||
public static final <T extends Enum<T>> SQLType<T> ENUM(Class<T> enumType) {
|
||||
if (enumType == null) throw new IllegalArgumentException("enumType can't be null.");
|
||||
String enumStr = "'";
|
||||
boolean first = true;
|
||||
for (T el : enumType.getEnumConstants()) {
|
||||
if (!first) enumStr += "', '";
|
||||
first = false;
|
||||
enumStr += el.name();
|
||||
|
||||
}
|
||||
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);
|
||||
|
||||
}
|
||||
|
@ -1,16 +1,23 @@
|
||||
package fr.pandacube.java.util.orm;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.javatuples.Pair;
|
||||
|
||||
public abstract class SQLWhere {
|
||||
|
||||
public abstract Pair<String, List<Object>> toSQL();
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return toSQL().getValue0();
|
||||
}
|
||||
|
||||
}
|
||||
package fr.pandacube.java.util.orm;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import org.javatuples.Pair;
|
||||
|
||||
import fr.pandacube.java.util.Log;
|
||||
|
||||
public abstract class SQLWhere {
|
||||
|
||||
public abstract Pair<String, List<Object>> toSQL() throws ORMException;
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
try {
|
||||
return toSQL().getValue0();
|
||||
} catch (ORMException e) {
|
||||
Log.warning(e);
|
||||
return "[SQLWhere.toString() error (see logs)]";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,54 +1,54 @@
|
||||
package fr.pandacube.java.util.orm;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.javatuples.Pair;
|
||||
|
||||
public class SQLWhereChain extends SQLWhere {
|
||||
|
||||
private SQLBoolOp operator;
|
||||
private List<SQLWhere> conditions = new ArrayList<>();
|
||||
|
||||
public SQLWhereChain(SQLBoolOp op) {
|
||||
if (op == null) throw new IllegalArgumentException("op can't be null");
|
||||
operator = op;
|
||||
}
|
||||
|
||||
public SQLWhereChain add(SQLWhere sqlWhere) {
|
||||
if (sqlWhere == null) throw new IllegalArgumentException("sqlWhere can't be null");
|
||||
conditions.add(sqlWhere);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Pair<String, List<Object>> toSQL() {
|
||||
String sql = "";
|
||||
List<Object> params = new ArrayList<>();
|
||||
boolean first = true;
|
||||
|
||||
for (SQLWhere w : conditions) {
|
||||
if (!first) sql += " " + operator.sql + " ";
|
||||
first = false;
|
||||
|
||||
Pair<String, List<Object>> ret = w.toSQL();
|
||||
sql += "(" + ret.getValue0() + ")";
|
||||
params.addAll(ret.getValue1());
|
||||
}
|
||||
|
||||
return new Pair<>(sql, params);
|
||||
}
|
||||
|
||||
public enum SQLBoolOp {
|
||||
/** Equivalent to SQL "<code>AND</code>" */
|
||||
AND("AND"), /** Equivalent to SQL "<code>OR</code>" */
|
||||
OR("OR");
|
||||
public final String sql;
|
||||
|
||||
private SQLBoolOp(String s) {
|
||||
sql = s;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
package fr.pandacube.java.util.orm;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.javatuples.Pair;
|
||||
|
||||
public class SQLWhereChain extends SQLWhere {
|
||||
|
||||
private SQLBoolOp operator;
|
||||
private List<SQLWhere> conditions = new ArrayList<>();
|
||||
|
||||
public SQLWhereChain(SQLBoolOp op) {
|
||||
if (op == null) throw new IllegalArgumentException("op can't be null");
|
||||
operator = op;
|
||||
}
|
||||
|
||||
public SQLWhereChain add(SQLWhere sqlWhere) {
|
||||
if (sqlWhere == null) throw new IllegalArgumentException("sqlWhere can't be null");
|
||||
conditions.add(sqlWhere);
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Pair<String, List<Object>> toSQL() throws ORMException {
|
||||
String sql = "";
|
||||
List<Object> params = new ArrayList<>();
|
||||
boolean first = true;
|
||||
|
||||
for (SQLWhere w : conditions) {
|
||||
if (!first) sql += " " + operator.sql + " ";
|
||||
first = false;
|
||||
|
||||
Pair<String, List<Object>> ret = w.toSQL();
|
||||
sql += "(" + ret.getValue0() + ")";
|
||||
params.addAll(ret.getValue1());
|
||||
}
|
||||
|
||||
return new Pair<>(sql, params);
|
||||
}
|
||||
|
||||
public enum SQLBoolOp {
|
||||
/** Equivalent to SQL "<code>AND</code>" */
|
||||
AND("AND"), /** Equivalent to SQL "<code>OR</code>" */
|
||||
OR("OR");
|
||||
public final String sql;
|
||||
|
||||
private SQLBoolOp(String s) {
|
||||
sql = s;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,53 +1,58 @@
|
||||
package fr.pandacube.java.util.orm;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.javatuples.Pair;
|
||||
|
||||
public class SQLWhereComp extends SQLWhere {
|
||||
|
||||
private SQLField<?, ?> left;
|
||||
private SQLComparator comp;
|
||||
private Object right;
|
||||
|
||||
/**
|
||||
* Compare a field with a value
|
||||
*
|
||||
* @param l the field at left of 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
|
||||
*/
|
||||
public <T> SQLWhereComp(SQLField<?, T> l, SQLComparator c, T r) {
|
||||
if (l == null || r == null || c == null)
|
||||
throw new IllegalArgumentException("All arguments for SQLWhereComp constructor can't be null");
|
||||
left = l;
|
||||
comp = c;
|
||||
right = r;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Pair<String, List<Object>> toSQL() {
|
||||
List<Object> params = new ArrayList<>();
|
||||
params.add(right);
|
||||
return new Pair<>(left.name + " " + comp.sql + " ? ", params);
|
||||
}
|
||||
|
||||
public enum SQLComparator {
|
||||
/** Equivalent to SQL "<code>=</code>" */
|
||||
EQ("="), /** Equivalent to SQL "<code>></code>" */
|
||||
GT(">"), /** Equivalent to SQL "<code>>=</code>" */
|
||||
GEQ(">="), /** Equivalent to SQL "<code><</code>" */
|
||||
LT("<"), /** Equivalent to SQL "<code><=</code>" */
|
||||
LEQ("<="), /** Equivalent to SQL "<code>!=</code>" */
|
||||
NEQ("!=");
|
||||
|
||||
public final String sql;
|
||||
|
||||
private SQLComparator(String s) {
|
||||
sql = s;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
package fr.pandacube.java.util.orm;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.javatuples.Pair;
|
||||
|
||||
public class SQLWhereComp extends SQLWhere {
|
||||
|
||||
private SQLField<?, ?> left;
|
||||
private SQLComparator comp;
|
||||
private Object right;
|
||||
|
||||
/**
|
||||
* Compare a field with a value
|
||||
*
|
||||
* @param l the field at left of 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
|
||||
*/
|
||||
public <T> SQLWhereComp(SQLField<?, T> l, SQLComparator c, T r) {
|
||||
if (l == null || r == null || c == null)
|
||||
throw new IllegalArgumentException("All arguments for SQLWhereComp constructor can't be null");
|
||||
left = l;
|
||||
comp = c;
|
||||
right = r;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Pair<String, List<Object>> toSQL() throws ORMException {
|
||||
List<Object> params = new ArrayList<>();
|
||||
SQLElement.addValueToSQLObjectList(params, left, right);
|
||||
return new Pair<>(left.getName() + " " + comp.sql + " ? ", params);
|
||||
}
|
||||
|
||||
public enum SQLComparator {
|
||||
/** Equivalent to SQL "<code>=</code>" */
|
||||
EQ("="),
|
||||
/** Equivalent to SQL "<code>></code>" */
|
||||
GT(">"),
|
||||
/** Equivalent to SQL "<code>>=</code>" */
|
||||
GEQ(">="),
|
||||
/** Equivalent to SQL "<code><</code>" */
|
||||
LT("<"),
|
||||
/** Equivalent to SQL "<code><=</code>" */
|
||||
LEQ("<="),
|
||||
/** Equivalent to SQL "<code>!=</code>" */
|
||||
NEQ("!=");
|
||||
|
||||
public final String sql;
|
||||
|
||||
private SQLComparator(String s) {
|
||||
sql = s;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,33 +1,33 @@
|
||||
package fr.pandacube.java.util.orm;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.javatuples.Pair;
|
||||
|
||||
public class SQLWhereLike extends SQLWhere {
|
||||
|
||||
private SQLField<?, String> field;
|
||||
private String likeExpr;
|
||||
|
||||
/**
|
||||
* Compare a field with a value
|
||||
*
|
||||
* @param f the field at left of the LIKE keyword. Can't be null
|
||||
* @param like the like expression.
|
||||
*/
|
||||
public SQLWhereLike(SQLField<?, String> f, String like) {
|
||||
if (f == null || like == null)
|
||||
throw new IllegalArgumentException("All arguments for SQLWhereLike constructor can't be null");
|
||||
field = f;
|
||||
likeExpr = like;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Pair<String, List<Object>> toSQL() {
|
||||
ArrayList<Object> params = new ArrayList<>();
|
||||
params.add(likeExpr);
|
||||
return new Pair<>(field.name + " LIKE ? ", params);
|
||||
}
|
||||
|
||||
}
|
||||
package fr.pandacube.java.util.orm;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import org.javatuples.Pair;
|
||||
|
||||
public class SQLWhereLike extends SQLWhere {
|
||||
|
||||
private SQLField<?, String> field;
|
||||
private String likeExpr;
|
||||
|
||||
/**
|
||||
* Compare a field with a value
|
||||
*
|
||||
* @param f the field at left of the LIKE keyword. Can't be null
|
||||
* @param like the like expression.
|
||||
*/
|
||||
public SQLWhereLike(SQLField<?, String> f, String like) {
|
||||
if (f == null || like == null)
|
||||
throw new IllegalArgumentException("All arguments for SQLWhereLike constructor can't be null");
|
||||
field = f;
|
||||
likeExpr = like;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Pair<String, List<Object>> toSQL() {
|
||||
ArrayList<Object> params = new ArrayList<>();
|
||||
params.add(likeExpr);
|
||||
return new Pair<>(field.getName() + " LIKE ? ", params);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -1,36 +1,36 @@
|
||||
package fr.pandacube.java.util.orm;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import fr.pandacube.java.util.Log;
|
||||
import org.javatuples.Pair;
|
||||
|
||||
public class SQLWhereNull extends SQLWhere {
|
||||
|
||||
private SQLField<?, ?> fild;
|
||||
private boolean nulll;
|
||||
|
||||
/**
|
||||
* Init a IS NULL / IS NOT NULL expression for a SQL WHERE condition.
|
||||
*
|
||||
* @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
|
||||
* "IS NOT NULL"
|
||||
*/
|
||||
public SQLWhereNull(SQLField<?, ?> field, boolean isNull) {
|
||||
if (field == null) throw new IllegalArgumentException("field can't be null");
|
||||
if (!field.canBeNull) Log.getLogger().log(Level.WARNING,
|
||||
"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'");
|
||||
fild = field;
|
||||
nulll = isNull;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Pair<String, List<Object>> toSQL() {
|
||||
return new Pair<>(fild.name + " IS" + ((nulll) ? " NULL" : " NOT NULL"), new ArrayList<>());
|
||||
}
|
||||
|
||||
}
|
||||
package fr.pandacube.java.util.orm;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import fr.pandacube.java.util.Log;
|
||||
import org.javatuples.Pair;
|
||||
|
||||
public class SQLWhereNull extends SQLWhere {
|
||||
|
||||
private SQLField<?, ?> fild;
|
||||
private boolean nulll;
|
||||
|
||||
/**
|
||||
* Init a IS NULL / IS NOT NULL expression for a SQL WHERE condition.
|
||||
*
|
||||
* @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
|
||||
* "IS NOT NULL"
|
||||
*/
|
||||
public SQLWhereNull(SQLField<?, ?> field, boolean isNull) {
|
||||
if (field == null) throw new IllegalArgumentException("field can't be null");
|
||||
if (!field.canBeNull) Log.getLogger().log(Level.WARNING,
|
||||
"Useless : Trying to check IS [NOT] NULL on the field " + field.getSQLElementType().getName() + "#"
|
||||
+ field.getName() + " which is declared in the ORM as 'can't be null'");
|
||||
fild = field;
|
||||
nulll = isNull;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Pair<String, List<Object>> toSQL() {
|
||||
return new Pair<>(fild.getName() + ((nulll) ? " IS NULL" : " IS NOT NULL"), new ArrayList<>());
|
||||
}
|
||||
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user