Java сервлет: как ускорить это? - PullRequest
2 голосов
/ 20 мая 2009

У меня есть следующая функция, которая вызывается для каждой произведенной позиции. У кого-нибудь есть идеи как это ускорить?

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 ]

8 голосов
/ 20 мая 2009

Используйте предварительно скомпилированный параметризованный PreparedStatment, а не создавайте его с конкатенацией строк каждый раз. Это также исправит тот факт, что ваш текущий код (если doc - введенная пользователем переменная) подвержен атакам SQL-инъекций.

7 голосов
/ 20 мая 2009

Узнайте, как писать JSP и как прекратить встраивать HTML и CSS в сервлеты. Это была плохая идея еще в 1998 году, когда она была распространена; с тех пор мы далеко продвинулись.

Когда вы зацикливаетесь на одном ResultSet и выполняете запрос внутри цикла, это означает, что в каждом цикле происходит возврат в сеть для каждой строки, которую вы возвращаете с первым запросом. Это наверняка будет самым большим узким местом в вашей проблеме. Устраните это для начала.

Я бы порекомендовал переместить код базы данных из сервлета в объект персистентности, который можно протестировать без запуска механизма сервлета.

В этом подходе нет ничего объектно-ориентированного. Выглядит как Invoice или BillOfMaterials, но я не вижу никаких объектов.

5 голосов
/ 20 мая 2009

Добавление строк с оператором «+» очень медленное, потому что JVM необходимо перебирать каждый символ в строке для каждого добавления из-за неизменности строк.

Просмотр StringBuilder

Вы также можете использовать SQL JOIN s вместо необходимости перебирать каждую запись и формулировать новые запросы. По сути, вам нужно всего лишь один раз попасть в базу данных.

У вас также есть запрос внутри ваших циклов, который всегда возвращает одни и те же данные:

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

Это следует переместить за пределы цикла, хотя я подозреваю, что вы можете устранить этот и другие вложенные запросы, используя JOIN.

2 голосов
/ 20 мая 2009

Получить запросы из цикла. Соедините все необходимые таблицы вне цикла. Уже одно это даст вам максимально возможное повышение производительности. Вы можете сэкономить несколько дополнительных наносекунд, используя StringBuffer или StringBuilder.

2 голосов
/ 20 мая 2009

Это хорошее время для изучения использования профилировщика. Это скажет вам, где на самом деле тратится время, так что вы знаете, на что обратить внимание для повышения производительности.

Если вы используете последнюю версию Java 6, то я рекомендую вам посмотреть на visualvm. (jvisualvm в каталоге bin JDK).

Может профилировать уже запущенную Java-программу!

2 голосов
/ 20 мая 2009

В коде есть ряд улучшений.

Я постараюсь обратиться к некоторым из них, наиболее актуальным.

  • Наиболее актуально. Для этого следует использовать jsp / servlet / beans.

    Но так как код не структурирован таким образом, я не буду объяснять дальше. Лучше объяснить улучшения сервлета как есть.

  • Метод делает слишком много вещей в одном месте. Чтобы сделать его лучше, необходимо несколько служебных методов, хотя это не улучшает производительность, безусловно, помогает поддерживать код и проясним, какова цель метода (позволяя модифицировать код для улучшений.

  • Некоторые части кода вообще не меняются. Они должны быть объявлены как константы. Например, есть три комбинации, которые никогда не меняются. Таким образом, они должны быть постоянными, поэтому не имеет значения, использует ли код строку «+», потому что они вызываются только один раз.

  • Открыть вложенные результаты. Перед открытием и использованием ResultSet предыдущий должен быть обработан и закрыт. Таким образом, соединения имеют возможность сэкономить ресурсы БД.

  • qw.DBquery Возможно, реальная реальная проблема в этом коде - это объект. Я не знаю, как выглядит код, но, вероятно, он не использует пул соединений и не закрывает соединение при каждом выполнении запроса. Это может потенциально означать, что для данного кода может быть открыто до 5 одновременных соединений. Пока нужен только один. В худшем случае в следующий раз (если соединения не закрыты) могут быть использованы еще 5 новых соединений, когда, вероятно, одно соединение может выполнять всю работу в течение нескольких часов. Мы никогда не узнаем.

Помимо замены конкатенации строк с использованием "+", я думаю, что использование ресурсов БД - это то, что снижает производительность этого сервлета.

Наконец, вот моя попытка сделать главный рефакторинг кода.

Надеюсь, это поможет лучше понять, как сделать Java-приложение. Я не скомпилировал его, поэтому он не сработает. Это делается только для того, чтобы показать, как можно исправить вышеперечисленные элементы.

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") );

Тогда, хотя это определенно неправильный путь, сервлет может определить некоторые 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);
    }

Есть два метода, которые здесь не включены. Они все еще грязные, но они помогают создавать константы. Таким образом, вместо создания этих строк каждый раз, они просто создаются один раз в течение жизни сервлета.

Полный код здесь http://pastebin.com/f2ad1510d

Надеюсь, это поможет.

2 голосов
/ 20 мая 2009

Я вообще не получаю второй запрос.

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

Вы выполняете запрос и выполняете итерацию по нему, но из rs2 ничего не получаете, вы устанавливаете классы на основе значения нечетного, но не изменяете значение нечетного внутри цикла, то есть нет смысла В то время как он вообще цикл, гораздо меньше, чем запрос. Кроме того, если запрос не имеет результатов, четная / нечетная логика никогда не запускается вообще.

РЕДАКТИРОВАТЬ:

«Нечетная» обработка нечетна. Используйте логическое значение ...

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

Ваш код включает

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

Объявите классы рядом с первым использованием и инициализируйте его разумным значением по умолчанию.

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

Эта строка плохая

if (clas == "red") {

Во-первых, сравнение ссылок со строками - плохая форма. Я использую идиомн

if ("red".equals(clas))

Но даже тогда вам лучше хранить логическое значение того, нашли ли вы значение и проверить его, а не сравнивать строки.

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

Как указано в другом месте, выполнение множества запросов внутри цикла, которые не зависят от переменных цикла, является огромной тратой. Сделайте все запросы заранее и кэшируйте полученные значения или HTML.

2 голосов
/ 20 мая 2009

У вас есть строка 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 сохранят пару циклов ЦП, но, вероятно, это не будет заметным улучшением; но это еще более понятно, если вы используете логическое значение и вместо if (odd==0) odd=1; else odd=0 делаете odd = !odd.

1 голос
/ 20 мая 2009

Пожалуйста, используйте шаблонизатор, что вы там делаете, это ужасное преступление:)

Я бы порекомендовал Apache Velocity , очень легко выполнять итерации в любом Java-приложении ... и, конечно, попытаться оптимизировать способ извлечения данных ...

0 голосов
/ 20 мая 2009

Вот фрагмент вашего кода, извлеченный в его собственную функцию и адаптированный для использования 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();
}

Наличие таких меньших методов облегчает поиск узких мест в вашем коде и упрощает настройку кода для его ускорения (например, перемещая вызовы методов вне цикла, где это возможно). Меньшие методы также легче тестировать и отлаживать.

Этот рефакторинг вашего кода сам по себе не сделает ваш код быстрее, но он облегчит работу с вашим кодом, и вам будет проще сделать ваш код быстрее.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...