Skip to content

Commit 989449e

Browse files
committed
Add Objectify.transactReadOnly()
1 parent edd4150 commit 989449e

10 files changed

Lines changed: 214 additions & 117 deletions

File tree

src/main/java/com/googlecode/objectify/Objectify.java

Lines changed: 30 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -193,7 +193,11 @@ public interface Objectify
193193

194194
/**
195195
* <p>Executes work in a transaction. If there is already a transaction context, that context will be inherited.
196-
* If there is not already a transaction context, a new transaction will be started.</p>
196+
* If there is not already a transaction context, a new read-write transaction will be started.</p>
197+
*
198+
* <p>Transactions are read-only or read-write depending on how they are started. Inner transact() or
199+
* transactReadOnly() blocks will inherit the outer transaction, whether it is read-only or not.
200+
* However, attempts to write to the datastore inside read-only transactions will fail with an exception.</p>
197201
*
198202
* <p>Within {@code Work.run()}, obtain the correct transactional {@code Objectify} instance by calling
199203
* {@code ObjectifyService.ofy()}</p>
@@ -214,7 +218,7 @@ public interface Objectify
214218
void transact(Runnable work);
215219

216220
/**
217-
* <p>Executes work in a new transaction. Note that this is equivalent to {@code transactNew(Integer.MAX_VALUE, work);}</p>
221+
* <p>Executes work in a new read-write transaction. Note that this is equivalent to {@code transactNew(Integer.MAX_VALUE, work);}</p>
218222
*
219223
* <p>ConcurrentModificationExceptions will cause the transaction to repeat as many times as necessary to
220224
* finish the job. Work <b>MUST</b> idempotent.</p>
@@ -233,7 +237,7 @@ public interface Objectify
233237
void transactNew(Runnable work);
234238

235239
/**
236-
* <p>Executes the work in a new transaction, repeating up to limitTries times when a ConcurrentModificationException
240+
* <p>Executes the work in a new read-write transaction, repeating up to limitTries times when a ConcurrentModificationException
237241
* is thrown. This requires your Work to be idempotent; otherwise limit tries to 1.
238242
*
239243
* <p>Within {@code Work.run()}, obtain the new transactional {@code Objectify} instance by calling {@code ObjectifyService.ofy()}</p>
@@ -250,9 +254,31 @@ public interface Objectify
250254
*/
251255
void transactNew(int limitTries, Runnable work);
252256

