Coverage Report - org.apache.ojb.broker.util.batch.BatchConnection
 
Classes in this File Line Coverage Branch Coverage Complexity
BatchConnection
N/A
N/A
4.154
 
 1  
 package org.apache.ojb.broker.util.batch;
 2  
 
 3  
 /* Copyright 2002-2005 The Apache Software Foundation
 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  
  */
 17  
 
 18  
 import java.lang.reflect.Proxy;
 19  
 import java.sql.Connection;
 20  
 import java.sql.PreparedStatement;
 21  
 import java.sql.SQLException;
 22  
 import java.sql.Statement;
 23  
 import java.util.ArrayList;
 24  
 import java.util.Collection;
 25  
 import java.util.HashMap;
 26  
 import java.util.HashSet;
 27  
 import java.util.Iterator;
 28  
 import java.util.List;
 29  
 
 30  
 import org.apache.ojb.broker.PersistenceBroker;
 31  
 import org.apache.ojb.broker.metadata.ClassDescriptor;
 32  
 import org.apache.ojb.broker.metadata.CollectionDescriptor;
 33  
 import org.apache.ojb.broker.metadata.DescriptorRepository;
 34  
 import org.apache.ojb.broker.metadata.JdbcConnectionDescriptor;
 35  
 import org.apache.ojb.broker.metadata.ObjectReferenceDescriptor;
 36  
 import org.apache.ojb.broker.util.WrappedConnection;
 37  
 
 38  
 /**
 39  
  * The implementation of {@link java.sql.Connection} which
 40  
  * automatically gathers INSERT, UPDATE and DELETE
 41  
  * PreparedStatements into batches.
 42  
  *
 43  
  * @author Oleg Nitz (<a href="mailto:olegnitz@apache.org">olegnitz@apache.org</a>)
 44  
  * @version $Id: BatchConnection.java,v 1.1 2007-08-24 22:17:42 ewestfal Exp $
 45  
  */
 46  
 public class BatchConnection extends WrappedConnection
 47  
 {
 48  
     private static final int MAX_COUNT = 100;
 49  
 
 50  
     /**
 51  
      * Maps PBKey to another HashMap,
 52  
      * which maps table name to List of related tables (N:1 or 1:1)
 53  
      */
 54  
     private static HashMap _pbkeyToFKInfo = new HashMap();
 55  
 
 56  
     private boolean _useBatchInserts = true;
 57  
     private HashMap _statements = new HashMap();
 58  
     private ArrayList _order = new ArrayList();
 59  
     private HashMap _fkInfo;
 60  
     private HashSet _deleted;
 61  
     private HashSet _dontInsert;
 62  
     private HashSet _touched = new HashSet();
 63  
     private int count = 0;
 64  
     private JdbcConnectionDescriptor m_jcd;
 65  
 
 66  
     public BatchConnection(Connection conn, PersistenceBroker broker)
 67  
     {
 68  
         super(conn);
 69  
         m_jcd = broker.serviceConnectionManager().getConnectionDescriptor();
 70  
         _fkInfo = (HashMap) _pbkeyToFKInfo.get(broker.getPBKey());
 71  
         if (_fkInfo != null)
 72  
         {
 73  
             return;
 74  
         }
 75  
 
 76  
         DescriptorRepository repos = broker.getDescriptorRepository();
 77  
         _fkInfo = new HashMap();
 78  
         for (Iterator it = repos.iterator(); it.hasNext();)
 79  
         {
 80  
             ClassDescriptor desc = (ClassDescriptor) it.next();
 81  
             List ordList = desc.getObjectReferenceDescriptors();
 82  
             if (!ordList.isEmpty())
 83  
             {
 84  
                 HashSet fkTables = getFKTablesFor(desc.getFullTableName());
 85  
                 for (Iterator it2 = ordList.iterator(); it2.hasNext();)
 86  
                 {
 87  
                     ObjectReferenceDescriptor ord = (ObjectReferenceDescriptor) it2.next();
 88  
                     ClassDescriptor oneDesc = repos.getDescriptorFor(ord.getItemClass());
 89  
                     fkTables.addAll(getFullTableNames(oneDesc, repos));
 90  
                 }
 91  
             }
 92  
 
 93  
             List codList = desc.getCollectionDescriptors();
 94  
             for (Iterator it2 = codList.iterator(); it2.hasNext();)
 95  
             {
 96  
                 CollectionDescriptor cod = (CollectionDescriptor) it2.next();
 97  
                 ClassDescriptor manyDesc = repos.getDescriptorFor(cod.getItemClass());
 98  
                 if (cod.isMtoNRelation())
 99  
                 {
 100  
                     HashSet fkTables = getFKTablesFor(cod.getIndirectionTable());
 101  
                     fkTables.addAll(getFullTableNames(desc, repos));
 102  
                     fkTables.addAll(getFullTableNames(manyDesc, repos));
 103  
                 }
 104  
                 else
 105  
                 {
 106  
                     HashSet manyTableNames = getFullTableNames(manyDesc, repos);
 107  
                     for (Iterator it3 = manyTableNames.iterator(); it3.hasNext();)
 108  
                     {
 109  
                         HashSet fkTables = getFKTablesFor((String) it3.next());
 110  
                         fkTables.addAll(getFullTableNames(desc, repos));
 111  
                     }
 112  
                 }
 113  
             }
 114  
         }
 115  
         _pbkeyToFKInfo.put(broker.getPBKey(), _fkInfo);
 116  
     }
 117  
 
 118  
     private HashSet getFKTablesFor(String tableName)
 119  
     {
 120  
         HashSet fkTables = (HashSet) _fkInfo.get(tableName);
 121  
 
 122  
         if (fkTables == null)
 123  
         {
 124  
             fkTables = new HashSet();
 125  
             _fkInfo.put(tableName, fkTables);
 126  
         }
 127  
         return fkTables;
 128  
     }
 129  
 
 130  
     private HashSet getFullTableNames(ClassDescriptor desc, DescriptorRepository repos)
 131  
     {
 132  
         String tableName;
 133  
         HashSet tableNamesSet = new HashSet();
 134  
         Collection extents = desc.getExtentClasses();
 135  
 
 136  
         tableName = desc.getFullTableName();
 137  
         if (tableName != null)
 138  
         {
 139  
             tableNamesSet.add(tableName);
 140  
         }
 141  
         for (Iterator it = extents.iterator(); it.hasNext();)
 142  
         {
 143  
             Class extClass = (Class) it.next();
 144  
             ClassDescriptor extDesc = repos.getDescriptorFor(extClass);
 145  
             tableName = extDesc.getFullTableName();
 146  
             if (tableName != null)
 147  
             {
 148  
                 tableNamesSet.add(tableName);
 149  
             }
 150  
         }
 151  
         return tableNamesSet;
 152  
     }
 153  
 
 154  
     public void setUseBatchInserts(boolean useBatchInserts)
 155  
     {
 156  
         _useBatchInserts = useBatchInserts;
 157  
     }
 158  
 
 159  
     /**
 160  
      * Remember the order of execution
 161  
      */
 162  
     void nextExecuted(String sql) throws SQLException
 163  
     {
 164  
         count++;
 165  
 
 166  
         if (_order.contains(sql))
 167  
         {
 168  
             return;
 169  
         }
 170  
 
 171  
         String sqlCmd = sql.substring(0, 7);
 172  
         String rest = sql.substring(sqlCmd.equals("UPDATE ") ? 7 // "UPDATE "
 173  
                 : 12); // "INSERT INTO " or "DELETE FROM "
 174  
         String tableName = rest.substring(0, rest.indexOf(' '));
 175  
         HashSet fkTables = (HashSet) _fkInfo.get(tableName);
 176  
 
 177  
         // we should not change order of INSERT/DELETE/UPDATE
 178  
         // statements for the same table
 179  
         if (_touched.contains(tableName))
 180  
         {
 181  
             executeBatch();
 182  
         }
 183  
         if (sqlCmd.equals("INSERT "))
 184  
         {
 185  
             if (_dontInsert != null && _dontInsert.contains(tableName))
 186  
             {
 187  
                 // one of the previous INSERTs contained a table
 188  
                 // that references this table.
 189  
                 // Let's execute that previous INSERT right now so that
 190  
                 // in the future INSERTs into this table will go first
 191  
                 // in the _order array.
 192  
                 executeBatch();
 193  
             }
 194  
         }
 195  
         else
 196  
         //if (sqlCmd.equals("DELETE ") || sqlCmd.equals("UPDATE "))
 197  
         {
 198  
             // We process UPDATEs in the same way as DELETEs
 199  
             // because setting FK to NULL in UPDATE is equivalent
 200  
             // to DELETE from the referential integrity point of view.
 201  
 
 202  
             if (_deleted != null && fkTables != null)
 203  
             {
 204  
                 HashSet intersection = (HashSet) _deleted.clone();
 205  
 
 206  
                 intersection.retainAll(fkTables);
 207  
                 if (!intersection.isEmpty())
 208  
                 {
 209  
                     // one of the previous DELETEs contained a table
 210  
                     // that is referenced from this table.
 211  
                     // Let's execute that previous DELETE right now so that
 212  
                     // in the future DELETEs into this table will go first
 213  
                     // in the _order array.
 214  
                     executeBatch();
 215  
                 }
 216  
             }
 217  
         }
 218  
 
 219  
         _order.add(sql);
 220  
 
 221  
         _touched.add(tableName);
 222  
         if (sqlCmd.equals("INSERT "))
 223  
         {
 224  
             if (fkTables != null)
 225  
             {
 226  
                 if (_dontInsert == null)
 227  
                 {
 228  
                     _dontInsert = new HashSet();
 229  
                 }
 230  
                 _dontInsert.addAll(fkTables);
 231  
             }
 232  
         }
 233  
         else if (sqlCmd.equals("DELETE "))
 234  
         {
 235  
             if (_deleted == null)
 236  
             {
 237  
                 _deleted = new HashSet();
 238  
             }
 239  
             _deleted.add(tableName);
 240  
         }
 241  
     }
 242  
 
 243  
     /**
 244  
      * If UPDATE, INSERT or DELETE, return BatchPreparedStatement,
 245  
      * otherwise return null.
 246  
      */
 247  
     private PreparedStatement prepareBatchStatement(String sql)
 248  
     {
 249  
         String sqlCmd = sql.substring(0, 7);
 250  
 
 251  
         if (sqlCmd.equals("UPDATE ") || sqlCmd.equals("DELETE ") || (_useBatchInserts && sqlCmd.equals("INSERT ")))
 252  
         {
 253  
             PreparedStatement stmt = (PreparedStatement) _statements.get(sql);
 254  
             if (stmt == null)
 255  
             {
 256  
                 // [olegnitz] for JDK 1.2 we need to list both PreparedStatement and Statement
 257  
                 // interfaces, otherwise proxy.jar works incorrectly
 258  
                 stmt = (PreparedStatement) Proxy.newProxyInstance(getClass().getClassLoader(), new Class[]{
 259  
                         PreparedStatement.class, Statement.class, BatchPreparedStatement.class},
 260  
                         new PreparedStatementInvocationHandler(this, sql, m_jcd));
 261  
                 _statements.put(sql, stmt);
 262  
             }
 263  
             return stmt;
 264  
         }
 265  
         else
 266  
         {
 267  
             return null;
 268  
         }
 269  
     }
 270  
 
 271  
     public PreparedStatement prepareStatement(String sql) throws SQLException
 272  
     {
 273  
         PreparedStatement stmt = null;
 274  
         stmt = prepareBatchStatement(sql);
 275  
 
 276  
         if (stmt == null)
 277  
         {
 278  
             stmt = getDelegate().prepareStatement(sql);
 279  
         }
 280  
         return stmt;
 281  
     }
 282  
 
 283  
     public PreparedStatement prepareStatement(String sql, int resultSetType, int resultSetConcurrency)
 284  
             throws SQLException
 285  
     {
 286  
         PreparedStatement stmt = null;
 287  
         stmt = prepareBatchStatement(sql);
 288  
 
 289  
         if (stmt == null)
 290  
         {
 291  
             stmt = getDelegate().prepareStatement(sql, resultSetType, resultSetConcurrency);
 292  
         }
 293  
         return stmt;
 294  
     }
 295  
 
 296  
     public void executeBatch() throws SQLException
 297  
     {
 298  
         BatchPreparedStatement batchStmt;
 299  
         Connection conn = getDelegate();
 300  
 
 301  
         try
 302  
         {
 303  
             for (Iterator it = _order.iterator(); it.hasNext();)
 304  
             {
 305  
                 batchStmt = (BatchPreparedStatement) _statements.get(it.next());
 306  
                 batchStmt.doExecute(conn);
 307  
             }
 308  
         }
 309  
         finally
 310  
         {
 311  
             _order.clear();
 312  
 
 313  
             if (_dontInsert != null)
 314  
             {
 315  
                 _dontInsert.clear();
 316  
             }
 317  
 
 318  
             if (_deleted != null)
 319  
             {
 320  
                 _deleted.clear();
 321  
             }
 322  
             _touched.clear();
 323  
             count = 0;
 324  
         }
 325  
     }
 326  
 
 327  
     public void executeBatchIfNecessary() throws SQLException
 328  
     {
 329  
         if (count >= MAX_COUNT)
 330  
         {
 331  
             executeBatch();
 332  
         }
 333  
     }
 334  
 
 335  
     public void clearBatch()
 336  
     {
 337  
         _order.clear();
 338  
         _statements.clear();
 339  
 
 340  
         if (_dontInsert != null)
 341  
         {
 342  
             _dontInsert.clear();
 343  
         }
 344  
 
 345  
         if (_deleted != null)
 346  
         {
 347  
             _deleted.clear();
 348  
         }
 349  
     }
 350  
 
 351  
     public void commit() throws SQLException
 352  
     {
 353  
         executeBatch();
 354  
         _statements.clear();
 355  
         getDelegate().commit();
 356  
     }
 357  
 
 358  
     public void rollback() throws SQLException
 359  
     {
 360  
         clearBatch();
 361  
         getDelegate().rollback();
 362  
     }
 363  
 }