source: trunk/fmgVen/src/com/fmguler/ven/QueryGenerator.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: 11.0 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.util.Date;
24import java.util.HashSet;
25import java.util.Iterator;
26import java.util.List;
27import java.util.Set;
28import org.springframework.beans.BeanWrapper;
29import org.springframework.beans.BeanWrapperImpl;
30
31/**
32 * Generates queries in the form of 'Convention over Configuration' for the specified objects.
33 * @author Fatih Mehmet Güler
34 */
35public class QueryGenerator {
36    private Set domainPackages;
37    private Set dbClasses;
38    private boolean debug = true;
39
40    public QueryGenerator() {
41        domainPackages = new HashSet();
42        dbClasses = new HashSet();
43        //the predefined database classes;
44        this.dbClasses.add(Integer.class);
45        this.dbClasses.add(String.class);
46        this.dbClasses.add(Date.class);
47        this.dbClasses.add(Double.class);
48        this.dbClasses.add(Boolean.class);
49    }
50
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();
67    }
68
69    public String generateCountQuery() {
70        return null;
71    }
72
73    /**
74     * Generates insert query for the specified object
75     * @param object the object to generate insert query for
76     * @return the insert SQL query
77     */
78    public String generateInsertQuery(Object object) {
79        BeanWrapper wr = new BeanWrapperImpl(object);
80        String objectName = Convert.toSimpleName(object.getClass().getName());
81        String tableName = Convert.toDB(objectName);
82        PropertyDescriptor[] pdArr = wr.getPropertyDescriptors();
83
84        //generate insert query
85        StringBuffer query = new StringBuffer("insert into " + tableName + "(");
86        StringBuffer values = new StringBuffer(" values(");
87        for (int i = 0; i < pdArr.length; i++) {
88            Class fieldClass = pdArr[i].getPropertyType(); //field class
89            String columnName = Convert.toDB(pdArr[i].getName()); //column name
90            String fieldName = pdArr[i].getName(); //field name
91            //if (fieldName.equals("id")) continue; //remove if it does not break the sequence
92            if (dbClasses.contains(fieldClass)) { //direct database field (Integer,String,Date, etc)
93                query.append(columnName);
94                query.append(",");
95                values.append(":").append(fieldName);
96                values.append(",");
97            }
98            if (fieldClass.getPackage() != null && domainPackages.contains(fieldClass.getPackage().getName())) { //object
99                query.append(Convert.toDB(fieldName)).append("_id");
100                query.append(",");
101                values.append(":").append(fieldName).append(".id");
102                values.append(",");
103            }
104        }
105        query.deleteCharAt(query.length() - 1);
106        query.append(")");
107        values.deleteCharAt(values.length() - 1);
108        values.append(");");
109        query.append(values);
110
111        return query.toString();
112    }
113
114    /**
115     * Generates update query for the specified object
116     * @param object the object to generate update query for
117     * @return the update SQL query
118     */
119    public String generateUpdateQuery(Object object) throws VenException {
120        BeanWrapper wr = new BeanWrapperImpl(object);
121        String objectName = Convert.toSimpleName(object.getClass().getName());
122        String tableName = Convert.toDB(objectName);
123        PropertyDescriptor[] pdArr = wr.getPropertyDescriptors();
124
125        StringBuffer query = new StringBuffer("update " + tableName + " set ");
126        for (int i = 0; i < pdArr.length; i++) {
127            Class fieldClass = pdArr[i].getPropertyType(); //field class
128            String columnName = Convert.toDB(pdArr[i].getName()); //column name
129            String fieldName = pdArr[i].getName(); //field name
130            if (dbClasses.contains(fieldClass)) { //direct database field (Integer,String,Date, etc)
131                query.append(columnName).append("=:").append(fieldName);
132                query.append(",");
133            }
134            if (fieldClass.getPackage() != null && domainPackages.contains(fieldClass.getPackage().getName())) { //object
135                query.append(columnName).append("_id=:").append(fieldName).append(".id");
136                query.append(",");
137            }
138        }
139        query.deleteCharAt(query.length() - 1);
140        query.append(" where id = :id ;");
141        return query.toString();
142    }
143
144    /**
145     * Generates delete query for the specified object class
146     * @param objectClass the object class to generate query for
147     * @return the delete SQL query
148     */
149    public String generateDeleteQuery(Class objectClass) {
150        StringBuffer query = new StringBuffer();
151        query.append("delete from ").append(Convert.toDB(Convert.toSimpleName(objectClass.getName()))).append(" where id = :id;");
152        return query.toString();
153    }
154
155    /**
156     * Generates sequence query for the specified object
157     * @param object the objectc to generate sequence query for
158     * @return the SQL query to select next id
159     */
160    public String generateSequenceQuery(Object object) throws VenException {
161        String objectName = Convert.toSimpleName(object.getClass().getName());
162        String tableName = Convert.toDB(objectName);
163        return "select nextval('" + tableName + "_id_seq');";
164    }
165
166    //--------------------------------------------------------------------------
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    //--------------------------------------------------------------------------
231    //SETTERS
232    /**
233     * Add the domain packages that will be considered persistent
234     * @param domainPackage the domain package
235     */
236    public void addDomainPackage(String domainPackage) {
237        domainPackages.add(domainPackage);
238    }
239}
Note: See TracBrowser for help on using the repository browser.