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.slf4j.Logger;
17  import org.slf4j.LoggerFactory;
18  import org.junit.Before;
19  import org.junit.Test;
20  
21  public class QueryLocksTest extends QueryBase {
22      private static final Logger log = LoggerFactory.getLogger(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                      if (!em_.contains(actor.getPerson())) {
88                          em_.persist(actor.getPerson());
89                      }
90                      em_.persist(actor);
91                      action=Action.INSERT;
92              } else {
93                      log.debug(context + " updating entity");
94                      actors.get(0).setBirthDate(actor.getBirthDate());
95                      action=Action.UPDATE;
96              }
97              em_.flush();
98              log.debug(context + " committing transaction version=" + actor.getVersion());
99                      em_.getTransaction().commit();
100             log.debug(context + " committed transaction version=" + actor.getVersion());
101     		} catch (PersistenceException ex) {
102     			log.debug(context + " failed " + ex);
103     			em_.getTransaction().rollback();
104     			action = Action.FAIL; errorText = ex.toString();
105     		} finally {
106     			em_.close(); em_=null;
107     		}
108     	}
109     }
110     
111     /**
112      * This method is used to setup all locking tests based on a provided locking mode.
113      * @param lockMode
114      * @return
115      */
116     protected int testUpsert(LockModeType lockMode, int count) {
117     	List<Writer> writers = new ArrayList<QueryLocksTest.Writer>();
118     	//create writer instances within their own thread
119     	for (int i=0; i<count; i++) {
120     		Date birthDate = new GregorianCalendar(1969+i, Calendar.MAY, 25).getTime();
121         	Actor actor = new Actor(new Person("test-actor" + i)
122         		.setFirstName("Anne")
123         		.setLastName("Heche")
124         		.setBirthDate(birthDate));
125     		writers.add(new Writer("writer" + i, actor, lockMode));
126     	}
127     	
128     	//start each of the threads
129     	List<Writer> working = new ArrayList<Writer>();
130     	for (Writer writer : writers) {
131     		working.add(writer); writer.start();
132     	}
133 
134     	//run until all writers complete
135     	while (!working.isEmpty()) {
136     		try { Thread.sleep(100); } catch (Exception ex) {}
137     		Iterator<Writer> itr = working.iterator();
138     		while (itr.hasNext()) {
139     			if (itr.next().isDone()) { itr.remove(); }
140     		}
141     	}
142     	
143     	//get the resultant entries in database
144 		List<Actor> actors = em.createQuery(
145 			"select a from Actor a JOIN FETCH a.person as p " +
146 			"where p.firstName=:firstName and p.lastName=:lastName", Actor.class)
147 			.setParameter("firstName", "Anne")
148 			.setParameter("lastName", "Heche")
149 			.getResultList();
150 		log.debug("actors=" + actors);
151 		for (Writer w : writers) {
152 			log.debug(String.format("%s => %s %s", w.getContext(), w.getAction(), w.getErrorText()==null?"":w.getErrorText()));
153 		}
154 		return actors.size();
155     }
156     
157     @Test
158     public void testSimple() {
159     	log.info("*** testPersistentSimple ***");
160         assertEquals("unexpected number of actors", 1, testUpsert(LockModeType.NONE, 1));
161     }
162 
163     @Test
164     public void testNONE() {
165     	log.info("*** testNONE ***");
166         int count=testUpsert(LockModeType.NONE, 5);
167         for (int i=0; i<10 && count<=1; i++) {
168             //can't always trigger race condition -- so retry
169             cleanup(em);
170             populate(em);
171             count=testUpsert(LockModeType.NONE, 5);
172         }
173         assertTrue("unexpected number of actors", count > 1);
174     }
175 
176     @Test
177     public void testPessimisticWrite1() {
178     	log.info("*** testPersistentWrite1 ***");
179         assertEquals("unexpected number of actors", 1, testUpsert(LockModeType.PESSIMISTIC_WRITE, 1));
180     }
181 
182     @Test @org.junit.Ignore //TODO: figure out why this does not work with server instance
183     public void testPessimisticWrite() {
184     	log.info("*** testPersistentWrite ***");
185         assertEquals("unexpected number of actors", 1, testUpsert(LockModeType.PESSIMISTIC_WRITE, 5));
186     }
187 
188     @Test @org.junit.Ignore //TODO: figure out why this does not work with server instance
189     public void testPessimisticForceIncrement() {
190     	log.info("*** testPersistentForceIncrement ***");
191         assertEquals("unexpected number of actors", 1, testUpsert(LockModeType.PESSIMISTIC_FORCE_INCREMENT, 5));
192     }
193 }