Ignore:
Timestamp:
Feb 9, 2011, 10:53:45 PM (14 years ago)
Author:
fmguler
Message:

Refs #3 - Ven.get() is converted to the new format. QueryGenerator and QueryMapper are mostly OK, generateRecursively and mapRecursively are converted and checked. Handles joins (includes/associations) many to one and one to many. For one to many, the reverse join field can be determined in a couple of ways. First way is (prefereed) having VenList as the list implementation which specifies the element class and the join field. Second way is using Java 1.5 generic type to detect element class (not yet implemented) and guessing join field by convention (if multiple joins exist, this won't work). The last way is to have some kind of annotation or configuration, which is of course the least preferred way. VenList has a static method to determine the element class in the object list, which currently calls getElementClass if the list is an instance of VenList. In the future other options can be implemented.

Getting object using joins (includes/associations) are tested using dummy assocations between SomeDomainObject and AnotherDomainObject. The Sample class builds the database, tests the operations and rolls back to the initial state. Database refactoring operations are moved to the LiquibaseUtil for clarity.

In the future, the generated queries will be shortened using hashed aliases, and the criteria subsystem will be implemented.

Location:
trunk/fmgVen/src/com/fmguler/ven
Files:
1 added
3 edited

Legend:

Unmodified
Added
Removed
  • trunk/fmgVen/src/com/fmguler/ven/QueryGenerator.java

    r27 r28  
    1919 
    2020import com.fmguler.ven.util.Convert; 
     21import com.fmguler.ven.util.VenList; 
    2122import java.beans.PropertyDescriptor; 
    2223import java.util.Date; 
    2324import java.util.HashSet; 
     25import java.util.Iterator; 
     26import java.util.List; 
    2427import java.util.Set; 
    2528import org.springframework.beans.BeanWrapper; 
     
    3336    private Set domainPackages; 
    3437    private Set dbClasses; 
     38    private boolean debug = true; 
    3539 
    3640    public QueryGenerator() { 
     
    4549    } 
    4650 
    47     public String generateSelectQuery() { 
    48         return null; 
     51    /** 
     52     * Generates select query for the specified object class and specified joins. 
     53     * @param joins set of joins that the query will contain 
     54     * @param objectClass the object class to select from 
     55     * @return the select SQL query 
     56     */ 
     57    public String generateSelectQuery(Class objectClass, Set joins) { 
     58        long t1 = System.currentTimeMillis(); 
     59        String objectName = Convert.toSimpleName(objectClass.getName()); 
     60        String tableName = Convert.toDB(objectName); 
     61        StringBuffer selectClause = new StringBuffer("select "); 
     62        StringBuffer fromClause = new StringBuffer("from " + tableName); 
     63        generateRecursively(0, tableName, objectName, objectClass, joins, selectClause, fromClause); 
     64        selectClause.append(" 1=1"); 
     65        if (debug) System.out.println("Ven - query generation time = " + (System.currentTimeMillis() - t1)); 
     66        return selectClause.toString() + " \n" + fromClause.toString(); 
    4967    } 
    5068 
     
    129147     * @return the delete SQL query 
    130148     */ 
    131     public String generateDeleteQuery(Class objectClass){ 
     149    public String generateDeleteQuery(Class objectClass) { 
    132150        StringBuffer query = new StringBuffer(); 
    133151        query.append("delete from ").append(Convert.toDB(Convert.toSimpleName(objectClass.getName()))).append(" where id = :id;"); 
    134152        return query.toString(); 
    135153    } 
    136          
     154 
    137155    /** 
    138156     * Generates sequence query for the specified object 
     
    147165 
    148166    //-------------------------------------------------------------------------- 
     167    //PRIVATE METHODS 
     168    //recursively generate select query 
     169    private void generateRecursively(int level, String tableName, String objectPath, Class objectClass, Set joins, StringBuffer selectClause, StringBuffer fromClause) { 
     170        BeanWrapper wr = new BeanWrapperImpl(objectClass); 
     171        PropertyDescriptor[] pdArr = wr.getPropertyDescriptors(); 
     172 
     173        for (int i = 0; i < pdArr.length; i++) { 
     174            Class fieldClass = pdArr[i].getPropertyType(); //field class 
     175            String fieldName = pdArr[i].getName(); //field name 
     176            Object fieldValue = wr.getPropertyValue(fieldName); 
     177            String columnName = Convert.toDB(pdArr[i].getName()); //column name 
     178 
     179            //direct database class (Integer, String, Date, etc) 
     180            if (dbClasses.contains(fieldClass)) { 
     181                selectClause.append(tableName).append(".").append(columnName).append(" as ").append(tableName).append("_").append(columnName); //column 
     182                selectClause.append(", "); 
     183            } 
     184 
     185            //many to one association (object property) 
     186            if (fieldClass.getPackage() != null && domainPackages.contains(fieldClass.getPackage().getName()) && joinsContain(joins, objectPath + "." + fieldName)) { 
     187                String joinTableAlias = tableName + "_" + columnName; //alias for table to join since there can be multiple joins to the same table 
     188                String joinTable = Convert.toDB(Convert.toSimpleName(fieldClass.getName())); //table to join 
     189                fromClause.append(" left join ").append(joinTable).append(" ").append(joinTableAlias); 
     190                fromClause.append(" on ").append(joinTableAlias).append(".id = ").append(tableName).append(".").append(columnName).append("_id"); 
     191                generateRecursively(++level, joinTableAlias, objectPath + "." + fieldName, fieldClass, joins, selectClause, fromClause); 
     192            } 
     193 
     194            //one to many association (list property) 
     195            if (fieldValue instanceof List && joinsContain(joins, objectPath + "." + fieldName)) { 
     196                Class elementClass = VenList.findElementClass((List)fieldValue); 
     197                String joinTableAlias = tableName + "_" + columnName; //alias for table to join since there can be multiple joins to the same table 
     198                String joinTable = Convert.toDB(Convert.toSimpleName(elementClass.getName())); //table to join 
     199                String joinField = Convert.toDB(findJoinField((List)fieldValue)); //field to join 
     200                fromClause.append(" left join ").append(joinTable).append(" ").append(joinTableAlias); 
     201                fromClause.append(" on ").append(joinTableAlias).append(".").append(joinField).append("_id = ").append(tableName).append(".id"); 
     202                generateRecursively(++level, joinTableAlias, objectPath + "." + fieldName, elementClass, joins, selectClause, fromClause); 
     203            } 
     204        } 
     205    } 
     206 
     207    //check if the joins contain the specified join 
     208    private boolean joinsContain(Set joins, String join) { 
     209        Iterator it = joins.iterator(); 
     210        while (it.hasNext()) { 
     211            String str = (String)it.next(); 
     212            if (str.startsWith(join)) { 
     213                if (str.length() == join.length()) return true; 
     214                else if (str.charAt(join.length()) == '.') return true; 
     215            } 
     216        } 
     217        return false; 
     218    } 
     219 
     220    //return the join field of the elements in the list 
     221    private String findJoinField(List list) { 
     222        if (list instanceof VenList) { 
     223            return ((VenList)list).getJoinField(); 
     224        } else { 
     225            //find according to 1.5 generic or some convention (e.g. parent_obj_id) 
     226            return null; 
     227        } 
     228    } 
     229 
     230    //-------------------------------------------------------------------------- 
    149231    //SETTERS 
     232    /** 
     233     * Add the domain packages that will be considered persistent 
     234     * @param domainPackage the domain package 
     235     */ 
    150236    public void addDomainPackage(String domainPackage) { 
    151237        domainPackages.add(domainPackage); 
  • trunk/fmgVen/src/com/fmguler/ven/QueryMapper.java

    r23 r28  
    1818package com.fmguler.ven; 
    1919 
     20import com.fmguler.ven.util.Convert; 
     21import com.fmguler.ven.util.VenList; 
     22import java.beans.PropertyDescriptor; 
     23import java.sql.ResultSet; 
     24import java.sql.SQLException; 
     25import java.util.ArrayList; 
     26import java.util.Date; 
     27import java.util.HashSet; 
     28import java.util.Iterator; 
     29import java.util.LinkedList; 
    2030import java.util.List; 
     31import java.util.Map; 
     32import java.util.Set; 
    2133import javax.sql.DataSource; 
     34import org.springframework.beans.BeanWrapper; 
     35import org.springframework.beans.BeanWrapperImpl; 
     36import org.springframework.jdbc.core.RowCallbackHandler; 
    2237import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate; 
    2338 
     
    2843public class QueryMapper { 
    2944    private NamedParameterJdbcTemplate template; 
     45    private Set domainPackages; 
     46    private Set dbClasses; 
     47    private boolean debug = true; 
    3048 
    31     public List list() { 
    32         return null; 
     49    public QueryMapper() { 
     50        domainPackages = new HashSet(); 
     51        dbClasses = new HashSet(); 
     52        //the predefined database classes; 
     53        this.dbClasses.add(Integer.class); 
     54        this.dbClasses.add(String.class); 
     55        this.dbClasses.add(Date.class); 
     56        this.dbClasses.add(Double.class); 
     57        this.dbClasses.add(Boolean.class); 
    3358    } 
    3459 
    35     //SETTERS------------------------------------------------------------------- 
     60    /** 
     61     * Executes the specified query setting the specified parameters, 
     62     * and maps the results to the instances of the specified objectClass. 
     63     * @param query select SQL query in the conventional format 
     64     * @param parameters named query parameter values 
     65     * @param objectClass the type of the object to be mapped 
     66     * @return the list of mapped objects 
     67     */ 
     68    public List list(String query, Map parameters, final Class objectClass) { 
     69        long t1 = System.currentTimeMillis(); 
     70        final List results = new LinkedList(); 
     71        final String tableName = Convert.toDB(Convert.toSimpleName(objectClass.getName())); 
     72        final Set columns = new HashSet(); 
     73 
     74        template.query(query, parameters, new RowCallbackHandler() { 
     75            public void processRow(ResultSet rs) throws SQLException { 
     76                enumerateColumns(columns, rs); 
     77                mapRecursively(rs, columns, tableName, objectClass, results); 
     78            } 
     79        }); 
     80 
     81        System.out.println("Ven - list time = " + (System.currentTimeMillis() - t1)); 
     82        return results; 
     83    } 
     84 
     85    //-------------------------------------------------------------------------- 
     86    //PRIVATE METHODS 
     87    //recursively map the resultSet to the object and add to the list 
     88    protected void mapRecursively(ResultSet rs, Set columns, String tableName, Class objectClass, List parentList) { 
     89        try { 
     90            if (!columns.contains(tableName + "_id")) return; //this object does not exist in the columns 
     91            Object id = rs.getObject(tableName + "_id"); 
     92            if (id == null) return; //this object exists in the columns but null, probably because of left join 
     93 
     94            //create bean wrapper for the object class 
     95            BeanWrapper wr = new BeanWrapperImpl(objectClass); //already caches class introspection (CachedIntrospectionResults.forClass()) 
     96            wr.setPropertyValue("id", id); //set the id property 
     97            Object object = wr.getWrappedInstance(); 
     98            boolean map = true; 
     99 
     100            //check if this object exists in the parent list (since SQL joins are cartesian products, do not create new object if this row is just the same as previous) 
     101            for (Iterator it = parentList.iterator(); it.hasNext();) { 
     102                Object objectInList = (Object)it.next(); 
     103                if (objectIdEquals(objectInList, id)) { 
     104                    wr.setWrappedInstance(objectInList); //already exists in the list, use that instance 
     105                    map = false; // and do not map again 
     106                    break; 
     107                } 
     108            } 
     109            if (map) parentList.add(object); //could not find in the parent list, add the new object 
     110 
     111            PropertyDescriptor[] pdArr = wr.getPropertyDescriptors(); 
     112            for (int i = 0; i < pdArr.length; i++) { 
     113                PropertyDescriptor pd = pdArr[i]; 
     114                Class fieldClass = pd.getPropertyType(); //field class 
     115                String fieldName = Convert.toDB(pd.getName()); //field name 
     116                Object fieldValue = wr.getPropertyValue(pd.getName()); 
     117                String columnName = tableName + "_" + fieldName; 
     118 
     119                //database class (primitive property) 
     120                if (map && dbClasses.contains(fieldClass)) { 
     121                    if (columns.contains(columnName)) { 
     122                        if (debug) System.out.println(">>field is found: " + columnName); 
     123                        wr.setPropertyValue(pd.getName(), rs.getObject(columnName)); 
     124                    } else { 
     125                        if (debug) System.out.println("--field not found: " + columnName); 
     126                    } 
     127                } 
     128 
     129                //many to one association (object property) 
     130                if (map && fieldClass.getPackage() != null && domainPackages.contains(fieldClass.getPackage().getName())) { 
     131                    if (columns.contains(columnName + "_id")) { 
     132                        if (debug) System.out.println(">>object is found " + columnName); 
     133                        List list = new ArrayList(1); //we know there will be single result 
     134                        mapRecursively(rs, columns, columnName, fieldClass, list); 
     135                        if (list.size() > 0) wr.setPropertyValue(pd.getName(), list.get(0)); 
     136                    } else { 
     137                        if (debug) System.out.println("--object not found: " + columnName); 
     138                    } 
     139                } 
     140 
     141                //one to many association (list property) 
     142                if (fieldValue instanceof List) { //Note: here recurring row's list property is mapped and add to parent's list 
     143                    if (columns.contains(columnName + "_id")) { 
     144                        Class elementClass = VenList.findElementClass((List)fieldValue); 
     145                        if (debug) System.out.println(">>list is found " + columnName); 
     146                        mapRecursively(rs, columns, columnName, elementClass, (List)fieldValue); 
     147                    } else { 
     148                        if (debug) System.out.println("--list not found: " + columnName); 
     149                    } 
     150                } 
     151            } 
     152        } catch (Exception ex) { 
     153            if (debug) { 
     154                System.out.println("Ven - error while mapping row; "); 
     155                ex.printStackTrace(); 
     156            } 
     157        } 
     158    } 
     159 
     160    //enumerate columns from the resultset 
     161    private void enumerateColumns(Set columns, ResultSet rs) throws SQLException { 
     162        if (!columns.isEmpty()) return; 
     163        for (int i = 1; i < rs.getMetaData().getColumnCount() + 1; i++) { 
     164            columns.add(rs.getMetaData().getColumnName(i)); 
     165        } 
     166    } 
     167 
     168    //check if the specified objects are the same entity (according to id fields) 
     169    private boolean objectIdEquals(Object object, Object id) { 
     170        //return obj1.equals(obj2); //objects need to implement equals() 
     171        //TODO: more efficient (invoke getId method) 
     172        BeanWrapper wr = new BeanWrapperImpl(object); 
     173        return id.equals(wr.getPropertyValue("id")); 
     174    } 
     175 
     176    //-------------------------------------------------------------------------- 
     177    //SETTERS 
     178    /** 
     179     * @param dataSource used for accessing database 
     180     */ 
    36181    public void setDataSource(DataSource dataSource) { 
    37182        if (dataSource == null) throw new RuntimeException("fmgVen - DataSource cannot be null"); 
     
    39184    } 
    40185 
    41     public void addDomainPackage(String domainPackage){ 
    42  
     186    /** 
     187     * Add the domain packages that will be considered persistent 
     188     * @param domainPackage the domain package 
     189     */ 
     190    public void addDomainPackage(String domainPackage) { 
     191        domainPackages.add(domainPackage); 
    43192    } 
    44193} 
  • trunk/fmgVen/src/com/fmguler/ven/Ven.java

    r27 r28  
    1818package com.fmguler.ven; 
    1919 
     20import com.fmguler.ven.util.Convert; 
    2021import java.util.HashMap; 
    2122import java.util.List; 
    2223import java.util.Map; 
     24import java.util.Set; 
    2325import javax.sql.DataSource; 
    2426import org.springframework.beans.BeanWrapper; 
     
    3638    private QueryGenerator generator; 
    3739    private QueryMapper mapper; 
     40    private boolean debug = true; 
    3841 
    3942    public Ven() { 
     
    5053    } 
    5154 
    52     public Object get(int id, Class objectClass) { 
    53         return null; 
     55    public Object get(int id, Class objectClass, Set joins) { 
     56        String query = generator.generateSelectQuery(objectClass, joins); 
     57        query += " where 1=1 and " + Convert.toDB(Convert.toSimpleName(objectClass.getName())) + ".id = :___id "; 
     58 
     59        Map paramMap = new HashMap(); 
     60        paramMap.put("___id", new Integer(id)); 
     61        if (debug) System.out.println("Ven - SQL: " + query); 
     62 
     63        List result = mapper.list(query, paramMap, objectClass); 
     64        if (result.isEmpty()) return null; 
     65        if (result.size() > 1) System.out.println("Ven - WARNING >> get(id) returns more than one row"); 
     66        return result.get(0); 
    5467    } 
    5568 
     
    114127        if (dataSource == null) throw new RuntimeException("fmgVen - DataSource cannot be null"); 
    115128        this.template = new NamedParameterJdbcTemplate(dataSource); 
     129        mapper.setDataSource(dataSource); 
    116130    } 
    117131 
Note: See TracChangeset for help on using the changeset viewer.