source: trunk/fmgVen/src/com/fmguler/ven/QueryMapper.java @ 28

Last change on this file since 28 was 28, checked in by fmguler, 14 years ago

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.

File size: 8.7 KB
Line 
1/*
2 *  fmgVen - A Convention over Configuration Java ORM Tool
3 *  Copyright 2010 Fatih Mehmet Güler
4 *
5 *  Licensed under the Apache License, Version 2.0 (the "License");
6 *  you may not use this file except in compliance with the License.
7 *  You may obtain a copy of the License at
8 *
9 *       http://www.apache.org/licenses/LICENSE-2.0
10 *
11 *  Unless required by applicable law or agreed to in writing, software
12 *  distributed under the License is distributed on an "AS IS" BASIS,
13 *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14 *  See the License for the specific language governing permissions and
15 *  limitations under the License.
16 *  under the License.
17 */
18package com.fmguler.ven;
19
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;
30import java.util.List;
31import java.util.Map;
32import java.util.Set;
33import javax.sql.DataSource;
34import org.springframework.beans.BeanWrapper;
35import org.springframework.beans.BeanWrapperImpl;
36import org.springframework.jdbc.core.RowCallbackHandler;
37import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
38
39/**
40 * Maps the result of the query generated in the form of 'Convention over Configuration' to the specified object.
41 * @author Fatih Mehmet Güler
42 */
43public class QueryMapper {
44    private NamedParameterJdbcTemplate template;
45    private Set domainPackages;
46    private Set dbClasses;
47    private boolean debug = true;
48
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);
58    }
59
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     */
181    public void setDataSource(DataSource dataSource) {
182        if (dataSource == null) throw new RuntimeException("fmgVen - DataSource cannot be null");
183        this.template = new NamedParameterJdbcTemplate(dataSource);
184    }
185
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);
192    }
193}
Note: See TracBrowser for help on using the repository browser.