Skip to content

Instantly share code, notes, and snippets.

@oscarryz
Created June 3, 2011 21:43
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save oscarryz/1007227 to your computer and use it in GitHub Desktop.
Save oscarryz/1007227 to your computer and use it in GitHub Desktop.
SQL - Java binder
/*
* Copyright (c) 2011, Oscar Reyes.
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* - Redistributions of source code must retain the above copyright notice,
* this list of conditions and the folLowing disclaimer.
* - Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation and/or
* other materials provided with the distribution.
* - Neither the name of Oscar nor the names of its contributors may be used
* to endorse or promote products derived from this software without specific
* prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
* AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
* LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES ( INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED
* AND ON ANY THEORY OR LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* ( INCLUDING NEGLIGENCE OR OTHERWISE ) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package sqlbinder;
// java.util
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.Arrays;
import java.util.HashMap;
import java.util.ArrayList;
import java.util.Collections;
// reflect
import java.lang.reflect.Method;
// regex
import java.util.regex.Pattern;
import java.util.regex.Matcher;
// sql
import java.sql.PreparedStatement;
import static java.lang.System.out;
/**
* Holds a sql statment and the methods needed for a bean to
* fill the attributes in that statement;
*/
class QueryAndMethods {
// Class level part
/** "property" pattern like :propertyName in "select a from b where c = :propertyName" */
private final static Pattern propertiesPattern = Pattern.compile(":([^,\\s]+)");
/** Factory method to create an instance of this class using the given class object and a statement
* containing properties.
* @param theClass - A class from where to get "getters" methods.
* @param statementWithProperties - A SQL statement with bean property names like, name in
* "DELETE FROM table WHERE id = :userId".
* @return An object holding both, the SQL statement to be used in a prepared statments,
* and a list of methods to get values from an instance of @link theClass
*/
public static QueryAndMethods createFrom( Class<?> theClass , String statementWithProperties )
throws NoSuchMethodException {
// Get the statement to use in a preparedStatement
Matcher m = propertiesPattern.matcher( statementWithProperties );
String statement = m.replaceAll( "?");
m.reset();
// Get the getters to use
List<Method> methods = new ArrayList<Method>();
while( m.find() ) {
methods.add( theClass.getMethod(getGetterFor( m.group( 1 ))));
}
// Create the instance.
QueryAndMethods instance = new QueryAndMethods( statement, Collections.unmodifiableList(methods));
out.println( "Returning: "+ instance );
return instance;
}
/**
* Given a property name, this method retuns the "obvious" getter for it.
* For instance method would return "getName" if the passed property is "name"
* @param propertyName - The name of a Java property
* @return The obvious property name, as in the example.
*/
private static String getGetterFor( String propertyName ) {
return "get"+Character.toUpperCase( propertyName.charAt( 0 ) ) + propertyName.substring( 1 );
}
// Instance level part
/** an statment to be used in a prepared statement */
private final String statement;
/** the list of methods used to get values from a bean */
private final List<Method> methods;
// private constructor
private QueryAndMethods( String statement , List<Method> methods ) {
this.statement = statement;
this.methods = methods;
}
// Getters
public String getStatement() {
return this.statement;
}
public List<Method> getMethods() {
return this.methods;
}
@Override
public boolean equals( Object other ) {
if( ! ( other instanceof QueryAndMethods ) ) {
return false;
}
return this.statement.equals( ((QueryAndMethods) other).statement );
}
@Override
public int hashCode() {
return this.statement.hashCode();
}
@Override
public String toString() {
return String.format("Statement=%s, methods=%s",statement,methods);
}
}
/**
* This class executes a SQL statment binding the values to an object.
*/
class DbDao {
/**
* Keep a reference to existing "QueryAndMethods" objects to avoid using
* too much reflection.
*/
private final static Map<String, QueryAndMethods> methodsForQuery
= new HashMap<String,QueryAndMethods>();
public <T> void delete( T t, String deleteStatement ) {
update( t , deleteStatement );
}
public <T> void insert( T t, String insertStatement ) {
update( t , insertStatement );
}
/**
* Update the given object using the passed statement.
* The statment should have property names like:
* DELETE FROM table WHRE ID = :someId
* or
* INSERT INTO table_nam values( :name, :lastName )
*
* This method doen't try to validate if the query is correct.
* It just passes it to the underlaying RDBMS.
* @param T t the object to update.
* @param updateStatement String containing SQL statment with property name as in the description.
*/
public <T> void update( T t, String updateStatement ) {
try {
QueryAndMethods qam = getQueryAndMethodsFor( t, updateStatement );
//Connection connection = getConnection();
PreparedStatement pstmt = null;//connection.prepareStatement( qam.getStatement() );
setValuesInto( pstmt, t, qam );
//pstmt.executeUpdate();
//pstmt.close();
//connection.close();
} catch( Exception e ) {
throw new RuntimeException( e );
}
}
/**
* Sets the values from the object t into the prepareStatement.
* The values are the result from invoking each one of the methods
* in the qam object.
* @param pstmt - Where to set the values.
* @param t - where to get the values from
* @param qam - This method contains the "method" objects used to get ...
*/
private <T> void setValuesInto( PreparedStatement pstmt , T t, QueryAndMethods qam ) {
/*
((Persona)t).getName();
((Persona)t).getLastName();
((Persona)t).getBirthDate();
((Persona)t).getId();
Object o = null;
*/
int i = 1;
for( Method m : qam.getMethods() ) try {
Object o = m.invoke( t );
if( o == null ) {
//pstmt.setNull( o, i++ );
} else {
//pstmt.setObject( o, i++ );
}
} catch( Exception e ) {
throw new RuntimeException( e );
}
}
/**
* Gets a instance of QueryAndMethods to be used with the given statment, or creates one if needed.
* @param t - The object where to get the "getters" from.
* @param statment - The statment to be used to get the properties from.
* @return An instance of the QueryAndMethods class with the correct information.
*/
private <T> QueryAndMethods getQueryAndMethodsFor( T t, String statement )
throws NoSuchMethodException {
QueryAndMethods qam = methodsForQuery.get( statement );
if( qam == null ) {
methodsForQuery.put( statement, QueryAndMethods.createFrom( t.getClass() , statement ));
qam = methodsForQuery.get( statement );
}
return qam;
}
// Probarlo 10 millones de veces
public static void main( String ... args ) {
DbDao dao = new DbDao();
Persona p = new Persona(1,"Oscar","Reyes", new Date());
String insert = "INSERT INTO PERSONA values( :name )";
int times = 10000000;
for( int i = 0 ; i < times ; i++ ) {
dao.update( p, "INSERT INTO PERSONA values( :name )" );
dao.update( p, "UPDATE OTRA_TABLA set nombre_empleado = :name "+
", fecha_ingreso = :birthDate WHERE id_empleado = :id " );
dao.update( p, "DELETE FROM una_mas WHERE fecha_nac = :birthDate");
}
}
}
// Clase de prueba..
class Persona {
private final int id;
private final String name;
private final String lastName;
private final Date birthDate;
public Persona( int id, String name, String lastName, Date birthDate ) {
this.id = id;
this.name = name;
this.lastName = lastName;
this.birthDate = birthDate;
}
public int getId() {
return this.id;
}
public String getName() {
return this.name;
}
public String getLastName() {
return this.lastName;
}
public Date getBirthDate() {
// Esto es lo malo de los
// objetos mutables...
// siempre se tienen que hace copias de ellos
return this.birthDate;
//return new Date( this.birthDate.getTime() );
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment