Created
June 3, 2011 21:43
-
-
Save oscarryz/1007227 to your computer and use it in GitHub Desktop.
SQL - Java binder
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* 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