java servlet:如何加快速度?

时间:2009-05-19 22:24:17

标签: java performance optimization servlets

我为每个生成的订单项调用了以下函数。有没有人有任何想法如何加快这一点?

private String getDetails(String doc){
    String table="";
    java.sql.ResultSet rs = qw.DBquery("select " +
        "id,LineType, QtyTotal, ManufacturerPartNumber, Description, UnitCost,UnitPrice " +
        "From DocumentItems  " +
        "where DocID="+doc+" order by linenumber " +
        "");
    table+= "<table class=inner><thead><colgroup><col id='col1'><col id='col2'><col id='col3'><col id='col4'><col id='col5'></colgroup>" +
            "<tr class='enetBlue'><th>Qty</th><th>Part Num</th><th>Description</th><th>Unit Cost</th><th>Unit Price</th></tr></thead>" +
            "<tbody>";
    try{            
        int odd = 0;
        while(rs.next()){

            int lineType = rs.getInt("LineType");
            int qty = rs.getInt("QtyTotal");
            String part = rs.getString("ManufacturerPartNumber");
            String desc = rs.getString("Description");
            float cost = rs.getFloat("UnitCost");
            float price = rs.getFloat("UnitPrice");
            String id = rs.getString("id");

            String clas="";

           if (odd==0) odd=1; else odd=0;

           clas="red";
           if (lineType==2) clas="yellow";
           if (lineType==3) clas="yellow";
           if (lineType==4) clas="yellow";
           if (qty==0) clas="yellow";
           java.sql.ResultSet rs2 = mas.DBquery("select itemkey from timitem where itemid = '"+part+"'"); 
           while (rs2.next())
           {
               if (odd==1) clas="odd";
               if (odd==0) clas="even";
           }
           table+="<tr class='"+clas+"'><td>"+qty+"</td>\n"+
                        "<td>"+part+"</td>\n"+
                        "<td>"+desc+"</td>\n"+
                        "<td>"+cost+"</td>\n"+
                        "<td>"+price+"</td></tr>\n";

                       //if clas=red | means item is not found in MAS, gear for insert. 
                       if (clas=="red") { 

                        table+="<tr ><td colspan=5><table border=1><tr><td colspan=2>\n";
                        //get unit measure key  
                        try {
                            table+="<form name=masinsert"+id+" method=get action=MASInsert>\n";

                    table+="<input type=hidden name=\"partnumber"+id+"\" value=\""+part+"\">\n";
                    table+="<input type=hidden name=\"itemcost"+id+"\" value=\""+cost+"\">\n";
                    table+="<input type=hidden name=\"itemlistprice"+id+"\" value=\""+price+"\">\n";
                    table+="<input type=hidden name=\"itemdescription"+id+"\" value=\""+desc+"\">\n";
                    table+="</td><tr>\n";

                            java.sql.ResultSet rsUM = mas.DBquery("select * from tciUnitMeasure where companyid like 'ENS' ");
                                table+="<tr bgcolor=#990033><td align=left valign=top>Unit Measure</td><td align=left valign=top><select name=\"UnitMeasKey\">";
                                        while(rsUM.next())
                                {
                                    table+="<option value=\"" + rsUM.getString("UnitMeasKey") + "\">" + rsUM.getString("UnitMeasID") + "</option>\n"; 
                            }//end while rs1 
                            table+="</select></td></tr>\n"; 


                    //build ItemClass options from mas: Puchase ProductLine 
                        java.sql.ResultSet rsPP = mas.DBquery("select * from timPurchProdLine where companyID = 'ENS'");
                        int k = 0; 

                        table+= "<tr bgcolor=#990033><td align=left valign=top>Purchase Product Line</td><td align=left valign=top><select name=\"PurchProdLine\">\n"; 
                        while(rsPP.next())
                        {
                            table+="<option value=\"" + rsPP.getString("PurchProdLineKey") + "\">" + rsPP.getString("Description") + "</option>\n"; 

                        }//end while rsPP 
                        table+="</select></td></tr>\n"; 

                        //build item classkey options
                        java.sql.ResultSet rsIC = mas.DBquery("select * from timItemClass where companyID = 'ENS' order by itemclassname desc");

                        table+= "<tr bgcolor=#990033><td align=left valign=top>Item Class :</td><td align=left valign=top><select name=\"itemclasskey\">\n"; 
                        while(rsIC.next())
                        {
                            table+="<option value=\"" + rsIC.getString("itemclasskey") + "\">" + rsIC.getString("ItemClassName") + "</option>\n"; 

                        }//end while rs1 
                        table+="</select></td></tr>";
                        table+="<tr><td colspan=2><input id='m"+id+"' type=\"button\" onclick=\"masinsert('"+ id +"')\" value=\"Add to MAS\"></td></tr>";
                        table+="</table>\n"; 

                }catch(Exception e){}   //end try

                    table+="</form>\n"; 
                        table+="</td></tr>";


                }//end if clas=red
            }//end while 
    }catch(java.sql.SQLException e){
        e.printStackTrace();}        
    table+="</tbody></table>";
    return table;
}

