|
| 1 | +/* |
| 2 | + * Copyright 2016 Google Inc. All Rights Reserved. |
| 3 | + * |
| 4 | + * Licensed under the Apache License, Version 2.0 (the "License"); |
| 5 | + * you may not use this file except in compliance with the License. |
| 6 | + * You may obtain a copy of the License at |
| 7 | + * |
| 8 | + * http://www.apache.org/licenses/LICENSE-2.0 |
| 9 | + * |
| 10 | + * Unless required by applicable law or agreed to in writing, software |
| 11 | + * distributed under the License is distributed on an "AS IS" BASIS, |
| 12 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 13 | + * See the License for the specific language governing permissions and |
| 14 | + * limitations under the License. |
| 15 | + */ |
| 16 | + |
| 17 | +package com.example.appengine; |
| 18 | + |
| 19 | +import static com.google.common.truth.Truth.assertThat; |
| 20 | +import static org.junit.Assert.fail; |
| 21 | + |
| 22 | +import com.google.appengine.api.datastore.DatastoreService; |
| 23 | +import com.google.appengine.api.datastore.DatastoreServiceFactory; |
| 24 | +import com.google.appengine.api.datastore.Entity; |
| 25 | +import com.google.appengine.api.datastore.EntityNotFoundException; |
| 26 | +import com.google.appengine.api.datastore.FetchOptions; |
| 27 | +import com.google.appengine.api.datastore.Key; |
| 28 | +import com.google.appengine.api.datastore.KeyFactory; |
| 29 | +import com.google.appengine.api.datastore.PreparedQuery; |
| 30 | +import com.google.appengine.api.datastore.Query; |
| 31 | +import com.google.appengine.api.datastore.Transaction; |
| 32 | +import com.google.appengine.api.datastore.TransactionOptions; |
| 33 | +import com.google.appengine.api.taskqueue.Queue; |
| 34 | +import com.google.appengine.api.taskqueue.QueueFactory; |
| 35 | +import com.google.appengine.api.taskqueue.TaskOptions; |
| 36 | +import com.google.appengine.tools.development.testing.LocalDatastoreServiceTestConfig; |
| 37 | +import com.google.appengine.tools.development.testing.LocalServiceTestHelper; |
| 38 | +import org.junit.After; |
| 39 | +import org.junit.Before; |
| 40 | +import org.junit.Test; |
| 41 | +import org.junit.runner.RunWith; |
| 42 | +import org.junit.runners.JUnit4; |
| 43 | + |
| 44 | +import java.util.ConcurrentModificationException; |
| 45 | +import java.util.Date; |
| 46 | +import java.util.List; |
| 47 | + |
| 48 | +/** |
| 49 | + * Unit tests to demonstrate App Engine Datastore transactions. |
| 50 | + */ |
| 51 | +@RunWith(JUnit4.class) |
| 52 | +public class TransactionsTest { |
| 53 | + |
| 54 | + private final LocalServiceTestHelper helper = |
| 55 | + new LocalServiceTestHelper( |
| 56 | + // Use High Rep job policy to allow cross group transactions in tests. |
| 57 | + new LocalDatastoreServiceTestConfig().setApplyAllHighRepJobPolicy()); |
| 58 | + |
| 59 | + private DatastoreService datastore; |
| 60 | + |
| 61 | + @Before |
| 62 | + public void setUp() { |
| 63 | + helper.setUp(); |
| 64 | + datastore = DatastoreServiceFactory.getDatastoreService(); |
| 65 | + } |
| 66 | + |
| 67 | + @After |
| 68 | + public void tearDown() { |
| 69 | + // Clean up any dangling transactions. |
| 70 | + Transaction txn = datastore.getCurrentTransaction(null); |
| 71 | + if (txn != null && txn.isActive()) { |
| 72 | + txn.rollback(); |
| 73 | + } |
| 74 | + helper.tearDown(); |
| 75 | + } |
| 76 | + |
| 77 | + @Test |
| 78 | + public void usingTransactions() throws Exception { |
| 79 | + Entity joe = new Entity("Employee", "Joe"); |
| 80 | + datastore.put(joe); |
| 81 | + |
| 82 | + // [START using_transactions] |
| 83 | + DatastoreService datastore = DatastoreServiceFactory.getDatastoreService(); |
| 84 | + Transaction txn = datastore.beginTransaction(); |
| 85 | + try { |
| 86 | + Key employeeKey = KeyFactory.createKey("Employee", "Joe"); |
| 87 | + Entity employee = datastore.get(employeeKey); |
| 88 | + employee.setProperty("vacationDays", 10); |
| 89 | + |
| 90 | + datastore.put(txn, employee); |
| 91 | + |
| 92 | + txn.commit(); |
| 93 | + } finally { |
| 94 | + if (txn.isActive()) { |
| 95 | + txn.rollback(); |
| 96 | + } |
| 97 | + } |
| 98 | + // [END using_transactions] |
| 99 | + } |
| 100 | + |
| 101 | + @Test |
| 102 | + public void entityGroups() throws Exception { |
| 103 | + try { |
| 104 | + // [START entity_groups] |
| 105 | + DatastoreService datastore = DatastoreServiceFactory.getDatastoreService(); |
| 106 | + Entity person = new Entity("Person", "tom"); |
| 107 | + datastore.put(person); |
| 108 | + |
| 109 | + // Transactions on root entities |
| 110 | + Transaction txn = datastore.beginTransaction(); |
| 111 | + |
| 112 | + Entity tom = datastore.get(person.getKey()); |
| 113 | + tom.setProperty("age", 40); |
| 114 | + datastore.put(txn, tom); |
| 115 | + txn.commit(); |
| 116 | + |
| 117 | + // Transactions on child entities |
| 118 | + txn = datastore.beginTransaction(); |
| 119 | + tom = datastore.get(person.getKey()); |
| 120 | + Entity photo = new Entity("Photo", tom.getKey()); |
| 121 | + |
| 122 | + // Create a Photo that is a child of the Person entity named "tom" |
| 123 | + photo.setProperty("photoUrl", "http://domain.com/path/to/photo.jpg"); |
| 124 | + datastore.put(txn, photo); |
| 125 | + txn.commit(); |
| 126 | + |
| 127 | + // Transactions on entities in different entity groups |
| 128 | + txn = datastore.beginTransaction(); |
| 129 | + tom = datastore.get(person.getKey()); |
| 130 | + Entity photoNotAChild = new Entity("Photo"); |
| 131 | + photoNotAChild.setProperty("photoUrl", "http://domain.com/path/to/photo.jpg"); |
| 132 | + datastore.put(txn, photoNotAChild); |
| 133 | + |
| 134 | + // Throws IllegalArgumentException because the Person entity |
| 135 | + // and the Photo entity belong to different entity groups. |
| 136 | + txn.commit(); |
| 137 | + // [END entity_groups] |
| 138 | + fail("Expected IllegalArgumentException"); |
| 139 | + } catch (IllegalArgumentException expected) { |
| 140 | + // We expect to get an exception that complains that we don't have a XG-transaction. |
| 141 | + } |
| 142 | + } |
| 143 | + |
| 144 | + @Test |
| 145 | + public void creatingAnEntityInASpecificEntityGroup() throws Exception { |
| 146 | + String boardName = "my-message-board"; |
| 147 | + |
| 148 | + // [START creating_an_entity_in_a_specific_entity_group] |
| 149 | + DatastoreService datastore = DatastoreServiceFactory.getDatastoreService(); |
| 150 | + |
| 151 | + String messageTitle = "Some Title"; |
| 152 | + String messageText = "Some message."; |
| 153 | + Date postDate = new Date(); |
| 154 | + |
| 155 | + Transaction txn = datastore.beginTransaction(); |
| 156 | + |
| 157 | + Key messageBoardKey = KeyFactory.createKey("MessageBoard", boardName); |
| 158 | + |
| 159 | + Entity message = new Entity("Message", messageBoardKey); |
| 160 | + message.setProperty("message_title", messageTitle); |
| 161 | + message.setProperty("message_text", messageText); |
| 162 | + message.setProperty("post_date", postDate); |
| 163 | + datastore.put(txn, message); |
| 164 | + |
| 165 | + txn.commit(); |
| 166 | + // [END creating_an_entity_in_a_specific_entity_group] |
| 167 | + } |
| 168 | + |
| 169 | + @Test |
| 170 | + public void crossGroupTransactions() throws Exception { |
| 171 | + // [START cross-group_XG_transactions_using_the_Java_low-level_API] |
| 172 | + DatastoreService datastore = DatastoreServiceFactory.getDatastoreService(); |
| 173 | + TransactionOptions options = TransactionOptions.Builder.withXG(true); |
| 174 | + Transaction txn = datastore.beginTransaction(options); |
| 175 | + |
| 176 | + Entity a = new Entity("A"); |
| 177 | + a.setProperty("a", 22); |
| 178 | + datastore.put(txn, a); |
| 179 | + |
| 180 | + Entity b = new Entity("B"); |
| 181 | + b.setProperty("b", 11); |
| 182 | + datastore.put(txn, b); |
| 183 | + |
| 184 | + txn.commit(); |
| 185 | + // [END cross-group_XG_transactions_using_the_Java_low-level_API] |
| 186 | + } |
| 187 | + |
| 188 | + @Test |
| 189 | + public void usesForTransactions_relativeUpdates() throws Exception { |
| 190 | + String boardName = "my-message-board"; |
| 191 | + Entity b = new Entity("MessageBoard", boardName); |
| 192 | + b.setProperty("count", 41); |
| 193 | + datastore.put(b); |
| 194 | + |
| 195 | + // [START uses_for_transactions_1] |
| 196 | + int retries = 3; |
| 197 | + while (true) { |
| 198 | + Transaction txn = datastore.beginTransaction(); |
| 199 | + try { |
| 200 | + Key boardKey = KeyFactory.createKey("MessageBoard", boardName); |
| 201 | + Entity messageBoard = datastore.get(boardKey); |
| 202 | + |
| 203 | + long count = (Long) messageBoard.getProperty("count"); |
| 204 | + ++count; |
| 205 | + messageBoard.setProperty("count", count); |
| 206 | + datastore.put(txn, messageBoard); |
| 207 | + |
| 208 | + txn.commit(); |
| 209 | + break; |
| 210 | + } catch (ConcurrentModificationException e) { |
| 211 | + if (retries == 0) { |
| 212 | + throw e; |
| 213 | + } |
| 214 | + // Allow retry to occur |
| 215 | + --retries; |
| 216 | + } finally { |
| 217 | + if (txn.isActive()) { |
| 218 | + txn.rollback(); |
| 219 | + } |
| 220 | + } |
| 221 | + } |
| 222 | + // [END uses_for_transactions_1] |
| 223 | + |
| 224 | + b = datastore.get(KeyFactory.createKey("MessageBoard", boardName)); |
| 225 | + assertThat((long) b.getProperty("count")).named("board.count").isEqualTo(42L); |
| 226 | + } |
| 227 | + |
| 228 | + private Entity fetchOrCreate(String boardName) { |
| 229 | + // [START uses_for_transactions_2] |
| 230 | + Transaction txn = datastore.beginTransaction(); |
| 231 | + Entity messageBoard; |
| 232 | + Key boardKey; |
| 233 | + try { |
| 234 | + boardKey = KeyFactory.createKey("MessageBoard", boardName); |
| 235 | + messageBoard = datastore.get(boardKey); |
| 236 | + } catch (EntityNotFoundException e) { |
| 237 | + messageBoard = new Entity("MessageBoard", boardName); |
| 238 | + messageBoard.setProperty("count", 0L); |
| 239 | + boardKey = datastore.put(txn, messageBoard); |
| 240 | + } |
| 241 | + txn.commit(); |
| 242 | + // [END uses_for_transactions_2] |
| 243 | + |
| 244 | + return messageBoard; |
| 245 | + } |
| 246 | + |
| 247 | + @Test |
| 248 | + public void usesForTransactions_fetchOrCreate_fetchesExisting() throws Exception { |
| 249 | + Entity b = new Entity("MessageBoard", "my-message-board"); |
| 250 | + b.setProperty("count", 7); |
| 251 | + datastore.put(b); |
| 252 | + |
| 253 | + Entity board = fetchOrCreate("my-message-board"); |
| 254 | + |
| 255 | + assertThat((long) board.getProperty("count")).named("board.count").isEqualTo(7L); |
| 256 | + } |
| 257 | + |
| 258 | + @Test |
| 259 | + public void usesForTransactions_fetchOrCreate_createsNew() throws Exception { |
| 260 | + Entity board = fetchOrCreate("my-message-board"); |
| 261 | + assertThat((long) board.getProperty("count")).named("board.count").isEqualTo(0L); |
| 262 | + } |
| 263 | + |
| 264 | + @Test |
| 265 | + public void usesForTransactions_readSnapshot() throws Exception { |
| 266 | + String boardName = "my-message-board"; |
| 267 | + Entity b = new Entity("MessageBoard", boardName); |
| 268 | + b.setProperty("count", 13); |
| 269 | + datastore.put(b); |
| 270 | + |
| 271 | + // [START uses_for_transactions_3] |
| 272 | + DatastoreService ds = DatastoreServiceFactory.getDatastoreService(); |
| 273 | + |
| 274 | + // Display information about a message board and its first 10 messages. |
| 275 | + Key boardKey = KeyFactory.createKey("MessageBoard", boardName); |
| 276 | + |
| 277 | + Transaction txn = datastore.beginTransaction(); |
| 278 | + |
| 279 | + Entity messageBoard = datastore.get(boardKey); |
| 280 | + long count = (Long) messageBoard.getProperty("count"); |
| 281 | + |
| 282 | + Query q = new Query("Message", boardKey); |
| 283 | + |
| 284 | + // This is an ancestor query. |
| 285 | + PreparedQuery pq = datastore.prepare(txn, q); |
| 286 | + List<Entity> messages = pq.asList(FetchOptions.Builder.withLimit(10)); |
| 287 | + |
| 288 | + txn.commit(); |
| 289 | + // [END uses_for_transactions_3] |
| 290 | + |
| 291 | + assertThat(count).named("board.count").isEqualTo(13L); |
| 292 | + } |
| 293 | + |
| 294 | + @Test |
| 295 | + public void transactionalTaskEnqueuing() throws Exception { |
| 296 | + // [START transactional_task_enqueuing] |
| 297 | + DatastoreService datastore = DatastoreServiceFactory.getDatastoreService(); |
| 298 | + Queue queue = QueueFactory.getDefaultQueue(); |
| 299 | + Transaction txn = datastore.beginTransaction(); |
| 300 | + // ... |
| 301 | + |
| 302 | + queue.add(TaskOptions.Builder.withUrl("/path/to/handler")); |
| 303 | + |
| 304 | + // ... |
| 305 | + |
| 306 | + txn.commit(); |
| 307 | + // [END transactional_task_enqueuing] |
| 308 | + } |
| 309 | +} |
0 commit comments