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

Last change on this file since 38 was 37, checked in by fmguler, 13 years ago

Refs #7 - Implemented orderAsc and orderDesc methods of Criteria. Have been testing these for a while, no problem so far. Added BigDecimal to db classes (Numeric db type). If the column name is "order" it is escaped while insert/update. (This should be done for all db keywords). Fixed missing mapping of one to many assc. (lists) of many to one (object) assc (obj.obj.list).

File size: 9.4 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.math.BigDecimal;
24import java.sql.ResultSet;
25import java.sql.SQLException;
26import java.util.ArrayList;
27import java.util.Date;
28import java.util.HashSet;
29import java.util.Iterator;
30import java.util.LinkedList;
31import java.util.List;
32import java.util.Map;
33import java.util.Set;
34import javax.sql.DataSource;
35import org.springframework.beans.BeanWrapper;
36import org.springframework.beans.BeanWrapperImpl;
37import org.springframework.jdbc.core.RowCallbackHandler;
38import org.springframework.jdbc.core.namedparam.NamedParameterJdbcTemplate;
39
40/**
41 * Maps the result of the query generated in the form of 'Convention over Configuration' to the specified object.
42 * @author Fatih Mehmet Güler
43 */
44public class QueryMapper {
45    private NamedParameterJdbcTemplate template;
46    private Set domainPackages;
47    private Set dbClasses;
48    private boolean debug = false;
49
50    public QueryMapper() {
51        domainPackages = new HashSet();
52        dbClasses = new HashSet();
53        //the predefined database classes;
54        this.dbClasses.add(Integer.class);
55        this.dbClasses.add(String.class);
56        this.dbClasses.add(Date.class);
57        this.dbClasses.add(Double.class);
58        this.dbClasses.add(Boolean.class);
59        this.dbClasses.add(BigDecimal.class);
60    }
61
62    /**
63     * Executes the specified query setting the specified parameters,
64     * and maps the results to the instances of the specified objectClass.
65     * @param query select SQL query in the conventional format
66     * @param parameters named query parameter values
67     * @param objectClass the type of the object to be mapped
68     * @return the list of mapped objects
69     */
70    public List list(String query, Map parameters, final Class objectClass) {
71        long t1 = System.currentTimeMillis();
72        final List results = new LinkedList();
73        final String tableName = Convert.toDB(Convert.toSimpleName(objectClass.getName()));
74        final Set columns = new HashSet();
75
76        template.query(query, parameters, new RowCallbackHandler() {
77            public void processRow(ResultSet rs) throws SQLException {
78                enumerateColumns(columns, rs);
79                mapRecursively(rs, columns, tableName, objectClass, results);
80            }
81        });
82
83        System.out.println("Ven - list time = " + (System.currentTimeMillis() - t1));
84        return results;
85    }
86
87    //--------------------------------------------------------------------------
88    //PRIVATE METHODS
89    //recursively map the resultSet to the object and add to the list
90    protected void mapRecursively(ResultSet rs, Set columns, String tableName, Class objectClass, List parentList) {
91        try {
92            if (!columns.contains(tableName + "_id")) return; //this object does not exist in the columns
93            Object id = rs.getObject(tableName + "_id");
94            if (id == null) return; //this object exists in the columns but null, probably because of left join
95
96            //create bean wrapper for the object class
97            BeanWrapperImpl wr = new BeanWrapperImpl(objectClass); //already caches class introspection (CachedIntrospectionResults.forClass())
98            wr.setPropertyValue("id", id); //set the id property
99            Object object = wr.getWrappedInstance();
100            boolean map = true;
101
102            //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)
103            for (Iterator it = parentList.iterator(); it.hasNext();) {
104                Object objectInList = (Object)it.next();
105                if (objectIdEquals(objectInList, id)) {
106                    wr.setWrappedInstance(objectInList); //already exists in the list, use that instance
107                    map = false; // and do not map again
108                    break;
109                }
110            }
111            if (map) parentList.add(object); //could not find in the parent list, add the new object
112
113            PropertyDescriptor[] pdArr = wr.getPropertyDescriptors();
114            for (int i = 0; i < pdArr.length; i++) {
115                PropertyDescriptor pd = pdArr[i];
116                Class fieldClass = pd.getPropertyType(); //field class
117                String fieldName = Convert.toDB(pd.getName()); //field name
118                Object fieldValue = wr.getPropertyValue(pd.getName());
119                String columnName = tableName + "_" + fieldName;
120
121                //database class (primitive property)
122                if (map && dbClasses.contains(fieldClass)) {
123                    if (columns.contains(columnName)) {
124                        if (debug) System.out.println(">>field is found: " + columnName);
125                        wr.setPropertyValue(pd.getName(), rs.getObject(columnName));
126                    } else {
127                        if (debug) System.out.println("--field not found: " + columnName);
128                    }
129                    continue; //if this is a primitive property, it cannot be an object or list
130                }
131
132                //many to one association (object property)
133                if (fieldClass.getPackage() != null && domainPackages.contains(fieldClass.getPackage().getName())) {
134                    if (columns.contains(columnName + "_id")) {
135                        if (debug) System.out.println(">>object is found " + columnName);
136                        List list = new ArrayList(1); //we know there will be single result
137                        if (!map) list.add(fieldValue); //otherwise we cannot catch one to many assc. (lists) of many to one (object) assc.
138                        mapRecursively(rs, columns, columnName, fieldClass, list);
139                        if (list.size() > 0) wr.setPropertyValue(pd.getName(), list.get(0));
140                    } else {
141                        if (debug) System.out.println("--object not found: " + columnName);
142                    }
143                }
144
145                //one to many association (list property)
146                if (fieldValue instanceof List) { //Note: here recurring row's list property is mapped and add to parent's list
147                    if (columns.contains(columnName + "_id")) {
148                        Class elementClass = VenList.findElementClass((List)fieldValue);
149                        if (debug) System.out.println(">>list is found " + columnName);
150                        mapRecursively(rs, columns, columnName, elementClass, (List)fieldValue);
151                    } else {
152                        if (debug) System.out.println("--list not found: " + columnName);
153                    }
154                }
155            }
156        } catch (Exception ex) {
157            System.out.println("Ven - error while mapping row, table: " + tableName + " object class: " + objectClass + " error: " + ex.getMessage());
158            if (debug) {
159                ex.printStackTrace();
160            }
161        }
162    }
163
164    //enumerate columns from the resultset
165    private void enumerateColumns(Set columns, ResultSet rs) throws SQLException {
166        if (!columns.isEmpty()) return;
167        for (int i = 1; i < rs.getMetaData().getColumnCount() + 1; i++) {
168            columns.add(rs.getMetaData().getColumnName(i));
169        }
170    }
171
172    //check if the specified objects are the same entity (according to id fields)
173    private boolean objectIdEquals(Object object, Object id) {
174        //return obj1.equals(obj2); //objects need to implement equals()
175        //TODO: more efficient (invoke getId method)
176        BeanWrapper wr = new BeanWrapperImpl(object);
177        return id.equals(wr.getPropertyValue("id"));
178    }
179
180    //--------------------------------------------------------------------------
181    //SETTERS
182    /**
183     * @param dataSource used for accessing database
184     */
185    public void setDataSource(DataSource dataSource) {
186        if (dataSource == null) throw new RuntimeException("fmgVen - DataSource cannot be null");
187        this.template = new NamedParameterJdbcTemplate(dataSource);
188    }
189
190    /**
191     * Add the domain packages that will be considered persistent
192     * @param domainPackage the domain package
193     */
194    public void addDomainPackage(String domainPackage) {
195        domainPackages.add(domainPackage);
196    }
197
198    /**
199     * Set debug mode, true will log all debug messages to System.out
200     * <p>
201     * Note: Use debug mode to detect problems only. It is not a general purpose logging mode.
202     * @param debug set true to enable debug mode
203     */
204    public void setDebug(boolean debug) {
205        this.debug = debug;
206    }
207}
Note: See TracBrowser for help on using the repository browser.