257+
/**
258+
* <p>Executes work in a readonly transaction. If there is already a transaction context, that
259+
* context will be inherited. If there is not already a transaction context, a new read-only transaction
260+
* will be started.</p>
261+
*
262+
* <p>Transactions are read-only or read-write depending on how they are started. Inner transact() or
263+
* transactReadOnly() blocks will inherit the outer transaction, whether it is read-only or not.
264+
* However, attempts to write to the datastore inside read-only transactions will fail with an exception.</p>
265+
*
266+
* <p>Within {@code Work.run()}, obtain the correct transactional {@code Objectify} instance by calling
267+
* {@code ObjectifyService.ofy()}</p>
268+
*
269+
* <p>Readonly transactions do not retry.</p>
270+
*/
271+
<R> R transactReadOnly(Work<R> work);
272+
273+
/**
274+
* <p>Exactly the same behavior as the Work version, but doesn't force you to return something from your lambda.</p>
275+
*/
276+
void transactReadOnly(Runnable work);
277+
253278
/**
254279
* <p>Executes the work with the transactional behavior defined by the parameter txnType. This is very similar
255-
* to EJB semantics. The work can inherit a transaction, create a new transaction, prevent transactions, etc.</p>
280+
* to EJB semantics. The work can inherit a transaction, create a new transaction, prevent transactions, etc.
281+
* Created transactions are always read-write, though inherited transactions can be either.</p>
256282
*
257283
* <p>This method principally exists to facilitate implementation of AOP interceptors that provide EJB-like behavior.
258284
* Usually you will call {@code transact()} or {@code transactNew()} when writing code.</p>

src/main/java/com/googlecode/objectify/cache/CachingAsyncDatastore.java

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515
import java.util.HashMap;
1616
import java.util.List;
1717
import java.util.Map;
18+
import java.util.Optional;
1819
import java.util.concurrent.Future;
1920

2021
/**
@@ -59,13 +60,8 @@ protected void empty(final Iterable<Key> keys) {
5960
}
6061

6162
@Override
62-
public AsyncTransaction newTransaction(final Runnable afterCommit) {
63-
return new CachingAsyncTransaction(raw.newTransaction(afterCommit), memcache);
64-
}
65-
66-
@Override
67-
public AsyncTransaction newTransaction(final Runnable afterCommit, ByteString prevTxnHandle) {
68-
return new CachingAsyncTransaction(raw.newTransaction(afterCommit, prevTxnHandle), memcache);
63+
public AsyncTransaction newTransaction(final boolean readOnly, final Runnable afterCommit, Optional<ByteString> prevTxnHandle) {
64+
return new CachingAsyncTransaction(raw.newTransaction(readOnly, afterCommit, prevTxnHandle), memcache);
6965
}
7066

7167
@Override

src/main/java/com/googlecode/objectify/impl/AsyncDatastore.java

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,18 +2,24 @@
22

33
import com.google.protobuf.ByteString;
44

5+
import java.util.Optional;
6+
57
/**
68
* The new datastore SDK has a neat structure of interfaces and implementations (transaction, datastorereader, etc)
79
* but doesn't currently support async operations. We need to shim in a Future-based API so that we can seamlessly
810
* support it when it becomes available. We'll remove this parallel hierarchy then.
911
*/
1012
public interface AsyncDatastore extends AsyncDatastoreReaderWriter {
1113

12-
/**
13-
*/
14-
AsyncTransaction newTransaction(Runnable afterCommit);
14+
@Deprecated
15+
default AsyncTransaction newTransaction(Runnable afterCommit) {
16+
return newTransaction(false, afterCommit, Optional.empty());
17+
}
18+
19+
@Deprecated
20+
default AsyncTransaction newTransaction(Runnable afterCommit, ByteString prevTxnHandle) {
21+
return newTransaction(false, afterCommit, Optional.ofNullable(prevTxnHandle));
22+
}
1523

16-
/**
17-
*/
18-
AsyncTransaction newTransaction(Runnable afterCommit, ByteString prevTxnHandle);
24+
AsyncTransaction newTransaction(boolean readOnly, Runnable afterCommit, Optional<ByteString> prevTxnHandle);
1925
}

src/main/java/com/googlecode/objectify/impl/AsyncDatastoreImpl.java

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2,8 +2,11 @@
22

33
import com.google.cloud.datastore.Datastore;
44
import com.google.datastore.v1.TransactionOptions;
5+
import com.google.datastore.v1.TransactionOptions.ReadOnly;
56
import com.google.protobuf.ByteString;
67

