View Javadoc
1   package myorg.queryex;
2   
3   import static org.junit.Assert.*;
4   
5   import java.util.ArrayList;
6   import java.util.Calendar;
7   import java.util.Date;
8   import java.util.GregorianCalendar;
9   import java.util.Iterator;
10  import java.util.List;
11  
12  import javax.persistence.EntityManager;
13  import javax.persistence.LockModeType;
14  import javax.persistence.PersistenceException;
15  
16  import org.apache.commons.logging.Log;
17  import org.apache.commons.logging.LogFactory;
18  import org.junit.Before;
19  import org.junit.Test;
20  
21  public class QueryLocksTest extends QueryBase {
22      private static final Log log = LogFactory.getLog(QueryLocksTest.class);
23  	public static enum Action { INSERT, UPDATE, FAIL };
24  	
25  	@Before
26  	public void setUpLocksTest() {
27  		em.getTransaction().commit();
28  		cleanup(em);
29  		populate(em);
30  	}
31  
32  	/**
33  	 * This class is used to perform write actions to an object within the database
34  	 * within a Thread.
35  	 */
36      private class Writer extends Thread {
37  		private String context;
38      	private Actor actor;
39      	private LockModeType lockMode;
40  		private EntityManager em_;
41  		private Action action;
42  		private int sleepTime=100;
43  		private String errorText;
44      	public Writer(String context, Actor actor, LockModeType lockMode) {
45      		this.context = context;
46      		this.actor = actor;
47      		this.lockMode = lockMode;
48      		em_ = emf.createEntityManager();
49  			em_.getTransaction().begin();
50  			log.debug(context + " transaction started");
51      	}
52      	public boolean isDone() { return action != null && em_==null; }
53      	public String getContext() { return context; }
54      	public Action getAction() { return action; }
55      	public String getErrorText() { return errorText; }
56      	public void run() {
57      		try {
58                      log.debug(context + " selecting with lockMode=" + lockMode);
59                      //h2 won't let us lock on a JOIN -- use subquery
60                      List<Actor> actors = em_.createQuery(
61                              "select a from Actor a "
62                              + "where a.person in ("
63                              + "select p from Person p "
64                              + "where p.firstName=:firstName and p.lastName=:lastName "
65                              + "or p.firstName='" + context + "')", Actor.class)
66                              .setLockMode(lockMode)
67                              .setParameter("firstName", actor.getFirstName())
68                              .setParameter("lastName", actor.getLastName())
69                              .setMaxResults(1)
70                              .getResultList();
71                      /*
72                      List<Actor> actorsx = em_.createQuery(
73                                      "select a from Actor a JOIN a.person as p " +
74                                      "where p.firstName=:firstName and p.lastName=:lastName " +
75                                      "or p.firstName='" + context + "'", Actor.class)
76                                      .setLockMode(lockMode)
77                                      .setParameter("firstName", actor.getFirstName())
78                                      .setParameter("lastName", actor.getLastName())
79                                      .setMaxResults(1)
80                                      .getResultList();*/
81                      try { 
82                          log.debug(context + " sleeping " + sleepTime + " msecs"); 
83                          Thread.sleep(sleepTime); 
84                      } catch (Exception ex){}
85                      if (actors.size()==0) {
86                              log.debug(context + " creating entity");
87                              em_.persist(actor);
88                              action=Action.INSERT;
89                      } else {
90                              log.debug(context + " updating entity");
91                              actors.get(0).setBirthDate(actor.getBirthDate());
92                              action=Action.UPDATE;
93                      }
94                      em_.flush();
95                      log.debug(context + " committing transaction version=" + actor.getVersion());
96                              em_.getTransaction().commit();
97                      log.debug(context + " committed transaction version=" + actor.getVersion());
98      		} catch (PersistenceException ex) {
99      			log.debug(context + " failed " + ex);
100     			em_.getTransaction().rollback();
101     			action = Action.FAIL; errorText = ex.toString();
102     		} finally {
103     			em_.close(); em_=null;
104     		}
105     	}
106     }
107     
108     /**
109      * This method is used to setup all locking tests based on a provided locking mode.
110      * @param lockMode
111      * @return
112      */
113     protected int testUpsert(LockModeType lockMode, int count) {
114     	List<Writer> writers = new ArrayList<QueryLocksTest.Writer>();
115     	//create writer instances within their own thread
116     	for (int i=0; i<count; i++) {
117     		Date birthDate = new GregorianCalendar(1969+i, Calendar.MAY, 25).getTime();
118         	Actor actor = new Actor(new Person("test-actor" + i)
119         		.setFirstName("Anne")
120         		.setLastName("Heche")
121         		.setBirthDate(birthDate));
122     		writers.add(new Writer("writer" + i, actor, lockMode));
123     	}
124     	
125     	//start each of the threads
126     	List<Writer> working = new ArrayList<Writer>();
127     	for (Writer writer : writers) {
128     		working.add(writer); writer.start();
129     	}
130 
131     	//run until all writers complete
132     	while (!working.isEmpty()) {
133     		try { Thread.sleep(100); } catch (Exception ex) {}
134     		Iterator<Writer> itr = working.iterator();
135     		while (itr.hasNext()) {
136     			if (itr.next().isDone()) { itr.remove(); }
137     		}
138     	}
139     	
140     	//get the resultant entries in database
141 		List<Actor> actors = em.createQuery(
142 			"select a from Actor a JOIN FETCH a.person as p " +
143 			"where p.firstName=:firstName and p.lastName=:lastName", Actor.class)
144 			.setParameter("firstName", "Anne")
145 			.setParameter("lastName", "Heche")
146 			.getResultList();
147 		log.debug("actors=" + actors);
148 		for (Writer w : writers) {
149 			log.debug(String.format("%s => %s %s", w.getContext(), w.getAction(), w.getErrorText()==null?"":w.getErrorText()));
150 		}
151 		return actors.size();
152     }
153     
154     @Test
155     public void testSimple() {
156     	log.info("*** testPersistentSimple ***");
157         assertEquals("unexpected number of actors", 1, testUpsert(LockModeType.NONE, 1));
158     }
159 
160     @Test
161     public void testNONE() {
162     	log.info("*** testNONE ***");
163         int count=testUpsert(LockModeType.NONE, 5);
164         for (int i=0; i<10 && count<=1; i++) {
165             //can't always trigger race condition -- so retry
166             cleanup(em);
167             populate(em);
168             count=testUpsert(LockModeType.NONE, 5);
169         }
170         assertTrue("unexpected number of actors", count > 1);
171     }
172 
173     @Test
174     public void testPessimisticWrite1() {
175     	log.info("*** testPersistentWrite1 ***");
176         assertEquals("unexpected number of actors", 1, testUpsert(LockModeType.PESSIMISTIC_WRITE, 1));
177     }
178 
179     @Test @org.junit.Ignore //TODO: figure out why this does not work with server instance
180     public void testPessimisticWrite() {
181     	log.info("*** testPersistentWrite ***");
182         assertEquals("unexpected number of actors", 1, testUpsert(LockModeType.PESSIMISTIC_WRITE, 5));
183     }
184 
185     @Test @org.junit.Ignore //TODO: figure out why this does not work with server instance
186     public void testPessimisticForceIncrement() {
187     	log.info("*** testPersistentForceIncrement ***");
188         assertEquals("unexpected number of actors", 1, testUpsert(LockModeType.PESSIMISTIC_FORCE_INCREMENT, 5));
189     }
190 }