Some ORM refactoring

This commit is contained in:
Marc Baloup 2019-12-02 01:25:40 +01:00
parent ccaf9bf26c
commit ea9bfd1270
4 changed files with 181 additions and 165 deletions

View File

@ -6,7 +6,9 @@ import java.sql.SQLException;
import java.sql.Statement; import java.sql.Statement;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collection; import java.util.Collection;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map;
import java.util.function.Consumer; import java.util.function.Consumer;
import org.javatuples.Pair; import org.javatuples.Pair;
@ -24,6 +26,7 @@ import fr.pandacube.util.orm.SQLWhereComp.SQLComparator;
public final class ORM { public final class ORM {
private static List<Class<? extends SQLElement<?>>> tables = new ArrayList<>(); private static List<Class<? extends SQLElement<?>>> tables = new ArrayList<>();
private static Map<Class<? extends SQLElement<?>>, String> tableNames = new HashMap<>();
private static DBConnection connection; private static DBConnection connection;
@ -45,7 +48,8 @@ public final class ORM {
Log.debug("[ORM] Start Init SQL table "+elemClass.getSimpleName()); Log.debug("[ORM] Start Init SQL table "+elemClass.getSimpleName());
E instance = elemClass.newInstance(); E instance = elemClass.newInstance();
String tableName = instance.tableName(); String tableName = instance.tableName();
if (!tableExist(tableName)) createTable(instance); tableNames.put(elemClass, tableName);
if (!tableExistInDB(tableName)) createTable(instance);
Log.debug("[ORM] End init SQL table "+elemClass.getSimpleName()); Log.debug("[ORM] End init SQL table "+elemClass.getSimpleName());
} catch (Exception|ExceptionInInitializerError e) { } catch (Exception|ExceptionInInitializerError e) {
throw new ORMInitTableException(elemClass, e); throw new ORMInitTableException(elemClass, e);
@ -79,7 +83,12 @@ public final class ORM {
} }
} }
private static boolean tableExist(String tableName) throws SQLException { public static <E extends SQLElement<E>> String getTableName(Class<E> elemClass) throws ORMException {
initTable(elemClass);
return tableNames.get(elemClass);
}
private static boolean tableExistInDB(String tableName) throws SQLException {
boolean exist = false; boolean exist = false;
try (ResultSet set = connection.getNativeConnection().getMetaData().getTables(null, null, tableName, null)) { try (ResultSet set = connection.getNativeConnection().getMetaData().getTables(null, null, tableName, null)) {
exist = set.next(); exist = set.next();
@ -176,7 +185,7 @@ public final class ORM {
initTable(elemClass); initTable(elemClass);
try { try {
String sql = "SELECT * FROM " + elemClass.newInstance().tableName(); String sql = "SELECT * FROM " + getTableName(elemClass);
List<Object> params = new ArrayList<>(); List<Object> params = new ArrayList<>();
@ -190,66 +199,19 @@ public final class ORM {
if (offset != null) sql += " OFFSET " + offset; if (offset != null) sql += " OFFSET " + offset;
sql += ";"; sql += ";";
try (PreparedStatement ps = connection.getNativeConnection().prepareStatement(sql)) { try (ResultSet set = customQueryStatement(sql, params)) {
while (set.next()) {
int i = 1; E elm = getElementInstance(set, elemClass);
for (Object val : params) { action.accept(elm);
if (val instanceof Enum<?>) val = ((Enum<?>) val).name();
ps.setObject(i++, val);
}
Log.debug(ps.toString());
try (ResultSet set = ps.executeQuery()) {
while (set.next()) {
E elm = getElementInstance(set, elemClass);
action.accept(elm);
}
} }
} }
} catch (ReflectiveOperationException | SQLException e) { } catch (SQLException e) {
throw new ORMException(e); throw new ORMException(e);
} }
} }
/**
* Delete the elements of the table represented by {@code elemClass} which meet the condition {@code where}.
* @param elemClass the SQLElement representing the table.
* @param where the condition to meet for an element to be deleted from the table. If null, the table is truncated using {@link #truncateTable(Class)}.
* @return The return value of {@link PreparedStatement#executeUpdate()}, for an SQL query {@code DELETE}.
* @throws ORMException
*/
public static <E extends SQLElement<E>> int delete(Class<E> elemClass, SQLWhere where) throws ORMException {
initTable(elemClass);
if (where == null) {
return truncateTable(elemClass);
}
try {
Pair<String, List<Object>> whereData = where.toSQL();
String sql = "DELETE FROM " + elemClass.newInstance().tableName()
+ " WHERE " + whereData.getValue0()
+ ";";
List<Object> params = new ArrayList<>(whereData.getValue1());
try (PreparedStatement ps = connection.getNativeConnection().prepareStatement(sql)) {
int i = 1;
for (Object val : params) {
if (val instanceof Enum<?>) val = ((Enum<?>) val).name();
ps.setObject(i++, val);
}
Log.debug(ps.toString());
return ps.executeUpdate();
}
} catch (ReflectiveOperationException | SQLException e) {
throw new ORMException(e);
}
}
public static <E extends SQLElement<E>> long count(Class<E> elemClass) throws ORMException { public static <E extends SQLElement<E>> long count(Class<E> elemClass) throws ORMException {
return count(elemClass, null); return count(elemClass, null);
@ -259,7 +221,7 @@ public final class ORM {
initTable(elemClass); initTable(elemClass);
try { try {
String sql = "SELECT COUNT(*) as count FROM " + elemClass.newInstance().tableName(); String sql = "SELECT COUNT(*) as count FROM " + getTableName(elemClass);
List<Object> params = new ArrayList<>(); List<Object> params = new ArrayList<>();
@ -270,22 +232,12 @@ public final class ORM {
} }
sql += ";"; sql += ";";
try (PreparedStatement ps = connection.getNativeConnection().prepareStatement(sql)) { try (ResultSet set = customQueryStatement(sql, params)) {
if (set.next()) {
int i = 1; return set.getLong(1);
for (Object val : params) {
if (val instanceof Enum<?>) val = ((Enum<?>) val).name();
ps.setObject(i++, val);
}
Log.debug(ps.toString());
try (ResultSet set = ps.executeQuery()) {
while (set.next()) {
return set.getLong(1);
}
} }
} }
} catch (ReflectiveOperationException | SQLException e) { } catch (SQLException e) {
throw new ORMException(e); throw new ORMException(e);
} }
@ -295,16 +247,8 @@ public final class ORM {
public static <E extends SQLElement<E>> int truncateTable(Class<E> elemClass) throws ORMException {
try (Statement stmt = connection.getNativeConnection().createStatement()) {
return stmt.executeUpdate("TRUNCATE `" + elemClass.newInstance().tableName() + "`");
} catch(SQLException | ReflectiveOperationException e) {
throw new ORMException(e);
}
}
public static ResultSet customQueryStatement(String sql, List<Object> params) throws ORMException {
public static ResultSet getCustomResult(String sql, List<Object> params) throws ORMException {
try { try {
PreparedStatement ps = connection.getNativeConnection().prepareStatement(sql); PreparedStatement ps = connection.getNativeConnection().prepareStatement(sql);
int i = 1; int i = 1;
@ -325,6 +269,71 @@ public final class ORM {
} }
public static <E extends SQLElement<E>> SQLUpdate<E> update(Class<E> elemClass, SQLWhere where) throws ORMException {
return new SQLUpdate<>(elemClass, where);
}
/* package */ static <E extends SQLElement<E>> int update(Class<E> elemClass, SQLWhere where, Map<SQLField<E, ?>, Object> values) throws ORMException {
return new SQLUpdate<>(elemClass, where, values).execute();
}
/**
* Delete the elements of the table represented by {@code elemClass} which meet the condition {@code where}.
* @param elemClass the SQLElement representing the table.
* @param where the condition to meet for an element to be deleted from the table. If null, the table is truncated using {@link #truncateTable(Class)}.
* @return The return value of {@link PreparedStatement#executeUpdate()}, for an SQL query {@code DELETE}.
* @throws ORMException
*/
public static <E extends SQLElement<E>> int delete(Class<E> elemClass, SQLWhere where) throws ORMException {
initTable(elemClass);
if (where == null) {
return truncateTable(elemClass);
}
Pair<String, List<Object>> whereData = where.toSQL();
String sql = "DELETE FROM " + getTableName(elemClass)
+ " WHERE " + whereData.getValue0()
+ ";";
List<Object> params = new ArrayList<>(whereData.getValue1());
return customUpdateStatement(sql, params);
}
public static int customUpdateStatement(String sql, List<Object> params) throws ORMException {
try (PreparedStatement ps = connection.getNativeConnection().prepareStatement(sql)) {
int i = 1;
for (Object val : params) {
if (val instanceof Enum<?>) val = ((Enum<?>) val).name();
ps.setObject(i++, val);
}
Log.debug(ps.toString());
return ps.executeUpdate();
} catch (SQLException e) {
throw new ORMException(e);
}
}
public static <E extends SQLElement<E>> int truncateTable(Class<E> elemClass) throws ORMException {
try (Statement stmt = connection.getNativeConnection().createStatement()) {
return stmt.executeUpdate("TRUNCATE `" + getTableName(elemClass) + "`");
} catch(SQLException e) {
throw new ORMException(e);
}
}
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private static <E extends SQLElement<E>> E getElementInstance(ResultSet set, Class<E> elemClass) throws ORMException { private static <E extends SQLElement<E>> E getElementInstance(ResultSet set, Class<E> elemClass) throws ORMException {
try { try {
@ -379,23 +388,4 @@ public final class ORM {
private ORM() {} // rend la classe non instanciable 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)
* }
*/
} }

View File

@ -32,7 +32,6 @@ public abstract class SQLElement<E extends SQLElement<E>> {
private boolean stored = false; private boolean stored = false;
private int id; private int id;
private final String tableName;
private final SQLFieldMap<E> fields; private final SQLFieldMap<E> fields;
private final Map<SQLField<E, ?>, Object> values; private final Map<SQLField<E, ?>, Object> values;
@ -40,7 +39,6 @@ public abstract class SQLElement<E extends SQLElement<E>> {
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
public SQLElement() { public SQLElement() {
tableName = tableName();
try { try {
ORM.initTable((Class<E>)getClass()); ORM.initTable((Class<E>)getClass());
@ -230,7 +228,6 @@ public abstract class SQLElement<E extends SQLElement<E>> {
throw new IllegalStateException(toString() + " has at least one undefined value and can't be saved."); throw new IllegalStateException(toString() + " has at least one undefined value and can't be saved.");
ORM.initTable((Class<E>)getClass()); ORM.initTable((Class<E>)getClass());
String toStringStatement = "";
try { try {
if (stored) { // mettre à jour les valeurs dans la base if (stored) { // mettre à jour les valeurs dans la base
@ -244,26 +241,7 @@ public abstract class SQLElement<E extends SQLElement<E>> {
if (modifiedValues.isEmpty()) return; if (modifiedValues.isEmpty()) return;
String sql = ""; ORM.update((Class<E>)getClass(), new SQLWhereComp(getFieldId(), SQLComparator.EQ, getId()), modifiedValues);
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);
try (PreparedStatement ps = db.getNativeConnection()
.prepareStatement("UPDATE " + tableName + " SET " + sql + " WHERE id=" + id)) {
int i = 1;
for (Object val : psValues)
ps.setObject(i++, val);
toStringStatement = ps.toString();
ps.executeUpdate();
}
} }
else { // ajouter dans la base else { // ajouter dans la base
@ -288,14 +266,13 @@ public abstract class SQLElement<E extends SQLElement<E>> {
} }
try (PreparedStatement ps = db.getNativeConnection().prepareStatement( try (PreparedStatement ps = db.getNativeConnection().prepareStatement(
"INSERT INTO " + tableName + " (" + concat_fields + ") VALUES (" + concat_vals + ")", "INSERT INTO " + tableName() + " (" + concat_fields + ") VALUES (" + concat_vals + ")",
Statement.RETURN_GENERATED_KEYS)) { Statement.RETURN_GENERATED_KEYS)) {
int i = 1; int i = 1;
for (Object val : psValues) for (Object val : psValues)
ps.setObject(i++, val); ps.setObject(i++, val);
toStringStatement = ps.toString();
ps.executeUpdate(); ps.executeUpdate();
try (ResultSet rs = ps.getGeneratedKeys()) { try (ResultSet rs = ps.getGeneratedKeys()) {
@ -308,9 +285,8 @@ public abstract class SQLElement<E extends SQLElement<E>> {
modifiedSinceLastSave.clear(); modifiedSinceLastSave.clear();
} catch (SQLException e) { } catch (SQLException e) {
throw new ORMException("Error while executing SQL statement " + toStringStatement, e); throw new ORMException("Error while saving data", e);
} }
Log.debug(toStringStatement);
} }
@ -344,7 +320,7 @@ public abstract class SQLElement<E extends SQLElement<E>> {
if (stored) { // supprimer la ligne de la base if (stored) { // supprimer la ligne de la base
try (PreparedStatement st = db.getNativeConnection() try (PreparedStatement st = db.getNativeConnection()
.prepareStatement("DELETE FROM " + tableName + " WHERE id=" + id)) { .prepareStatement("DELETE FROM " + tableName() + " WHERE id=" + id)) {
Log.debug(st.toString()); Log.debug(st.toString());
st.executeUpdate(); st.executeUpdate();
markAsNotStored(); markAsNotStored();

View File

@ -78,42 +78,22 @@ public class SQLElementList<E extends SQLElement<E>> extends ArrayList<E> {
* *
* @throws SQLException * @throws SQLException
*/ */
public synchronized void saveCommon() throws ORMException { public synchronized int saveCommon() throws ORMException {
List<E> storedEl = getStoredEl(); List<E> storedEl = getStoredEl();
if (storedEl.isEmpty()) return; if (storedEl.isEmpty()) return 0;
String sqlSet = ""; @SuppressWarnings("unchecked")
List<Object> psValues = new ArrayList<>(); Class<E> classEl = (Class<E>)storedEl.get(0).getClass();
for (Map.Entry<SQLField<E, ?>, Object> entry : modifiedValues.entrySet()) { int ret = ORM.update(classEl,
sqlSet += "`" + entry.getKey().getName() + "` = ? ,"; new SQLWhereIn(storedEl.get(0).getFieldId(),
SQLElement.addValueToSQLObjectList(psValues, entry.getKey(), entry.getValue()); storedEl.stream().map(SQLElement::getId).collect(Collectors.toList())
} ),
modifiedValues);
if (sqlSet.length() > 0) sqlSet = sqlSet.substring(0, sqlSet.length() - 1); applyNewValuesToElements(storedEl);
String sqlWhere = ""; return ret;
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") @SuppressWarnings("unchecked")

View File

@ -0,0 +1,70 @@
package fr.pandacube.util.orm;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.javatuples.Pair;
import fr.pandacube.util.Log;
public class SQLUpdate<E extends SQLElement<E>> {
private final Class<E> elemClass;
private final SQLWhere where;
private final Map<SQLField<E, ?>, Object> values;
/* package */ SQLUpdate(Class<E> el, SQLWhere w) {
elemClass = el;
where = w;
values = new HashMap<>();
}
/* package */ SQLUpdate(Class<E> el, SQLWhere w, Map<SQLField<E, ?>, Object> v) {
elemClass = el;
where = w;
values = v;
}
public <T> SQLUpdate<E> set(SQLField<E, T> field, T value) {
values.put(field, value);
return this;
}
public SQLUpdate<E> setUnsafe(SQLField<E, ?> field, Object value) {
values.put(field, value);
return this;
}
public int execute() throws ORMException {
if (values.isEmpty()) {
Log.warning(new ORMException("Trying to do an UPDATE with no values to SET. Query aborted."));
return 0;
}
String sql = "UPDATE " + ORM.getTableName(elemClass) + " SET ";
List<Object> params = new ArrayList<>();
boolean first = true;
for (Map.Entry<SQLField<E, ?>, Object> entry : values.entrySet()) {
if (!first)
sql += ", ";
sql += "`" + entry.getKey().getName() + "` = ? ";
SQLElement.addValueToSQLObjectList(params, entry.getKey(), entry.getValue());
first = false;
}
if (where != null) {
Pair<String, List<Object>> ret = where.toSQL();
sql += " WHERE " + ret.getValue0();
params.addAll(ret.getValue1());
}
sql += ";";
return ORM.customUpdateStatement(sql, params);
}
}