提前致谢

11 个答案:

答案 0 :(得分:8)

使用预编译的参数化PreparedStatment,而不是每次都使用String连接构建它。这也将解决当前代码(如果 doc 是用户输入的变量)易受SQL注入攻击的事实。

答案 1 :(得分:7)

了解如何编写JSP并停止在servlet中嵌入HTML和CSS。这在1998年是个坏主意,当时很常见;从那以后,我们取得了长足的进步。

当循环遍历一个ResultSet并在循环内执行查询时,这意味着对于您带回第一个查询的每一行的网络往返。这肯定是你问题中最大的瓶颈。消除对于初学者。

我建议将数据库代码移出servlet并转移到可以测试的持久性对象中,而无需启动servlet引擎。

根本没有关于这种方法的面向对象。看起来像发票或BillOfMaterials,但我没有看到任何对象。

答案 2 :(得分:5)

使用'+'运算符追加字符串非常慢,因为由于字符串的不变性,JVM需要迭代每个追加的字符串中的每个字符。

查看StringBuilder

您也可以使用SQL JOIN,而不必迭代每条记录并制定新查询。你基本上只需要打一次数据库。

您的循环内部也有一个查询,它始终返回相同的数据:

 java.sql.ResultSet rsUM = mas.DBquery("select * from tciUnitMeasure where companyid like 'ENS' ");

这应该移到循环之外,但我怀疑你可以使用JOIN消除这个和其他嵌套查询。

答案 3 :(得分:2)

代码有很多改进。

