/*
* codjo.net
*
* Common Apache License 2.0
*/
package net.codjo.utils;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.sql.Statement;
import java.util.Collection;
import java.util.Iterator;
import org.apache.log4j.Logger;
/**
* Assistant pour la creation de requete SQL.
*
* <p>
* Cette assistant les particularites suivantes :
*
* <ul>
* <li>
* Specifique a une table.
* </li>
* <li>
* Les requetes sont de type PreparedStatement.
* </li>
* <li>
* Les requetes ne font pas de commit explicite.
* </li>
* <li>
* Il peut �tre creer, et seulement etre utilise plus tard.
* </li>
* </ul>
*
* Pour des exemples d'utilisation, voir la classe de test (QueryHelperTest).
* </p>
*
* @author $Author: marcona $
* @version $Revision: 1.7 $
*/
public class QueryHelper {
// Log
private static final Logger APP = Logger.getLogger(QueryHelper.class);
private Connection connection;
private String dbTableName;
// Delete Statement
private PreparedStatement deleteOne;
private PreparedStatement insertOne;
private SQLFieldList insertValues;
private PreparedStatement forceOne;
private SQLFieldList forceValues;
// Save Statement
private PreparedStatement maxID;
// Select Statement
private PreparedStatement selectAll;
private PreparedStatement selectOne;
private SQLFieldList selector;
private PreparedStatement updateOne;
/**
* Construit QueryHelper ne pouvant faire qu'un doInsert ou un doSelectAll.
*
* @param dbTableName Le nom de la table.
* @param con La connection utilise
* @param insertValues Liste des champs SQL servant a l'insertion.
*
* @exception SQLException En cas d'erreur lors de l'acces a la base.
*/
public QueryHelper(String dbTableName, Connection con, SQLFieldList insertValues)
throws SQLException {
this(dbTableName, con, insertValues, null);
}
/**
* Constructor for the QueryHelper object
*
* @param dbTableName Le nom de la table.
* @param con La connection utilise
* @param insertValues Liste des champs SQL servant a l'insertion.
* @param selectors Liste des champs SQL servant a la selection (dans les clauses
* where)
*
* @exception SQLException En cas d'erreur lors de l'acces a la base.
* @throws IllegalArgumentException TODO
*/
public QueryHelper(String dbTableName, Connection con, SQLFieldList insertValues,
SQLFieldList selectors) throws SQLException {
if (dbTableName == null || con == null || insertValues == null) {
throw new IllegalArgumentException();
}
this.dbTableName = dbTableName;
this.selector = selectors;
this.insertValues = insertValues;
this.connection = con;
selectAll = connection.prepareStatement("select * from " + dbTableName);
if (selector != null) {
selectOne =
connection.prepareStatement("select * from " + dbTableName + " where "
+ buildClause(selector.fieldNamesSet(), " and "));
deleteOne =
connection.prepareStatement("delete from " + dbTableName + " where "
+ buildClause(selector.fieldNamesSet(), " and "));
String sqlstat =
"update " + dbTableName
+ " set RECORD_ACCESS=0 where RECORD_ACCESS <> 0 and "
+ buildClause(selector.fieldNamesSet(), " and ");
forceOne = connection.prepareStatement(sqlstat);
}
insertOne =
connection.prepareStatement(buildInsertQuery(dbTableName,
insertValues.fieldNamesSet()) + " select @@identity");
if (selector != null) {
updateOne =
buildUpdateStatement(dbTableName, insertValues.fieldNamesSet(),
selector.fieldNamesSet(), connection);
maxID =
connection.prepareStatement("select max(" + selector.fieldNames().next()
+ ") from " + dbTableName);
}
}
/**
* Construit la requete d'insertion des champs defini dans <code>columns</code>. La
* requete construite peut etre utilise pour un <code>PreparedStatement</code>
*
* @param dbTableName Le nom physique de la table
* @param columns Les colonnes a inserer
*
* @return La requete insert (eg. "insert into MA_TABLE (CA, CB) values (?, ?)")
*/
public static String buildInsertQuery(String dbTableName, Collection columns) {
String query =
"insert into " + dbTableName + " " + "(" + buildDBFieldNameList(columns)
+ ")" + " " + buildDBFieldValuesList(columns.size());
// Log
if (APP.isDebugEnabled()) {
APP.debug("\tRequ�te : " + query);
}
return query;
}
/**
* Construction du preparedStatement pour un insertion.
*
* @param dbTableName Le nom physique de la table
* @param columns Les colonnes a inserer
* @param con La connection supportant le PreparedStatement
*
* @return Le PreparedStatement
*
* @exception SQLException En cas d'erreur lors de l'acces a la base.
*/
public static PreparedStatement buildInsertStatement(String dbTableName,
Collection columns, Connection con) throws SQLException {
return con.prepareStatement(buildInsertQuery(dbTableName, columns));
}
/**
* Construit la requete de selection des champs defini dans <code>columns</code> . La
* ligne a mettre a selectionner est defini par la liste <code>whereList</code> . La
* requete construite peut etre utilise pour un <code>PreparedStatement</code>
*
* @param dbTableName Le nom physique de la table
* @param columns Les colonnes a selectionner
* @param whereList Les colonnes utilise dans la clause where
*
* @return La requete update (eg. "select CA, CB from MA_TABLE where CX=?")
*/
public static String buildSelectQuery(String dbTableName, Collection columns,
Collection whereList) {
String cols = "*";
if (columns != null) {
cols = buildDBFieldNameList(columns);
}
return "select " + cols + " from " + dbTableName + " where "
+ buildClause(whereList, " and ");
}
/**
* Construit la requete d'update des champs defini dans <code>columns</code> . La
* ligne a mettre a jour est defini par la liste <code>whereList</code> . La requete
* construite peut etre utilise pour un <code>PreparedStatement </code>
*
* @param dbTableName Le nom physique de la table
* @param columns Les colonnes a inserer
* @param whereList Les colonnes utilise dans la clause where
*
* @return La requete update (eg. "update MA_TABLE set CA=? where CX=?")
*/
public static String buildUpdateQuery(String dbTableName, Collection columns,
Collection whereList) {
return "update " + dbTableName + " set " + buildClause(columns, " , ")
+ " where " + buildClause(whereList, " and ");
}
/**
* Construit la requete d'update des champs defini dans <code>columns</code> . La
* ligne a mettre a jour est defini par la liste <code>whereList</code> et la clause
* where � ajouter � la fin . La requete construite peut etre utilise pour un
* <code>PreparedStatement </code>
*
* @param dbTableName Le nom physique de la table
* @param columns Les colonnes a inserer
* @param whereList Les colonnes utilise dans la clause where (CX=?)
* @param whereClause La clause where � ajouter (CZ='TOTO'and ...)
*
* @return La requete update (eg. "update MA_TABLE set CA=? where CX=? and
* CZ='TOTO'and ...")
*/
public static String buildUpdateQueryWithWhereClause(String dbTableName,
Collection columns, Collection whereList, String whereClause) {
String str = "";
if (whereClause != null) {
str = " and " + whereClause;
}
return buildUpdateQuery(dbTableName, columns, whereList) + str;
}
/**
* Construction du preparedStatement pour un update.
*
* @param dbTableName Le nom physique de la table
* @param columns Les colonnes a inserer
* @param whereList Les colonnes utilise dans la clause where
* @param con La connection supportant le PreparedStatement
*
* @return Le PreparedStatement
*
* @exception SQLException En cas d'erreur lors de l'acces a la base.
*/
public static PreparedStatement buildUpdateStatement(String dbTableName,
Collection columns, Collection whereList, Connection con)
throws SQLException {
return con.prepareStatement(buildUpdateQuery(dbTableName, columns, whereList));
}
/**
* Construction du preparedStatement pour un update avec une clause where. Celle-ci
* est utilis�e pour affiner la clause where de l'update (and "clauseWhere").
*
* @param dbTableName Le nom physique de la table
* @param columns Les colonnes a inserer
* @param whereList Les colonnes utilise dans la clause where
* @param whereClause La clause where � ajouter
* @param con La connection supportant le PreparedStatement
*
* @return Le PreparedStatement
*
* @exception SQLException En cas d'erreur lors de l'acces a la base.
*/
public static PreparedStatement buildUpdateStatementWithWhereClause(
String dbTableName,
Collection columns,
Collection whereList,
String whereClause,
Connection con) throws SQLException {
PreparedStatement stmt = null;
if (!((whereClause != null || !"".equals(whereClause)))) {
String str = buildUpdateQuery(dbTableName, columns, whereList);
// Log
if (APP.isDebugEnabled()) {
APP.debug("Requete pour l'Update avec clause: " + str);
}
stmt = con.prepareStatement(str);
}
else {
String str =
buildUpdateQueryWithWhereClause(dbTableName, columns, whereList,
whereClause);
// Log
if (APP.isDebugEnabled()) {
APP.debug("Requete pour l'Update sans clause : " + str);
}
stmt = con.prepareStatement(str);
}
return stmt;
}
/**
* Positionne la valeur date d'un champs de la ligne.
*
* @param dbFieldName Le nom du champs SQL.
* @param d The new InsertValue value
*
* @see SQLFieldList#setFieldValue(java.lang.String, java.util.Date )
*/
public void setInsertValue(String dbFieldName, java.util.Date d) {
insertValues.setFieldValue(dbFieldName, d);
}
/**
* Positionne la valeur d'un champs de la ligne. Cette methode positionne la valeur
* du champs <code>dbFieldName</code> utilise pour modifier la base.
*
* @param dbFieldName Le nom du champs SQL.
* @param v La valeur.
*/
public void setInsertValue(String dbFieldName, Object v) {
insertValues.setFieldValue(dbFieldName, v);
}
/**
* Sets the InsertValue attribute of the QueryHelper object
*
* @param dbFieldName The new InsertValue value
* @param v The new InsertValue value
*/
public void setInsertValue(String dbFieldName, int v) {
insertValues.setFieldValue(dbFieldName, new Integer(v));
}
/**
* Sets the InsertValue attribute of the QueryHelper object
*
* @param dbFieldName The new InsertValue value
* @param v The new InsertValue value
*/
public void setInsertValue(String dbFieldName, boolean v) {
insertValues.setFieldValue(dbFieldName, new Boolean(v));
}
/**
* Positionne la valeur d'un champs selecteur. Cette methode positionne la valeur du
* champs <code>dbFieldName</code> utilise dans la clause where.
*
* @param dbFieldName Le nom du champs selecteur
* @param v La nouvelle valeur.
*/
public void setSelectorValue(String dbFieldName, Object v) {
selector.setFieldValue(dbFieldName, v);
}
/**
* Sets the SelectorValue attribute of the QueryHelper object
*
* @param dbFieldName The new SelectorValue value
* @param v The new SelectorValue value
*/
public void setSelectorValue(String dbFieldName, int v) {
selector.setFieldValue(dbFieldName, new Integer(v));
}
/**
* Retourne la connection de ce <code>queryHelper</code> .
*
* @return La Connection
*/
public Connection getConnection() {
return connection;
}
/**
* Retourne la valeur du champ.
*
* @param dbFieldName Le nom du champs SQL.
*
* @return La valeur
*/
public Object getInsertValue(String dbFieldName) {
return insertValues.getFieldValue(dbFieldName);
}
/**
* Retourne un identifiant unique non utilise. La colonne utilisee pour identifiant
* est le premier champs SQL utilise comme selector. La methode retourne le max+1.
*
* @return L'identifiant unique.
*
* @exception SQLException En cas d'erreur lors de l'acces a la base.
*/
public int getUniqueID() throws SQLException {
ResultSet rs = maxID.executeQuery();
try {
rs.next();
int id = rs.getInt(1) + 1;
return id;
}
finally {
rs.close();
}
}
/**
* Effacement d'un enregistrement.
*
* @exception SQLException En cas d'erreur lors de l'acces a la base.
*/
public void doDelete() throws SQLException {
fillStatement(1, deleteOne, selector);
deleteOne.executeUpdate();
}
/**
* For�age "record_acess" d'un enregistrement.
*
* @exception SQLException En cas d'erreur lors de l'acces a la base.
*/
public void doForce() throws SQLException {
fillStatement(1, forceOne, selector);
forceOne.executeUpdate();
}
/**
* Insertion d'un enregistrement. L'enregistrement est definie par la liste de champs
* d'insertion (modifiable par setInsertValue())). La methode retourne le resultat
* de la requete <code>select identity</code> .
*
* @return l'id de la ligne insere(si identity)
*
* @exception SQLException En cas d'erreur lors de l'acces a la base.
*
* @see #setInsertValue
*/
public java.math.BigDecimal doInsert() throws SQLException {
fillStatement(1, insertOne, insertValues);
ResultSet rs = insertOne.executeQuery();
try {
rs.next();
return rs.getBigDecimal(1);
}
finally {
rs.close();
}
}
/**
* Applique un select avec clause where sur la table.
*
* @return Le ResultSet de la requete.
*
* @exception SQLException En cas d'erreur lors de l'acces a la base.
*/
public ResultSet doSelect() throws SQLException {
fillStatement(1, selectOne, selector);
return selectOne.executeQuery();
}
/**
* Applique un select sur la table.
*
* @return Le ResultSet de la requete.
*
* @exception SQLException En cas d'erreur lors de l'acces a la base.
*/
public ResultSet doSelectAll() throws SQLException {
return selectAll.executeQuery();
}
/**
* Applique un select sur la table avec un tri. Attention : Cette requete n'est pas
* optimisee (Statement standard).
*
* @param orderClause La clause de tri (ex : "TABLE_NAME").
*
* @return Le ResultSet de la requete trie.
*
* @exception SQLException En cas d'erreur lors de l'acces a la base.
* @throws IllegalArgumentException TODO
*/
public ResultSet doSelectAllOrderedBy(String orderClause)
throws SQLException {
if (orderClause == null) {
throw new IllegalArgumentException("Clause de tri non renseignee");
}
Statement stmt = connection.createStatement();
return stmt.executeQuery("select * from " + dbTableName + " order by "
+ orderClause);
}
/**
* Mise-a-jours d'un enregistrement.
*
* @exception SQLException En cas d'erreur lors de l'acces a la base.
*/
public void doUpdate() throws SQLException {
fillStatement(1, updateOne, insertValues);
fillStatement(insertValues.size() + 1, updateOne, selector);
updateOne.executeUpdate();
}
/**
* Construction d'une clause liste.
*
* @param list Liste des champs pour la clause
* @param sep Le separateur de champs (ex " and ")
*
* @return la clause : "PERIOD=? and P=?"
*/
private static String buildClause(Collection list, String sep) {
StringBuffer buffer = new StringBuffer();
for (Iterator iter = list.iterator(); iter.hasNext();) {
buffer.append(iter.next()).append("=?");
if (iter.hasNext()) {
buffer.append(sep);
}
}
return buffer.toString();
}
/**
* Construit la liste des noms de colonne.
*
* @param columns Liste des colonnes a inserer
*
* @return La liste. La liste est de la forme : "COL1, COL2..."
*/
private static String buildDBFieldNameList(Collection columns) {
StringBuffer nameList = new StringBuffer();
for (Iterator iter = columns.iterator(); iter.hasNext();) {
nameList.append((String)iter.next());
if (iter.hasNext()) {
nameList.append(", ");
}
}
return nameList.toString();
}
/**
* Construit le squelette liste des valeurs � inserer dans la BD.
*
* @param nbOfValues Nombre de valeurs
*
* @return le squelette, de la forme : "values (?, ?...)"
*/
private static String buildDBFieldValuesList(int nbOfValues) {
StringBuffer buffer = new StringBuffer("values (");
for (int i = 0; i < nbOfValues; i++) {
buffer.append("?");
if (i < nbOfValues - 1) {
buffer.append(", ");
}
}
buffer.append(")");
return buffer.toString();
}
/**
* Remplissage du prepared Statement avec la liste de champs.
*
* @param i L'index a partir duquel on remplit le statement.
* @param pstmt Le prepared statement
* @param list La liste des champs.
*
* @exception SQLException En cas d'erreur lors de l'acces a la base.
*/
private static void fillStatement(int i, PreparedStatement pstmt, SQLFieldList list)
throws SQLException {
for (Iterator iter = list.sqlFields(); iter.hasNext(); i++) {
SQLField field = (SQLField)iter.next();
pstmt.setObject(i, field.getValue(), field.getSQLType());
}
list.clearValues();
}
}