8+
import java.util.Optional;
9+
710
/** */
811
public class AsyncDatastoreImpl extends AsyncDatastoreReaderWriterImpl implements AsyncDatastore {
912

@@ -16,16 +19,24 @@ public AsyncDatastoreImpl(final Datastore raw) {
1619
}
1720

1821
@Override
19-
public AsyncTransaction newTransaction(final Runnable afterCommit) {
20-
return newTransaction(afterCommit, null);
21-
}
22+
public AsyncTransaction newTransaction(final boolean readOnly, final Runnable afterCommit, final Optional<ByteString> prevTxnHandle) {
23+
final TransactionOptions.Builder txnOptions = TransactionOptions.newBuilder();
2224

23-
@Override
24-
public AsyncTransaction newTransaction(final Runnable afterCommit, final ByteString prevTxnHandle) {
25-
TransactionOptions.Builder txnOptions = TransactionOptions.newBuilder();
26-
if (prevTxnHandle != null) {
27-
txnOptions.getReadWriteBuilder().setPreviousTransaction(prevTxnHandle);
25+
if (readOnly) {
26+
txnOptions.setReadOnly(ReadOnly.newBuilder().build());
2827
}
28+
29+
prevTxnHandle.ifPresent(handle -> {
30+
if (readOnly) {
31+
// setPreviousTransaction() doesn't exist on the readonly version, presumably because
32+
// readonly transactions don't retry.
33+
//txnOptions.getReadOnlyBuilder().setPreviousTransaction(handle);
34+
throw new IllegalStateException("This should be impossible; readonly transactions don't retry");
35+
} else {
36+
txnOptions.getReadWriteBuilder().setPreviousTransaction(handle);
37+
}
38+
});
39+
2940
return new AsyncTransactionImpl(datastore.newTransaction(txnOptions.build()), afterCommit);
3041
}
3142
}

src/main/java/com/googlecode/objectify/impl/ObjectifyImpl.java

Lines changed: 15 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -36,20 +36,16 @@
3636
*
3737
* @author Jeff Schnitzer <jeff@infohazard.org>
3838
*/
39-
public class ObjectifyImpl implements Objectify, Closeable
40-
{
39+
public class ObjectifyImpl implements Objectify, Closeable {
40+
4141
/** The factory that produced us */
4242
protected final ObjectifyFactory factory;
4343

44-
/** */
4544
@Getter
4645
protected final ObjectifyOptions options;
4746

48-
/** */
4947
protected final Transactor transactor;
5048

51-
/**
52-
*/
5349
public ObjectifyImpl(final ObjectifyFactory fact) {
5450
this.factory = fact;
5551
this.options = new ObjectifyOptions();
@@ -62,9 +58,6 @@ public ObjectifyImpl(final ObjectifyFactory factory, final ObjectifyOptions opti
6258
this.transactor = transactor;
6359
}
6460

65-
/* (non-Javadoc)
66-
* @see com.googlecode.objectify.Objectify#getFactory()
67-
*/
6861
public ObjectifyFactory factory() {
6962
return this.factory;
7063
}
@@ -74,58 +67,37 @@ public Objectify namespace(final String namespace) {
7467
return options(options.namespace(namespace));
7568
}
7669

77-
/* (non-Javadoc)
78-
* @see com.googlecode.objectify.Objectify#find()
79-
*/
8070
@Override
8171
public Loader load() {
8272
return new LoaderImpl(this);
8373
}
8474

85-
/* (non-Javadoc)
86-
* @see com.googlecode.objectify.Objectify#put()
87-
*/
8875
@Override
8976
public Saver save() {
9077
return new SaverImpl(this);
9178
}
9279

93-
/* (non-Javadoc)
94-
* @see com.googlecode.objectify.Objectify#delete()
95-
*/
9680
@Override
9781
public Deleter delete() {
9882
return new DeleterImpl(this);
9983
}
10084

101-
/* (non-Javadoc)
102-
* @see com.googlecode.objectify.Objectify#defer()
103-
*/
10485
@Override
10586
public Deferred defer() {
10687
return new DeferredImpl(this);
10788
}
10889

109-
/* (non-Javadoc)
110-
* @see com.googlecode.objectify.Objectify#deadline(java.lang.Double)
111-
*/
11290
@Override
11391
public Objectify deadline(final Double value) {
11492
// A no-op
11593
return this;
11694
}
11795

118-
/* (non-Javadoc)
119-
* @see com.googlecode.objectify.Objectify#cache(boolean)
120-
*/
12196
@Override
12297
public Objectify cache(boolean value) {
12398
return options(options.cache(value));
12499
}
125100

126-
/* (non-Javadoc)
127-
* @see com.googlecode.objectify.Objectify#mandatoryTransactions(boolean)
128-
*/
129101
@Override
130102
public Objectify mandatoryTransactions(boolean value) {
131103
return options(options.mandatoryTransactions(value));
@@ -146,16 +118,10 @@ protected ObjectifyImpl makeNew(final ObjectifyOptions opts, final Transactor tr
146118
return new ObjectifyImpl(factory, opts, transactor);
147119
}
148120

149-
/* (non-Javadoc)
150-
* @see com.googlecode.objectify.Objectify#getTxn()
151-
*/
152121
public AsyncTransaction getTransaction() {
153122
return transactor.getTransaction();
154123
}
155124

156-
/* (non-Javadoc)
157-
* @see com.googlecode.objectify.Objectify#execute(com.googlecode.objectify.TxnType, com.googlecode.objectify.Work)
158-
*/
159125
@Override
160126
public <R> R execute(final TxnType txnType, final Work<R> work) {
161127
return transactor.execute(this, txnType, work);
@@ -182,9 +148,6 @@ public void transactionless(final Runnable work) {
182148
});
183149
}
184150

185-
/* (non-Javadoc)
186-
* @see com.googlecode.objectify.Objectify#transact(com.googlecode.objectify.Work)
187-
*/
188151
@Override
189152
public <R> R transact(Work<R> work) {
190153
return transactor.transact(this, work);
@@ -198,9 +161,19 @@ public void transact(final Runnable work) {
198161
});
199162
}
200163

201-
/* (non-Javadoc)
202-
* @see com.googlecode.objectify.Objectify#transact(com.googlecode.objectify.Work)
203-
*/
164+
@Override
165+
public <R> R transactReadOnly(Work<R> work) {
166+
return transactor.transactReadOnly(this, work);
167+
}
168+
169+
@Override
170+
public void transactReadOnly(final Runnable work) {
171+
transactReadOnly((Work<Void>)() -> {
172+
work.run();
173+
return null;
174+
});
175+
}
176+
204177
@Override
205178
public <R> R transactNew(Work<R> work) {
206179
return this.transactNew(Transactor.DEFAULT_TRY_LIMIT, work);
@@ -214,9 +187,6 @@ public void transactNew(final Runnable work) {
214187
});
215188
}
216189

217-
/* (non-Javadoc)
218-
* @see com.googlecode.objectify.Objectify#transactNew(com.googlecode.objectify.Work)
219-
*/
220190
@Override
221191
public <R> R transactNew(int limitTries, Work<R> work) {
222192
return transactor.transactNew(this, limitTries, work);
@@ -230,9 +200,6 @@ public void transactNew(int limitTries, final Runnable work) {
230200
});
231201
}
232202

233-
/* (non-Javadoc)
234-
* @see com.googlecode.objectify.Objectify#clear()
235-
*/
236203
@Override
237204
public void clear() {
238205
transactor.getSession().clear();
@@ -317,9 +284,6 @@ protected Session getSession() {
317284
return this.transactor.getSession();
318285
}
319286

320-
/* (non-Javadoc)
321-
* @see com.googlecode.objectify.Objectify#isLoaded(com.googlecode.objectify.Key)
322-
*/
323287
@Override
324288
public boolean isLoaded(final Key<?> key) {
325289
return transactor.getSession().contains(key);

src/main/java/com/googlecode/objectify/impl/Transactor.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -68,6 +68,11 @@ public abstract class Transactor
6868
*/
6969
abstract public <R> R transact(ObjectifyImpl parent, Work<R> work);
7070

71+
/**
72+
* @see Objectify#transactReadOnly(Work)
73+
*/
74+
abstract public <R> R transactReadOnly(ObjectifyImpl parent, Work<R> work);
75+
7176
/**
7277
* @see Objectify#transactNew(int, Work)
7378
*/

0 commit comments

Comments
 (0)