我会尝试解决其中一些最相关的问题。

  • 最相关的。这应该使用jsp / servlet / beans。

    但由于代码的结构不是那样,我不会进一步解释。更好的是解释servlet的改进。

  • 该方法在一个地方做了太多事情。为了使它更好,需要几种实用方法,虽然这并不能提高它们的性能,但绝对有助于维护代码并更清楚地了解方法的目的(允许修改代码以进行改进。

  • 代码的某些部分根本不会改变。它们应该声明为常量。例如,有三个组合永远不会改变。它们应该是常量,这样,如果代码使用String“+”并不重要,因为它们只被调用一次。

  • 打开嵌套的结果集。在打开并使用 ResultSet 之前,应该使用并关闭之前的结果。这样连接就有机会节省数据库资源。

  • qw.DBquery 也许这个代码中真正存在的问题就是这个对象。我不知道代码是什么样的,但可能它不使用连接池,也不会在每次执行查询时关闭连接。这可能意味着对于给定的代码,最多可以打开5个同时连接。虽然只需要一个。下一次更糟糕的是(如果连接没有关闭)可以使用另一个新的5个连接,可能一个连接可以完成几个小时的所有工作。我们不知道。

除了使用“+”替换字符串连接之外,我认为数据库资源的使用正在扼杀这个servlet的性能。

最后,我尝试对代码进行重大的重构。

我希望这有助于更好地理解应用程序应该如何完成。我没有编译它,所以它不会工作。这样做只是为了显示如何修复上述项目。

import java.sql.ResultSet;
import java.util.List;
import java.util.ArrayList;
// etc etc. etc

首先,有许多字符串可以声明为常量。

public class SomeServlet extends HttpServlet {


    // ALL THE FOLLOWING ARE CONSTANTA IN THE SERVLET.
    // THEIR VALUE DOES'T CHANGE DURING EXECUTION
    private static final String SELECT_DOCUMENT_QUERY =
                                   "SELECT \n" +
                                  "   id,LineType, QtyTotal, ManufacturerPartNumber, Description, UnitCost,UnitPrice \n" +
                                  " FROM DocumentItems  \n" +
                                  " WHERE DocID=%s order by linenumber ";

    private static final String HTML_HEADER_TABLE =
                     "<table class=inner><thead><colgroup><col id='col1'>"+
                     "<col id='col2'><col id='col3'><col id='col4'><col id='col5'></colgroup>" +
                     "<tr class='enetBlue'><th>Qty</th><th>Part Num</th><th>Description</th>"+
                     "<th>Unit Cost</th><th>Unit Price</th></tr></thead><tbody>";


    // These constants are the HTML Commbo for the given queries. 
    // If they are initialized once at the beginning will increase
    // the servlets performace considerabily.
    private static final String UNIT_MEASURES_COMBO = 
                      getCombo( "Unit Measure", "UnitMeasKey", 
                          getListFrom( "select * from tciUnitMeasure where companyid like 'ENS' ") );

    private static final String ITEM_CLASS_COMBO    = 
                      getCombo( "Purchase Product Line", "PurchProdLine", 
                          getListFrom( "select * from timPurchProdLine where companyID = 'ENS' ") );

    private static final String CLASS_KEY_COMBO     = 
                      getCombo( "Item Class :", "itemclasskey", 
                          getListFrom( "select * from timItemClass where companyID = 'ENS' order by itemclassname desc") );

然后,尽管这绝对不是正确的方法,但是servlet可能会定义一些bean来帮助他完成这项工作,我们可以创建这样的类:

    // Use a class to separete domain objects from presentation
    // This class becomes handy do pass data through methods. 
    class Document {
        int lineType;
        int qty;
        String part;
        String desc;
        float cost;
        float price;
        String id;
    }
    // simple clas that holds a key/value pair
    class ComboPair {
        String key;
        String value;
        public ComboPair( String key, String value ) { 
            this.key = key;
            this.value = value;
        }
    }

最后重构的代码。当一个方法做了太多事情时,它可以将工作委托给其他人。当两段代码看起来相同时,它们应该使用辅助方法。

    /*
    * Finally the fixed code.
    *
    * Basically a method should do only one thig. If the method is doing a lot of things 
    * several functionality at the same time, it should delegate the details of the subfunctionality 
    * to another function. This way each function or method is easier to read/maintain/understand.
    * This method should read like this:
    * 
    * For a given docId, 
    *    - query the database and display details of the document
    *    - if the document is not present in mas?
    *         - create a form to inser it
    *  
    * differntiate each record with different style.
    */
    private String getDetails(String doc){
        // Close each result set before openning a new one.
        ResultSet rs = qw.DBquery( String.format( SELECT_DOCUMENT_QUERY, doc ));
        List<Document> documents = new ArrayList<Document>();
        while(rs.next()){
            documents.add( createDocumentFrom( rs ));
        }
        // Iterate through 
        StringBuilder resultingTable = new StringBuilder( HTML_HEADER_TABLE );
        boolean isEven = false;// starts as odd
        for( Document doc : documents ) { 
            String clazz = getClassFor( doc , isEven );
            isEven = !isEven;

            resultingTable.append("<tr class='"+clazz+"'>"+
                        "<td>"+doc.qty+"</td>\n"+
                        "<td>"+doc.part+"</td>\n"+
                        "<td>"+doc.desc+"</td>\n"+
                        "<td>"+doc.cost+"</td>\n"+
                        "<td>"+doc.price+"</td></tr>\n");

            if( needsInsertForm( clazz ) ) { 
                resultingTable.append( getInsertForm( document ) );
            }

            resultingTable.append("</tbody></table>");
        }
        return table;
    }


    /**
     * This methods craates an instance of "Document". 
     * Instead of mixing the fetch code with the render code
     * this method allows to separete the data from its presentation
     */
    private Document createDocumentFrom( ResultSet rs ) { 
        Document document = new Document();

        document.lineType = rs.getInt("LineType");
        document.qty = rs.getInt("QtyTotal");
        document.part = rs.getString("ManufacturerPartNumber");
        document.desc = rs.getString("Description");
        document.cost = rs.getFloat("UnitCost");
        document.price = rs.getFloat("UnitPrice");
        document.id = rs.getString("id");

        return document;
    }



    // Computes the correct css class for the given document. 
    private static String getClassFor( Document document, boolean isEven ) { 
        String clazz = "red";
        switch( document.lineType ) {
            case 2:
            case 3:
            case 4: clazz ="yellow";
        }
        if( document.qty == 0 ) { 
            clazz = "yellow";
        }
       ResultSet rs = mas.DBquery( String.format("select itemkey from timitem where itemid = '%s'", document.part); 
       while (rs.next()) {
           clazz = isEven? "even" : "odd";
       }
       rs.close();
       return clazz;

    }

    // Creates the inser form for the given document
    private static String getInsertForm( Document document ) { 
        StringBuilder form = new StringBuilder();

        form.append("<tr ><td colspan=5><table border=1><tr><td colspan=2>\n");
        form.append("<form name=masinsert"+document.id+" method=get action=MASInsert>\n");
        form.append("<input type=hidden name=\"partnumber"+document.id+"\" value=\""+document.part+"\">\n");
        form.append("<input type=hidden name=\"itemdescription"+document.id+"\" value=\""+document.desc+"\">\n");
        form.append("<input type=hidden name=\"itemcost"+document.id+"\" value=\""+document.cost+"\">\n");
        form.append("<input type=hidden name=\"itemlistprice"+document.id+"\" value=\""+document.price+"\">\n");
        form.append("</td><tr>\n");
        //----------------------------------------------------------------------------------------------------------------------------                      
        //get unit measure key  
        form.append(UNIT_MEASURES_COMBO);                           

        //build ItemClass options from mas: Puchase ProductLine 
        form.append(ITEM_CLASS_COMBO);

        //build item classkey options
        form.append( CLASS_KEY_COMBO);
        //----------------------------------------------------------------------------------------------------------------------------
        form.append("<tr><td colspan=2><input id='m"+document.id+"' type=\"button\" onclick=\"masinsert('"+ document.id +"')\" value=\"Add to MAS\"></td></tr>");
        form.append("</table>\n"; 
        form.append("</form>\n"; 
        form.append("</td></tr>");
        return form.toString();

    }


    // This is an utility method that reads bettern when used in the 
    // main method.
    // if( needsInsertForm( clazzz ) ) {
    // is much clearer
    private static boolean needsInsertForm( String clazz ) { 
        return "red".equals(clazz);
    }

此处不包括两种方法。它们仍然很混乱,但它们有助于创建常量。这样,它们不是每次都创建这些字符串,而是在servlet生命期间创建一次。

完整的代码在http://pastebin.com/f2ad1510d

我希望它有所帮助。

答案 4 :(得分:2)

这是学习使用分析器的好时机。这将告诉您实际花费的时间,以便了解性能改进的内容。

如果您使用最新的java 6,那么我建议您查看visualvm。 (jvisualvm在JDK bin目录中)。

它可以分析已经运行的java程序!

答案 5 :(得分:2)

从循环中获取查询。加入循环外的所有必要表。仅此一项就可以为您提供最佳性能提升。您可以使用StringBuffer resp节省几个额外的纳秒。 StringBuilder的。

答案 6 :(得分:2)

您在每次迭代中都有一条select * from timPurchProdLine where companyID = 'ENS'行。该表的内容是否会随着每次迭代而改变?因为如果它们是静态的,如果你只在循环之前读取该表的内容一次,那么你将节省很多时间,从而创建你需要的子字符串。然后,您只需在需要时将该字符串附加到循环中。 select * from timItemClass where companyID = 'ENS' order by itemclassname desc查询也是如此。

不要使用“+”来连接字符串。使用StringBuilder或者如果您使用的是Java 5或6,请使用String.format()。此外,我认为使用ResultSet方法更快,这些方法采用列号而不是参数的列名,也就是说,getString(1)比getString(“id”)更快。

odd = 1-odd这样的东西可以节省几个CPU周期,但它可能不会有明显的改进;但如果你使用布尔值而做odd = !odd而不是if (odd==0) odd=1; else odd=0的东西,那就更容易理解了。

答案 7 :(得分:2)

我根本没有得到第二个查询。

 java.sql.ResultSet rs2 = mas.DBquery(...); 
 while (rs2.next())
 {
   if (odd==1) clas="odd";
   if (odd==0) clas="even";
 }

你执行查询并迭代它但你没有从rs2中得到任何东西,你根据odd的值设置clas,但不要修改循环内的odd值,这意味着没有任何意义int while while while循环,更不用说查询了。此外,如果查询没有结果,则偶数/奇数逻辑根本不会运行。

编辑:

'奇怪'处理是奇怪的。使用布尔值...

boolean odd = false;
...
odd = !odd; // instead of your if statement
... 
clas = odd ? "odd" : "even"; // inside the while loop.

您的代码包含

String clas = "";
...
clas = "red";

在第一次使用时声明clas并使用合理的默认值初始化它。

// String clas = ""; remove this line
...
String clas = "red";

此行不好

if (clas == "red") {

首先,与字符串进行参考比较是不好的形式。我使用idiomn

if ("red".equals(clas))

但即便如此,你最好还是存储一个布尔值,看看你是否找到了一个值并对其进行测试而不是字符串比较。

java.sql.ResultSet rsPP = mas.DBquery("select * from timPurchProdLine where companyID = 'ENS'");

如其他地方所述,在循环内做一堆不依赖循环变量的查询是一个巨大的浪费。事先做所有查询并缓存结果值或HTML。

答案 8 :(得分:1)

请使用模板引擎,你在那里做什么这是一个可怕的犯罪:)

我建议Apache Velocity,在任何java应用程序中迭代都很容易......当然也会尝试优化检索数据的方式......

答案 9 :(得分:0)

这里有一些代码被提取到自己的函数中并适合使用StringBuffer和String.format()。

private String timItemClassTable() throws SQLException {
    StringBuffer sb = new StringBuffer();
    ResultSet rs = mas.DBquery("select * from timItemClass where companyID = 'ENS' order by itemclassname desc");
    sb.append("<tr bgcolor=#990033><td align=left valign=top>Item Class :</td><td align=left valign=top><select name=\"itemclasskey\">\n");
    while (rs.next())
        sb.append(String.format("<option value=\"%s\">%s</option>\n", rs.getString("itemclasskey"), rs.getString("ItemClassName")));
    sb.append("</select></td></tr>");
    return sb.toString();
}

使用这样的较小方法可以更容易地找到代码中的瓶颈,并且更容易调整代码以使其更快(例如通过尽可能在循环外移动方法调用)。较小的方法也更容易测试和调试。

对代码进行重构本身并不会使代码更快,但它会让您的代码更易于使用,它可以让您更轻松地使代码更快。

答案 10 :(得分:0)

我会缓存在doc参数上键入的函数的输出。根据应用程序使用模式,它可以极大地加速您的应用程序。