2 Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
4 This program is free software; you can redistribute it and/or modify
5 it under the terms of the GNU General Public License as published by
6 the Free Software Foundation; version 2 of the License.
8 This program is distributed in the hope that it will be useful,
9 but WITHOUT ANY WARRANTY; without even the implied warranty of
10 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
11 GNU General Public License for more details.
13 You should have received a copy of the GNU General Public License
14 along with this program; if not, write to the Free Software
15 Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
18 package testsuite.clusterj;
20 import java.util.ArrayList;
21 import java.util.Collection;
22 import java.util.Comparator;
23 import java.util.List;
24 import java.util.Random;
26 import java.util.TreeSet;
28 import com.mysql.clusterj.Query;
29 import com.mysql.clusterj.Session;
30 import com.mysql.clusterj.query.QueryDomainType;
32 import testsuite.clusterj.model.Customer;
33 import testsuite.clusterj.model.Order;
34 import testsuite.clusterj.model.OrderLine;
36 public class MultithreadedTest extends AbstractClusterJModelTest {
39 protected boolean getDebug() {
43 private int numberOfThreads = 50;
44 private int numberOfNewCustomersPerThread = 5;
45 private int numberOfNewOrdersPerNewCustomer = 5;
46 private int numberOfUpdatesPerThread = 2;
48 private int maximumOrderLinesPerOrder = 5;
49 private int maximumQuantityPerOrderLine = 100;
50 private int maximumUnitPrice = 100;
53 private int numberOfInitialCustomers = 10;
54 private int nextCustomerId = numberOfInitialCustomers;
55 private int nextOrderId = 0;
56 private int nextOrderLineId = 0;
58 private int numberOfUpdatedOrderLines = 0;
59 private int numberOfDeletedOrders = 0;
60 private int numberOfDeletedOrderLines = 0;
62 private ThreadGroup threadGroup;
65 List<Customer> customers = new ArrayList<Customer>();
68 List<Order> orders = new ArrayList<Order>();
71 Set<OrderLine> orderlines = new TreeSet<OrderLine>(
72 new Comparator<OrderLine>() {
73 public int compare(OrderLine o1, OrderLine o2) {
74 return o1.getId() - o2.getId();
80 public void localSetUp() {
81 createSessionFactory();
82 session = sessionFactory.getSession();
83 // first delete all customers, orders, and order lines
84 tx = session.currentTransaction();
86 session.deletePersistentAll(Customer.class);
87 session.deletePersistentAll(Order.class);
88 session.deletePersistentAll(OrderLine.class);
90 // start out with some customers
91 createCustomerInstances(nextCustomerId);
92 // add new customer instances
94 session.makePersistentAll(customers);
96 // get rid of them when we're done
97 addTearDownClasses(Customer.class);
98 addTearDownClasses(Order.class);
99 addTearDownClasses(OrderLine.class);
102 private void createCustomerInstances(int numberToCreate) {
103 for (int i = 0; i < numberToCreate; ++i) {
104 Customer customer = session.newInstance(Customer.class);
106 customer.setName("Customer number " + i);
107 customer.setMagic(i * 100);
108 customers.add(customer);
112 /** The test method creates numberOfThreads threads and starts them.
113 * Once the threads are started, the main thread waits until all threads complete.
114 * The main thread then checks that the proper number of instances are
115 * created in the database and verifies that all orders are consistent
116 * with their order lines. Inconsistency might be due to thread interaction
117 * or improper database updates.
120 List<Thread> threads = new ArrayList<Thread>();
121 // create thread group
122 threadGroup = new ThreadGroup("Stuff");
123 // create uncaught exception handler
124 MyUncaughtExceptionHandler uncaughtExceptionHandler = new MyUncaughtExceptionHandler();
125 Thread.setDefaultUncaughtExceptionHandler(uncaughtExceptionHandler);
126 // create all threads
127 for (int i = 0; i < numberOfThreads ; ++i) {
128 Thread thread = new Thread(threadGroup, new StuffToDo());
132 // wait until all threads have finished
133 for (Thread t: threads) {
136 } catch (InterruptedException e) {
137 throw new RuntimeException("Interrupted while joining threads.");
140 // if any uncaught exceptions (from threads) signal an error
141 for (Throwable thrown: uncaughtExceptionHandler.getUncaughtExceptions()) {
142 error("Caught exception: " + thrown.getClass().getName() + ": " + thrown.getMessage());
143 StackTraceElement[] elements = thrown.getStackTrace();
144 for (StackTraceElement element: elements) {
145 error(" at " + element.toString());
148 // summarize for the record
150 System.out.println("Number of threads: " + numberOfThreads +
151 "; number of new customers per thread: " + numberOfNewCustomersPerThread +
152 "; number of orders per new customer: " + numberOfNewOrdersPerNewCustomer);
153 System.out.println("Created " + nextCustomerId + " customers; " +
154 nextOrderId + " orders; and " + nextOrderLineId + " order lines.");
155 System.out.println("Deleted " + numberOfDeletedOrders + " orders; and " +
156 numberOfDeletedOrderLines + " order lines.");
157 System.out.println("Updated " + numberOfUpdatedOrderLines + " order lines.");
159 errorIfNotEqual("Failed to create customers.",
160 numberOfThreads * numberOfNewCustomersPerThread + numberOfInitialCustomers, nextCustomerId);
161 errorIfNotEqual("Failed to create orders. ",
162 numberOfThreads * numberOfNewCustomersPerThread * numberOfNewOrdersPerNewCustomer, nextOrderId);
163 // double check the orders to make sure they were updated correctly
164 Session session = sessionFactory.getSession();
165 QueryDomainType<OrderLine> queryOrderType = session.getQueryBuilder().createQueryDefinition(OrderLine.class);
166 queryOrderType.where(queryOrderType.get("orderId").equal(queryOrderType.param("orderId")));
167 Query<OrderLine> query = session.createQuery(queryOrderType);
168 for (Order order: orders) {
169 int orderId = order.getId();
170 // replace order with its persistent representation
171 order = session.find(Order.class, orderId);
172 double expectedTotal = order.getValue();
173 double actualTotal = 0.0d;
174 for (OrderLine orderLine: getOrderLines(session, query, orderId)) {
175 actualTotal += orderLine.getTotalValue();
177 errorIfNotEqual("For order " + orderId + ", order value does not equal sum of order line values.",
178 expectedTotal, actualTotal);
183 /** This class implements the logic per thread. For each thread created,
184 * the run method is invoked.
185 * Each thread uses its own session and shares the customer, order, and order line
186 * collections. Collections are synchronized to avoid threading conflicts.
187 * Each thread creates numberOfNewCustomersPerThread customers, each of which
188 * contains a random number of orders, each of which contains a random number
190 * Each thread then updates numberOfUpdatesPerThread orders by changing one
192 * Each thread then deletes one order and its associated order lines.
194 class StuffToDo implements Runnable {
196 private Random myRandom = new Random();
199 // get my own session
200 Session session = sessionFactory.getSession();
201 QueryDomainType<OrderLine> queryOrderType = session.getQueryBuilder().createQueryDefinition(OrderLine.class);
202 queryOrderType.where(queryOrderType.get("orderId").equal(queryOrderType.param("orderId")));
203 Query<OrderLine> query = session.createQuery(queryOrderType);
204 for (int i = 0; i < numberOfNewCustomersPerThread; ++i) {
205 // create a new customer
206 createCustomer(session, String.valueOf(Thread.currentThread().getId()));
207 for (int j = 0; j < numberOfNewOrdersPerNewCustomer ; ++j) {
208 // create a new order
209 createOrder(session, myRandom);
213 for (int j = 0; j < numberOfUpdatesPerThread; ++j) {
214 updateOrder(session, myRandom, query);
217 deleteOrder(session, myRandom, query);
222 /** Create a new customer.
224 * @param session the session
225 * @param threadId the thread id of the creating thread
227 private void createCustomer(Session session, String threadId) {
228 Customer customer = session.newInstance(Customer.class);
229 int id = getNextCustomerId();
231 customer.setName("Customer number " + id + " thread " + threadId);
232 customer.setMagic(id * 10000);
233 session.makePersistent(customer); // autocommit for this
234 addCustomer(customer);
237 /** Create a new order. Add a new order with a random number of order lines
238 * and a random unit price and quantity.
240 * @param session the session
241 * @param random a random number generator
243 public void createOrder(Session session, Random random) {
244 session.currentTransaction().begin();
245 // get an order number
246 int orderid = getNextOrderId();
247 Order order = session.newInstance(Order.class);
248 order.setId(orderid);
249 // get a random customer number
250 int customerId = random .nextInt(nextCustomerId);
251 order.setCustomerId(customerId);
252 order.setDescription("Order " + orderid + " for Customer " + customerId);
253 Double orderValue = 0.0d;
254 // now create some order lines
255 int numberOfOrderLines = random.nextInt(maximumOrderLinesPerOrder);
256 for (int i = 0; i < numberOfOrderLines; ++i) {
257 int orderLineNumber = getNextOrderLineId();
258 OrderLine orderLine = session.newInstance(OrderLine.class);
259 orderLine.setId(orderLineNumber);
260 orderLine.setOrderId(orderid);
261 long quantity = random.nextInt(maximumQuantityPerOrderLine);
262 orderLine.setQuantity(quantity);
263 float unitPrice = ((float)random.nextInt(maximumUnitPrice)) / 4;
264 orderLine.setUnitPrice(unitPrice);
265 double orderLineValue = unitPrice * quantity;
266 orderValue += orderLineValue;
267 if (getDebug()) System.out.println("For order " + orderid + " orderline " + orderLineNumber +
268 " order line value " + orderLineValue + " order value " + orderValue);
269 orderLine.setTotalValue(orderLineValue);
270 addOrderLine(orderLine);
271 session.persist(orderLine);
273 order.setValue(orderValue);
274 session.persist(order);
275 session.currentTransaction().commit();
279 /** Update an order; change one or more order lines
281 * @param session the session
282 * @param random a random number generator
285 public void updateOrder(Session session, Random random, Query<OrderLine> query) {
286 session.currentTransaction().begin();
288 // pick an order to update; prevent anyone else from updating the same order
289 order = removeOrderFromOrdersCollection(random);
293 int orderId = order.getId();
294 // replace order with its persistent representation
295 order = session.find(Order.class, orderId);
296 List<OrderLine> orderLines = getOrderLines(session, query, orderId);
297 int numberOfOrderLines = orderLines.size();
298 OrderLine orderLine = null;
299 double orderValue = order.getValue();
300 if (numberOfOrderLines > 0) {
301 int index = random.nextInt(numberOfOrderLines);
302 orderLine = orderLines.get(index);
303 orderValue -= orderLine.getTotalValue();
304 updateOrderLine(orderLine, random);
305 orderValue += orderLine.getTotalValue();
307 order.setValue(orderValue);
308 session.updatePersistent(orderLine);
309 session.updatePersistent(order);
310 session.currentTransaction().commit();
311 // put order back now that we're done updating it
315 /** Update an order line by randomly changing unit price and quantity.
317 * @param orderLine the order line to update
318 * @param random a random number generator
320 private void updateOrderLine(OrderLine orderLine, Random random) {
321 int orderid = orderLine.getOrderId();
322 int orderLineNumber = orderLine.getId();
323 double previousValue = orderLine.getTotalValue();
324 long quantity = random.nextInt(maximumQuantityPerOrderLine );
325 orderLine.setQuantity(quantity);
326 float unitPrice = ((float)random.nextInt(maximumUnitPrice)) / 4;
327 orderLine.setUnitPrice(unitPrice);
328 double orderLineValue = unitPrice * quantity;
329 orderLine.setTotalValue(orderLineValue);
330 if (getDebug()) System.out.println("For order " + orderid + " orderline " + orderLineNumber +
331 " previous order line value " + previousValue + " new order line value " + orderLineValue);
332 synchronized (orderlines) {
333 ++numberOfUpdatedOrderLines;
337 /** Delete an order from the database.
339 * @param session the session
340 * @param random a random number generator
341 * @param query the query instance to query for OrderLines by OrderId
343 public void deleteOrder(Session session, Random random, Query<OrderLine> query) {
344 session.currentTransaction().begin();
346 // pick an order to delete
347 order = removeOrderFromOrdersCollection(random);
351 int orderId = order.getId();
352 List<OrderLine> orderLines = getOrderLines(session, query, orderId);
353 removeOrderLinesFromOrderLinesCollection(orderLines);
354 session.deletePersistentAll(orderLines);
355 session.deletePersistent(order);
356 session.currentTransaction().commit();
359 private List<OrderLine> getOrderLines(Session session, Query<OrderLine> query, int orderId) {
360 query.setParameter("orderId", orderId);
361 return query.getResultList();
364 private Order removeOrderFromOrdersCollection(Random random) {
365 synchronized(orders) {
366 int numberOfOrders = orders.size();
367 if (numberOfOrders < 10) {
370 int orderId = random.nextInt(numberOfOrders);
371 ++numberOfDeletedOrders;
372 return orders.remove(orderId);
376 private void removeOrderLinesFromOrderLinesCollection(Collection<OrderLine> orderLinesToRemove) {
377 synchronized(orderlines) {
378 orderlines.removeAll(orderLinesToRemove);
379 numberOfDeletedOrderLines += orderLinesToRemove.size();
383 /** Add a new customer to the list of customers
386 private void addCustomer(Customer customer) {
387 synchronized(customers) {
388 customers.add(customer);
392 /** Get the next customer number (multithread safe)
395 private int getNextCustomerId() {
396 synchronized(customers) {
397 return nextCustomerId++;
401 /** Get the next order number (multithread safe)
404 private int getNextOrderId() {
405 synchronized(orders) {
406 return nextOrderId++;
410 /** Get the next order line number (multithread safe)
413 private int getNextOrderLineId() {
414 synchronized(orderlines) {
415 return nextOrderLineId++;
419 /** Add an order to the list of orders.
421 * @param order the order
423 private void addOrder(Order order) {
424 synchronized(orders) {
429 /** Add an order line to the list of order lines.
431 * @param orderLine the order line
433 private void addOrderLine(OrderLine orderLine) {
434 synchronized(orderlines) {
435 orderlines.add(orderLine);