Transaction API for Single, Master/Slave and Sentinel mode. #1372
parent
fad11d51e2
commit
042cfcb7cb
@ -0,0 +1,166 @@
|
||||
/**
|
||||
* Copyright 2018 Nikita Koksharov
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.redisson.api;
|
||||
|
||||
import org.redisson.client.codec.Codec;
|
||||
|
||||
/**
|
||||
* Transaction object allows to execute transactions over Redisson objects.
|
||||
* Uses locks for write operations and maintains data modification operations list till the commit/rollback operation.
|
||||
* <p>
|
||||
* Transaction isolation level: <b>READ_COMMITTED</b>
|
||||
*
|
||||
* @author Nikita Koksharov
|
||||
*
|
||||
*/
|
||||
public interface RTransaction {
|
||||
|
||||
/**
|
||||
* Returns transactional object holder instance by name.
|
||||
*
|
||||
* @param <V> type of value
|
||||
* @param name - name of object
|
||||
* @return Bucket object
|
||||
*/
|
||||
<V> RBucket<V> getBucket(String name);
|
||||
|
||||
/**
|
||||
* Returns transactional object holder instance by name
|
||||
* using provided codec for object.
|
||||
*
|
||||
* @param <V> type of value
|
||||
* @param name - name of object
|
||||
* @param codec - codec for values
|
||||
* @return Bucket object
|
||||
*/
|
||||
<V> RBucket<V> getBucket(String name, Codec codec);
|
||||
|
||||
/**
|
||||
* Returns transactional map instance by name.
|
||||
*
|
||||
* @param <K> type of key
|
||||
* @param <V> type of value
|
||||
* @param name - name of object
|
||||
* @return Map object
|
||||
*/
|
||||
<K, V> RMap<K, V> getMap(String name);
|
||||
|
||||
/**
|
||||
* Returns transactional map instance by name
|
||||
* using provided codec for both map keys and values.
|
||||
*
|
||||
* @param <K> type of key
|
||||
* @param <V> type of value
|
||||
* @param name - name of object
|
||||
* @param codec - codec for keys and values
|
||||
* @return Map object
|
||||
*/
|
||||
<K, V> RMap<K, V> getMap(String name, Codec codec);
|
||||
|
||||
/**
|
||||
* Returns transactional set instance by name.
|
||||
*
|
||||
* @param <V> type of value
|
||||
* @param name - name of object
|
||||
* @return Set object
|
||||
*/
|
||||
<V> RSet<V> getSet(String name);
|
||||
|
||||
/**
|
||||
* Returns transactional set instance by name
|
||||
* using provided codec for set objects.
|
||||
*
|
||||
* @param <V> type of value
|
||||
* @param name - name of object
|
||||
* @param codec - codec for values
|
||||
* @return Set object
|
||||
*/
|
||||
<V> RSet<V> getSet(String name, Codec codec);
|
||||
|
||||
/**
|
||||
* Returns transactional set-based cache instance by <code>name</code>.
|
||||
* Supports value eviction with a given TTL value.
|
||||
*
|
||||
* <p>If eviction is not required then it's better to use regular map {@link #getSet(String)}.</p>
|
||||
*
|
||||
* @param <V> type of value
|
||||
* @param name - name of object
|
||||
* @return SetCache object
|
||||
*/
|
||||
<V> RSetCache<V> getSetCache(String name);
|
||||
|
||||
/**
|
||||
* Returns transactional set-based cache instance by <code>name</code>.
|
||||
* Supports value eviction with a given TTL value.
|
||||
*
|
||||
* <p>If eviction is not required then it's better to use regular map {@link #getSet(String, Codec)}.</p>
|
||||
*
|
||||
* @param <V> type of value
|
||||
* @param name - name of object
|
||||
* @param codec - codec for values
|
||||
* @return SetCache object
|
||||
*/
|
||||
<V> RSetCache<V> getSetCache(String name, Codec codec);
|
||||
|
||||
/**
|
||||
* Returns transactional map-based cache instance by name.
|
||||
* Supports entry eviction with a given MaxIdleTime and TTL settings.
|
||||
* <p>
|
||||
* If eviction is not required then it's better to use regular map {@link #getMap(String)}.</p>
|
||||
*
|
||||
* @param <K> type of key
|
||||
* @param <V> type of value
|
||||
* @param name - name of object
|
||||
* @return MapCache object
|
||||
*/
|
||||
<K, V> RMapCache<K, V> getMapCache(String name);
|
||||
|
||||
/**
|
||||
* Returns transactional map-based cache instance by <code>name</code>
|
||||
* using provided <code>codec</code> for both cache keys and values.
|
||||
* Supports entry eviction with a given MaxIdleTime and TTL settings.
|
||||
* <p>
|
||||
* If eviction is not required then it's better to use regular map {@link #getMap(String, Codec)}.
|
||||
*
|
||||
* @param <K> type of key
|
||||
* @param <V> type of value
|
||||
* @param name - object name
|
||||
* @param codec - codec for keys and values
|
||||
* @return MapCache object
|
||||
*/
|
||||
<K, V> RMapCache<K, V> getMapCache(String name, Codec codec);
|
||||
|
||||
/**
|
||||
* Returns transactional local cached map proxy for specified local cached map instance.
|
||||
*
|
||||
* @param <K> type of key
|
||||
* @param <V> type of value
|
||||
* @param fromInstance - local cache map instance
|
||||
* @return LocalCachedMap object
|
||||
*/
|
||||
<K, V> RLocalCachedMap<K, V> getLocalCachedMap(RLocalCachedMap<K, V> fromInstance);
|
||||
|
||||
/**
|
||||
* Commits all changes made on this transaction.
|
||||
*/
|
||||
void commit();
|
||||
|
||||
/**
|
||||
* Rollback all changes made on this transaction.
|
||||
*/
|
||||
void rollback();
|
||||
|
||||
}
|
@ -0,0 +1,134 @@
|
||||
/**
|
||||
* Copyright 2018 Nikita Koksharov
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.redisson.api;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
/**
|
||||
* Configuration for Transaction.
|
||||
*
|
||||
* @author Nikita Koksharov
|
||||
*
|
||||
*/
|
||||
public class TransactionOptions {
|
||||
|
||||
private long responseTimeout = 3000;
|
||||
private int retryAttempts = 3;
|
||||
private long retryInterval = 1500;
|
||||
|
||||
private long syncTimeout = 5000;
|
||||
|
||||
private long timeout = 5000;
|
||||
|
||||
private TransactionOptions() {
|
||||
}
|
||||
|
||||
public static TransactionOptions defaults() {
|
||||
return new TransactionOptions();
|
||||
}
|
||||
|
||||
public long getResponseTimeout() {
|
||||
return responseTimeout;
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines timeout for Redis response.
|
||||
* Starts to countdown when Redis command has been successfully sent.
|
||||
* <p>
|
||||
* Default is <code>3000 milliseconds</code>
|
||||
*
|
||||
* @param timeout value
|
||||
* @param unit value
|
||||
* @return self instance
|
||||
*/
|
||||
public TransactionOptions responseTimeout(long timeout, TimeUnit unit) {
|
||||
this.responseTimeout = unit.toMillis(timeout);
|
||||
return this;
|
||||
}
|
||||
|
||||
public int getRetryAttempts() {
|
||||
return retryAttempts;
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines attempts amount to send Redis commands batch
|
||||
* if it hasn't been sent already.
|
||||
* <p>
|
||||
* Default is <code>3 attempts</code>
|
||||
*
|
||||
* @param retryAttempts value
|
||||
* @return self instance
|
||||
*/
|
||||
public TransactionOptions retryAttempts(int retryAttempts) {
|
||||
this.retryAttempts = retryAttempts;
|
||||
return this;
|
||||
}
|
||||
|
||||
public long getRetryInterval() {
|
||||
return retryInterval;
|
||||
}
|
||||
|
||||
/**
|
||||
* Defines time interval for each attempt to send Redis commands batch
|
||||
* if it hasn't been sent already.
|
||||
* <p>
|
||||
* Default is <code>1500 milliseconds</code>
|
||||
*
|
||||
* @param retryInterval - time interval
|
||||
* @param retryIntervalUnit - time interval unit
|
||||
* @return self instance
|
||||
*/
|
||||
public TransactionOptions retryInterval(long retryInterval, TimeUnit retryIntervalUnit) {
|
||||
this.retryInterval = retryIntervalUnit.toMillis(retryInterval);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronization data timeout between Redis master participating in transaction and its slaves.
|
||||
* <p>
|
||||
* Default syncSlaves is <code>5000 milliseconds</code>
|
||||
*
|
||||
* @param syncTimeout - synchronization timeout
|
||||
* @param syncUnit - synchronization timeout time unit
|
||||
* @return self instance
|
||||
*/
|
||||
public TransactionOptions syncSlavesTimeout(long syncTimeout, TimeUnit syncUnit) {
|
||||
this.syncTimeout = syncUnit.toMillis(syncTimeout);
|
||||
return this;
|
||||
}
|
||||
public long getSyncTimeout() {
|
||||
return syncTimeout;
|
||||
}
|
||||
|
||||
public long getTimeout() {
|
||||
return timeout;
|
||||
}
|
||||
/**
|
||||
* If transaction hasn't committed within <code>timeout</code> it will rollback automatically.
|
||||
* <p>
|
||||
* Default is <code>5000 milliseconds</code>
|
||||
*
|
||||
* @param timeout in milliseconds
|
||||
* @param timeoutUnit timeout time unit
|
||||
* @return self instance
|
||||
*/
|
||||
public TransactionOptions timeout(long timeout, TimeUnit timeoutUnit) {
|
||||
this.timeout = timeoutUnit.toMillis(timeout);
|
||||
return this;
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
/**
|
||||
* Copyright 2018 Nikita Koksharov
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.redisson.cache;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Nikita Koksharov
|
||||
*
|
||||
*/
|
||||
public class LocalCachedMapDisable {
|
||||
|
||||
private byte[][] keyHashes;
|
||||
private long timeout;
|
||||
private String requestId;
|
||||
|
||||
public LocalCachedMapDisable(String requestId, byte[][] keyHashes, long timeout) {
|
||||
super();
|
||||
this.requestId = requestId;
|
||||
this.keyHashes = keyHashes;
|
||||
this.timeout = timeout;
|
||||
}
|
||||
|
||||
public String getRequestId() {
|
||||
return requestId;
|
||||
}
|
||||
|
||||
public long getTimeout() {
|
||||
return timeout;
|
||||
}
|
||||
|
||||
public byte[][] getKeyHashes() {
|
||||
return keyHashes;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,31 @@
|
||||
/**
|
||||
* Copyright 2018 Nikita Koksharov
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.redisson.cache;
|
||||
|
||||
import java.io.Serializable;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Nikita Koksharov
|
||||
*
|
||||
*/
|
||||
@SuppressWarnings("serial")
|
||||
public class LocalCachedMapDisableAck implements Serializable {
|
||||
|
||||
public LocalCachedMapDisableAck() {
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,45 @@
|
||||
/**
|
||||
* Copyright 2018 Nikita Koksharov
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.redisson.cache;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Nikita Koksharov
|
||||
*
|
||||
*/
|
||||
public class LocalCachedMapDisabledKey {
|
||||
|
||||
private String requestId;
|
||||
private long timeout;
|
||||
|
||||
public LocalCachedMapDisabledKey() {
|
||||
}
|
||||
|
||||
public LocalCachedMapDisabledKey(String requestId, long timeout) {
|
||||
super();
|
||||
this.requestId = requestId;
|
||||
this.timeout = timeout;
|
||||
}
|
||||
|
||||
public String getRequestId() {
|
||||
return requestId;
|
||||
}
|
||||
|
||||
public long getTimeout() {
|
||||
return timeout;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,42 @@
|
||||
/**
|
||||
* Copyright 2018 Nikita Koksharov
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.redisson.cache;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Nikita Koksharov
|
||||
*
|
||||
*/
|
||||
public class LocalCachedMapEnable {
|
||||
|
||||
private byte[][] keyHashes;
|
||||
private String requestId;
|
||||
|
||||
public LocalCachedMapEnable(String requestId, byte[][] keyHashes) {
|
||||
super();
|
||||
this.requestId = requestId;
|
||||
this.keyHashes = keyHashes;
|
||||
}
|
||||
|
||||
public String getRequestId() {
|
||||
return requestId;
|
||||
}
|
||||
|
||||
public byte[][] getKeyHashes() {
|
||||
return keyHashes;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,75 @@
|
||||
/**
|
||||
* Copyright 2018 Nikita Koksharov
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.redisson.spring.transaction;
|
||||
|
||||
import org.redisson.api.RBatch;
|
||||
import org.redisson.api.RedissonClient;
|
||||
import org.springframework.transaction.TransactionDefinition;
|
||||
import org.springframework.transaction.TransactionException;
|
||||
import org.springframework.transaction.support.AbstractPlatformTransactionManager;
|
||||
import org.springframework.transaction.support.DefaultTransactionStatus;
|
||||
import org.springframework.transaction.support.ResourceTransactionManager;
|
||||
import org.springframework.transaction.support.TransactionSynchronizationManager;
|
||||
|
||||
public class RedissonTransactionManager extends AbstractPlatformTransactionManager implements ResourceTransactionManager {
|
||||
|
||||
private RedissonClient redisson;
|
||||
|
||||
@Override
|
||||
protected Object doGetTransaction() throws TransactionException {
|
||||
RedissonTransactionObject tObject = new RedissonTransactionObject();
|
||||
|
||||
TransactionSynchronizationManager.getResource(redisson);
|
||||
// TODO Auto-generated method stub
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected boolean isExistingTransaction(Object transaction) throws TransactionException {
|
||||
// TODO Auto-generated method stub
|
||||
return super.isExistingTransaction(transaction);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doBegin(Object transaction, TransactionDefinition definition) throws TransactionException {
|
||||
RedissonTransactionObject tObject = (RedissonTransactionObject) transaction;
|
||||
|
||||
if (tObject.getBatch() == null) {
|
||||
RBatch batch = redisson.createBatch();
|
||||
batch.atomic();
|
||||
tObject.setBatch(batch);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doCommit(DefaultTransactionStatus status) throws TransactionException {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void doRollback(DefaultTransactionStatus status) throws TransactionException {
|
||||
// TODO Auto-generated method stub
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public Object getResourceFactory() {
|
||||
return redisson;
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
/**
|
||||
* Copyright 2018 Nikita Koksharov
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.redisson.spring.transaction;
|
||||
|
||||
import org.redisson.api.RBatch;
|
||||
import org.springframework.transaction.support.SmartTransactionObject;
|
||||
|
||||
public class RedissonTransactionObject implements SmartTransactionObject {
|
||||
|
||||
private RBatch batch;
|
||||
|
||||
public RBatch getBatch() {
|
||||
return batch;
|
||||
}
|
||||
|
||||
public void setBatch(RBatch batch) {
|
||||
this.batch = batch;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRollbackOnly() {
|
||||
// TODO Auto-generated method stub
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void flush() {
|
||||
// skip
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,967 @@
|
||||
/**
|
||||
* Copyright 2018 Nikita Koksharov
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.redisson.transaction;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
||||
import org.redisson.RedissonMap;
|
||||
import org.redisson.RedissonMultiLock;
|
||||
import org.redisson.RedissonObject;
|
||||
import org.redisson.api.RFuture;
|
||||
import org.redisson.api.RLock;
|
||||
import org.redisson.api.RMap;
|
||||
import org.redisson.client.RedisClient;
|
||||
import org.redisson.client.protocol.convertor.NumberConvertor;
|
||||
import org.redisson.client.protocol.decoder.MapScanResult;
|
||||
import org.redisson.client.protocol.decoder.ScanObjectEntry;
|
||||
import org.redisson.command.CommandAsyncExecutor;
|
||||
import org.redisson.misc.Hash;
|
||||
import org.redisson.misc.HashValue;
|
||||
import org.redisson.misc.RPromise;
|
||||
import org.redisson.misc.RedissonPromise;
|
||||
import org.redisson.transaction.operation.DeleteOperation;
|
||||
import org.redisson.transaction.operation.TouchOperation;
|
||||
import org.redisson.transaction.operation.TransactionalOperation;
|
||||
import org.redisson.transaction.operation.UnlinkOperation;
|
||||
import org.redisson.transaction.operation.map.MapAddAndGetOperation;
|
||||
import org.redisson.transaction.operation.map.MapFastPutIfAbsentOperation;
|
||||
import org.redisson.transaction.operation.map.MapFastPutOperation;
|
||||
import org.redisson.transaction.operation.map.MapFastRemoveOperation;
|
||||
import org.redisson.transaction.operation.map.MapOperation;
|
||||
import org.redisson.transaction.operation.map.MapPutIfAbsentOperation;
|
||||
import org.redisson.transaction.operation.map.MapPutOperation;
|
||||
import org.redisson.transaction.operation.map.MapRemoveOperation;
|
||||
import org.redisson.transaction.operation.map.MapReplaceOperation;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.util.concurrent.Future;
|
||||
import io.netty.util.concurrent.FutureListener;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Nikita Koksharov
|
||||
*
|
||||
* @param <K> key type
|
||||
* @param <V> value type
|
||||
*/
|
||||
public class BaseTransactionalMap<K, V> {
|
||||
|
||||
public static class MapEntry {
|
||||
|
||||
public static final MapEntry NULL = new MapEntry(null, null);
|
||||
|
||||
private final Object key;
|
||||
private final Object value;
|
||||
|
||||
public MapEntry(Object key, Object value) {
|
||||
super();
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
public Object getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
public Object getValue() {
|
||||
return value;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private final long timeout;
|
||||
final List<TransactionalOperation> operations;
|
||||
final Map<HashValue, MapEntry> state = new HashMap<HashValue, MapEntry>();
|
||||
final RMap<K, V> map;
|
||||
Boolean deleted;
|
||||
|
||||
public BaseTransactionalMap(long timeout, List<TransactionalOperation> operations, RMap<K, V> map) {
|
||||
super();
|
||||
this.timeout = timeout;
|
||||
this.operations = operations;
|
||||
this.map = map;
|
||||
}
|
||||
|
||||
HashValue toKeyHash(Object key) {
|
||||
ByteBuf keyState = ((RedissonObject)map).encodeMapKey(key);
|
||||
try {
|
||||
return new HashValue(Hash.hash128(keyState));
|
||||
} finally {
|
||||
keyState.release();
|
||||
}
|
||||
}
|
||||
|
||||
private HashValue toValueHash(Object value) {
|
||||
ByteBuf keyState = ((RedissonObject)map).encodeMapValue(value);
|
||||
try {
|
||||
return new HashValue(Hash.hash128(keyState));
|
||||
} finally {
|
||||
keyState.release();
|
||||
}
|
||||
}
|
||||
|
||||
public RFuture<Boolean> isExistsAsync() {
|
||||
if (deleted != null) {
|
||||
return RedissonPromise.newSucceededFuture(!deleted);
|
||||
}
|
||||
|
||||
return map.isExistsAsync();
|
||||
}
|
||||
|
||||
public RFuture<Boolean> unlinkAsync(CommandAsyncExecutor commandExecutor) {
|
||||
return deleteAsync(commandExecutor, new UnlinkOperation(map.getName(), null));
|
||||
}
|
||||
|
||||
public RFuture<Boolean> touchAsync(CommandAsyncExecutor commandExecutor) {
|
||||
final RPromise<Boolean> result = new RedissonPromise<Boolean>();
|
||||
if (deleted != null && deleted) {
|
||||
operations.add(new TouchOperation(map.getName()));
|
||||
result.trySuccess(false);
|
||||
return result;
|
||||
}
|
||||
|
||||
map.isExistsAsync().addListener(new FutureListener<Boolean>() {
|
||||
@Override
|
||||
public void operationComplete(Future<Boolean> future) throws Exception {
|
||||
if (!future.isSuccess()) {
|
||||
result.tryFailure(future.cause());
|
||||
return;
|
||||
}
|
||||
|
||||
operations.add(new TouchOperation(map.getName()));
|
||||
boolean exists = future.getNow();
|
||||
if (!exists) {
|
||||
for (MapEntry entry : state.values()) {
|
||||
if (entry != MapEntry.NULL) {
|
||||
exists = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
result.trySuccess(exists);
|
||||
}
|
||||
});
|
||||
|
||||
result.trySuccess(null);
|
||||
return result;
|
||||
}
|
||||
|
||||
public RFuture<Boolean> deleteAsync(CommandAsyncExecutor commandExecutor) {
|
||||
return deleteAsync(commandExecutor, new DeleteOperation(map.getName()));
|
||||
}
|
||||
|
||||
protected RFuture<Boolean> deleteAsync(CommandAsyncExecutor commandExecutor, final TransactionalOperation operation) {
|
||||
final RPromise<Boolean> result = new RedissonPromise<Boolean>();
|
||||
if (deleted != null) {
|
||||
operations.add(operation);
|
||||
result.trySuccess(!deleted);
|
||||
deleted = true;
|
||||
return result;
|
||||
}
|
||||
|
||||
map.isExistsAsync().addListener(new FutureListener<Boolean>() {
|
||||
@Override
|
||||
public void operationComplete(Future<Boolean> future) throws Exception {
|
||||
if (!future.isSuccess()) {
|
||||
result.tryFailure(future.cause());
|
||||
return;
|
||||
}
|
||||
|
||||
operations.add(operation);
|
||||
for (HashValue key : state.keySet()) {
|
||||
state.put(key, MapEntry.NULL);
|
||||
}
|
||||
deleted = true;
|
||||
result.trySuccess(future.getNow());
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
protected MapScanResult<ScanObjectEntry, ScanObjectEntry> scanIterator(String name, RedisClient client,
|
||||
long startPos, String pattern) {
|
||||
MapScanResult<ScanObjectEntry, ScanObjectEntry> res = ((RedissonMap<?, ?>)map).scanIterator(name, client, startPos, pattern);
|
||||
Map<HashValue, MapEntry> newstate = new HashMap<HashValue, MapEntry>(state);
|
||||
for (Iterator<ScanObjectEntry> iterator = res.getMap().keySet().iterator(); iterator.hasNext();) {
|
||||
ScanObjectEntry entry = iterator.next();
|
||||
MapEntry mapEntry = newstate.remove(entry.getHash());
|
||||
if (mapEntry != null) {
|
||||
if (mapEntry == MapEntry.NULL) {
|
||||
iterator.remove();
|
||||
continue;
|
||||
}
|
||||
|
||||
HashValue valueHash = toValueHash(mapEntry.getValue());
|
||||
res.getMap().put(entry, new ScanObjectEntry(valueHash, mapEntry.getValue()));
|
||||
}
|
||||
}
|
||||
|
||||
if (startPos == 0) {
|
||||
for (Entry<HashValue, MapEntry> entry : newstate.entrySet()) {
|
||||
if (entry.getValue() == MapEntry.NULL) {
|
||||
continue;
|
||||
}
|
||||
|
||||
ScanObjectEntry key = new ScanObjectEntry(entry.getKey(), entry.getValue().getKey());
|
||||
ScanObjectEntry value = new ScanObjectEntry(toValueHash(entry.getValue().getValue()), entry.getValue().getValue());
|
||||
res.getMap().put(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
public RFuture<Boolean> containsKeyAsync(Object key) {
|
||||
HashValue keyHash = toKeyHash(key);
|
||||
MapEntry currentValue = state.get(keyHash);
|
||||
if (currentValue != null) {
|
||||
if (currentValue == MapEntry.NULL) {
|
||||
return RedissonPromise.newSucceededFuture(false);
|
||||
} else {
|
||||
return RedissonPromise.newSucceededFuture(true);
|
||||
}
|
||||
}
|
||||
|
||||
return map.containsKeyAsync(key);
|
||||
}
|
||||
|
||||
public RFuture<Boolean> containsValueAsync(Object value) {
|
||||
for (MapEntry entry : state.values()) {
|
||||
if (entry != MapEntry.NULL && isEqual(entry.getValue(), value)) {
|
||||
return RedissonPromise.newSucceededFuture(true);
|
||||
}
|
||||
}
|
||||
|
||||
return map.containsValueAsync(value);
|
||||
}
|
||||
|
||||
protected RFuture<V> addAndGetOperationAsync(final K key, final Number value) {
|
||||
final RPromise<V> result = new RedissonPromise<V>();
|
||||
executeLocked(result, key, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
final HashValue keyHash = toKeyHash(key);
|
||||
MapEntry entry = state.get(keyHash);
|
||||
if (entry != null) {
|
||||
BigDecimal currentValue = BigDecimal.ZERO;
|
||||
if (entry != MapEntry.NULL) {
|
||||
currentValue = (BigDecimal) entry.getValue();
|
||||
}
|
||||
BigDecimal res = currentValue.add(new BigDecimal(value.toString()));
|
||||
|
||||
operations.add(new MapAddAndGetOperation(map, key, value));
|
||||
state.put(keyHash, new MapEntry(key, res));
|
||||
if (deleted != null) {
|
||||
deleted = false;
|
||||
}
|
||||
|
||||
NumberConvertor convertor = new NumberConvertor(value.getClass());
|
||||
result.trySuccess((V) convertor.convert(res.toPlainString()));
|
||||
return;
|
||||
}
|
||||
|
||||
map.getAsync(key).addListener(new FutureListener<V>() {
|
||||
@Override
|
||||
public void operationComplete(Future<V> future) throws Exception {
|
||||
if (!future.isSuccess()) {
|
||||
result.tryFailure(future.cause());
|
||||
return;
|
||||
}
|
||||
BigDecimal currentValue = new BigDecimal(future.getNow().toString());
|
||||
BigDecimal res = currentValue.add(new BigDecimal(value.toString()));
|
||||
|
||||
operations.add(new MapAddAndGetOperation(map, key, value));
|
||||
state.put(keyHash, new MapEntry(key, res));
|
||||
if (deleted != null) {
|
||||
deleted = false;
|
||||
}
|
||||
|
||||
NumberConvertor convertor = new NumberConvertor(value.getClass());
|
||||
result.trySuccess((V) convertor.convert(res.toPlainString()));
|
||||
}
|
||||
|
||||
});
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
protected RFuture<V> putIfAbsentOperationAsync(K key, V value) {
|
||||
return putIfAbsentOperationAsync(key, value, new MapPutIfAbsentOperation(map, key, value));
|
||||
}
|
||||
|
||||
protected RFuture<V> putIfAbsentOperationAsync(final K key, final V value, final MapOperation mapOperation) {
|
||||
final RPromise<V> result = new RedissonPromise<V>();
|
||||
executeLocked(result, key, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
final HashValue keyHash = toKeyHash(key);
|
||||
MapEntry entry = state.get(keyHash);
|
||||
if (entry != null) {
|
||||
operations.add(mapOperation);
|
||||
if (entry == MapEntry.NULL) {
|
||||
state.put(keyHash, new MapEntry(key, value));
|
||||
if (deleted != null) {
|
||||
deleted = false;
|
||||
}
|
||||
|
||||
result.trySuccess(null);
|
||||
} else {
|
||||
result.trySuccess((V) entry.getValue());
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
map.getAsync(key).addListener(new FutureListener<V>() {
|
||||
@Override
|
||||
public void operationComplete(Future<V> future) throws Exception {
|
||||
if (!future.isSuccess()) {
|
||||
result.tryFailure(future.cause());
|
||||
return;
|
||||
}
|
||||
|
||||
operations.add(mapOperation);
|
||||
if (future.getNow() == null) {
|
||||
state.put(keyHash, new MapEntry(key, value));
|
||||
if (deleted != null) {
|
||||
deleted = false;
|
||||
}
|
||||
}
|
||||
result.trySuccess(future.getNow());
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
protected final RFuture<V> putOperationAsync(K key, V value) {
|
||||
return putOperationAsync(key, value, new MapPutOperation(map, key, value));
|
||||
}
|
||||
|
||||
protected RFuture<V> putOperationAsync(final K key, final V value, final MapOperation operation) {
|
||||
final RPromise<V> result = new RedissonPromise<V>();
|
||||
executeLocked(result, key, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
final HashValue keyHash = toKeyHash(key);
|
||||
MapEntry entry = state.get(keyHash);
|
||||
if (entry != null) {
|
||||
operations.add(operation);
|
||||
state.put(keyHash, new MapEntry(key, value));
|
||||
if (deleted != null) {
|
||||
deleted = false;
|
||||
}
|
||||
|
||||
if (entry == MapEntry.NULL) {
|
||||
result.trySuccess(null);
|
||||
} else {
|
||||
result.trySuccess((V) entry.getValue());
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
map.getAsync(key).addListener(new FutureListener<V>() {
|
||||
@Override
|
||||
public void operationComplete(Future<V> future) throws Exception {
|
||||
if (!future.isSuccess()) {
|
||||
result.tryFailure(future.cause());
|
||||
return;
|
||||
}
|
||||
|
||||
operations.add(operation);
|
||||
state.put(keyHash, new MapEntry(key, value));
|
||||
if (deleted != null) {
|
||||
deleted = false;
|
||||
}
|
||||
result.trySuccess(future.getNow());
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
protected RFuture<Boolean> fastPutIfAbsentOperationAsync(K key, V value) {
|
||||
return fastPutIfAbsentOperationAsync(key, value, new MapFastPutIfAbsentOperation(map, key, value));
|
||||
}
|
||||
|
||||
protected RFuture<Boolean> fastPutIfAbsentOperationAsync(final K key, final V value, final MapOperation mapOperation) {
|
||||
final RPromise<Boolean> result = new RedissonPromise<Boolean>();
|
||||
executeLocked(result, key, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
final HashValue keyHash = toKeyHash(key);
|
||||
MapEntry entry = state.get(keyHash);
|
||||
if (entry != null) {
|
||||
operations.add(mapOperation);
|
||||
if (entry == MapEntry.NULL) {
|
||||
state.put(keyHash, new MapEntry(key, value));
|
||||
if (deleted != null) {
|
||||
deleted = false;
|
||||
}
|
||||
result.trySuccess(true);
|
||||
} else {
|
||||
result.trySuccess(false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
map.getAsync(key).addListener(new FutureListener<V>() {
|
||||
@Override
|
||||
public void operationComplete(Future<V> future) throws Exception {
|
||||
if (!future.isSuccess()) {
|
||||
result.tryFailure(future.cause());
|
||||
return;
|
||||
}
|
||||
|
||||
operations.add(mapOperation);
|
||||
boolean isUpdated = future.getNow() == null;
|
||||
if (isUpdated) {
|
||||
state.put(keyHash, new MapEntry(key, value));
|
||||
if (deleted != null) {
|
||||
deleted = false;
|
||||
}
|
||||
}
|
||||
result.trySuccess(isUpdated);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
protected RFuture<Boolean> fastPutOperationAsync(K key, V value) {
|
||||
return fastPutOperationAsync(key, value, new MapFastPutOperation(map, key, value));
|
||||
}
|
||||
|
||||
protected RFuture<Boolean> fastPutOperationAsync(final K key, final V value, final MapOperation operation) {
|
||||
final RPromise<Boolean> result = new RedissonPromise<Boolean>();
|
||||
executeLocked(result, key, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
final HashValue keyHash = toKeyHash(key);
|
||||
MapEntry entry = state.get(keyHash);
|
||||
if (entry != null) {
|
||||
operations.add(operation);
|
||||
state.put(keyHash, new MapEntry(key, value));
|
||||
if (deleted != null) {
|
||||
deleted = false;
|
||||
}
|
||||
|
||||
if (entry == MapEntry.NULL) {
|
||||
result.trySuccess(true);
|
||||
} else {
|
||||
result.trySuccess(false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
map.getAsync(key).addListener(new FutureListener<V>() {
|
||||
@Override
|
||||
public void operationComplete(Future<V> future) throws Exception {
|
||||
if (!future.isSuccess()) {
|
||||
result.tryFailure(future.cause());
|
||||
return;
|
||||
}
|
||||
|
||||
operations.add(operation);
|
||||
state.put(keyHash, new MapEntry(key, value));
|
||||
if (deleted != null) {
|
||||
deleted = false;
|
||||
}
|
||||
|
||||
boolean isNew = future.getNow() == null;
|
||||
result.trySuccess(isNew);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
protected RFuture<Long> fastRemoveOperationAsync(final K... keys) {
|
||||
final RPromise<Long> result = new RedissonPromise<Long>();
|
||||
executeLocked(result, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
AtomicLong counter = new AtomicLong();
|
||||
List<K> keyList = Arrays.asList(keys);
|
||||
for (Iterator<K> iterator = keyList.iterator(); iterator.hasNext();) {
|
||||
K key = iterator.next();
|
||||
HashValue keyHash = toKeyHash(key);
|
||||
MapEntry currentValue = state.get(keyHash);
|
||||
if (currentValue != null && currentValue != MapEntry.NULL) {
|
||||
operations.add(new MapFastRemoveOperation(map, key));
|
||||
state.put(keyHash, MapEntry.NULL);
|
||||
|
||||
counter.incrementAndGet();
|
||||
iterator.remove();
|
||||
}
|
||||
}
|
||||
|
||||
// TODO optimize
|
||||
map.getAllAsync(new HashSet<K>(keyList)).addListener(new FutureListener<Map<K, V>>() {
|
||||
@Override
|
||||
public void operationComplete(Future<Map<K, V>> future) throws Exception {
|
||||
if (future.isSuccess()) {
|
||||
result.tryFailure(future.cause());
|
||||
return;
|
||||
}
|
||||
|
||||
for (K key : keys) {
|
||||
HashValue keyHash = toKeyHash(key);
|
||||
operations.add(new MapFastRemoveOperation(map, key));
|
||||
state.put(keyHash, MapEntry.NULL);
|
||||
}
|
||||
|
||||
result.trySuccess(null);
|
||||
}
|
||||
});
|
||||
}
|
||||
}, Arrays.asList(keys));
|
||||
return result;
|
||||
}
|
||||
|
||||
public RFuture<Integer> valueSizeAsync(K key) {
|
||||
HashValue keyHash = toKeyHash(key);
|
||||
MapEntry entry = state.get(keyHash);
|
||||
if (entry != null) {
|
||||
if (entry == MapEntry.NULL) {
|
||||
return RedissonPromise.newSucceededFuture(null);
|
||||
} else {
|
||||
ByteBuf valueState = ((RedissonObject)map).encodeMapValue(entry.getValue());
|
||||
try {
|
||||
return RedissonPromise.newSucceededFuture(valueState.readableBytes());
|
||||
} finally {
|
||||
valueState.release();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return map.valueSizeAsync(key);
|
||||
}
|
||||
|
||||
protected RFuture<V> getOperationAsync(K key) {
|
||||
HashValue keyHash = toKeyHash(key);
|
||||
MapEntry entry = state.get(keyHash);
|
||||
if (entry != null) {
|
||||
if (entry == MapEntry.NULL) {
|
||||
return RedissonPromise.newSucceededFuture(null);
|
||||
} else {
|
||||
return RedissonPromise.newSucceededFuture((V)entry.getValue());
|
||||
}
|
||||
}
|
||||
return ((RedissonMap<K, V>)map).getOperationAsync(key);
|
||||
}
|
||||
|
||||
public RFuture<Set<K>> readAllKeySetAsync() {
|
||||
final RPromise<Set<K>> result = new RedissonPromise<Set<K>>();
|
||||
RFuture<Set<K>> future = map.readAllKeySetAsync();
|
||||
future.addListener(new FutureListener<Set<K>>() {
|
||||
|
||||
@Override
|
||||
public void operationComplete(Future<Set<K>> future) throws Exception {
|
||||
if (!future.isSuccess()) {
|
||||
result.tryFailure(future.cause());
|
||||
return;
|
||||
}
|
||||
|
||||
Set<K> set = future.getNow();
|
||||
Map<HashValue, MapEntry> newstate = new HashMap<HashValue, MapEntry>(state);
|
||||
for (Iterator<K> iterator = set.iterator(); iterator.hasNext();) {
|
||||
K key = iterator.next();
|
||||
MapEntry value = newstate.remove(toKeyHash(key));
|
||||
if (value == MapEntry.NULL) {
|
||||
iterator.remove();
|
||||
}
|
||||
}
|
||||
|
||||
for (MapEntry entry : newstate.values()) {
|
||||
if (entry == MapEntry.NULL) {
|
||||
continue;
|
||||
}
|
||||
set.add((K) entry.getKey());
|
||||
}
|
||||
|
||||
result.trySuccess(set);
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public RFuture<Set<Entry<K, V>>> readAllEntrySetAsync() {
|
||||
final RPromise<Set<Entry<K, V>>> result = new RedissonPromise<Set<Entry<K, V>>>();
|
||||
RFuture<Map<K, V>> future = readAllMapAsync();
|
||||
future.addListener(new FutureListener<Map<K, V>>() {
|
||||
@Override
|
||||
public void operationComplete(Future<Map<K, V>> future) throws Exception {
|
||||
if (!future.isSuccess()) {
|
||||
result.tryFailure(future.cause());
|
||||
return;
|
||||
}
|
||||
|
||||
result.trySuccess(future.getNow().entrySet());
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public RFuture<Collection<V>> readAllValuesAsync() {
|
||||
final RPromise<Collection<V>> result = new RedissonPromise<Collection<V>>();
|
||||
RFuture<Map<K, V>> future = readAllMapAsync();
|
||||
future.addListener(new FutureListener<Map<K, V>>() {
|
||||
@Override
|
||||
public void operationComplete(Future<Map<K, V>> future) throws Exception {
|
||||
if (!future.isSuccess()) {
|
||||
result.tryFailure(future.cause());
|
||||
return;
|
||||
}
|
||||
|
||||
result.trySuccess(future.getNow().values());
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public RFuture<Map<K, V>> readAllMapAsync() {
|
||||
final RPromise<Map<K, V>> result = new RedissonPromise<Map<K, V>>();
|
||||
RFuture<Map<K, V>> future = map.readAllMapAsync();
|
||||
future.addListener(new FutureListener<Map<K, V>>() {
|
||||
|
||||
@Override
|
||||
public void operationComplete(Future<Map<K, V>> future) throws Exception {
|
||||
if (!future.isSuccess()) {
|
||||
result.tryFailure(future.cause());
|
||||
return;
|
||||
}
|
||||
|
||||
Map<HashValue, MapEntry> newstate = new HashMap<HashValue, MapEntry>(state);
|
||||
Map<K, V> map = future.getNow();
|
||||
for (Iterator<K> iterator = map.keySet().iterator(); iterator.hasNext();) {
|
||||
K key = iterator.next();
|
||||
MapEntry entry = newstate.remove(toKeyHash(key));
|
||||
if (entry == MapEntry.NULL) {
|
||||
iterator.remove();
|
||||
} else if (entry != null) {
|
||||
map.put(key, (V) entry.getValue());
|
||||
}
|
||||
}
|
||||
|
||||
for (MapEntry entry : newstate.values()) {
|
||||
if (entry == MapEntry.NULL) {
|
||||
continue;
|
||||
}
|
||||
map.put((K)entry.getKey(), (V)entry.getValue());
|
||||
}
|
||||
|
||||
result.trySuccess(map);
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
protected RFuture<Map<K, V>> getAllOperationAsync(Set<K> keys) {
|
||||
final RPromise<Map<K, V>> result = new RedissonPromise<Map<K, V>>();
|
||||
Set<K> keysToLoad = new HashSet<K>(keys);
|
||||
final Map<K, V> map = new HashMap<K, V>();
|
||||
for (K key : keys) {
|
||||
HashValue keyHash = toKeyHash(key);
|
||||
|
||||
MapEntry entry = state.get(keyHash);
|
||||
if (entry != null) {
|
||||
if (entry != MapEntry.NULL) {
|
||||
map.put(key, (V)entry.getValue());
|
||||
}
|
||||
} else {
|
||||
keysToLoad.add(key);
|
||||
}
|
||||
}
|
||||
|
||||
RFuture<Map<K, V>> future = ((RedissonMap<K, V>)map).getAllOperationAsync(keysToLoad);
|
||||
future.addListener(new FutureListener<Map<K, V>>() {
|
||||
@Override
|
||||
public void operationComplete(Future<Map<K, V>> future) throws Exception {
|
||||
if (!future.isSuccess()) {
|
||||
result.tryFailure(future.cause());
|
||||
return;
|
||||
}
|
||||
|
||||
map.putAll(future.getNow());
|
||||
result.trySuccess(map);
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
protected RFuture<V> removeOperationAsync(final K key) {
|
||||
final RPromise<V> result = new RedissonPromise<V>();
|
||||
executeLocked(result, key, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
final HashValue keyHash = toKeyHash(key);
|
||||
MapEntry entry = state.get(keyHash);
|
||||
if (entry != null) {
|
||||
operations.add(new MapRemoveOperation(map, key));
|
||||
if (entry == MapEntry.NULL) {
|
||||
result.trySuccess(null);
|
||||
} else {
|
||||
state.put(keyHash, MapEntry.NULL);
|
||||
result.trySuccess((V) entry.getValue());
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
map.getAsync(key).addListener(new FutureListener<V>() {
|
||||
@Override
|
||||
public void operationComplete(Future<V> future) throws Exception {
|
||||
if (!future.isSuccess()) {
|
||||
result.tryFailure(future.cause());
|
||||
return;
|
||||
}
|
||||
operations.add(new MapRemoveOperation(map, key));
|
||||
if (future.getNow() != null) {
|
||||
state.put(keyHash, MapEntry.NULL);
|
||||
}
|
||||
|
||||
result.trySuccess(future.getNow());
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
protected RFuture<Boolean> removeOperationAsync(final Object key, final Object value) {
|
||||
final RPromise<Boolean> result = new RedissonPromise<Boolean>();
|
||||
executeLocked(result, (K)key, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
final HashValue keyHash = toKeyHash(key);
|
||||
MapEntry entry = state.get(keyHash);
|
||||
if (entry != null) {
|
||||
if (entry == MapEntry.NULL) {
|
||||
result.trySuccess(false);
|
||||
return;
|
||||
}
|
||||
|
||||
operations.add(new MapRemoveOperation(map, key, value));
|
||||
if (isEqual(entry.getValue(), value)) {
|
||||
state.put(keyHash, MapEntry.NULL);
|
||||
result.trySuccess(true);
|
||||
return;
|
||||
}
|
||||
|
||||
result.trySuccess(false);
|
||||
return;
|
||||
}
|
||||
|
||||
map.getAsync((K)key).addListener(new FutureListener<V>() {
|
||||
@Override
|
||||
public void operationComplete(Future<V> future) throws Exception {
|
||||
if (!future.isSuccess()) {
|
||||
result.tryFailure(future.cause());
|
||||
return;
|
||||
}
|
||||
operations.add(new MapRemoveOperation(map, key, value));
|
||||
boolean res = isEqual(future.getNow(), value);
|
||||
if (res) {
|
||||
state.put(keyHash, MapEntry.NULL);
|
||||
}
|
||||
result.trySuccess(res);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
private boolean isEqual(Object value, Object oldValue) {
|
||||
ByteBuf valueBuf = ((RedissonObject)map).encodeMapValue(value);
|
||||
ByteBuf oldValueBuf = ((RedissonObject)map).encodeMapValue(oldValue);
|
||||
|
||||
try {
|
||||
return valueBuf.equals(oldValueBuf);
|
||||
} finally {
|
||||
valueBuf.readableBytes();
|
||||
oldValueBuf.readableBytes();
|
||||
}
|
||||
}
|
||||
|
||||
protected RFuture<Void> putAllOperationAsync(final Map<? extends K, ? extends V> entries) {
|
||||
final RPromise<Void> result = new RedissonPromise<Void>();
|
||||
executeLocked(result, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
for (Entry<? extends K, ? extends V> entry : entries.entrySet()) {
|
||||
operations.add(new MapPutOperation(map, entry.getKey(), entry.getValue()));
|
||||
HashValue keyHash = toKeyHash(entry.getKey());
|
||||
state.put(keyHash, new MapEntry(entry.getKey(), entry.getValue()));
|
||||
}
|
||||
|
||||
if (deleted != null) {
|
||||
deleted = false;
|
||||
}
|
||||
|
||||
result.trySuccess(null);
|
||||
}
|
||||
}, (Collection<K>)entries.keySet());
|
||||
return result;
|
||||
}
|
||||
|
||||
protected RFuture<Boolean> replaceOperationAsync(final K key, final V oldValue, final V newValue) {
|
||||
final RPromise<Boolean> result = new RedissonPromise<Boolean>();
|
||||
executeLocked(result, key, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
final HashValue keyHash = toKeyHash(key);
|
||||
MapEntry entry = state.get(keyHash);
|
||||
if (entry != null) {
|
||||
if (entry == MapEntry.NULL) {
|
||||
result.trySuccess(false);
|
||||
return;
|
||||
}
|
||||
|
||||
operations.add(new MapReplaceOperation(map, key, newValue, oldValue));
|
||||
if (isEqual(entry.getValue(), oldValue)) {
|
||||
state.put(keyHash, new MapEntry(key, newValue));
|
||||
result.trySuccess(true);
|
||||
return;
|
||||
}
|
||||
|
||||
result.trySuccess(false);
|
||||
return;
|
||||
}
|
||||
|
||||
map.getAsync(key).addListener(new FutureListener<V>() {
|
||||
@Override
|
||||
public void operationComplete(Future<V> future) throws Exception {
|
||||
if (!future.isSuccess()) {
|
||||
result.tryFailure(future.cause());
|
||||
return;
|
||||
}
|
||||
|
||||
operations.add(new MapReplaceOperation(map, key, newValue, oldValue));
|
||||
boolean res = isEqual(future.getNow(), oldValue);
|
||||
if (res) {
|
||||
state.put(keyHash, new MapEntry(key, newValue));
|
||||
}
|
||||
result.trySuccess(res);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
protected RFuture<V> replaceOperationAsync(final K key, final V value) {
|
||||
final RPromise<V> result = new RedissonPromise<V>();
|
||||
executeLocked(result, key, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
final HashValue keyHash = toKeyHash(key);
|
||||
MapEntry entry = state.get(keyHash);
|
||||
operations.add(new MapReplaceOperation(map, key, value));
|
||||
if (entry != null) {
|
||||
if (entry == MapEntry.NULL) {
|
||||
result.trySuccess(null);
|
||||
return;
|
||||
}
|
||||
|
||||
state.put(keyHash, new MapEntry(key, value));
|
||||
result.trySuccess((V) entry.getValue());
|
||||
return;
|
||||
}
|
||||
|
||||
map.getAsync(key).addListener(new FutureListener<V>() {
|
||||
@Override
|
||||
public void operationComplete(Future<V> future) throws Exception {
|
||||
if (!future.isSuccess()) {
|
||||
result.tryFailure(future.cause());
|
||||
return;
|
||||
}
|
||||
|
||||
operations.add(new MapReplaceOperation(map, key, value));
|
||||
if (future.getNow() != null) {
|
||||
state.put(keyHash, new MapEntry(key, value));
|
||||
}
|
||||
result.trySuccess(future.getNow());
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
protected <R> void executeLocked(final RPromise<R> promise, K key, final Runnable runnable) {
|
||||
RLock lock = map.getLock(key);
|
||||
executeLocked(promise, runnable, lock);
|
||||
}
|
||||
|
||||
protected <R> void executeLocked(final RPromise<R> promise, final Runnable runnable, RLock lock) {
|
||||
lock.lockAsync(timeout, TimeUnit.MILLISECONDS).addListener(new FutureListener<Void>() {
|
||||
@Override
|
||||
public void operationComplete(Future<Void> future) throws Exception {
|
||||
if (future.isSuccess()) {
|
||||
runnable.run();
|
||||
} else {
|
||||
promise.tryFailure(future.cause());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected <R> void executeLocked(final RPromise<R> promise, final Runnable runnable, Collection<K> keys) {
|
||||
List<RLock> locks = new ArrayList<RLock>(keys.size());
|
||||
for (K key : keys) {
|
||||
RLock lock = map.getLock(key);
|
||||
locks.add(lock);
|
||||
}
|
||||
final RedissonMultiLock multiLock = new RedissonMultiLock(locks.toArray(new RLock[locks.size()]));
|
||||
final long threadId = Thread.currentThread().getId();
|
||||
multiLock.lockAsync(timeout, TimeUnit.MILLISECONDS).addListener(new FutureListener<Void>() {
|
||||
@Override
|
||||
public void operationComplete(Future<Void> future) throws Exception {
|
||||
if (future.isSuccess()) {
|
||||
runnable.run();
|
||||
} else {
|
||||
multiLock.unlockAsync(threadId);
|
||||
promise.tryFailure(future.cause());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
/**
|
||||
* Copyright 2018 Nikita Koksharov
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.redisson.transaction;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.redisson.api.RFuture;
|
||||
import org.redisson.api.RMap;
|
||||
import org.redisson.transaction.operation.TransactionalOperation;
|
||||
import org.redisson.transaction.operation.map.MapCacheFastPutIfAbsentOperation;
|
||||
import org.redisson.transaction.operation.map.MapCacheFastPutOperation;
|
||||
import org.redisson.transaction.operation.map.MapCachePutIfAbsentOperation;
|
||||
import org.redisson.transaction.operation.map.MapCachePutOperation;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Nikita Koksharov
|
||||
*
|
||||
* @param <K> key type
|
||||
* @param <V> value type
|
||||
*/
|
||||
public class BaseTransactionalMapCache<K, V> extends BaseTransactionalMap<K, V> {
|
||||
|
||||
public BaseTransactionalMapCache(long timeout, List<TransactionalOperation> operations, RMap<K, V> map) {
|
||||
super(timeout, operations, map);
|
||||
}
|
||||
|
||||
public RFuture<V> putIfAbsentAsync(K key, V value, long ttl, TimeUnit ttlUnit, long maxIdleTime, TimeUnit maxIdleUnit) {
|
||||
return putIfAbsentOperationAsync(key, value, new MapCachePutIfAbsentOperation(map, key, value, ttl, ttlUnit, maxIdleTime, maxIdleUnit));
|
||||
}
|
||||
|
||||
public RFuture<Boolean> fastPutOperationAsync(K key, V value, long ttl, TimeUnit ttlUnit, long maxIdleTime, TimeUnit maxIdleUnit) {
|
||||
return fastPutOperationAsync(key, value, new MapCacheFastPutOperation(map, key, value, ttl, ttlUnit, maxIdleTime, maxIdleUnit));
|
||||
}
|
||||
|
||||
public RFuture<V> putOperationAsync(K key, V value, long ttlTimeout, long maxIdleTimeout, long maxIdleDelta) {
|
||||
return putOperationAsync(key, value, new MapCachePutOperation(map, key, value,
|
||||
ttlTimeout, TimeUnit.MILLISECONDS, maxIdleTimeout, TimeUnit.MILLISECONDS));
|
||||
}
|
||||
|
||||
public RFuture<Boolean> fastPutIfAbsentAsync(K key, V value, long ttl, TimeUnit ttlUnit, long maxIdleTime, TimeUnit maxIdleUnit) {
|
||||
return fastPutIfAbsentOperationAsync(key, value, new MapCacheFastPutIfAbsentOperation(map, key, value,
|
||||
ttl, ttlUnit, maxIdleTime, maxIdleUnit));
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
/**
|
||||
* Copyright 2018 Nikita Koksharov
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.redisson.transaction;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.redisson.api.RFuture;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Nikita Koksharov
|
||||
*
|
||||
*/
|
||||
public class BaseTransactionalObject {
|
||||
|
||||
public RFuture<Boolean> expireAsync(long timeToLive, TimeUnit timeUnit) {
|
||||
throw new UnsupportedOperationException("expire method is not supported in transaction");
|
||||
}
|
||||
|
||||
public RFuture<Boolean> expireAtAsync(Date timestamp) {
|
||||
throw new UnsupportedOperationException("expireAt method is not supported in transaction");
|
||||
}
|
||||
|
||||
public RFuture<Boolean> expireAtAsync(long timestamp) {
|
||||
throw new UnsupportedOperationException("expireAt method is not supported in transaction");
|
||||
}
|
||||
|
||||
public RFuture<Boolean> clearExpireAsync() {
|
||||
throw new UnsupportedOperationException("clearExpire method is not supported in transaction");
|
||||
}
|
||||
|
||||
public RFuture<Boolean> moveAsync(int database) {
|
||||
throw new UnsupportedOperationException("move method is not supported in transaction");
|
||||
}
|
||||
|
||||
public RFuture<Void> migrateAsync(String host, int port, int database) {
|
||||
throw new UnsupportedOperationException("migrate method is not supported in transaction");
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,570 @@
|
||||
/**
|
||||
* Copyright 2018 Nikita Koksharov
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.redisson.transaction;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.redisson.RedissonMultiLock;
|
||||
import org.redisson.RedissonObject;
|
||||
import org.redisson.RedissonSet;
|
||||
import org.redisson.api.RCollectionAsync;
|
||||
import org.redisson.api.RFuture;
|
||||
import org.redisson.api.RLock;
|
||||
import org.redisson.api.RObject;
|
||||
import org.redisson.api.RSet;
|
||||
import org.redisson.api.SortOrder;
|
||||
import org.redisson.client.RedisClient;
|
||||
import org.redisson.client.protocol.decoder.ListScanResult;
|
||||
import org.redisson.client.protocol.decoder.ScanObjectEntry;
|
||||
import org.redisson.command.CommandAsyncExecutor;
|
||||
import org.redisson.misc.Hash;
|
||||
import org.redisson.misc.HashValue;
|
||||
import org.redisson.misc.RPromise;
|
||||
import org.redisson.misc.RedissonPromise;
|
||||
import org.redisson.transaction.operation.DeleteOperation;
|
||||
import org.redisson.transaction.operation.TouchOperation;
|
||||
import org.redisson.transaction.operation.TransactionalOperation;
|
||||
import org.redisson.transaction.operation.UnlinkOperation;
|
||||
import org.redisson.transaction.operation.set.MoveOperation;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.util.concurrent.Future;
|
||||
import io.netty.util.concurrent.FutureListener;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Nikita Koksharov
|
||||
*
|
||||
* @param <V> value type
|
||||
*/
|
||||
public abstract class BaseTransactionalSet<V> extends BaseTransactionalObject {
|
||||
|
||||
static final Object NULL = new Object();
|
||||
|
||||
private final long timeout;
|
||||
final Map<HashValue, Object> state = new HashMap<HashValue, Object>();
|
||||
final List<TransactionalOperation> operations;
|
||||
final RCollectionAsync<V> set;
|
||||
final RObject object;
|
||||
final String name;
|
||||
final CommandAsyncExecutor commandExecutor;
|
||||
Boolean deleted;
|
||||
|
||||
public BaseTransactionalSet(CommandAsyncExecutor commandExecutor, long timeout, List<TransactionalOperation> operations, RCollectionAsync<V> set) {
|
||||
this.commandExecutor = commandExecutor;
|
||||
this.timeout = timeout;
|
||||
this.operations = operations;
|
||||
this.set = set;
|
||||
this.object = (RObject) set;
|
||||
this.name = object.getName();
|
||||
}
|
||||
|
||||
private HashValue toHash(Object value) {
|
||||
ByteBuf state = ((RedissonObject)set).encode(value);
|
||||
try {
|
||||
return new HashValue(Hash.hash128(state));
|
||||
} finally {
|
||||
state.release();
|
||||
}
|
||||
}
|
||||
|
||||
public RFuture<Boolean> isExistsAsync() {
|
||||
if (deleted != null) {
|
||||
return RedissonPromise.newSucceededFuture(!deleted);
|
||||
}
|
||||
|
||||
return set.isExistsAsync();
|
||||
}
|
||||
|
||||
public RFuture<Boolean> unlinkAsync(CommandAsyncExecutor commandExecutor) {
|
||||
return deleteAsync(commandExecutor, new UnlinkOperation(name));
|
||||
}
|
||||
|
||||
public RFuture<Boolean> touchAsync(CommandAsyncExecutor commandExecutor) {
|
||||
final RPromise<Boolean> result = new RedissonPromise<Boolean>();
|
||||
if (deleted != null && deleted) {
|
||||
operations.add(new TouchOperation(name));
|
||||
result.trySuccess(false);
|
||||
return result;
|
||||
}
|
||||
|
||||
set.isExistsAsync().addListener(new FutureListener<Boolean>() {
|
||||
@Override
|
||||
public void operationComplete(Future<Boolean> future) throws Exception {
|
||||
if (!future.isSuccess()) {
|
||||
result.tryFailure(future.cause());
|
||||
return;
|
||||
}
|
||||
|
||||
operations.add(new TouchOperation(name));
|
||||
boolean exists = future.getNow();
|
||||
if (!exists) {
|
||||
for (Object value : state.values()) {
|
||||
if (value != NULL) {
|
||||
exists = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
result.trySuccess(exists);
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public RFuture<Boolean> deleteAsync(CommandAsyncExecutor commandExecutor) {
|
||||
return deleteAsync(commandExecutor, new DeleteOperation(name));
|
||||
}
|
||||
|
||||
protected RFuture<Boolean> deleteAsync(CommandAsyncExecutor commandExecutor, final TransactionalOperation operation) {
|
||||
final RPromise<Boolean> result = new RedissonPromise<Boolean>();
|
||||
if (deleted != null) {
|
||||
operations.add(operation);
|
||||
result.trySuccess(!deleted);
|
||||
deleted = true;
|
||||
return result;
|
||||
}
|
||||
|
||||
set.isExistsAsync().addListener(new FutureListener<Boolean>() {
|
||||
@Override
|
||||
public void operationComplete(Future<Boolean> future) throws Exception {
|
||||
if (!future.isSuccess()) {
|
||||
result.tryFailure(future.cause());
|
||||
return;
|
||||
}
|
||||
|
||||
operations.add(operation);
|
||||
for (HashValue key : state.keySet()) {
|
||||
state.put(key, NULL);
|
||||
}
|
||||
deleted = true;
|
||||
result.trySuccess(future.getNow());
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
public RFuture<Boolean> containsAsync(Object value) {
|
||||
for (Object val : state.values()) {
|
||||
if (val != NULL && isEqual(val, value)) {
|
||||
return RedissonPromise.newSucceededFuture(true);
|
||||
}
|
||||
}
|
||||
|
||||
return set.containsAsync(value);
|
||||
}
|
||||
|
||||
protected abstract ListScanResult<ScanObjectEntry> scanIteratorSource(String name, RedisClient client,
|
||||
long startPos, String pattern);
|
||||
|
||||
protected ListScanResult<ScanObjectEntry> scanIterator(String name, RedisClient client,
|
||||
long startPos, String pattern) {
|
||||
ListScanResult<ScanObjectEntry> res = scanIteratorSource(name, client, startPos, pattern);
|
||||
Map<HashValue, Object> newstate = new HashMap<HashValue, Object>(state);
|
||||
for (Iterator<ScanObjectEntry> iterator = res.getValues().iterator(); iterator.hasNext();) {
|
||||
ScanObjectEntry entry = iterator.next();
|
||||
Object value = newstate.remove(entry.getHash());
|
||||
if (value == NULL) {
|
||||
iterator.remove();
|
||||
}
|
||||
}
|
||||
|
||||
if (startPos == 0) {
|
||||
for (Entry<HashValue, Object> entry : newstate.entrySet()) {
|
||||
if (entry.getValue() == NULL) {
|
||||
continue;
|
||||
}
|
||||
res.getValues().add(new ScanObjectEntry(entry.getKey(), entry.getValue()));
|
||||
}
|
||||
}
|
||||
|
||||
return res;
|
||||
}
|
||||
|
||||
protected abstract RFuture<Set<V>> readAllAsyncSource();
|
||||
|
||||
public RFuture<Set<V>> readAllAsync() {
|
||||
final RPromise<Set<V>> result = new RedissonPromise<Set<V>>();
|
||||
RFuture<Set<V>> future = readAllAsyncSource();
|
||||
future.addListener(new FutureListener<Set<V>>() {
|
||||
|
||||
@Override
|
||||
public void operationComplete(Future<Set<V>> future) throws Exception {
|
||||
if (!future.isSuccess()) {
|
||||
result.tryFailure(future.cause());
|
||||
return;
|
||||
}
|
||||
|
||||
Set<V> set = future.getNow();
|
||||
Map<HashValue, Object> newstate = new HashMap<HashValue, Object>(state);
|
||||
for (Iterator<V> iterator = set.iterator(); iterator.hasNext();) {
|
||||
V key = iterator.next();
|
||||
Object value = newstate.remove(toHash(key));
|
||||
if (value == NULL) {
|
||||
iterator.remove();
|
||||
}
|
||||
}
|
||||
|
||||
for (Object value : newstate.values()) {
|
||||
if (value == NULL) {
|
||||
continue;
|
||||
}
|
||||
set.add((V) value);
|
||||
}
|
||||
|
||||
result.trySuccess(set);
|
||||
}
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
public RFuture<Boolean> addAsync(V value) {
|
||||
final TransactionalOperation operation = createAddOperation(value);
|
||||
return addAsync(value, operation);
|
||||
}
|
||||
|
||||
public RFuture<Boolean> addAsync(final V value, final TransactionalOperation operation) {
|
||||
final RPromise<Boolean> result = new RedissonPromise<Boolean>();
|
||||
executeLocked(result, value, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
final HashValue keyHash = toHash(value);
|
||||
Object entry = state.get(keyHash);
|
||||
if (entry != null) {
|
||||
operations.add(operation);
|
||||
state.put(keyHash, value);
|
||||
if (deleted != null) {
|
||||
deleted = false;
|
||||
}
|
||||
|
||||
result.trySuccess(entry == NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
set.containsAsync(value).addListener(new FutureListener<Boolean>() {
|
||||
@Override
|
||||
public void operationComplete(Future<Boolean> future) throws Exception {
|
||||
if (!future.isSuccess()) {
|
||||
result.tryFailure(future.cause());
|
||||
return;
|
||||
}
|
||||
|
||||
operations.add(operation);
|
||||
state.put(keyHash, value);
|
||||
if (deleted != null) {
|
||||
deleted = false;
|
||||
}
|
||||
result.trySuccess(!future.getNow());
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
protected abstract TransactionalOperation createAddOperation(V value);
|
||||
|
||||
public RFuture<V> removeRandomAsync() {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public RFuture<Set<V>> removeRandomAsync(int amount) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public RFuture<Boolean> moveAsync(final String destination, final V value) {
|
||||
RSet<V> destinationSet = new RedissonSet<V>(object.getCodec(), commandExecutor, destination, null);
|
||||
|
||||
final RPromise<Boolean> result = new RedissonPromise<Boolean>();
|
||||
RLock destinationLock = destinationSet.getLock(value);
|
||||
RLock lock = getLock(value);
|
||||
final RedissonMultiLock multiLock = new RedissonMultiLock(destinationLock, lock);
|
||||
final long threadId = Thread.currentThread().getId();
|
||||
multiLock.lockAsync(timeout, TimeUnit.MILLISECONDS).addListener(new FutureListener<Void>() {
|
||||
@Override
|
||||
public void operationComplete(Future<Void> future) throws Exception {
|
||||
if (!future.isSuccess()) {
|
||||
multiLock.unlockAsync(threadId);
|
||||
result.tryFailure(future.cause());
|
||||
return;
|
||||
}
|
||||
|
||||
final HashValue keyHash = toHash(value);
|
||||
Object currentValue = state.get(keyHash);
|
||||
if (currentValue != null) {
|
||||
operations.add(createMoveOperation(destination, value, threadId));
|
||||
if (currentValue == NULL) {
|
||||
result.trySuccess(false);
|
||||
} else {
|
||||
state.put(keyHash, NULL);
|
||||
result.trySuccess(true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
set.containsAsync(value).addListener(new FutureListener<Boolean>() {
|
||||
@Override
|
||||
public void operationComplete(Future<Boolean> future) throws Exception {
|
||||
if (!future.isSuccess()) {
|
||||
result.tryFailure(future.cause());
|
||||
return;
|
||||
}
|
||||
|
||||
operations.add(createMoveOperation(destination, value, threadId));
|
||||
if (future.getNow()) {
|
||||
state.put(keyHash, NULL);
|
||||
}
|
||||
|
||||
result.trySuccess(future.getNow());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
protected abstract MoveOperation createMoveOperation(String destination, V value, long threadId);
|
||||
|
||||
protected abstract RLock getLock(V value);
|
||||
|
||||
public RFuture<Boolean> removeAsync(final Object value) {
|
||||
final RPromise<Boolean> result = new RedissonPromise<Boolean>();
|
||||
executeLocked(result, (V)value, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
final HashValue keyHash = toHash(value);
|
||||
Object currentValue = state.get(keyHash);
|
||||
if (currentValue != null) {
|
||||
operations.add(createRemoveOperation(value));
|
||||
if (currentValue == NULL) {
|
||||
result.trySuccess(false);
|
||||
} else {
|
||||
state.put(keyHash, NULL);
|
||||
result.trySuccess(true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
set.containsAsync(value).addListener(new FutureListener<Boolean>() {
|
||||
@Override
|
||||
public void operationComplete(Future<Boolean> future) throws Exception {
|
||||
if (!future.isSuccess()) {
|
||||
result.tryFailure(future.cause());
|
||||
return;
|
||||
}
|
||||
|
||||
operations.add(createRemoveOperation(value));
|
||||
if (future.getNow()) {
|
||||
state.put(keyHash, NULL);
|
||||
}
|
||||
|
||||
result.trySuccess(future.getNow());
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
});
|
||||
return result;
|
||||
|
||||
}
|
||||
|
||||
protected abstract TransactionalOperation createRemoveOperation(Object value);
|
||||
|
||||
public RFuture<Boolean> containsAllAsync(Collection<?> c) {
|
||||
List<Object> coll = new ArrayList<Object>(c);
|
||||
for (Iterator<Object> iterator = coll.iterator(); iterator.hasNext();) {
|
||||
Object value = iterator.next();
|
||||
for (Object val : state.values()) {
|
||||
if (val != NULL && isEqual(val, value)) {
|
||||
iterator.remove();
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return set.containsAllAsync(coll);
|
||||
}
|
||||
|
||||
public RFuture<Boolean> addAllAsync(final Collection<? extends V> c) {
|
||||
final RPromise<Boolean> result = new RedissonPromise<Boolean>();
|
||||
executeLocked(result, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
containsAllAsync(c).addListener(new FutureListener<Boolean>() {
|
||||
@Override
|
||||
public void operationComplete(Future<Boolean> future) throws Exception {
|
||||
if (!future.isSuccess()) {
|
||||
result.tryFailure(future.cause());
|
||||
return;
|
||||
}
|
||||
|
||||
for (V value : c) {
|
||||
operations.add(createAddOperation(value));
|
||||
HashValue keyHash = toHash(value);
|
||||
state.put(keyHash, value);
|
||||
}
|
||||
|
||||
if (deleted != null) {
|
||||
deleted = false;
|
||||
}
|
||||
|
||||
result.trySuccess(!future.getNow());
|
||||
}
|
||||
});
|
||||
}
|
||||
}, c);
|
||||
return result;
|
||||
}
|
||||
|
||||
public RFuture<Boolean> retainAllAsync(Collection<?> c) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public RFuture<Boolean> removeAllAsync(final Collection<?> c) {
|
||||
final RPromise<Boolean> result = new RedissonPromise<Boolean>();
|
||||
executeLocked(result, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
containsAllAsync(c).addListener(new FutureListener<Boolean>() {
|
||||
@Override
|
||||
public void operationComplete(Future<Boolean> future) throws Exception {
|
||||
if (!future.isSuccess()) {
|
||||
result.tryFailure(future.cause());
|
||||
return;
|
||||
}
|
||||
|
||||
for (Object value : c) {
|
||||
operations.add(createRemoveOperation(value));
|
||||
HashValue keyHash = toHash(value);
|
||||
state.put(keyHash, NULL);
|
||||
}
|
||||
|
||||
result.trySuccess(!future.getNow());
|
||||
}
|
||||
});
|
||||
}
|
||||
}, c);
|
||||
return result;
|
||||
}
|
||||
|
||||
public RFuture<Integer> unionAsync(String... names) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public RFuture<Integer> diffAsync(String... names) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public RFuture<Integer> intersectionAsync(String... names) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public RFuture<Set<V>> readSortAsync(SortOrder order) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public RFuture<Set<V>> readSortAsync(SortOrder order, int offset, int count) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public RFuture<Set<V>> readSortAsync(String byPattern, SortOrder order) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public <T> RFuture<Collection<T>> readSortAsync(String byPattern, List<String> getPatterns, SortOrder order, int offset, int count) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public RFuture<Integer> sortToAsync(String destName, String byPattern, List<String> getPatterns, SortOrder order, int offset, int count) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public RFuture<Set<V>> readUnionAsync(String... names) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public RFuture<Set<V>> readDiffAsync(String... names) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
public RFuture<Set<V>> readIntersectionAsync(String... names) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
private boolean isEqual(Object value, Object oldValue) {
|
||||
ByteBuf valueBuf = ((RedissonObject)set).encode(value);
|
||||
ByteBuf oldValueBuf = ((RedissonObject)set).encode(oldValue);
|
||||
|
||||
try {
|
||||
return valueBuf.equals(oldValueBuf);
|
||||
} finally {
|
||||
valueBuf.readableBytes();
|
||||
oldValueBuf.readableBytes();
|
||||
}
|
||||
}
|
||||
|
||||
protected <R> void executeLocked(final RPromise<R> promise, Object value, final Runnable runnable) {
|
||||
RLock lock = getLock((V) value);
|
||||
executeLocked(promise, runnable, lock);
|
||||
}
|
||||
|
||||
protected <R> void executeLocked(final RPromise<R> promise, final Runnable runnable, RLock lock) {
|
||||
lock.lockAsync(timeout, TimeUnit.MILLISECONDS).addListener(new FutureListener<Void>() {
|
||||
@Override
|
||||
public void operationComplete(Future<Void> future) throws Exception {
|
||||
if (future.isSuccess()) {
|
||||
runnable.run();
|
||||
} else {
|
||||
promise.tryFailure(future.cause());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
protected <R> void executeLocked(final RPromise<R> promise, final Runnable runnable, Collection<?> values) {
|
||||
List<RLock> locks = new ArrayList<RLock>(values.size());
|
||||
for (Object value : values) {
|
||||
RLock lock = getLock((V) value);
|
||||
locks.add(lock);
|
||||
}
|
||||
final RedissonMultiLock multiLock = new RedissonMultiLock(locks.toArray(new RLock[locks.size()]));
|
||||
final long threadId = Thread.currentThread().getId();
|
||||
multiLock.lockAsync(timeout, TimeUnit.MILLISECONDS).addListener(new FutureListener<Void>() {
|
||||
@Override
|
||||
public void operationComplete(Future<Void> future) throws Exception {
|
||||
if (future.isSuccess()) {
|
||||
runnable.run();
|
||||
} else {
|
||||
multiLock.unlockAsync(threadId);
|
||||
promise.tryFailure(future.cause());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,350 @@
|
||||
/**
|
||||
* Copyright 2018 Nikita Koksharov
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.redisson.transaction;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import org.redisson.RedissonBatch;
|
||||
import org.redisson.RedissonLocalCachedMap;
|
||||
import org.redisson.RedissonObject;
|
||||
import org.redisson.RedissonTopic;
|
||||
import org.redisson.api.RBucket;
|
||||
import org.redisson.api.RFuture;
|
||||
import org.redisson.api.RLocalCachedMap;
|
||||
import org.redisson.api.RMap;
|
||||
import org.redisson.api.RMapCache;
|
||||
import org.redisson.api.RMultimapCacheAsync;
|
||||
import org.redisson.api.RSet;
|
||||
import org.redisson.api.RSetCache;
|
||||
import org.redisson.api.RTopic;
|
||||
import org.redisson.api.RTopicAsync;
|
||||
import org.redisson.api.RTransaction;
|
||||
import org.redisson.api.TransactionOptions;
|
||||
import org.redisson.api.listener.MessageListener;
|
||||
import org.redisson.cache.LocalCachedMapDisable;
|
||||
import org.redisson.cache.LocalCachedMapDisabledKey;
|
||||
import org.redisson.cache.LocalCachedMapEnable;
|
||||
import org.redisson.cache.LocalCachedMessageCodec;
|
||||
import org.redisson.client.codec.Codec;
|
||||
import org.redisson.command.CommandAsyncExecutor;
|
||||
import org.redisson.command.CommandBatchService;
|
||||
import org.redisson.connection.MasterSlaveEntry;
|
||||
import org.redisson.transaction.operation.TransactionalOperation;
|
||||
import org.redisson.transaction.operation.map.MapOperation;
|
||||
|
||||
import io.netty.buffer.ByteBufUtil;
|
||||
import io.netty.util.concurrent.Future;
|
||||
import io.netty.util.concurrent.FutureListener;
|
||||
import io.netty.util.internal.PlatformDependent;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Nikita Koksharov
|
||||
*
|
||||
*/
|
||||
public class RedissonTransaction implements RTransaction {
|
||||
|
||||
private final CommandAsyncExecutor commandExecutor;
|
||||
private final AtomicBoolean executed = new AtomicBoolean();
|
||||
|
||||
private final TransactionOptions options;
|
||||
private final List<TransactionalOperation> operations = new ArrayList<TransactionalOperation>();
|
||||
private final Set<String> localCaches = new HashSet<String>();
|
||||
private final long startTime = System.currentTimeMillis();
|
||||
|
||||
public RedissonTransaction(CommandAsyncExecutor commandExecutor, TransactionOptions options) {
|
||||
super();
|
||||
this.options = options;
|
||||
this.commandExecutor = commandExecutor;
|
||||
}
|
||||
|
||||
@Override
|
||||
public <K, V> RLocalCachedMap<K, V> getLocalCachedMap(RLocalCachedMap<K, V> fromInstance) {
|
||||
checkState();
|
||||
|
||||
localCaches.add(fromInstance.getName());
|
||||
return new RedissonTransactionalLocalCachedMap<K, V>(commandExecutor,
|
||||
operations, options.getTimeout(), executed, fromInstance);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <V> RBucket<V> getBucket(String name) {
|
||||
checkState();
|
||||
|
||||
return new RedissonTransactionalBucket<V>(commandExecutor, name, operations, executed);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <V> RBucket<V> getBucket(String name, Codec codec) {
|
||||
checkState();
|
||||
|
||||
return new RedissonTransactionalBucket<V>(codec, commandExecutor, name, operations, executed);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <V> RSet<V> getSet(String name) {
|
||||
checkState();
|
||||
|
||||
return new RedissonTransactionalSet<V>(commandExecutor, name, operations, options.getTimeout(), executed);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <V> RSet<V> getSet(String name, Codec codec) {
|
||||
checkState();
|
||||
|
||||
return new RedissonTransactionalSet<V>(codec, commandExecutor, name, operations, options.getTimeout(), executed);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <V> RSetCache<V> getSetCache(String name) {
|
||||
checkState();
|
||||
|
||||
return new RedissonTransactionalSetCache<V>(commandExecutor, name, operations, options.getTimeout(), executed);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <V> RSetCache<V> getSetCache(String name, Codec codec) {
|
||||
checkState();
|
||||
|
||||
return new RedissonTransactionalSetCache<V>(codec, commandExecutor, name, operations, options.getTimeout(), executed);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <K, V> RMap<K, V> getMap(String name) {
|
||||
checkState();
|
||||
|
||||
return new RedissonTransactionalMap<K, V>(commandExecutor, name, operations, options.getTimeout(), executed);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <K, V> RMap<K, V> getMap(String name, Codec codec) {
|
||||
checkState();
|
||||
|
||||
return new RedissonTransactionalMap<K, V>(codec, commandExecutor, name, operations, options.getTimeout(), executed);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <K, V> RMapCache<K, V> getMapCache(String name) {
|
||||
checkState();
|
||||
|
||||
return new RedissonTransactionalMapCache<K, V>(commandExecutor, name, operations, options.getTimeout(), executed);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <K, V> RMapCache<K, V> getMapCache(String name, Codec codec) {
|
||||
checkState();
|
||||
|
||||
return new RedissonTransactionalMapCache<K, V>(codec, commandExecutor, name, operations, options.getTimeout(), executed);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void commit() {
|
||||
checkState();
|
||||
|
||||
checkTimeout();
|
||||
|
||||
|
||||
CommandBatchService transactionExecutor = new CommandBatchService(commandExecutor.getConnectionManager());
|
||||
for (TransactionalOperation transactionalOperation : operations) {
|
||||
transactionalOperation.commit(transactionExecutor);
|
||||
}
|
||||
|
||||
String id = generateId();
|
||||
Map<TransactionalOperation, List<byte[]>> hashes = disableLocalCache(id);
|
||||
|
||||
try {
|
||||
checkTimeout();
|
||||
} catch (TransactionTimeoutException e) {
|
||||
enableLocalCache(id, hashes);
|
||||
throw e;
|
||||
}
|
||||
|
||||
int syncSlaves = 0;
|
||||
if (!commandExecutor.getConnectionManager().isClusterMode()) {
|
||||
MasterSlaveEntry entry = commandExecutor.getConnectionManager().getEntrySet().iterator().next();
|
||||
syncSlaves = entry.getAvailableClients() - 1;
|
||||
}
|
||||
|
||||
transactionExecutor.execute(syncSlaves, options.getSyncTimeout(), false,
|
||||
options.getResponseTimeout(), options.getRetryAttempts(), options.getRetryInterval(), true);
|
||||
|
||||
enableLocalCache(id, hashes);
|
||||
|
||||
executed.set(true);
|
||||
}
|
||||
|
||||
private void checkTimeout() {
|
||||
if (System.currentTimeMillis() - startTime > options.getTimeout()) {
|
||||
throw new TransactionTimeoutException("Transaction was discarded due to timeout " + options.getTimeout() + " milliseconds");
|
||||
}
|
||||
}
|
||||
|
||||
private void enableLocalCache(String requestId, Map<TransactionalOperation, List<byte[]>> hashes) {
|
||||
if (hashes.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
RedissonBatch publishBatch = new RedissonBatch(null, commandExecutor.getConnectionManager());
|
||||
for (Entry<TransactionalOperation, List<byte[]>> entry : hashes.entrySet()) {
|
||||
String name = RedissonObject.suffixName(entry.getKey().getName(), RedissonLocalCachedMap.TOPIC_SUFFIX);
|
||||
RTopicAsync<Object> topic = publishBatch.getTopic(name, LocalCachedMessageCodec.INSTANCE);
|
||||
LocalCachedMapEnable msg = new LocalCachedMapEnable(requestId, entry.getValue().toArray(new byte[entry.getValue().size()][]));
|
||||
topic.publishAsync(msg);
|
||||
}
|
||||
|
||||
try {
|
||||
publishBatch.execute();
|
||||
} catch (Exception e) {
|
||||
throw new TransactionException("Unable to finish transaction over local cached map objects: " + localCaches, e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private Map<TransactionalOperation, List<byte[]>> disableLocalCache(String requestId) {
|
||||
if (localCaches.isEmpty()) {
|
||||
return Collections.emptyMap();
|
||||
}
|
||||
|
||||
Map<TransactionalOperation, List<byte[]>> hashes = new HashMap<TransactionalOperation, List<byte[]>>(localCaches.size());
|
||||
RedissonBatch batch = new RedissonBatch(null, commandExecutor.getConnectionManager());
|
||||
for (TransactionalOperation transactionalOperation : operations) {
|
||||
if (localCaches.contains(transactionalOperation.getName())) {
|
||||
MapOperation mapOperation = (MapOperation) transactionalOperation;
|
||||
RedissonLocalCachedMap<?, ?> map = (RedissonLocalCachedMap<?, ?>)mapOperation.getMap();
|
||||
|
||||
byte[] key = map.toCacheKey(mapOperation.getKey()).getKeyHash();
|
||||
List<byte[]> list = hashes.get(transactionalOperation);
|
||||
if (list == null) {
|
||||
list = new ArrayList<byte[]>();
|
||||
hashes.put(transactionalOperation, list);
|
||||
}
|
||||
list.add(key);
|
||||
|
||||
String disabledKeysName = RedissonObject.suffixName(transactionalOperation.getName(), RedissonLocalCachedMap.DISABLED_KEYS_SUFFIX);
|
||||
RMultimapCacheAsync<LocalCachedMapDisabledKey, String> multimap = batch.getListMultimapCache(disabledKeysName, transactionalOperation.getCodec());
|
||||
LocalCachedMapDisabledKey localCacheKey = new LocalCachedMapDisabledKey(requestId, options.getResponseTimeout());
|
||||
multimap.putAsync(localCacheKey, ByteBufUtil.hexDump(key));
|
||||
multimap.expireKeyAsync(localCacheKey, options.getResponseTimeout(), TimeUnit.MILLISECONDS);
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
batch.execute();
|
||||
} catch (Exception e) {
|
||||
throw new TransactionException("Unable to execute transaction over local cached map objects: " + localCaches, e);
|
||||
}
|
||||
|
||||
final Map<String, AtomicInteger> map = new HashMap<String, AtomicInteger>();
|
||||
final CountDownLatch latch = new CountDownLatch(hashes.size());
|
||||
List<RTopic<Object>> topics = new ArrayList<RTopic<Object>>();
|
||||
for (final Entry<TransactionalOperation, List<byte[]>> entry : hashes.entrySet()) {
|
||||
RTopic<Object> topic = new RedissonTopic<Object>(LocalCachedMessageCodec.INSTANCE,
|
||||
commandExecutor, RedissonObject.suffixName(entry.getKey().getName(), requestId + RedissonLocalCachedMap.DISABLED_ACK_SUFFIX));
|
||||
topics.add(topic);
|
||||
map.put(entry.getKey().getName(), new AtomicInteger());
|
||||
topic.addListener(new MessageListener<Object>() {
|
||||
@Override
|
||||
public void onMessage(String channel, Object msg) {
|
||||
AtomicInteger counter = map.get(entry.getKey().getName());
|
||||
if (counter.decrementAndGet() == 0) {
|
||||
latch.countDown();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
RedissonBatch publishBatch = new RedissonBatch(null, commandExecutor.getConnectionManager());
|
||||
for (final Entry<TransactionalOperation, List<byte[]>> entry : hashes.entrySet()) {
|
||||
String disabledKeysName = RedissonObject.suffixName(entry.getKey().getName(), RedissonLocalCachedMap.DISABLED_KEYS_SUFFIX);
|
||||
RMultimapCacheAsync<LocalCachedMapDisabledKey, String> multimap = publishBatch.getListMultimapCache(disabledKeysName, entry.getKey().getCodec());
|
||||
LocalCachedMapDisabledKey localCacheKey = new LocalCachedMapDisabledKey(requestId, options.getResponseTimeout());
|
||||
multimap.removeAllAsync(localCacheKey);
|
||||
|
||||
RTopicAsync<Object> topic = publishBatch.getTopic(RedissonObject.suffixName(entry.getKey().getName(), RedissonLocalCachedMap.TOPIC_SUFFIX), LocalCachedMessageCodec.INSTANCE);
|
||||
RFuture<Long> future = topic.publishAsync(new LocalCachedMapDisable(requestId,
|
||||
entry.getValue().toArray(new byte[entry.getValue().size()][]), options.getResponseTimeout()));
|
||||
future.addListener(new FutureListener<Long>() {
|
||||
@Override
|
||||
public void operationComplete(Future<Long> future) throws Exception {
|
||||
if (!future.isSuccess()) {
|
||||
return;
|
||||
}
|
||||
|
||||
int receivers = future.getNow().intValue();
|
||||
AtomicInteger counter = map.get(entry.getKey().getName());
|
||||
if (counter.addAndGet(receivers) == 0) {
|
||||
latch.countDown();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
try {
|
||||
publishBatch.execute();
|
||||
} catch (Exception e) {
|
||||
throw new TransactionException("Unable to execute transaction over local cached map objects: " + localCaches, e);
|
||||
}
|
||||
|
||||
for (RTopic<Object> topic : topics) {
|
||||
topic.removeAllListeners();
|
||||
}
|
||||
|
||||
try {
|
||||
latch.await(options.getResponseTimeout(), TimeUnit.MILLISECONDS);
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
}
|
||||
return hashes;
|
||||
}
|
||||
|
||||
protected static String generateId() {
|
||||
byte[] id = new byte[16];
|
||||
// TODO JDK UPGRADE replace to native ThreadLocalRandom
|
||||
PlatformDependent.threadLocalRandom().nextBytes(id);
|
||||
return ByteBufUtil.hexDump(id);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void rollback() {
|
||||
checkState();
|
||||
|
||||
CommandBatchService executorService = new CommandBatchService(commandExecutor.getConnectionManager());
|
||||
for (TransactionalOperation transactionalOperation : operations) {
|
||||
transactionalOperation.rollback(executorService);
|
||||
}
|
||||
executorService.execute(0, 0, false, 0, 0, 0, true);
|
||||
operations.clear();
|
||||
executed.set(true);
|
||||
}
|
||||
|
||||
protected void checkState() {
|
||||
if (executed.get()) {
|
||||
throw new IllegalStateException("Unable to execute operation. Transaction was finished!");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,518 @@
|
||||
/**
|
||||
* Copyright 2018 Nikita Koksharov
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.redisson.transaction;
|
||||
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import org.redisson.RedissonBucket;
|
||||
import org.redisson.RedissonLock;
|
||||
import org.redisson.api.RFuture;
|
||||
import org.redisson.api.RLock;
|
||||
import org.redisson.client.codec.Codec;
|
||||
import org.redisson.command.CommandAsyncExecutor;
|
||||
import org.redisson.misc.RPromise;
|
||||
import org.redisson.misc.RedissonPromise;
|
||||
import org.redisson.transaction.operation.DeleteOperation;
|
||||
import org.redisson.transaction.operation.TouchOperation;
|
||||
import org.redisson.transaction.operation.TransactionalOperation;
|
||||
import org.redisson.transaction.operation.UnlinkOperation;
|
||||
import org.redisson.transaction.operation.bucket.BucketCompareAndSetOperation;
|
||||
import org.redisson.transaction.operation.bucket.BucketGetAndDeleteOperation;
|
||||
import org.redisson.transaction.operation.bucket.BucketGetAndSetOperation;
|
||||
import org.redisson.transaction.operation.bucket.BucketSetOperation;
|
||||
import org.redisson.transaction.operation.bucket.BucketTrySetOperation;
|
||||
|
||||
import io.netty.buffer.ByteBuf;
|
||||
import io.netty.util.concurrent.Future;
|
||||
import io.netty.util.concurrent.FutureListener;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Nikita Koksharov
|
||||
*
|
||||
* @param <V> value type
|
||||
*/
|
||||
public class RedissonTransactionalBucket<V> extends RedissonBucket<V> {
|
||||
|
||||
static final Object NULL = new Object();
|
||||
|
||||
private long timeout;
|
||||
private final AtomicBoolean executed;
|
||||
private final List<TransactionalOperation> operations;
|
||||
private Object state;
|
||||
|
||||
public RedissonTransactionalBucket(CommandAsyncExecutor commandExecutor, String name, List<TransactionalOperation> operations, AtomicBoolean executed) {
|
||||
super(commandExecutor, name);
|
||||
this.operations = operations;
|
||||
this.executed = executed;
|
||||
}
|
||||
|
||||
public RedissonTransactionalBucket(Codec codec, CommandAsyncExecutor commandExecutor, String name, List<TransactionalOperation> operations, AtomicBoolean executed) {
|
||||
super(codec, commandExecutor, name);
|
||||
this.operations = operations;
|
||||
this.executed = executed;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RFuture<Boolean> expireAsync(long timeToLive, TimeUnit timeUnit) {
|
||||
throw new UnsupportedOperationException("expire method is not supported in transaction");
|
||||
}
|
||||
|
||||
@Override
|
||||
public RFuture<Boolean> expireAtAsync(Date timestamp) {
|
||||
throw new UnsupportedOperationException("expireAt method is not supported in transaction");
|
||||
}
|
||||
|
||||
@Override
|
||||
public RFuture<Boolean> expireAtAsync(long timestamp) {
|
||||
throw new UnsupportedOperationException("expireAt method is not supported in transaction");
|
||||
}
|
||||
|
||||
@Override
|
||||
public RFuture<Boolean> clearExpireAsync() {
|
||||
throw new UnsupportedOperationException("clearExpire method is not supported in transaction");
|
||||
}
|
||||
|
||||
@Override
|
||||
public RFuture<Boolean> moveAsync(int database) {
|
||||
throw new UnsupportedOperationException("moveAsync method is not supported in transaction");
|
||||
}
|
||||
|
||||
@Override
|
||||
public RFuture<Void> migrateAsync(String host, int port, int database, long timeout) {
|
||||
throw new UnsupportedOperationException("migrateAsync method is not supported in transaction");
|
||||
}
|
||||
|
||||
@Override
|
||||
public RFuture<Long> sizeAsync() {
|
||||
checkState();
|
||||
if (state != null) {
|
||||
if (state == NULL) {
|
||||
return RedissonPromise.newSucceededFuture(0L);
|
||||
} else {
|
||||
ByteBuf buf = encode(state);
|
||||
long size = buf.readableBytes();
|
||||
buf.release();
|
||||
return RedissonPromise.newSucceededFuture(size);
|
||||
}
|
||||
}
|
||||
|
||||
return super.sizeAsync();
|
||||
}
|
||||
|
||||
@Override
|
||||
public RFuture<Boolean> isExistsAsync() {
|
||||
checkState();
|
||||
if (state != null) {
|
||||
if (state == NULL) {
|
||||
return RedissonPromise.newSucceededFuture(null);
|
||||
} else {
|
||||
return RedissonPromise.newSucceededFuture(true);
|
||||
}
|
||||
}
|
||||
|
||||
return super.isExistsAsync();
|
||||
}
|
||||
|
||||
@Override
|
||||
public RFuture<Boolean> touchAsync() {
|
||||
checkState();
|
||||
final RPromise<Boolean> result = new RedissonPromise<Boolean>();
|
||||
executeLocked(result, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (state != null) {
|
||||
operations.add(new TouchOperation(getName(), getLockName()));
|
||||
result.trySuccess(state != NULL);
|
||||
return;
|
||||
}
|
||||
|
||||
isExistsAsync().addListener(new FutureListener<Boolean>() {
|
||||
@Override
|
||||
public void operationComplete(Future<Boolean> future) throws Exception {
|
||||
if (!future.isSuccess()) {
|
||||
result.tryFailure(future.cause());
|
||||
return;
|
||||
}
|
||||
|
||||
operations.add(new TouchOperation(getName(), getLockName()));
|
||||
result.trySuccess(future.getNow());
|
||||
}
|
||||
});
|
||||
|
||||
result.trySuccess(null);
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RFuture<Boolean> unlinkAsync() {
|
||||
checkState();
|
||||
final RPromise<Boolean> result = new RedissonPromise<Boolean>();
|
||||
executeLocked(result, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (state != null) {
|
||||
operations.add(new UnlinkOperation(getName(), getLockName()));
|
||||
if (state == NULL) {
|
||||
result.trySuccess(false);
|
||||
} else {
|
||||
state = NULL;
|
||||
result.trySuccess(true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
isExistsAsync().addListener(new FutureListener<Boolean>() {
|
||||
@Override
|
||||
public void operationComplete(Future<Boolean> future) throws Exception {
|
||||
if (!future.isSuccess()) {
|
||||
result.tryFailure(future.cause());
|
||||
return;
|
||||
}
|
||||
|
||||
operations.add(new UnlinkOperation(getName(), getLockName()));
|
||||
state = NULL;
|
||||
result.trySuccess(future.getNow());
|
||||
}
|
||||
});
|
||||
|
||||
result.trySuccess(null);
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RFuture<Boolean> deleteAsync() {
|
||||
checkState();
|
||||
final RPromise<Boolean> result = new RedissonPromise<Boolean>();
|
||||
executeLocked(result, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (state != null) {
|
||||
operations.add(new DeleteOperation(getName(), getLockName()));
|
||||
if (state == NULL) {
|
||||
result.trySuccess(false);
|
||||
} else {
|
||||
state = NULL;
|
||||
result.trySuccess(true);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
isExistsAsync().addListener(new FutureListener<Boolean>() {
|
||||
@Override
|
||||
public void operationComplete(Future<Boolean> future) throws Exception {
|
||||
if (!future.isSuccess()) {
|
||||
result.tryFailure(future.cause());
|
||||
return;
|
||||
}
|
||||
|
||||
operations.add(new DeleteOperation(getName(), getLockName()));
|
||||
state = NULL;
|
||||
result.trySuccess(future.getNow());
|
||||
}
|
||||
});
|
||||
|
||||
result.trySuccess(null);
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public RFuture<V> getAsync() {
|
||||
checkState();
|
||||
if (state != null) {
|
||||
if (state == NULL) {
|
||||
return RedissonPromise.newSucceededFuture(null);
|
||||
} else {
|
||||
return RedissonPromise.newSucceededFuture((V)state);
|
||||
}
|
||||
}
|
||||
|
||||
return super.getAsync();
|
||||
}
|
||||
|
||||
@Override
|
||||
public RFuture<Boolean> compareAndSetAsync(final V expect, final V update) {
|
||||
checkState();
|
||||
final RPromise<Boolean> result = new RedissonPromise<Boolean>();
|
||||
executeLocked(result, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (state != null) {
|
||||
operations.add(new BucketCompareAndSetOperation<V>(getName(), getLockName(), getCodec(), expect, update));
|
||||
if ((state == NULL && expect == null)
|
||||
|| isEquals(state, expect)) {
|
||||
if (update == null) {
|
||||
state = NULL;
|
||||
} else {
|
||||
state = update;
|
||||
}
|
||||
result.trySuccess(true);
|
||||
} else {
|
||||
result.trySuccess(false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
getAsync().addListener(new FutureListener<V>() {
|
||||
@Override
|
||||
public void operationComplete(Future<V> future) throws Exception {
|
||||
if (!future.isSuccess()) {
|
||||
result.tryFailure(future.cause());
|
||||
return;
|
||||
}
|
||||
|
||||
operations.add(new BucketCompareAndSetOperation<V>(getName(), getLockName(), getCodec(), expect, update));
|
||||
if ((future.getNow() == null && expect == null)
|
||||
|| isEquals(future.getNow(), expect)) {
|
||||
if (update == null) {
|
||||
state = NULL;
|
||||
} else {
|
||||
state = update;
|
||||
}
|
||||
result.trySuccess(true);
|
||||
} else {
|
||||
result.trySuccess(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public RFuture<V> getAndSetAsync(final V newValue) {
|
||||
checkState();
|
||||
final RPromise<V> result = new RedissonPromise<V>();
|
||||
executeLocked(result, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (state != null) {
|
||||
Object prevValue;
|
||||
if (state == NULL) {
|
||||
prevValue = null;
|
||||
} else {
|
||||
prevValue = state;
|
||||
}
|
||||
operations.add(new BucketGetAndSetOperation<V>(getName(), getLockName(), getCodec(), newValue));
|
||||
if (newValue == null) {
|
||||
state = NULL;
|
||||
} else {
|
||||
state = newValue;
|
||||
}
|
||||
result.trySuccess((V) prevValue);
|
||||
return;
|
||||
}
|
||||
|
||||
getAsync().addListener(new FutureListener<V>() {
|
||||
@Override
|
||||
public void operationComplete(Future<V> future) throws Exception {
|
||||
if (!future.isSuccess()) {
|
||||
result.tryFailure(future.cause());
|
||||
return;
|
||||
}
|
||||
|
||||
if (newValue == null) {
|
||||
state = NULL;
|
||||
} else {
|
||||
state = newValue;
|
||||
}
|
||||
operations.add(new BucketGetAndSetOperation<V>(getName(), getLockName(), getCodec(), newValue));
|
||||
result.trySuccess(future.getNow());
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public RFuture<V> getAndDeleteAsync() {
|
||||
checkState();
|
||||
final RPromise<V> result = new RedissonPromise<V>();
|
||||
executeLocked(result, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (state != null) {
|
||||
Object prevValue;
|
||||
if (state == NULL) {
|
||||
prevValue = null;
|
||||
} else {
|
||||
prevValue = state;
|
||||
}
|
||||
operations.add(new BucketGetAndDeleteOperation<V>(getName(), getLockName(), getCodec()));
|
||||
state = NULL;
|
||||
result.trySuccess((V) prevValue);
|
||||
return;
|
||||
}
|
||||
|
||||
getAsync().addListener(new FutureListener<V>() {
|
||||
@Override
|
||||
public void operationComplete(Future<V> future) throws Exception {
|
||||
if (!future.isSuccess()) {
|
||||
result.tryFailure(future.cause());
|
||||
return;
|
||||
}
|
||||
|
||||
state = NULL;
|
||||
operations.add(new BucketGetAndDeleteOperation<V>(getName(), getLockName(), getCodec()));
|
||||
result.trySuccess(future.getNow());
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RFuture<Void> setAsync(V newValue) {
|
||||
return setAsync(newValue, new BucketSetOperation<V>(getName(), getLockName(), getCodec(), newValue));
|
||||
}
|
||||
|
||||
private RFuture<Void> setAsync(final V newValue, final TransactionalOperation operation) {
|
||||
checkState();
|
||||
final RPromise<Void> result = new RedissonPromise<Void>();
|
||||
executeLocked(result, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
operations.add(operation);
|
||||
if (newValue == null) {
|
||||
state = NULL;
|
||||
} else {
|
||||
state = newValue;
|
||||
}
|
||||
result.trySuccess(null);
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RFuture<Void> setAsync(V value, long timeToLive, TimeUnit timeUnit) {
|
||||
return setAsync(value, new BucketSetOperation<V>(getName(), getLockName(), getCodec(), value, timeToLive, timeUnit));
|
||||
}
|
||||
|
||||
@Override
|
||||
public RFuture<Boolean> trySetAsync(V newValue) {
|
||||
return trySet(newValue, new BucketTrySetOperation<V>(getName(), getLockName(), getCodec(), newValue));
|
||||
}
|
||||
|
||||
@Override
|
||||
public RFuture<Boolean> trySetAsync(V value, long timeToLive, TimeUnit timeUnit) {
|
||||
return trySet(value, new BucketTrySetOperation<V>(getName(), getLockName(), getCodec(), value, timeToLive, timeUnit));
|
||||
}
|
||||
|
||||
private RFuture<Boolean> trySet(final V newValue, final TransactionalOperation operation) {
|
||||
checkState();
|
||||
final RPromise<Boolean> result = new RedissonPromise<Boolean>();
|
||||
executeLocked(result, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
if (state != null) {
|
||||
operations.add(operation);
|
||||
if (state == NULL) {
|
||||
if (newValue == null) {
|
||||
state = NULL;
|
||||
} else {
|
||||
state = newValue;
|
||||
}
|
||||
result.trySuccess(true);
|
||||
} else {
|
||||
result.trySuccess(false);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
getAsync().addListener(new FutureListener<V>() {
|
||||
@Override
|
||||
public void operationComplete(Future<V> future) throws Exception {
|
||||
if (!future.isSuccess()) {
|
||||
result.tryFailure(future.cause());
|
||||
return;
|
||||
}
|
||||
|
||||
operations.add(operation);
|
||||
if (future.getNow() == null) {
|
||||
if (newValue == null) {
|
||||
state = NULL;
|
||||
} else {
|
||||
state = newValue;
|
||||
}
|
||||
result.trySuccess(true);
|
||||
} else {
|
||||
result.trySuccess(false);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
private boolean isEquals(Object value, Object oldValue) {
|
||||
ByteBuf valueBuf = encode(value);
|
||||
ByteBuf oldValueBuf = encode(oldValue);
|
||||
|
||||
try {
|
||||
return valueBuf.equals(oldValueBuf);
|
||||
} finally {
|
||||
valueBuf.readableBytes();
|
||||
oldValueBuf.readableBytes();
|
||||
}
|
||||
}
|
||||
|
||||
protected <R> void executeLocked(final RPromise<R> promise, final Runnable runnable) {
|
||||
RLock lock = getLock();
|
||||
lock.lockAsync(timeout, TimeUnit.MILLISECONDS).addListener(new FutureListener<Void>() {
|
||||
@Override
|
||||
public void operationComplete(Future<Void> future) throws Exception {
|
||||
if (future.isSuccess()) {
|
||||
runnable.run();
|
||||
} else {
|
||||
promise.tryFailure(future.cause());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private RLock getLock() {
|
||||
return new RedissonLock(commandExecutor, getLockName());
|
||||
}
|
||||
|
||||
private String getLockName() {
|
||||
return getName() + ":transaction_lock";
|
||||
}
|
||||
|
||||
protected void checkState() {
|
||||
if (executed.get()) {
|
||||
throw new IllegalStateException("Unable to execute operation. Transaction is in finished state!");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,60 @@
|
||||
/**
|
||||
* Copyright 2018 Nikita Koksharov
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.redisson.transaction;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import org.redisson.api.RFuture;
|
||||
import org.redisson.api.RLocalCachedMap;
|
||||
import org.redisson.command.CommandAsyncExecutor;
|
||||
import org.redisson.transaction.operation.TransactionalOperation;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Nikita Koksharov
|
||||
*
|
||||
* @param <K> key type
|
||||
* @param <V> value type
|
||||
*/
|
||||
public class RedissonTransactionalLocalCachedMap<K, V> extends RedissonTransactionalMap<K, V> implements RLocalCachedMap<K, V> {
|
||||
|
||||
public RedissonTransactionalLocalCachedMap(CommandAsyncExecutor commandExecutor,
|
||||
List<TransactionalOperation> operations, long timeout, AtomicBoolean executed, RLocalCachedMap<K, V> innerMap) {
|
||||
super(commandExecutor, operations, timeout, executed, innerMap);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() {
|
||||
throw new UnsupportedOperationException("destroy method is not supported in transaction");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void preloadCache() {
|
||||
throw new UnsupportedOperationException("preloadCache method is not supported in transaction");
|
||||
}
|
||||
|
||||
@Override
|
||||
public RFuture<Void> clearLocalCacheAsync() {
|
||||
throw new UnsupportedOperationException("clearLocalCache method is not supported in transaction");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearLocalCache() {
|
||||
throw new UnsupportedOperationException("clearLocalCache method is not supported in transaction");
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,286 @@
|
||||
/**
|
||||
* Copyright 2018 Nikita Koksharov
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.redisson.transaction;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import org.redisson.RedissonMap;
|
||||
import org.redisson.api.RFuture;
|
||||
import org.redisson.api.RLock;
|
||||
import org.redisson.api.RMap;
|
||||
import org.redisson.api.RReadWriteLock;
|
||||
import org.redisson.api.mapreduce.RMapReduce;
|
||||
import org.redisson.client.RedisClient;
|
||||
import org.redisson.client.codec.Codec;
|
||||
import org.redisson.client.protocol.decoder.MapScanResult;
|
||||
import org.redisson.client.protocol.decoder.ScanObjectEntry;
|
||||
import org.redisson.command.CommandAsyncExecutor;
|
||||
import org.redisson.transaction.operation.TransactionalOperation;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Nikita Koksharov
|
||||
*
|
||||
* @param <K> key type
|
||||
* @param <V> value type
|
||||
*/
|
||||
public class RedissonTransactionalMap<K, V> extends RedissonMap<K, V> {
|
||||
|
||||
private final BaseTransactionalMap<K, V> transactionalMap;
|
||||
private final AtomicBoolean executed;
|
||||
|
||||
public RedissonTransactionalMap(CommandAsyncExecutor commandExecutor,
|
||||
List<TransactionalOperation> operations, long timeout, AtomicBoolean executed, RMap<K, V> innerMap) {
|
||||
super(innerMap.getCodec(), commandExecutor, innerMap.getName(), null, null);
|
||||
this.executed = executed;
|
||||
this.transactionalMap = new BaseTransactionalMap<K, V>(timeout, operations, innerMap);
|
||||
}
|
||||
|
||||
public RedissonTransactionalMap(CommandAsyncExecutor commandExecutor, String name,
|
||||
List<TransactionalOperation> operations, long timeout, AtomicBoolean executed) {
|
||||
super(commandExecutor, name, null, null);
|
||||
this.executed = executed;
|
||||
RedissonMap<K, V> innerMap = new RedissonMap<K, V>(commandExecutor, name, null, null);
|
||||
this.transactionalMap = new BaseTransactionalMap<K, V>(timeout, operations, innerMap);
|
||||
}
|
||||
|
||||
public RedissonTransactionalMap(Codec codec, CommandAsyncExecutor commandExecutor, String name,
|
||||
List<TransactionalOperation> operations, long timeout, AtomicBoolean executed) {
|
||||
super(codec, commandExecutor, name, null, null);
|
||||
this.executed = executed;
|
||||
RedissonMap<K, V> innerMap = new RedissonMap<K, V>(codec, commandExecutor, name, null, null);
|
||||
this.transactionalMap = new BaseTransactionalMap<K, V>(timeout, operations, innerMap);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RFuture<Boolean> expireAsync(long timeToLive, TimeUnit timeUnit) {
|
||||
throw new UnsupportedOperationException("expire method is not supported in transaction");
|
||||
}
|
||||
|
||||
@Override
|
||||
public RFuture<Boolean> expireAtAsync(Date timestamp) {
|
||||
throw new UnsupportedOperationException("expireAt method is not supported in transaction");
|
||||
}
|
||||
|
||||
@Override
|
||||
public RFuture<Boolean> expireAtAsync(long timestamp) {
|
||||
throw new UnsupportedOperationException("expireAt method is not supported in transaction");
|
||||
}
|
||||
|
||||
@Override
|
||||
public RFuture<Boolean> clearExpireAsync() {
|
||||
throw new UnsupportedOperationException("clearExpire method is not supported in transaction");
|
||||
}
|
||||
|
||||
@Override
|
||||
public RFuture<Boolean> moveAsync(int database) {
|
||||
throw new UnsupportedOperationException("move method is not supported in transaction");
|
||||
}
|
||||
|
||||
@Override
|
||||
public RFuture<Void> migrateAsync(String host, int port, int database, long timeout) {
|
||||
throw new UnsupportedOperationException("migrate method is not supported in transaction");
|
||||
}
|
||||
|
||||
@Override
|
||||
public <KOut, VOut> RMapReduce<K, V, KOut, VOut> mapReduce() {
|
||||
throw new UnsupportedOperationException("mapReduce method is not supported in transaction");
|
||||
}
|
||||
|
||||
@Override
|
||||
public MapScanResult<ScanObjectEntry, ScanObjectEntry> scanIterator(String name, RedisClient client,
|
||||
long startPos, String pattern) {
|
||||
checkState();
|
||||
return transactionalMap.scanIterator(name, client, startPos, pattern);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RFuture<Boolean> containsKeyAsync(Object key) {
|
||||
checkState();
|
||||
return transactionalMap.containsKeyAsync(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RFuture<Boolean> containsValueAsync(Object value) {
|
||||
checkState();
|
||||
return transactionalMap.containsValueAsync(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RFuture<V> addAndGetOperationAsync(K key, Number value) {
|
||||
checkState();
|
||||
return transactionalMap.addAndGetOperationAsync(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RFuture<V> putIfAbsentOperationAsync(K key, V value) {
|
||||
checkState();
|
||||
return transactionalMap.putIfAbsentOperationAsync(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RFuture<V> putOperationAsync(K key, V value) {
|
||||
checkState();
|
||||
return transactionalMap.putOperationAsync(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RFuture<Boolean> fastPutIfAbsentOperationAsync(K key, V value) {
|
||||
checkState();
|
||||
return transactionalMap.fastPutIfAbsentOperationAsync(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RFuture<Boolean> fastPutOperationAsync(K key, V value) {
|
||||
checkState();
|
||||
return transactionalMap.fastPutOperationAsync(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
protected RFuture<Long> fastRemoveOperationAsync(K... keys) {
|
||||
checkState();
|
||||
return transactionalMap.fastRemoveOperationAsync(keys);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RFuture<Integer> valueSizeAsync(K key) {
|
||||
checkState();
|
||||
return transactionalMap.valueSizeAsync(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RFuture<V> getOperationAsync(K key) {
|
||||
checkState();
|
||||
return transactionalMap.getOperationAsync(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RFuture<Set<K>> readAllKeySetAsync() {
|
||||
checkState();
|
||||
return transactionalMap.readAllKeySetAsync();
|
||||
}
|
||||
|
||||
@Override
|
||||
public RFuture<Set<Entry<K, V>>> readAllEntrySetAsync() {
|
||||
checkState();
|
||||
return transactionalMap.readAllEntrySetAsync();
|
||||
}
|
||||
|
||||
@Override
|
||||
public RFuture<Collection<V>> readAllValuesAsync() {
|
||||
checkState();
|
||||
return transactionalMap.readAllValuesAsync();
|
||||
}
|
||||
|
||||
@Override
|
||||
public RFuture<Map<K, V>> readAllMapAsync() {
|
||||
checkState();
|
||||
return transactionalMap.readAllMapAsync();
|
||||
}
|
||||
|
||||
@Override
|
||||
public RFuture<Map<K, V>> getAllOperationAsync(Set<K> keys) {
|
||||
checkState();
|
||||
return transactionalMap.getAllOperationAsync(keys);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RFuture<V> removeOperationAsync(K key) {
|
||||
checkState();
|
||||
return transactionalMap.removeOperationAsync(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RFuture<Boolean> removeOperationAsync(Object key, Object value) {
|
||||
checkState();
|
||||
return transactionalMap.removeOperationAsync(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RFuture<Void> putAllOperationAsync(Map<? extends K, ? extends V> entries) {
|
||||
checkState();
|
||||
return transactionalMap.putAllOperationAsync(entries);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RFuture<Boolean> replaceOperationAsync(final K key, final V oldValue, final V newValue) {
|
||||
checkState();
|
||||
return transactionalMap.replaceOperationAsync(key, oldValue, newValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RFuture<Boolean> touchAsync() {
|
||||
checkState();
|
||||
return transactionalMap.touchAsync(commandExecutor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RFuture<Boolean> isExistsAsync() {
|
||||
checkState();
|
||||
return transactionalMap.isExistsAsync();
|
||||
}
|
||||
|
||||
@Override
|
||||
public RFuture<Boolean> unlinkAsync() {
|
||||
return transactionalMap.unlinkAsync(commandExecutor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RFuture<Boolean> deleteAsync() {
|
||||
checkState();
|
||||
return transactionalMap.deleteAsync(commandExecutor);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RFuture<V> replaceOperationAsync(final K key, final V value) {
|
||||
checkState();
|
||||
return transactionalMap.replaceOperationAsync(key, value);
|
||||
}
|
||||
|
||||
protected void checkState() {
|
||||
if (executed.get()) {
|
||||
throw new IllegalStateException("Unable to execute operation. Transaction is in finished state!");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public RFuture<Void> loadAllAsync(boolean replaceExistingValues, int parallelism) {
|
||||
throw new UnsupportedOperationException("loadAll method is not supported in transaction");
|
||||
}
|
||||
|
||||
@Override
|
||||
public RFuture<Void> loadAllAsync(Set<? extends K> keys, boolean replaceExistingValues, int parallelism) {
|
||||
throw new UnsupportedOperationException("loadAll method is not supported in transaction");
|
||||
}
|
||||
|
||||
@Override
|
||||
public RLock getLock(K key) {
|
||||
throw new UnsupportedOperationException("getLock method is not supported in transaction");
|
||||
}
|
||||
|
||||
@Override
|
||||
public RReadWriteLock getReadWriteLock(K key) {
|
||||
throw new UnsupportedOperationException("getReadWriteLock method is not supported in transaction");
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,312 @@
|
||||
/**
|
||||
* Copyright 2018 Nikita Koksharov
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.redisson.transaction;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import org.redisson.RedissonMapCache;
|
||||
import org.redisson.api.RFuture;
|
||||
import org.redisson.api.RLock;
|
||||
import org.redisson.api.RReadWriteLock;
|
||||
import org.redisson.api.mapreduce.RMapReduce;
|
||||
import org.redisson.client.RedisClient;
|
||||
import org.redisson.client.codec.Codec;
|
||||
import org.redisson.client.protocol.decoder.MapScanResult;
|
||||
import org.redisson.client.protocol.decoder.ScanObjectEntry;
|
||||
import org.redisson.command.CommandAsyncExecutor;
|
||||
import org.redisson.transaction.operation.TransactionalOperation;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Nikita Koksharov
|
||||
*
|
||||
* @param <K> key type
|
||||
* @param <V> value type
|
||||
*/
|
||||
public class RedissonTransactionalMapCache<K, V> extends RedissonMapCache<K, V> {
|
||||
|
||||
private final BaseTransactionalMapCache<K, V> transactionalMap;
|
||||
private final AtomicBoolean executed;
|
||||
|
||||
public RedissonTransactionalMapCache(CommandAsyncExecutor commandExecutor, String name,
|
||||
List<TransactionalOperation> operations, long timeout, AtomicBoolean executed) {
|
||||
super(null, commandExecutor, name, null, null);
|
||||
this.executed = executed;
|
||||
RedissonMapCache<K, V> innerMap = new RedissonMapCache<K, V>(null, commandExecutor, name, null, null);
|
||||
this.transactionalMap = new BaseTransactionalMapCache<K, V>(timeout, operations, innerMap);
|
||||
}
|
||||
|
||||
public RedissonTransactionalMapCache(Codec codec, CommandAsyncExecutor commandExecutor, String name,
|
||||
List<TransactionalOperation> operations, long timeout, AtomicBoolean executed) {
|
||||
super(codec, null, commandExecutor, name, null, null);
|
||||
this.executed = executed;
|
||||
RedissonMapCache<K, V> innerMap = new RedissonMapCache<K, V>(codec, null, commandExecutor, name, null, null);
|
||||
this.transactionalMap = new BaseTransactionalMapCache<K, V>(timeout, operations, innerMap);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RFuture<Boolean> expireAsync(long timeToLive, TimeUnit timeUnit) {
|
||||
throw new UnsupportedOperationException("expire method is not supported in transaction");
|
||||
}
|
||||
|
||||
@Override
|
||||
public RFuture<Boolean> expireAtAsync(Date timestamp) {
|
||||
throw new UnsupportedOperationException("expireAt method is not supported in transaction");
|
||||
}
|
||||
|
||||
@Override
|
||||
public RFuture<Boolean> expireAtAsync(long timestamp) {
|
||||
throw new UnsupportedOperationException("expireAt method is not supported in transaction");
|
||||
}
|
||||
|
||||
@Override
|
||||
public RFuture<Boolean> clearExpireAsync() {
|
||||
throw new UnsupportedOperationException("clearExpire method is not supported in transaction");
|
||||
}
|
||||
|
||||
@Override
|
||||
public RFuture<Boolean> moveAsync(int database) {
|
||||
throw new UnsupportedOperationException("moveAsync method is not supported in transaction");
|
||||
}
|
||||
|
||||
@Override
|
||||
public RFuture<Void> migrateAsync(String host, int port, int database, long timeout) {
|
||||
throw new UnsupportedOperationException("migrateAsync method is not supported in transaction");
|
||||
}
|
||||
|
||||
@Override
|
||||
public RFuture<Boolean> touchAsync() {
|
||||
checkState();
|
||||
return transactionalMap.touchAsync(commandExecutor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RFuture<Boolean> isExistsAsync() {
|
||||
checkState();
|
||||
return transactionalMap.isExistsAsync();
|
||||
}
|
||||
|
||||
@Override
|
||||
public RFuture<Boolean> unlinkAsync() {
|
||||
return transactionalMap.unlinkAsync(commandExecutor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RFuture<Boolean> deleteAsync() {
|
||||
checkState();
|
||||
return transactionalMap.deleteAsync(commandExecutor);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RFuture<V> putIfAbsentAsync(K key, V value, long ttl, TimeUnit ttlUnit, long maxIdleTime, TimeUnit maxIdleUnit) {
|
||||
return transactionalMap.putIfAbsentAsync(key, value, ttl, ttlUnit, maxIdleTime, maxIdleUnit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RFuture<Boolean> fastPutOperationAsync(K key, V value, long ttl, TimeUnit ttlUnit, long maxIdleTime, TimeUnit maxIdleUnit) {
|
||||
return transactionalMap.fastPutOperationAsync(key, value, ttl, ttlUnit, maxIdleTime, maxIdleUnit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RFuture<V> putOperationAsync(K key, V value, long ttlTimeout, long maxIdleTimeout, long maxIdleDelta) {
|
||||
return transactionalMap.putOperationAsync(key, value, ttlTimeout, maxIdleTimeout, maxIdleDelta);
|
||||
}
|
||||
|
||||
public RFuture<Boolean> fastPutIfAbsentAsync(final K key, final V value, long ttl, TimeUnit ttlUnit, long maxIdleTime, TimeUnit maxIdleUnit) {
|
||||
return transactionalMap.fastPutIfAbsentAsync(key, value, ttl, ttlUnit, maxIdleTime, maxIdleUnit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RFuture<Long> remainTimeToLiveAsync() {
|
||||
throw new UnsupportedOperationException("remainTimeToLiveAsync method is not supported in transaction");
|
||||
}
|
||||
|
||||
@Override
|
||||
public RFuture<Void> setMaxSizeAsync(int maxSize) {
|
||||
throw new UnsupportedOperationException("setMaxSize method is not supported in transaction");
|
||||
}
|
||||
|
||||
@Override
|
||||
public RFuture<Boolean> trySetMaxSizeAsync(int maxSize) {
|
||||
throw new UnsupportedOperationException("trySetMaxSize method is not supported in transaction");
|
||||
}
|
||||
|
||||
@Override
|
||||
public <KOut, VOut> RMapReduce<K, V, KOut, VOut> mapReduce() {
|
||||
throw new UnsupportedOperationException("mapReduce method is not supported in transaction");
|
||||
}
|
||||
|
||||
@Override
|
||||
public MapScanResult<ScanObjectEntry, ScanObjectEntry> scanIterator(String name, RedisClient client,
|
||||
long startPos, String pattern) {
|
||||
checkState();
|
||||
return transactionalMap.scanIterator(name, client, startPos, pattern);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RFuture<Boolean> containsKeyAsync(Object key) {
|
||||
checkState();
|
||||
return transactionalMap.containsKeyAsync(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RFuture<Boolean> containsValueAsync(Object value) {
|
||||
checkState();
|
||||
return transactionalMap.containsValueAsync(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RFuture<V> addAndGetOperationAsync(K key, Number value) {
|
||||
checkState();
|
||||
return transactionalMap.addAndGetOperationAsync(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RFuture<V> putIfAbsentOperationAsync(K key, V value) {
|
||||
checkState();
|
||||
return transactionalMap.putIfAbsentOperationAsync(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RFuture<V> putOperationAsync(K key, V value) {
|
||||
checkState();
|
||||
return transactionalMap.putOperationAsync(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RFuture<Boolean> fastPutIfAbsentOperationAsync(K key, V value) {
|
||||
checkState();
|
||||
return transactionalMap.fastPutIfAbsentOperationAsync(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RFuture<Boolean> fastPutOperationAsync(K key, V value) {
|
||||
checkState();
|
||||
return transactionalMap.fastPutOperationAsync(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
protected RFuture<Long> fastRemoveOperationAsync(K... keys) {
|
||||
checkState();
|
||||
return transactionalMap.fastRemoveOperationAsync(keys);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RFuture<Integer> valueSizeAsync(K key) {
|
||||
checkState();
|
||||
return transactionalMap.valueSizeAsync(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RFuture<V> getOperationAsync(K key) {
|
||||
checkState();
|
||||
return transactionalMap.getOperationAsync(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RFuture<Set<K>> readAllKeySetAsync() {
|
||||
checkState();
|
||||
return transactionalMap.readAllKeySetAsync();
|
||||
}
|
||||
|
||||
@Override
|
||||
public RFuture<Set<Entry<K, V>>> readAllEntrySetAsync() {
|
||||
checkState();
|
||||
return transactionalMap.readAllEntrySetAsync();
|
||||
}
|
||||
|
||||
@Override
|
||||
public RFuture<Collection<V>> readAllValuesAsync() {
|
||||
checkState();
|
||||
return transactionalMap.readAllValuesAsync();
|
||||
}
|
||||
|
||||
@Override
|
||||
public RFuture<Map<K, V>> readAllMapAsync() {
|
||||
checkState();
|
||||
return transactionalMap.readAllMapAsync();
|
||||
}
|
||||
|
||||
@Override
|
||||
public RFuture<Map<K, V>> getAllOperationAsync(Set<K> keys) {
|
||||
checkState();
|
||||
return transactionalMap.getAllOperationAsync(keys);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RFuture<V> removeOperationAsync(K key) {
|
||||
checkState();
|
||||
return transactionalMap.removeOperationAsync(key);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RFuture<Boolean> removeOperationAsync(Object key, Object value) {
|
||||
checkState();
|
||||
return transactionalMap.removeOperationAsync(key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RFuture<Void> putAllOperationAsync(Map<? extends K, ? extends V> entries) {
|
||||
checkState();
|
||||
return transactionalMap.putAllOperationAsync(entries);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RFuture<Boolean> replaceOperationAsync(K key, V oldValue, V newValue) {
|
||||
checkState();
|
||||
return transactionalMap.replaceOperationAsync(key, oldValue, newValue);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RFuture<V> replaceOperationAsync(K key, V value) {
|
||||
checkState();
|
||||
return transactionalMap.replaceOperationAsync(key, value);
|
||||
}
|
||||
|
||||
protected void checkState() {
|
||||
if (executed.get()) {
|
||||
throw new IllegalStateException("Unable to execute operation. Transaction is in finished state!");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public RFuture<Void> loadAllAsync(boolean replaceExistingValues, int parallelism) {
|
||||
throw new UnsupportedOperationException("loadAll method is not supported in transaction");
|
||||
}
|
||||
|
||||
@Override
|
||||
public RFuture<Void> loadAllAsync(Set<? extends K> keys, boolean replaceExistingValues, int parallelism) {
|
||||
throw new UnsupportedOperationException("loadAll method is not supported in transaction");
|
||||
}
|
||||
|
||||
@Override
|
||||
public RLock getLock(K key) {
|
||||
throw new UnsupportedOperationException("getLock method is not supported in transaction");
|
||||
}
|
||||
|
||||
@Override
|
||||
public RReadWriteLock getReadWriteLock(K key) {
|
||||
throw new UnsupportedOperationException("getReadWriteLock method is not supported in transaction");
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,237 @@
|
||||
/**
|
||||
* Copyright 2018 Nikita Koksharov
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.redisson.transaction;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import org.redisson.RedissonSet;
|
||||
import org.redisson.api.RFuture;
|
||||
import org.redisson.api.SortOrder;
|
||||
import org.redisson.api.mapreduce.RCollectionMapReduce;
|
||||
import org.redisson.client.RedisClient;
|
||||
import org.redisson.client.codec.Codec;
|
||||
import org.redisson.client.protocol.decoder.ListScanResult;
|
||||
import org.redisson.client.protocol.decoder.ScanObjectEntry;
|
||||
import org.redisson.command.CommandAsyncExecutor;
|
||||
import org.redisson.transaction.operation.TransactionalOperation;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Nikita Koksharov
|
||||
*
|
||||
* @param <V> value type
|
||||
*/
|
||||
public class RedissonTransactionalSet<V> extends RedissonSet<V> {
|
||||
|
||||
private final TransactionalSet<V> transactionalSet;
|
||||
private final AtomicBoolean executed;
|
||||
|
||||
public RedissonTransactionalSet(CommandAsyncExecutor commandExecutor,
|
||||
String name, List<TransactionalOperation> operations, long timeout, AtomicBoolean executed) {
|
||||
super(commandExecutor, name, null);
|
||||
this.executed = executed;
|
||||
RedissonSet<V> innerSet = new RedissonSet<V>(commandExecutor, name, null);
|
||||
this.transactionalSet = new TransactionalSet<V>(commandExecutor, timeout, operations, innerSet);
|
||||
}
|
||||
|
||||
public RedissonTransactionalSet(Codec codec, CommandAsyncExecutor commandExecutor,
|
||||
String name, List<TransactionalOperation> operations, long timeout, AtomicBoolean executed) {
|
||||
super(codec, commandExecutor, name, null);
|
||||
this.executed = executed;
|
||||
RedissonSet<V> innerSet = new RedissonSet<V>(codec, commandExecutor, name, null);
|
||||
this.transactionalSet = new TransactionalSet<V>(commandExecutor, timeout, operations, innerSet);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RFuture<Boolean> expireAsync(long timeToLive, TimeUnit timeUnit) {
|
||||
throw new UnsupportedOperationException("expire method is not supported in transaction");
|
||||
}
|
||||
|
||||
@Override
|
||||
public RFuture<Boolean> expireAtAsync(Date timestamp) {
|
||||
throw new UnsupportedOperationException("expireAt method is not supported in transaction");
|
||||
}
|
||||
|
||||
@Override
|
||||
public RFuture<Boolean> expireAtAsync(long timestamp) {
|
||||
throw new UnsupportedOperationException("expireAt method is not supported in transaction");
|
||||
}
|
||||
|
||||
@Override
|
||||
public RFuture<Boolean> clearExpireAsync() {
|
||||
throw new UnsupportedOperationException("clearExpire method is not supported in transaction");
|
||||
}
|
||||
|
||||
@Override
|
||||
public RFuture<Boolean> moveAsync(int database) {
|
||||
throw new UnsupportedOperationException("move method is not supported in transaction");
|
||||
}
|
||||
|
||||
@Override
|
||||
public RFuture<Void> migrateAsync(String host, int port, int database, long timeout) {
|
||||
throw new UnsupportedOperationException("migrate method is not supported in transaction");
|
||||
}
|
||||
|
||||
@Override
|
||||
public <KOut, VOut> RCollectionMapReduce<V, KOut, VOut> mapReduce() {
|
||||
throw new UnsupportedOperationException("mapReduce method is not supported in transaction");
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListScanResult<ScanObjectEntry> scanIterator(String name, RedisClient client, long startPos, String pattern) {
|
||||
checkState();
|
||||
return transactionalSet.scanIterator(name, client, startPos, pattern);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RFuture<Boolean> containsAsync(Object o) {
|
||||
checkState();
|
||||
return transactionalSet.containsAsync(o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RFuture<Set<V>> readAllAsync() {
|
||||
checkState();
|
||||
return transactionalSet.readAllAsync();
|
||||
}
|
||||
|
||||
@Override
|
||||
public RFuture<Boolean> addAsync(V e) {
|
||||
checkState();
|
||||
return transactionalSet.addAsync(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RFuture<V> removeRandomAsync() {
|
||||
checkState();
|
||||
return transactionalSet.removeRandomAsync();
|
||||
}
|
||||
|
||||
@Override
|
||||
public RFuture<Set<V>> removeRandomAsync(int amount) {
|
||||
checkState();
|
||||
return transactionalSet.removeRandomAsync(amount);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RFuture<Boolean> removeAsync(Object o) {
|
||||
checkState();
|
||||
return transactionalSet.removeAsync(o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RFuture<Boolean> moveAsync(String destination, V member) {
|
||||
checkState();
|
||||
return transactionalSet.moveAsync(destination, member);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RFuture<Boolean> addAllAsync(Collection<? extends V> c) {
|
||||
checkState();
|
||||
return transactionalSet.addAllAsync(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RFuture<Boolean> retainAllAsync(Collection<?> c) {
|
||||
checkState();
|
||||
return transactionalSet.retainAllAsync(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RFuture<Boolean> removeAllAsync(Collection<?> c) {
|
||||
checkState();
|
||||
return transactionalSet.removeAllAsync(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RFuture<Integer> unionAsync(String... names) {
|
||||
checkState();
|
||||
return transactionalSet.unionAsync(names);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RFuture<Integer> diffAsync(String... names) {
|
||||
checkState();
|
||||
return transactionalSet.diffAsync(names);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RFuture<Integer> intersectionAsync(String... names) {
|
||||
checkState();
|
||||
return transactionalSet.intersectionAsync(names);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RFuture<Set<V>> readSortAsync(SortOrder order) {
|
||||
checkState();
|
||||
return transactionalSet.readSortAsync(order);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RFuture<Set<V>> readSortAsync(SortOrder order, int offset, int count) {
|
||||
checkState();
|
||||
return transactionalSet.readSortAsync(order, offset, count);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RFuture<Set<V>> readSortAsync(String byPattern, SortOrder order) {
|
||||
checkState();
|
||||
return transactionalSet.readSortAsync(byPattern, order);
|
||||
}
|
||||
|
||||
@Override
|
||||
public <T> RFuture<Collection<T>> readSortAsync(String byPattern, List<String> getPatterns, SortOrder order,
|
||||
int offset, int count) {
|
||||
checkState();
|
||||
return transactionalSet.readSortAsync(byPattern, getPatterns, order, offset, count);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RFuture<Integer> sortToAsync(String destName, String byPattern, List<String> getPatterns, SortOrder order, int offset, int count) {
|
||||
checkState();
|
||||
return transactionalSet.sortToAsync(destName, byPattern, getPatterns, order, offset, count);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RFuture<Set<V>> readUnionAsync(String... names) {
|
||||
checkState();
|
||||
return transactionalSet.readUnionAsync(names);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RFuture<Set<V>> readDiffAsync(String... names) {
|
||||
checkState();
|
||||
return transactionalSet.readDiffAsync(names);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RFuture<Set<V>> readIntersectionAsync(String... names) {
|
||||
checkState();
|
||||
return transactionalSet.readIntersectionAsync(names);
|
||||
}
|
||||
|
||||
protected void checkState() {
|
||||
if (executed.get()) {
|
||||
throw new IllegalStateException("Unable to execute operation. Transaction is in finished state!");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,158 @@
|
||||
/**
|
||||
* Copyright 2018 Nikita Koksharov
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.redisson.transaction;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import org.redisson.RedissonSetCache;
|
||||
import org.redisson.api.RFuture;
|
||||
import org.redisson.api.mapreduce.RCollectionMapReduce;
|
||||
import org.redisson.client.RedisClient;
|
||||
import org.redisson.client.codec.Codec;
|
||||
import org.redisson.client.protocol.decoder.ListScanResult;
|
||||
import org.redisson.client.protocol.decoder.ScanObjectEntry;
|
||||
import org.redisson.command.CommandAsyncExecutor;
|
||||
import org.redisson.transaction.operation.TransactionalOperation;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Nikita Koksharov
|
||||
*
|
||||
* @param <V> value type
|
||||
*/
|
||||
public class RedissonTransactionalSetCache<V> extends RedissonSetCache<V> {
|
||||
|
||||
private final TransactionalSetCache<V> transactionalSet;
|
||||
private final AtomicBoolean executed;
|
||||
|
||||
public RedissonTransactionalSetCache(CommandAsyncExecutor commandExecutor, String name,
|
||||
List<TransactionalOperation> operations, long timeout, AtomicBoolean executed) {
|
||||
super(null, commandExecutor, name, null);
|
||||
this.executed = executed;
|
||||
RedissonSetCache<V> innerSet = new RedissonSetCache<V>(null, commandExecutor, name, null);
|
||||
this.transactionalSet = new TransactionalSetCache<V>(commandExecutor, timeout, operations, innerSet);
|
||||
}
|
||||
|
||||
public RedissonTransactionalSetCache(Codec codec, CommandAsyncExecutor commandExecutor, String name,
|
||||
List<TransactionalOperation> operations, long timeout, AtomicBoolean executed) {
|
||||
super(null, commandExecutor, name, null);
|
||||
this.executed = executed;
|
||||
RedissonSetCache<V> innerSet = new RedissonSetCache<V>(codec, null, commandExecutor, name, null);
|
||||
this.transactionalSet = new TransactionalSetCache<V>(commandExecutor, timeout, operations, innerSet);
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public RFuture<Boolean> expireAsync(long timeToLive, TimeUnit timeUnit) {
|
||||
throw new UnsupportedOperationException("expire method is not supported in transaction");
|
||||
}
|
||||
|
||||
@Override
|
||||
public RFuture<Boolean> expireAtAsync(Date timestamp) {
|
||||
throw new UnsupportedOperationException("expireAt method is not supported in transaction");
|
||||
}
|
||||
|
||||
@Override
|
||||
public RFuture<Boolean> expireAtAsync(long timestamp) {
|
||||
throw new UnsupportedOperationException("expireAt method is not supported in transaction");
|
||||
}
|
||||
|
||||
@Override
|
||||
public RFuture<Boolean> clearExpireAsync() {
|
||||
throw new UnsupportedOperationException("clearExpire method is not supported in transaction");
|
||||
}
|
||||
|
||||
@Override
|
||||
public RFuture<Boolean> moveAsync(int database) {
|
||||
throw new UnsupportedOperationException("move method is not supported in transaction");
|
||||
}
|
||||
|
||||
@Override
|
||||
public RFuture<Void> migrateAsync(String host, int port, int database, long timeout) {
|
||||
throw new UnsupportedOperationException("migrate method is not supported in transaction");
|
||||
}
|
||||
|
||||
@Override
|
||||
public <KOut, VOut> RCollectionMapReduce<V, KOut, VOut> mapReduce() {
|
||||
throw new UnsupportedOperationException("mapReduce method is not supported in transaction");
|
||||
}
|
||||
|
||||
@Override
|
||||
public ListScanResult<ScanObjectEntry> scanIterator(String name, RedisClient client, long startPos, String pattern) {
|
||||
checkState();
|
||||
return transactionalSet.scanIterator(name, client, startPos, pattern);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RFuture<Boolean> containsAsync(Object o) {
|
||||
checkState();
|
||||
return transactionalSet.containsAsync(o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RFuture<Set<V>> readAllAsync() {
|
||||
checkState();
|
||||
return transactionalSet.readAllAsync();
|
||||
}
|
||||
|
||||
@Override
|
||||
public RFuture<Boolean> addAsync(V e) {
|
||||
checkState();
|
||||
return transactionalSet.addAsync(e);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RFuture<Boolean> addAsync(V value, long ttl, TimeUnit unit) {
|
||||
checkState();
|
||||
return transactionalSet.addAsync(value, ttl, unit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RFuture<Boolean> removeAsync(Object o) {
|
||||
checkState();
|
||||
return transactionalSet.removeAsync(o);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RFuture<Boolean> addAllAsync(Collection<? extends V> c) {
|
||||
checkState();
|
||||
return transactionalSet.addAllAsync(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RFuture<Boolean> retainAllAsync(Collection<?> c) {
|
||||
checkState();
|
||||
return transactionalSet.retainAllAsync(c);
|
||||
}
|
||||
|
||||
@Override
|
||||
public RFuture<Boolean> removeAllAsync(Collection<?> c) {
|
||||
checkState();
|
||||
return transactionalSet.removeAllAsync(c);
|
||||
}
|
||||
|
||||
protected void checkState() {
|
||||
if (executed.get()) {
|
||||
throw new IllegalStateException("Unable to execute operation. Transaction is in finished state!");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,39 @@
|
||||
/**
|
||||
* Copyright 2018 Nikita Koksharov
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.redisson.transaction;
|
||||
|
||||
import org.redisson.client.RedisException;
|
||||
|
||||
/**
|
||||
* This exception used to report an error during Transaction execution.
|
||||
*
|
||||
* @author Nikita Koksharov
|
||||
*
|
||||
*/
|
||||
public class TransactionException extends RedisException {
|
||||
|
||||
private static final long serialVersionUID = 7126673140273327142L;
|
||||
|
||||
public TransactionException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
public TransactionException(String message, Throwable cause) {
|
||||
super(message, cause);
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,32 @@
|
||||
/**
|
||||
* Copyright 2018 Nikita Koksharov
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.redisson.transaction;
|
||||
|
||||
/**
|
||||
* This exception used to report an error during Transaction execution.
|
||||
*
|
||||
* @author Nikita Koksharov
|
||||
*
|
||||
*/
|
||||
public class TransactionTimeoutException extends TransactionException {
|
||||
|
||||
private static final long serialVersionUID = 7126673140273327142L;
|
||||
|
||||
public TransactionTimeoutException(String message) {
|
||||
super(message);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,81 @@
|
||||
/**
|
||||
* Copyright 2018 Nikita Koksharov
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.redisson.transaction;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
import org.redisson.RedissonSet;
|
||||
import org.redisson.api.RFuture;
|
||||
import org.redisson.api.RLock;
|
||||
import org.redisson.api.RSet;
|
||||
import org.redisson.client.RedisClient;
|
||||
import org.redisson.client.protocol.decoder.ListScanResult;
|
||||
import org.redisson.client.protocol.decoder.ScanObjectEntry;
|
||||
import org.redisson.command.CommandAsyncExecutor;
|
||||
import org.redisson.transaction.operation.TransactionalOperation;
|
||||
import org.redisson.transaction.operation.set.AddOperation;
|
||||
import org.redisson.transaction.operation.set.MoveOperation;
|
||||
import org.redisson.transaction.operation.set.RemoveOperation;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Nikita Koksharov
|
||||
*
|
||||
* @param <V> value type
|
||||
*/
|
||||
public class TransactionalSet<V> extends BaseTransactionalSet<V> {
|
||||
|
||||
private final RSet<V> set;
|
||||
|
||||
public TransactionalSet(CommandAsyncExecutor commandExecutor, long timeout, List<TransactionalOperation> operations,
|
||||
RSet<V> set) {
|
||||
super(commandExecutor, timeout, operations, set);
|
||||
this.set = set;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ListScanResult<ScanObjectEntry> scanIteratorSource(String name, RedisClient client, long startPos,
|
||||
String pattern) {
|
||||
return ((RedissonSet<?>)set).scanIterator(name, client, startPos, pattern);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RFuture<Set<V>> readAllAsyncSource() {
|
||||
return set.readAllAsync();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TransactionalOperation createAddOperation(final V value) {
|
||||
return new AddOperation(set, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected MoveOperation createMoveOperation(final String destination, final V value, final long threadId) {
|
||||
return new MoveOperation(set, destination, threadId, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RLock getLock(V value) {
|
||||
return set.getLock(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TransactionalOperation createRemoveOperation(final Object value) {
|
||||
return new RemoveOperation(set, value);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,86 @@
|
||||
/**
|
||||
* Copyright 2018 Nikita Koksharov
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.redisson.transaction;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.redisson.RedissonSetCache;
|
||||
import org.redisson.api.RFuture;
|
||||
import org.redisson.api.RLock;
|
||||
import org.redisson.api.RSetCache;
|
||||
import org.redisson.client.RedisClient;
|
||||
import org.redisson.client.protocol.decoder.ListScanResult;
|
||||
import org.redisson.client.protocol.decoder.ScanObjectEntry;
|
||||
import org.redisson.command.CommandAsyncExecutor;
|
||||
import org.redisson.transaction.operation.TransactionalOperation;
|
||||
import org.redisson.transaction.operation.set.AddCacheOperation;
|
||||
import org.redisson.transaction.operation.set.MoveOperation;
|
||||
import org.redisson.transaction.operation.set.RemoveCacheOperation;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Nikita Koksharov
|
||||
*
|
||||
* @param <V> value type
|
||||
*/
|
||||
public class TransactionalSetCache<V> extends BaseTransactionalSet<V> {
|
||||
|
||||
private final RSetCache<V> set;
|
||||
|
||||
public TransactionalSetCache(CommandAsyncExecutor commandExecutor, long timeout, List<TransactionalOperation> operations,
|
||||
RSetCache<V> set) {
|
||||
super(commandExecutor, timeout, operations, set);
|
||||
this.set = set;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ListScanResult<ScanObjectEntry> scanIteratorSource(String name, RedisClient client, long startPos,
|
||||
String pattern) {
|
||||
return ((RedissonSetCache<?>)set).scanIterator(name, client, startPos, pattern);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RFuture<Set<V>> readAllAsyncSource() {
|
||||
return set.readAllAsync();
|
||||
}
|
||||
|
||||
public RFuture<Boolean> addAsync(V value, long ttl, TimeUnit ttlUnit) {
|
||||
return addAsync(value, new AddCacheOperation(set, value, ttl, ttlUnit));
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TransactionalOperation createAddOperation(final V value) {
|
||||
return new AddCacheOperation(set, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected MoveOperation createMoveOperation(final String destination, final V value, final long threadId) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RLock getLock(V value) {
|
||||
return set.getLock(value);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TransactionalOperation createRemoveOperation(final Object value) {
|
||||
return new RemoveCacheOperation(set, value);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
/**
|
||||
* Copyright 2018 Nikita Koksharov
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.redisson.transaction.operation;
|
||||
|
||||
import org.redisson.RedissonKeys;
|
||||
import org.redisson.RedissonLock;
|
||||
import org.redisson.api.RKeys;
|
||||
import org.redisson.command.CommandAsyncExecutor;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Nikita Koksharov
|
||||
*
|
||||
*/
|
||||
public class DeleteOperation extends TransactionalOperation {
|
||||
|
||||
private final String lockName;
|
||||
|
||||
public DeleteOperation(String name) {
|
||||
this(name, null);
|
||||
}
|
||||
|
||||
public DeleteOperation(String name, String lockName) {
|
||||
super(name, null);
|
||||
this.lockName = lockName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void commit(CommandAsyncExecutor commandExecutor) {
|
||||
RKeys keys = new RedissonKeys(commandExecutor);
|
||||
keys.deleteAsync(getName());
|
||||
if (lockName != null) {
|
||||
RedissonLock lock = new RedissonLock(commandExecutor, lockName);
|
||||
lock.unlockAsync();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void rollback(CommandAsyncExecutor commandExecutor) {
|
||||
if (lockName != null) {
|
||||
RedissonLock lock = new RedissonLock(commandExecutor, lockName);
|
||||
lock.unlockAsync();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
/**
|
||||
* Copyright 2018 Nikita Koksharov
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.redisson.transaction.operation;
|
||||
|
||||
import org.redisson.RedissonKeys;
|
||||
import org.redisson.RedissonLock;
|
||||
import org.redisson.api.RKeys;
|
||||
import org.redisson.command.CommandAsyncExecutor;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Nikita Koksharov
|
||||
*
|
||||
*/
|
||||
public class TouchOperation extends TransactionalOperation {
|
||||
|
||||
private final String lockName;
|
||||
|
||||
public TouchOperation(String name) {
|
||||
this(name, null);
|
||||
}
|
||||
|
||||
public TouchOperation(String name, String lockName) {
|
||||
super(name, null);
|
||||
this.lockName = lockName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void commit(CommandAsyncExecutor commandExecutor) {
|
||||
RKeys keys = new RedissonKeys(commandExecutor);
|
||||
keys.touchAsync(getName());
|
||||
RedissonLock lock = new RedissonLock(commandExecutor, lockName);
|
||||
lock.unlockAsync();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void rollback(CommandAsyncExecutor commandExecutor) {
|
||||
RedissonLock lock = new RedissonLock(commandExecutor, lockName);
|
||||
lock.unlockAsync();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
/**
|
||||
* Copyright 2018 Nikita Koksharov
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.redisson.transaction.operation;
|
||||
|
||||
import org.redisson.client.codec.Codec;
|
||||
import org.redisson.command.CommandAsyncExecutor;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Nikita Koksharov
|
||||
*
|
||||
*/
|
||||
public abstract class TransactionalOperation {
|
||||
|
||||
protected final Codec codec;
|
||||
protected final String name;
|
||||
|
||||
public TransactionalOperation(String name, Codec codec) {
|
||||
this.name = name;
|
||||
this.codec = codec;
|
||||
}
|
||||
|
||||
public Codec getCodec() {
|
||||
return codec;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
|
||||
public abstract void commit(CommandAsyncExecutor commandExecutor);
|
||||
|
||||
public abstract void rollback(CommandAsyncExecutor commandExecutor);
|
||||
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
/**
|
||||
* Copyright 2018 Nikita Koksharov
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.redisson.transaction.operation;
|
||||
|
||||
import org.redisson.RedissonKeys;
|
||||
import org.redisson.RedissonLock;
|
||||
import org.redisson.api.RKeys;
|
||||
import org.redisson.command.CommandAsyncExecutor;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Nikita Koksharov
|
||||
*
|
||||
*/
|
||||
public class UnlinkOperation extends TransactionalOperation {
|
||||
|
||||
private final String lockName;
|
||||
|
||||
public UnlinkOperation(String name) {
|
||||
this(name, null);
|
||||
}
|
||||
|
||||
public UnlinkOperation(String name, String lockName) {
|
||||
super(name, null);
|
||||
this.lockName = lockName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void commit(CommandAsyncExecutor commandExecutor) {
|
||||
RKeys keys = new RedissonKeys(commandExecutor);
|
||||
keys.unlinkAsync(getName());
|
||||
if (lockName != null) {
|
||||
RedissonLock lock = new RedissonLock(commandExecutor, lockName);
|
||||
lock.unlockAsync();
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void rollback(CommandAsyncExecutor commandExecutor) {
|
||||
if (lockName != null) {
|
||||
RedissonLock lock = new RedissonLock(commandExecutor, lockName);
|
||||
lock.unlockAsync();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,57 @@
|
||||
/**
|
||||
* Copyright 2018 Nikita Koksharov
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.redisson.transaction.operation.bucket;
|
||||
|
||||
import org.redisson.RedissonBucket;
|
||||
import org.redisson.RedissonLock;
|
||||
import org.redisson.client.codec.Codec;
|
||||
import org.redisson.command.CommandAsyncExecutor;
|
||||
import org.redisson.transaction.operation.TransactionalOperation;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Nikita Koksharov
|
||||
*
|
||||
* @param <V> value type
|
||||
*/
|
||||
public class BucketCompareAndSetOperation<V> extends TransactionalOperation {
|
||||
|
||||
private final V expected;
|
||||
private final V value;
|
||||
private final String lockName;
|
||||
|
||||
public BucketCompareAndSetOperation(String name, String lockName, Codec codec, V expected, V value) {
|
||||
super(name, codec);
|
||||
this.expected = expected;
|
||||
this.value = value;
|
||||
this.lockName = lockName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void commit(CommandAsyncExecutor commandExecutor) {
|
||||
RedissonBucket<V> bucket = new RedissonBucket<V>(codec, commandExecutor, name);
|
||||
bucket.compareAndSetAsync(expected, value);
|
||||
RedissonLock lock = new RedissonLock(commandExecutor, lockName);
|
||||
lock.unlockAsync();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void rollback(CommandAsyncExecutor commandExecutor) {
|
||||
RedissonLock lock = new RedissonLock(commandExecutor, lockName);
|
||||
lock.unlockAsync();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
/**
|
||||
* Copyright 2018 Nikita Koksharov
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.redisson.transaction.operation.bucket;
|
||||
|
||||
import org.redisson.RedissonBucket;
|
||||
import org.redisson.RedissonLock;
|
||||
import org.redisson.client.codec.Codec;
|
||||
import org.redisson.command.CommandAsyncExecutor;
|
||||
import org.redisson.transaction.operation.TransactionalOperation;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Nikita Koksharov
|
||||
*
|
||||
* @param <V> value type
|
||||
*/
|
||||
public class BucketGetAndDeleteOperation<V> extends TransactionalOperation {
|
||||
|
||||
private final String lockName;
|
||||
|
||||
public BucketGetAndDeleteOperation(String name, String lockName, Codec codec) {
|
||||
super(name, codec);
|
||||
this.lockName = lockName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void commit(CommandAsyncExecutor commandExecutor) {
|
||||
RedissonBucket<V> bucket = new RedissonBucket<V>(codec, commandExecutor, name);
|
||||
bucket.getAndDeleteAsync();
|
||||
RedissonLock lock = new RedissonLock(commandExecutor, lockName);
|
||||
lock.unlockAsync();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void rollback(CommandAsyncExecutor commandExecutor) {
|
||||
RedissonLock lock = new RedissonLock(commandExecutor, lockName);
|
||||
lock.unlockAsync();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,55 @@
|
||||
/**
|
||||
* Copyright 2018 Nikita Koksharov
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.redisson.transaction.operation.bucket;
|
||||
|
||||
import org.redisson.RedissonBucket;
|
||||
import org.redisson.RedissonLock;
|
||||
import org.redisson.client.codec.Codec;
|
||||
import org.redisson.command.CommandAsyncExecutor;
|
||||
import org.redisson.transaction.operation.TransactionalOperation;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Nikita Koksharov
|
||||
*
|
||||
* @param <V> value type
|
||||
*/
|
||||
public class BucketGetAndSetOperation<V> extends TransactionalOperation {
|
||||
|
||||
private final Object value;
|
||||
private final String lockName;
|
||||
|
||||
public BucketGetAndSetOperation(String name, String lockName, Codec codec, Object value) {
|
||||
super(name, codec);
|
||||
this.value = value;
|
||||
this.lockName = lockName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void commit(CommandAsyncExecutor commandExecutor) {
|
||||
RedissonBucket<V> bucket = new RedissonBucket<V>(codec, commandExecutor, name);
|
||||
bucket.getAndSetAsync((V) value);
|
||||
RedissonLock lock = new RedissonLock(commandExecutor, lockName);
|
||||
lock.unlockAsync();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void rollback(CommandAsyncExecutor commandExecutor) {
|
||||
RedissonLock lock = new RedissonLock(commandExecutor, lockName);
|
||||
lock.unlockAsync();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,69 @@
|
||||
/**
|
||||
* Copyright 2018 Nikita Koksharov
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.redisson.transaction.operation.bucket;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.redisson.RedissonBucket;
|
||||
import org.redisson.RedissonLock;
|
||||
import org.redisson.client.codec.Codec;
|
||||
import org.redisson.command.CommandAsyncExecutor;
|
||||
import org.redisson.transaction.operation.TransactionalOperation;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Nikita Koksharov
|
||||
*
|
||||
* @param <V> value type
|
||||
*/
|
||||
public class BucketSetOperation<V> extends TransactionalOperation {
|
||||
|
||||
private final Object value;
|
||||
private final String lockName;
|
||||
private long timeToLive;
|
||||
private TimeUnit timeUnit;
|
||||
|
||||
public BucketSetOperation(String name, String lockName, Codec codec, Object value, long timeToLive, TimeUnit timeUnit) {
|
||||
this(name, lockName, codec, value);
|
||||
this.timeToLive = timeToLive;
|
||||
this.timeUnit = timeUnit;
|
||||
}
|
||||
|
||||
public BucketSetOperation(String name, String lockName, Codec codec, Object value) {
|
||||
super(name, codec);
|
||||
this.value = value;
|
||||
this.lockName = lockName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void commit(CommandAsyncExecutor commandExecutor) {
|
||||
RedissonBucket<V> bucket = new RedissonBucket<V>(codec, commandExecutor, name);
|
||||
if (timeToLive != 0) {
|
||||
bucket.setAsync((V) value, timeToLive, timeUnit);
|
||||
} else {
|
||||
bucket.setAsync((V) value);
|
||||
}
|
||||
RedissonLock lock = new RedissonLock(commandExecutor, lockName);
|
||||
lock.unlockAsync();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void rollback(CommandAsyncExecutor commandExecutor) {
|
||||
RedissonLock lock = new RedissonLock(commandExecutor, lockName);
|
||||
lock.unlockAsync();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,69 @@
|
||||
/**
|
||||
* Copyright 2018 Nikita Koksharov
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.redisson.transaction.operation.bucket;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.redisson.RedissonBucket;
|
||||
import org.redisson.RedissonLock;
|
||||
import org.redisson.client.codec.Codec;
|
||||
import org.redisson.command.CommandAsyncExecutor;
|
||||
import org.redisson.transaction.operation.TransactionalOperation;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Nikita Koksharov
|
||||
*
|
||||
* @param <V> value type
|
||||
*/
|
||||
public class BucketTrySetOperation<V> extends TransactionalOperation {
|
||||
|
||||
private final Object value;
|
||||
private final String lockName;
|
||||
private long timeToLive;
|
||||
private TimeUnit timeUnit;
|
||||
|
||||
public BucketTrySetOperation(String name, String lockName, Codec codec, Object value, long timeToLive, TimeUnit timeUnit) {
|
||||
this(name, lockName, codec, value);
|
||||
this.timeToLive = timeToLive;
|
||||
this.timeUnit = timeUnit;
|
||||
}
|
||||
|
||||
public BucketTrySetOperation(String name, String lockName, Codec codec, Object value) {
|
||||
super(name, codec);
|
||||
this.value = value;
|
||||
this.lockName = lockName;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void commit(CommandAsyncExecutor commandExecutor) {
|
||||
RedissonBucket<V> bucket = new RedissonBucket<V>(codec, commandExecutor, name);
|
||||
if (timeToLive != 0) {
|
||||
bucket.trySetAsync((V) value, timeToLive, timeUnit);
|
||||
} else {
|
||||
bucket.trySetAsync((V) value);
|
||||
}
|
||||
RedissonLock lock = new RedissonLock(commandExecutor, lockName);
|
||||
lock.unlockAsync();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void rollback(CommandAsyncExecutor commandExecutor) {
|
||||
RedissonLock lock = new RedissonLock(commandExecutor, lockName);
|
||||
lock.unlockAsync();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
/**
|
||||
* Copyright 2018 Nikita Koksharov
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.redisson.transaction.operation.map;
|
||||
|
||||
import org.redisson.api.RMap;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Nikita Koksharov
|
||||
*
|
||||
*/
|
||||
public class MapAddAndGetOperation extends MapOperation {
|
||||
|
||||
public MapAddAndGetOperation(RMap<?, ?> map, Object key, Object value) {
|
||||
super(map, key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void commit(RMap<Object, Object> map) {
|
||||
map.addAndGetAsync(key, (Number) value);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,49 @@
|
||||
/**
|
||||
* Copyright 2018 Nikita Koksharov
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.redisson.transaction.operation.map;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.redisson.api.RMap;
|
||||
import org.redisson.api.RMapCache;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Nikita Koksharov
|
||||
*
|
||||
*/
|
||||
public class MapCacheFastPutIfAbsentOperation extends MapOperation {
|
||||
|
||||
private long ttl;
|
||||
private TimeUnit ttlUnit;
|
||||
private long maxIdleTime;
|
||||
private TimeUnit maxIdleUnit;
|
||||
|
||||
public MapCacheFastPutIfAbsentOperation(RMap<?, ?> map, Object key, Object value, long ttl, TimeUnit ttlUnit,
|
||||
long maxIdleTime, TimeUnit maxIdleUnit) {
|
||||
super(map, key, value);
|
||||
this.ttl = ttl;
|
||||
this.ttlUnit = ttlUnit;
|
||||
this.maxIdleTime = maxIdleTime;
|
||||
this.maxIdleUnit = maxIdleUnit;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void commit(RMap<Object, Object> map) {
|
||||
((RMapCache<Object, Object>)map).fastPutIfAbsentAsync(key, value, ttl, ttlUnit, maxIdleTime, maxIdleUnit);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
/**
|
||||
* Copyright 2018 Nikita Koksharov
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.redisson.transaction.operation.map;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.redisson.api.RMap;
|
||||
import org.redisson.api.RMapCache;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Nikita Koksharov
|
||||
*
|
||||
*/
|
||||
public class MapCacheFastPutOperation extends MapOperation {
|
||||
|
||||
private long ttl;
|
||||
private TimeUnit ttlUnit;
|
||||
private long maxIdleTime;
|
||||
private TimeUnit maxIdleUnit;
|
||||
|
||||
public MapCacheFastPutOperation(RMap<?, ?> map, Object key, Object value, long ttl, TimeUnit ttlUnit, long maxIdleTime, TimeUnit maxIdleUnit) {
|
||||
super(map, key, value);
|
||||
this.ttl = ttl;
|
||||
this.ttlUnit = ttlUnit;
|
||||
this.maxIdleTime = maxIdleTime;
|
||||
this.maxIdleUnit = maxIdleUnit;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void commit(RMap<Object, Object> map) {
|
||||
((RMapCache<Object, Object>)map).fastPutAsync(key, value, ttl, ttlUnit, maxIdleTime, maxIdleUnit);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,53 @@
|
||||
/**
|
||||
* Copyright 2018 Nikita Koksharov
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.redisson.transaction.operation.map;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.redisson.api.RMap;
|
||||
import org.redisson.api.RMapCache;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Nikita Koksharov
|
||||
*
|
||||
*/
|
||||
public class MapCachePutIfAbsentOperation extends MapOperation {
|
||||
|
||||
private long ttl;
|
||||
private TimeUnit unit;
|
||||
private long maxIdleTime;
|
||||
private TimeUnit maxIdleUnit;
|
||||
|
||||
public MapCachePutIfAbsentOperation(RMap<?, ?> map, Object key, Object value,
|
||||
long ttl, TimeUnit unit, long maxIdleTime, TimeUnit maxIdleUnit) {
|
||||
this(map, key, value);
|
||||
this.ttl = ttl;
|
||||
this.unit = unit;
|
||||
this.maxIdleTime = maxIdleTime;
|
||||
this.maxIdleUnit = maxIdleUnit;
|
||||
}
|
||||
|
||||
public MapCachePutIfAbsentOperation(RMap<?, ?> map, Object key, Object value) {
|
||||
super(map, key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void commit(RMap<Object, Object> map) {
|
||||
((RMapCache<Object, Object>)map).putIfAbsentAsync(key, value, ttl, unit, maxIdleTime, maxIdleUnit);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
/**
|
||||
* Copyright 2018 Nikita Koksharov
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.redisson.transaction.operation.map;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.redisson.api.RMap;
|
||||
import org.redisson.api.RMapCache;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Nikita Koksharov
|
||||
*
|
||||
*/
|
||||
public class MapCachePutOperation extends MapOperation {
|
||||
|
||||
private long ttlTimeout;
|
||||
private TimeUnit ttlUnit;
|
||||
private long maxIdleTimeout;
|
||||
private TimeUnit maxIdleUnit;
|
||||
|
||||
public MapCachePutOperation(RMap<?, ?> map, Object key, Object value, long ttlTimeout, TimeUnit ttlUnit, long maxIdleTimeout, TimeUnit maxIdleUnit) {
|
||||
super(map, key, value);
|
||||
this.ttlTimeout = ttlTimeout;
|
||||
this.ttlUnit = ttlUnit;
|
||||
this.maxIdleTimeout = maxIdleTimeout;
|
||||
this.maxIdleUnit = maxIdleUnit;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void commit(RMap<Object, Object> map) {
|
||||
((RMapCache<Object, Object>)map).putAsync(key, value, ttlTimeout, ttlUnit, maxIdleTimeout, maxIdleUnit);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
/**
|
||||
* Copyright 2018 Nikita Koksharov
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.redisson.transaction.operation.map;
|
||||
|
||||
import org.redisson.api.RMap;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Nikita Koksharov
|
||||
*
|
||||
*/
|
||||
public class MapFastPutIfAbsentOperation extends MapOperation {
|
||||
|
||||
public MapFastPutIfAbsentOperation(RMap<?, ?> map, Object key, Object value) {
|
||||
super(map, key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void commit(RMap<Object, Object> map) {
|
||||
map.fastPutIfAbsentAsync(key, value);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
/**
|
||||
* Copyright 2018 Nikita Koksharov
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.redisson.transaction.operation.map;
|
||||
|
||||
import org.redisson.api.RMap;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Nikita Koksharov
|
||||
*
|
||||
*/
|
||||
public class MapFastPutOperation extends MapOperation {
|
||||
|
||||
public MapFastPutOperation(RMap<?, ?> map, Object key, Object value) {
|
||||
super(map, key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void commit(RMap<Object, Object> map) {
|
||||
map.fastPutAsync(key, value);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
/**
|
||||
* Copyright 2018 Nikita Koksharov
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.redisson.transaction.operation.map;
|
||||
|
||||
import org.redisson.api.RMap;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Nikita Koksharov
|
||||
*
|
||||
*/
|
||||
public class MapFastRemoveOperation extends MapOperation {
|
||||
|
||||
public MapFastRemoveOperation(RMap<?, ?> map, Object key) {
|
||||
super(map, key, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void commit(RMap<Object, Object> map) {
|
||||
map.fastRemoveAsync(key);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,80 @@
|
||||
/**
|
||||
* Copyright 2018 Nikita Koksharov
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.redisson.transaction.operation.map;
|
||||
|
||||
import org.redisson.RedissonMap;
|
||||
import org.redisson.RedissonMapCache;
|
||||
import org.redisson.api.RMap;
|
||||
import org.redisson.api.RMapCache;
|
||||
import org.redisson.command.CommandAsyncExecutor;
|
||||
import org.redisson.transaction.operation.TransactionalOperation;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Nikita Koksharov
|
||||
*
|
||||
*/
|
||||
public abstract class MapOperation extends TransactionalOperation {
|
||||
|
||||
final Object key;
|
||||
final Object value;
|
||||
final Object oldValue;
|
||||
final RMap<?, ?> map;
|
||||
|
||||
public MapOperation(RMap<?, ?> map, Object key, Object value) {
|
||||
this(map, key, value, null);
|
||||
}
|
||||
|
||||
public MapOperation(RMap<?, ?> map, Object key, Object value, Object oldValue) {
|
||||
super(map.getName(), map.getCodec());
|
||||
this.map = map;
|
||||
this.key = key;
|
||||
this.value = value;
|
||||
this.oldValue = oldValue;
|
||||
}
|
||||
|
||||
public Object getKey() {
|
||||
return key;
|
||||
}
|
||||
|
||||
public RMap<?, ?> getMap() {
|
||||
return map;
|
||||
}
|
||||
|
||||
@Override
|
||||
public final void commit(CommandAsyncExecutor commandExecutor) {
|
||||
RMap<Object, Object> map = getMap(commandExecutor);
|
||||
commit(map);
|
||||
map.getLock(key).unlockAsync();
|
||||
}
|
||||
|
||||
protected RMap<Object, Object> getMap(CommandAsyncExecutor commandExecutor) {
|
||||
if (map instanceof RMapCache) {
|
||||
return new RedissonMapCache<Object, Object>(codec, null, commandExecutor, name, null, null);
|
||||
}
|
||||
return new RedissonMap<Object, Object>(codec, commandExecutor, name, null, null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void rollback(CommandAsyncExecutor commandExecutor) {
|
||||
RMap<Object, Object> map = getMap(commandExecutor);
|
||||
map.getLock(key).unlockAsync();
|
||||
}
|
||||
|
||||
protected abstract void commit(RMap<Object, Object> map);
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
/**
|
||||
* Copyright 2018 Nikita Koksharov
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.redisson.transaction.operation.map;
|
||||
|
||||
import org.redisson.api.RMap;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Nikita Koksharov
|
||||
*
|
||||
*/
|
||||
public class MapPutIfAbsentOperation extends MapOperation {
|
||||
|
||||
public MapPutIfAbsentOperation(RMap<?, ?> map, Object key, Object value) {
|
||||
super(map, key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void commit(RMap<Object, Object> map) {
|
||||
map.putIfAbsentAsync(key, value);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
/**
|
||||
* Copyright 2018 Nikita Koksharov
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.redisson.transaction.operation.map;
|
||||
|
||||
import org.redisson.api.RMap;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Nikita Koksharov
|
||||
*
|
||||
*/
|
||||
public class MapPutOperation extends MapOperation {
|
||||
|
||||
public MapPutOperation(RMap<?, ?> map, Object key, Object value) {
|
||||
super(map, key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void commit(RMap<Object, Object> map) {
|
||||
map.putAsync(key, value);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
/**
|
||||
* Copyright 2018 Nikita Koksharov
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.redisson.transaction.operation.map;
|
||||
|
||||
import org.redisson.api.RMap;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Nikita Koksharov
|
||||
*
|
||||
*/
|
||||
public class MapRemoveOperation extends MapOperation {
|
||||
|
||||
public MapRemoveOperation(RMap<?, ?> map, Object key) {
|
||||
super(map, key, null);
|
||||
}
|
||||
|
||||
public MapRemoveOperation(RMap<?, ?> map, Object key, Object value) {
|
||||
super(map, key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void commit(RMap<Object, Object> map) {
|
||||
if (value != null) {
|
||||
map.removeAsync(key, value);
|
||||
} else {
|
||||
map.removeAsync(key);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
/**
|
||||
* Copyright 2018 Nikita Koksharov
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.redisson.transaction.operation.map;
|
||||
|
||||
import org.redisson.api.RMap;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Nikita Koksharov
|
||||
*
|
||||
*/
|
||||
public class MapReplaceOperation extends MapOperation {
|
||||
|
||||
public MapReplaceOperation(RMap<?, ?> map, Object key, Object value, Object oldValue) {
|
||||
super(map, key, value, oldValue);
|
||||
}
|
||||
|
||||
public MapReplaceOperation(RMap<?, ?> map, Object key, Object value) {
|
||||
super(map, key, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void commit(RMap<Object, Object> map) {
|
||||
if (oldValue != null) {
|
||||
map.replaceAsync(key, oldValue, value);
|
||||
} else {
|
||||
map.replaceAsync(key, value);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,66 @@
|
||||
/**
|
||||
* Copyright 2018 Nikita Koksharov
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.redisson.transaction.operation.set;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.redisson.RedissonSetCache;
|
||||
import org.redisson.api.RObject;
|
||||
import org.redisson.api.RSetCache;
|
||||
import org.redisson.command.CommandAsyncExecutor;
|
||||
import org.redisson.transaction.operation.TransactionalOperation;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Nikita Koksharov
|
||||
*
|
||||
*/
|
||||
public class AddCacheOperation extends TransactionalOperation {
|
||||
|
||||
final Object value;
|
||||
final long ttl;
|
||||
final TimeUnit timeUnit;
|
||||
|
||||
public AddCacheOperation(RObject set, Object value) {
|
||||
this(set, value, 0, null);
|
||||
}
|
||||
|
||||
public AddCacheOperation(RObject set, Object value, long ttl, TimeUnit timeUnit) {
|
||||
super(set.getName(), set.getCodec());
|
||||
this.value = value;
|
||||
this.timeUnit = timeUnit;
|
||||
this.ttl = ttl;
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void commit(CommandAsyncExecutor commandExecutor) {
|
||||
RSetCache<Object> set = new RedissonSetCache<Object>(codec, null, commandExecutor, name, null);
|
||||
if (timeUnit != null) {
|
||||
set.addAsync(value, ttl, timeUnit);
|
||||
} else {
|
||||
set.addAsync(value);
|
||||
}
|
||||
set.getLock(value).unlockAsync();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void rollback(CommandAsyncExecutor commandExecutor) {
|
||||
RSetCache<Object> set = new RedissonSetCache<Object>(codec, null, commandExecutor, name, null);
|
||||
set.getLock(value).unlockAsync();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
/**
|
||||
* Copyright 2018 Nikita Koksharov
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.redisson.transaction.operation.set;
|
||||
|
||||
import org.redisson.RedissonSet;
|
||||
import org.redisson.api.RObject;
|
||||
import org.redisson.api.RSet;
|
||||
import org.redisson.command.CommandAsyncExecutor;
|
||||
import org.redisson.transaction.operation.TransactionalOperation;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Nikita Koksharov
|
||||
*
|
||||
*/
|
||||
public class AddOperation extends TransactionalOperation {
|
||||
|
||||
final Object value;
|
||||
|
||||
public AddOperation(RObject set, Object value) {
|
||||
super(set.getName(), set.getCodec());
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void commit(CommandAsyncExecutor commandExecutor) {
|
||||
RSet<Object> set = new RedissonSet<Object>(codec, commandExecutor, name, null);
|
||||
set.addAsync(value);
|
||||
set.getLock(value).unlockAsync();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void rollback(CommandAsyncExecutor commandExecutor) {
|
||||
RSet<Object> set = new RedissonSet<Object>(codec, commandExecutor, name, null);
|
||||
set.getLock(value).unlockAsync();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,59 @@
|
||||
/**
|
||||
* Copyright 2018 Nikita Koksharov
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.redisson.transaction.operation.set;
|
||||
|
||||
import org.redisson.RedissonSet;
|
||||
import org.redisson.api.RObject;
|
||||
import org.redisson.api.RSet;
|
||||
import org.redisson.command.CommandAsyncExecutor;
|
||||
import org.redisson.transaction.operation.TransactionalOperation;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Nikita Koksharov
|
||||
*
|
||||
*/
|
||||
public class MoveOperation extends TransactionalOperation {
|
||||
|
||||
final String destinationName;
|
||||
final Object value;
|
||||
final long threadId;
|
||||
|
||||
public MoveOperation(RObject set, String destinationName, long threadId, Object value) {
|
||||
super(set.getName(), set.getCodec());
|
||||
this.destinationName = destinationName;
|
||||
this.value = value;
|
||||
this.threadId = threadId;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void commit(CommandAsyncExecutor commandExecutor) {
|
||||
RSet<Object> set = new RedissonSet<Object>(codec, commandExecutor, name, null);
|
||||
RSet<Object> destinationSet = new RedissonSet<Object>(codec, commandExecutor, destinationName, null);
|
||||
set.moveAsync(destinationSet.getName(), value);
|
||||
destinationSet.getLock(value).unlockAsync(threadId);
|
||||
set.getLock(value).unlockAsync(threadId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void rollback(CommandAsyncExecutor commandExecutor) {
|
||||
RSet<Object> set = new RedissonSet<Object>(codec, commandExecutor, name, null);
|
||||
RSet<Object> destinationSet = new RedissonSet<Object>(codec, commandExecutor, destinationName, null);
|
||||
destinationSet.getLock(value).unlockAsync(threadId);
|
||||
set.getLock(value).unlockAsync(threadId);
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
/**
|
||||
* Copyright 2018 Nikita Koksharov
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.redisson.transaction.operation.set;
|
||||
|
||||
import org.redisson.RedissonSetCache;
|
||||
import org.redisson.api.RObject;
|
||||
import org.redisson.api.RSetCache;
|
||||
import org.redisson.command.CommandAsyncExecutor;
|
||||
import org.redisson.transaction.operation.TransactionalOperation;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Nikita Koksharov
|
||||
*
|
||||
*/
|
||||
public class RemoveCacheOperation extends TransactionalOperation {
|
||||
|
||||
final Object value;
|
||||
|
||||
public RemoveCacheOperation(RObject set, Object value) {
|
||||
super(set.getName(), set.getCodec());
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void commit(CommandAsyncExecutor commandExecutor) {
|
||||
RSetCache<Object> set = new RedissonSetCache<Object>(codec, null, commandExecutor, name, null);
|
||||
set.removeAsync(value);
|
||||
set.getLock(value).unlockAsync();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void rollback(CommandAsyncExecutor commandExecutor) {
|
||||
RSetCache<Object> set = new RedissonSetCache<Object>(codec, null, commandExecutor, name, null);
|
||||
set.getLock(value).unlockAsync();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
/**
|
||||
* Copyright 2018 Nikita Koksharov
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.redisson.transaction.operation.set;
|
||||
|
||||
import org.redisson.RedissonSet;
|
||||
import org.redisson.api.RObject;
|
||||
import org.redisson.api.RSet;
|
||||
import org.redisson.command.CommandAsyncExecutor;
|
||||
import org.redisson.transaction.operation.TransactionalOperation;
|
||||
|
||||
/**
|
||||
*
|
||||
* @author Nikita Koksharov
|
||||
*
|
||||
*/
|
||||
public class RemoveOperation extends TransactionalOperation {
|
||||
|
||||
final Object value;
|
||||
|
||||
public RemoveOperation(RObject set, Object value) {
|
||||
super(set.getName(), set.getCodec());
|
||||
this.value = value;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void commit(CommandAsyncExecutor commandExecutor) {
|
||||
RSet<Object> set = new RedissonSet<Object>(codec, commandExecutor, name, null);
|
||||
set.removeAsync(value);
|
||||
set.getLock(value).unlockAsync();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void rollback(CommandAsyncExecutor commandExecutor) {
|
||||
RSet<Object> set = new RedissonSet<Object>(codec, commandExecutor, name, null);
|
||||
set.getLock(value).unlockAsync();
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,239 @@
|
||||
package org.redisson.transaction;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.redisson.BaseTest;
|
||||
import org.redisson.api.RMap;
|
||||
import org.redisson.api.RTransaction;
|
||||
import org.redisson.api.TransactionOptions;
|
||||
|
||||
public abstract class RedissonBaseTransactionalMapTest extends BaseTest {
|
||||
|
||||
protected abstract RMap<String, String> getMap();
|
||||
|
||||
protected abstract RMap<String, String> getTransactionalMap(RTransaction transaction);
|
||||
|
||||
@Test
|
||||
public void testPutAll() {
|
||||
RMap<String, String> m = getMap();
|
||||
m.put("1", "2");
|
||||
m.put("3", "4");
|
||||
|
||||
RTransaction t = redisson.createTransaction(TransactionOptions.defaults());
|
||||
RMap<String, String> map = getTransactionalMap(t);
|
||||
Map<String, String> putMap = new HashMap<String, String>();
|
||||
putMap.put("4", "5");
|
||||
putMap.put("6", "7");
|
||||
map.putAll(putMap);
|
||||
assertThat(m.keySet()).containsOnly("1", "3");
|
||||
|
||||
t.commit();
|
||||
|
||||
assertThat(m.keySet()).containsOnly("1", "3", "4", "6");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testKeySet() {
|
||||
RMap<String, String> m = getMap();
|
||||
m.put("1", "2");
|
||||
m.put("3", "4");
|
||||
|
||||
RTransaction t = redisson.createTransaction(TransactionOptions.defaults());
|
||||
RMap<String, String> map = getTransactionalMap(t);
|
||||
map.remove("3");
|
||||
assertThat(map.keySet()).containsOnly("1");
|
||||
|
||||
assertThat(m.keySet()).containsOnly("1", "3");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReplace2() {
|
||||
RMap<String, String> m = getMap();
|
||||
m.put("1", "2");
|
||||
m.put("3", "4");
|
||||
|
||||
RTransaction transaction = redisson.createTransaction(TransactionOptions.defaults());
|
||||
RMap<String, String> map = getTransactionalMap(transaction);
|
||||
assertThat(map.replace("3", "4", "10")).isTrue();
|
||||
assertThat(map.replace("1", "1", "3")).isFalse();
|
||||
assertThat(map.replace("3", "10", "11")).isTrue();
|
||||
|
||||
assertThat(m.get("3")).isEqualTo("4");
|
||||
assertThat(m.get("1")).isEqualTo("2");
|
||||
assertThat(m.size()).isEqualTo(2);
|
||||
|
||||
transaction.commit();
|
||||
|
||||
assertThat(m.size()).isEqualTo(2);
|
||||
assertThat(m.get("3")).isEqualTo("11");
|
||||
assertThat(m.get("1")).isEqualTo("2");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testReplace() {
|
||||
RMap<String, String> m = getMap();
|
||||
m.put("1", "2");
|
||||
m.put("3", "4");
|
||||
|
||||
RTransaction transaction = redisson.createTransaction(TransactionOptions.defaults());
|
||||
RMap<String, String> map = getTransactionalMap(transaction);
|
||||
assertThat(map.replace("3", "10")).isEqualTo("4");
|
||||
assertThat(map.replace("5", "0")).isNull();
|
||||
|
||||
assertThat(m.get("3")).isEqualTo("4");
|
||||
assertThat(m.size()).isEqualTo(2);
|
||||
|
||||
transaction.commit();
|
||||
|
||||
assertThat(m.size()).isEqualTo(2);
|
||||
assertThat(m.get("3")).isEqualTo("10");
|
||||
assertThat(m.get("5")).isNull();
|
||||
|
||||
RTransaction transaction2 = redisson.createTransaction(TransactionOptions.defaults());
|
||||
RMap<String, String> map2 = getTransactionalMap(transaction2);
|
||||
assertThat(map2.replace("3", "20")).isEqualTo("10");
|
||||
assertThat(map2.replace("3", "30")).isEqualTo("20");
|
||||
|
||||
assertThat(m.get("3")).isEqualTo("10");
|
||||
assertThat(m.size()).isEqualTo(2);
|
||||
|
||||
transaction2.commit();
|
||||
|
||||
assertThat(m.get("3")).isEqualTo("30");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPutIfAbsent() {
|
||||
RMap<String, String> m = getMap();
|
||||
m.put("1", "2");
|
||||
m.put("3", "4");
|
||||
|
||||
RTransaction transaction = redisson.createTransaction(TransactionOptions.defaults());
|
||||
RMap<String, String> map = getTransactionalMap(transaction);
|
||||
assertThat(map.putIfAbsent("3", "2")).isEqualTo("4");
|
||||
assertThat(map.putIfAbsent("5", "6")).isNull();
|
||||
assertThat(map.putIfAbsent("5", "7")).isEqualTo("6");
|
||||
|
||||
assertThat(m.get("3")).isEqualTo("4");
|
||||
assertThat(m.size()).isEqualTo(2);
|
||||
|
||||
transaction.commit();
|
||||
|
||||
assertThat(m.get("1")).isEqualTo("2");
|
||||
assertThat(m.get("3")).isEqualTo("4");
|
||||
assertThat(m.get("5")).isEqualTo("6");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPutIfAbsentRemove() {
|
||||
RMap<String, String> m = getMap();
|
||||
m.put("1", "2");
|
||||
m.put("3", "4");
|
||||
|
||||
RTransaction transaction = redisson.createTransaction(TransactionOptions.defaults());
|
||||
RMap<String, String> map = getTransactionalMap(transaction);
|
||||
assertThat(map.putIfAbsent("3", "2")).isEqualTo("4");
|
||||
assertThat(map.putIfAbsent("5", "6")).isNull();
|
||||
assertThat(map.putIfAbsent("5", "7")).isEqualTo("6");
|
||||
assertThat(map.remove("5")).isEqualTo("6");
|
||||
assertThat(map.putIfAbsent("5", "8")).isNull();
|
||||
|
||||
assertThat(m.get("3")).isEqualTo("4");
|
||||
assertThat(m.size()).isEqualTo(2);
|
||||
|
||||
transaction.commit();
|
||||
|
||||
assertThat(m.get("1")).isEqualTo("2");
|
||||
assertThat(m.get("3")).isEqualTo("4");
|
||||
assertThat(m.get("5")).isEqualTo("8");
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testRemove() {
|
||||
RMap<String, String> m = getMap();
|
||||
m.put("1", "2");
|
||||
m.put("3", "4");
|
||||
|
||||
RTransaction transaction = redisson.createTransaction(TransactionOptions.defaults());
|
||||
RMap<String, String> map = getTransactionalMap(transaction);
|
||||
assertThat(map.get("1")).isEqualTo("2");
|
||||
assertThat(map.remove("3")).isEqualTo("4");
|
||||
assertThat(map.remove("3")).isNull();
|
||||
assertThat(map.remove("3")).isNull();
|
||||
|
||||
assertThat(m.get("3")).isEqualTo("4");
|
||||
|
||||
transaction.commit();
|
||||
|
||||
assertThat(m.get("1")).isEqualTo("2");
|
||||
assertThat(m.get("3")).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPut() {
|
||||
RMap<String, String> m = getMap();
|
||||
m.put("1", "2");
|
||||
m.put("3", "4");
|
||||
|
||||
RTransaction transaction = redisson.createTransaction(TransactionOptions.defaults());
|
||||
RMap<String, String> map = getTransactionalMap(transaction);
|
||||
assertThat(map.put("3", "5")).isEqualTo("4");
|
||||
assertThat(map.get("3")).isEqualTo("5");
|
||||
|
||||
assertThat(m.get("3")).isEqualTo("4");
|
||||
|
||||
transaction.commit();
|
||||
|
||||
assertThat(m.get("1")).isEqualTo("2");
|
||||
assertThat(m.get("3")).isEqualTo("5");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPutRemove() {
|
||||
RMap<String, String> m = getMap();
|
||||
m.put("1", "2");
|
||||
m.put("3", "4");
|
||||
|
||||
RTransaction transaction = redisson.createTransaction(TransactionOptions.defaults());
|
||||
RMap<String, String> map = getTransactionalMap(transaction);
|
||||
assertThat(map.get("1")).isEqualTo("2");
|
||||
assertThat(map.remove("3")).isEqualTo("4");
|
||||
assertThat(map.put("3", "5")).isNull();
|
||||
assertThat(map.get("3")).isEqualTo("5");
|
||||
|
||||
assertThat(m.get("3")).isEqualTo("4");
|
||||
|
||||
transaction.commit();
|
||||
|
||||
assertThat(m.get("1")).isEqualTo("2");
|
||||
assertThat(m.get("3")).isEqualTo("5");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRollback() {
|
||||
RMap<String, String> m = getMap();
|
||||
m.put("1", "2");
|
||||
m.put("3", "4");
|
||||
|
||||
RTransaction transaction = redisson.createTransaction(TransactionOptions.defaults());
|
||||
RMap<String, String> map = getTransactionalMap(transaction);
|
||||
assertThat(map.get("1")).isEqualTo("2");
|
||||
assertThat(map.remove("3")).isEqualTo("4");
|
||||
|
||||
assertThat(m.get("3")).isEqualTo("4");
|
||||
|
||||
transaction.rollback();
|
||||
|
||||
assertThat(redisson.getKeys().count()).isEqualTo(1);
|
||||
|
||||
assertThat(m.get("1")).isEqualTo("2");
|
||||
assertThat(m.get("3")).isEqualTo("4");
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,148 @@
|
||||
package org.redisson.transaction;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.junit.Assert;
|
||||
import org.junit.Test;
|
||||
import org.redisson.BaseTest;
|
||||
import org.redisson.api.RBucket;
|
||||
import org.redisson.api.RTransaction;
|
||||
import org.redisson.api.TransactionOptions;
|
||||
|
||||
public class RedissonTransactionalBucketTest extends BaseTest {
|
||||
|
||||
@Test
|
||||
public void testTimeout() throws InterruptedException {
|
||||
RBucket<String> b = redisson.getBucket("test");
|
||||
b.set("123");
|
||||
|
||||
RTransaction transaction = redisson.createTransaction(TransactionOptions.defaults().timeout(3, TimeUnit.SECONDS));
|
||||
RBucket<String> bucket = transaction.getBucket("test");
|
||||
bucket.set("234");
|
||||
|
||||
Thread.sleep(3000);
|
||||
|
||||
try {
|
||||
transaction.commit();
|
||||
Assert.fail();
|
||||
} catch (TransactionException e) {
|
||||
// skip
|
||||
}
|
||||
|
||||
Thread.sleep(1000);
|
||||
|
||||
assertThat(b.get()).isEqualTo("123");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testSet() {
|
||||
RBucket<String> b = redisson.getBucket("test");
|
||||
b.set("123");
|
||||
|
||||
RTransaction transaction = redisson.createTransaction(TransactionOptions.defaults());
|
||||
RBucket<String> bucket = transaction.getBucket("test");
|
||||
bucket.set("234");
|
||||
assertThat(bucket.get()).isEqualTo("234");
|
||||
|
||||
transaction.commit();
|
||||
|
||||
assertThat(redisson.getKeys().count()).isEqualTo(1);
|
||||
assertThat(b.get()).isEqualTo("234");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetAndSet() {
|
||||
RBucket<String> b = redisson.getBucket("test");
|
||||
b.set("123");
|
||||
|
||||
RTransaction transaction = redisson.createTransaction(TransactionOptions.defaults());
|
||||
RBucket<String> bucket = transaction.getBucket("test");
|
||||
assertThat(bucket.getAndSet("0")).isEqualTo("123");
|
||||
assertThat(bucket.get()).isEqualTo("0");
|
||||
assertThat(bucket.getAndSet("324")).isEqualTo("0");
|
||||
|
||||
transaction.commit();
|
||||
|
||||
assertThat(redisson.getKeys().count()).isEqualTo(1);
|
||||
assertThat(b.get()).isEqualTo("324");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testCompareAndSet() {
|
||||
RBucket<String> b = redisson.getBucket("test");
|
||||
b.set("123");
|
||||
|
||||
RTransaction transaction = redisson.createTransaction(TransactionOptions.defaults());
|
||||
RBucket<String> bucket = transaction.getBucket("test");
|
||||
assertThat(bucket.compareAndSet("0", "434")).isFalse();
|
||||
assertThat(bucket.get()).isEqualTo("123");
|
||||
assertThat(bucket.compareAndSet("123", "232")).isTrue();
|
||||
assertThat(bucket.get()).isEqualTo("232");
|
||||
|
||||
transaction.commit();
|
||||
|
||||
assertThat(redisson.getKeys().count()).isEqualTo(1);
|
||||
assertThat(b.get()).isEqualTo("232");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testTrySet() {
|
||||
RBucket<String> b = redisson.getBucket("test");
|
||||
b.set("123");
|
||||
|
||||
RTransaction transaction = redisson.createTransaction(TransactionOptions.defaults());
|
||||
RBucket<String> bucket = transaction.getBucket("test");
|
||||
assertThat(bucket.trySet("0")).isFalse();
|
||||
assertThat(bucket.delete()).isTrue();
|
||||
assertThat(bucket.trySet("324")).isTrue();
|
||||
assertThat(bucket.trySet("43")).isFalse();
|
||||
|
||||
transaction.commit();
|
||||
|
||||
assertThat(redisson.getKeys().count()).isEqualTo(1);
|
||||
assertThat(b.get()).isEqualTo("324");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testGetAndRemove() {
|
||||
RBucket<String> m = redisson.getBucket("test");
|
||||
m.set("123");
|
||||
|
||||
RTransaction transaction = redisson.createTransaction(TransactionOptions.defaults());
|
||||
RBucket<String> set = transaction.getBucket("test");
|
||||
assertThat(set.get()).isEqualTo("123");
|
||||
assertThat(set.size()).isEqualTo(5);
|
||||
assertThat(set.getAndDelete()).isEqualTo("123");
|
||||
assertThat(set.size()).isEqualTo(0);
|
||||
assertThat(set.get()).isNull();
|
||||
assertThat(set.getAndDelete()).isNull();
|
||||
|
||||
transaction.commit();
|
||||
|
||||
assertThat(redisson.getKeys().count()).isEqualTo(0);
|
||||
assertThat(m.get()).isNull();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRollback() {
|
||||
RBucket<Object> b = redisson.getBucket("test");
|
||||
b.set("1234");
|
||||
|
||||
RTransaction transaction = redisson.createTransaction(TransactionOptions.defaults());
|
||||
RBucket<Object> bucket = transaction.getBucket("test");
|
||||
assertThat(bucket.get()).isEqualTo("1234");
|
||||
assertThat(bucket.getAndDelete()).isEqualTo("1234");
|
||||
|
||||
assertThat(b.get()).isEqualTo("1234");
|
||||
|
||||
transaction.rollback();
|
||||
|
||||
assertThat(redisson.getKeys().count()).isEqualTo(1);
|
||||
|
||||
assertThat(b.get()).isEqualTo("1234");
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,94 @@
|
||||
package org.redisson.transaction;
|
||||
|
||||
import static org.assertj.core.api.Assertions.*;
|
||||
import org.junit.Test;
|
||||
import org.redisson.BaseTest;
|
||||
import org.redisson.api.LocalCachedMapOptions;
|
||||
import org.redisson.api.RLocalCachedMap;
|
||||
import org.redisson.api.RMap;
|
||||
import org.redisson.api.RTransaction;
|
||||
import org.redisson.api.TransactionOptions;
|
||||
|
||||
public class RedissonTransactionalLocalCachedMapTest extends BaseTest {
|
||||
|
||||
@Test
|
||||
public void testPut() throws InterruptedException {
|
||||
RLocalCachedMap<String, String> m1 = redisson.getLocalCachedMap("test", LocalCachedMapOptions.defaults());
|
||||
m1.put("1", "2");
|
||||
m1.put("3", "4");
|
||||
|
||||
RLocalCachedMap<String, String> m2 = redisson.getLocalCachedMap("test", LocalCachedMapOptions.defaults());
|
||||
m2.get("1");
|
||||
m2.get("3");
|
||||
|
||||
RTransaction transaction = redisson.createTransaction(TransactionOptions.defaults());
|
||||
RMap<String, String> map = transaction.getLocalCachedMap(m1);
|
||||
assertThat(map.put("3", "5")).isEqualTo("4");
|
||||
assertThat(map.get("3")).isEqualTo("5");
|
||||
|
||||
assertThat(m1.get("3")).isEqualTo("4");
|
||||
assertThat(m2.get("3")).isEqualTo("4");
|
||||
|
||||
transaction.commit();
|
||||
|
||||
assertThat(m1.get("3")).isEqualTo("5");
|
||||
assertThat(m2.get("3")).isEqualTo("5");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPutRemove() {
|
||||
RLocalCachedMap<String, String> m1 = redisson.getLocalCachedMap("test", LocalCachedMapOptions.defaults());
|
||||
m1.put("1", "2");
|
||||
m1.put("3", "4");
|
||||
|
||||
RLocalCachedMap<String, String> m2 = redisson.getLocalCachedMap("test", LocalCachedMapOptions.defaults());
|
||||
m2.get("1");
|
||||
m2.get("3");
|
||||
|
||||
RTransaction transaction = redisson.createTransaction(TransactionOptions.defaults());
|
||||
RMap<String, String> map = transaction.getLocalCachedMap(m1);
|
||||
assertThat(map.get("1")).isEqualTo("2");
|
||||
assertThat(map.remove("3")).isEqualTo("4");
|
||||
assertThat(map.put("3", "5")).isNull();
|
||||
assertThat(map.get("3")).isEqualTo("5");
|
||||
|
||||
assertThat(m1.get("3")).isEqualTo("4");
|
||||
assertThat(m2.get("3")).isEqualTo("4");
|
||||
|
||||
transaction.commit();
|
||||
|
||||
assertThat(m1.get("1")).isEqualTo("2");
|
||||
assertThat(m1.get("3")).isEqualTo("5");
|
||||
assertThat(m2.get("1")).isEqualTo("2");
|
||||
assertThat(m2.get("3")).isEqualTo("5");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRollback() {
|
||||
RLocalCachedMap<String, String> m1 = redisson.getLocalCachedMap("test", LocalCachedMapOptions.defaults());
|
||||
m1.put("1", "2");
|
||||
m1.put("3", "4");
|
||||
|
||||
RLocalCachedMap<String, String> m2 = redisson.getLocalCachedMap("test", LocalCachedMapOptions.defaults());
|
||||
m2.get("1");
|
||||
m2.get("3");
|
||||
|
||||
RTransaction transaction = redisson.createTransaction(TransactionOptions.defaults());
|
||||
RMap<String, String> map = transaction.getLocalCachedMap(m1);
|
||||
assertThat(map.get("1")).isEqualTo("2");
|
||||
assertThat(map.remove("3")).isEqualTo("4");
|
||||
|
||||
assertThat(m1.get("3")).isEqualTo("4");
|
||||
|
||||
transaction.rollback();
|
||||
|
||||
assertThat(redisson.getKeys().count()).isEqualTo(1);
|
||||
|
||||
assertThat(m1.get("1")).isEqualTo("2");
|
||||
assertThat(m1.get("3")).isEqualTo("4");
|
||||
assertThat(m2.get("1")).isEqualTo("2");
|
||||
assertThat(m2.get("3")).isEqualTo("4");
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,58 @@
|
||||
package org.redisson.transaction;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.redisson.api.RMap;
|
||||
import org.redisson.api.RMapCache;
|
||||
import org.redisson.api.RTransaction;
|
||||
import org.redisson.api.TransactionOptions;
|
||||
|
||||
public class RedissonTransactionalMapCacheTest extends RedissonBaseTransactionalMapTest {
|
||||
|
||||
@Test
|
||||
public void testPutIfAbsentTTL() throws InterruptedException {
|
||||
RMapCache<Object, Object> m = redisson.getMapCache("test");
|
||||
m.put("1", "2");
|
||||
m.put("3", "4");
|
||||
|
||||
RTransaction transaction = redisson.createTransaction(TransactionOptions.defaults());
|
||||
RMapCache<Object, Object> map = transaction.getMapCache("test");
|
||||
assertThat(map.putIfAbsent("3", "2", 1, TimeUnit.SECONDS)).isEqualTo("4");
|
||||
assertThat(map.putIfAbsent("5", "6", 3, TimeUnit.SECONDS)).isNull();
|
||||
assertThat(map.putIfAbsent("5", "7", 1, TimeUnit.SECONDS)).isEqualTo("6");
|
||||
|
||||
assertThat(m.get("3")).isEqualTo("4");
|
||||
assertThat(m.size()).isEqualTo(2);
|
||||
|
||||
transaction.commit();
|
||||
|
||||
assertThat(m.get("1")).isEqualTo("2");
|
||||
assertThat(m.get("3")).isEqualTo("4");
|
||||
assertThat(m.get("5")).isEqualTo("6");
|
||||
|
||||
Thread.sleep(1500);
|
||||
|
||||
assertThat(m.get("3")).isEqualTo("4");
|
||||
assertThat(m.get("5")).isEqualTo("6");
|
||||
|
||||
Thread.sleep(1500);
|
||||
|
||||
assertThat(m.get("3")).isEqualTo("4");
|
||||
assertThat(m.get("5")).isNull();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RMap<String, String> getMap() {
|
||||
return redisson.getMapCache("test");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RMap<String, String> getTransactionalMap(RTransaction transaction) {
|
||||
return transaction.getMapCache("test");
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,19 @@
|
||||
package org.redisson.transaction;
|
||||
|
||||
import org.redisson.api.RMap;
|
||||
import org.redisson.api.RTransaction;
|
||||
|
||||
public class RedissonTransactionalMapTest extends RedissonBaseTransactionalMapTest {
|
||||
|
||||
@Override
|
||||
protected RMap<String, String> getMap() {
|
||||
return redisson.getMap("test");
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RMap<String, String> getTransactionalMap(RTransaction transaction) {
|
||||
return transaction.getMap("test");
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,143 @@
|
||||
package org.redisson.transaction;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.redisson.BaseTest;
|
||||
import org.redisson.api.RSetCache;
|
||||
import org.redisson.api.RTransaction;
|
||||
import org.redisson.api.TransactionOptions;
|
||||
|
||||
public class RedissonTransactionalSetCacheTest extends BaseTest {
|
||||
|
||||
@Test
|
||||
public void testRemoveAll() {
|
||||
RSetCache<String> s = redisson.getSetCache("test");
|
||||
s.add("1");
|
||||
s.add("3");
|
||||
|
||||
RTransaction t = redisson.createTransaction(TransactionOptions.defaults());
|
||||
RSetCache<String> set = t.getSetCache("test");
|
||||
Set<String> putSet = new HashSet<String>();
|
||||
putSet.add("4");
|
||||
putSet.add("3");
|
||||
set.removeAll(putSet);
|
||||
assertThat(s).containsOnly("1", "3");
|
||||
assertThat(set).containsOnly("1");
|
||||
|
||||
t.commit();
|
||||
|
||||
assertThat(s).containsOnly("1");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPutAll() {
|
||||
RSetCache<String> s = redisson.getSetCache("test");
|
||||
s.add("1");
|
||||
s.add("3");
|
||||
|
||||
RTransaction t = redisson.createTransaction(TransactionOptions.defaults());
|
||||
RSetCache<String> set = t.getSetCache("test");
|
||||
Set<String> putSet = new HashSet<String>();
|
||||
putSet.add("4");
|
||||
putSet.add("6");
|
||||
set.addAll(putSet);
|
||||
assertThat(s).containsOnly("1", "3");
|
||||
assertThat(set).containsOnly("1", "3", "4", "6");
|
||||
|
||||
t.commit();
|
||||
|
||||
assertThat(s).containsOnly("1", "3", "4", "6");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testKeySet() {
|
||||
RSetCache<String> s = redisson.getSetCache("test");
|
||||
s.add("1");
|
||||
s.add("3");
|
||||
|
||||
RTransaction t = redisson.createTransaction(TransactionOptions.defaults());
|
||||
RSetCache<String> set = t.getSetCache("test");
|
||||
set.remove("3");
|
||||
assertThat(set).containsOnly("1");
|
||||
|
||||
assertThat(s).containsOnly("1", "3");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAdd() {
|
||||
RSetCache<String> s = redisson.getSetCache("test");
|
||||
s.add("1");
|
||||
s.add("3");
|
||||
|
||||
RTransaction transaction = redisson.createTransaction(TransactionOptions.defaults());
|
||||
RSetCache<String> set = transaction.getSetCache("test");
|
||||
assertThat(set.add("4")).isTrue();
|
||||
assertThat(set.add("3")).isFalse();
|
||||
assertThat(set.contains("4")).isTrue();
|
||||
|
||||
assertThat(s.contains("4")).isFalse();
|
||||
|
||||
transaction.commit();
|
||||
|
||||
assertThat(s.size()).isEqualTo(3);
|
||||
assertThat(s.contains("1")).isTrue();
|
||||
assertThat(s.contains("3")).isTrue();
|
||||
assertThat(s.contains("4")).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAddTTL() throws InterruptedException {
|
||||
RSetCache<String> s = redisson.getSetCache("test");
|
||||
s.add("1");
|
||||
s.add("3");
|
||||
|
||||
RTransaction transaction = redisson.createTransaction(TransactionOptions.defaults());
|
||||
RSetCache<String> set = transaction.getSetCache("test");
|
||||
assertThat(set.add("4", 2, TimeUnit.SECONDS)).isTrue();
|
||||
assertThat(set.add("3")).isFalse();
|
||||
assertThat(set.contains("4")).isTrue();
|
||||
|
||||
assertThat(s.contains("4")).isFalse();
|
||||
|
||||
transaction.commit();
|
||||
|
||||
assertThat(s.size()).isEqualTo(3);
|
||||
assertThat(s.contains("1")).isTrue();
|
||||
assertThat(s.contains("3")).isTrue();
|
||||
assertThat(s.contains("4")).isTrue();
|
||||
|
||||
Thread.sleep(2000);
|
||||
|
||||
assertThat(s.contains("4")).isFalse();
|
||||
}
|
||||
|
||||
|
||||
@Test
|
||||
public void testRemove() {
|
||||
RSetCache<String> s = redisson.getSetCache("test");
|
||||
s.add("1");
|
||||
s.add("3");
|
||||
|
||||
RTransaction transaction = redisson.createTransaction(TransactionOptions.defaults());
|
||||
RSetCache<String> set = transaction.getSetCache("test");
|
||||
assertThat(set.contains("1")).isTrue();
|
||||
assertThat(set.remove("3")).isTrue();
|
||||
assertThat(set.remove("3")).isFalse();
|
||||
assertThat(set.remove("3")).isFalse();
|
||||
|
||||
assertThat(s.contains("3")).isTrue();
|
||||
|
||||
transaction.commit();
|
||||
|
||||
assertThat(s.size()).isEqualTo(1);
|
||||
assertThat(s.contains("1")).isTrue();
|
||||
assertThat(s.contains("3")).isFalse();
|
||||
}
|
||||
|
||||
|
||||
}
|
@ -0,0 +1,115 @@
|
||||
package org.redisson.transaction;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
|
||||
import java.util.HashSet;
|
||||
import java.util.Set;
|
||||
|
||||
import org.junit.Test;
|
||||
import org.redisson.BaseTest;
|
||||
import org.redisson.api.RSet;
|
||||
import org.redisson.api.RTransaction;
|
||||
import org.redisson.api.TransactionOptions;
|
||||
|
||||
public class RedissonTransactionalSetTest extends BaseTest {
|
||||
|
||||
@Test
|
||||
public void testRemoveAll() {
|
||||
RSet<String> s = redisson.getSet("test");
|
||||
s.add("1");
|
||||
s.add("3");
|
||||
|
||||
RTransaction t = redisson.createTransaction(TransactionOptions.defaults());
|
||||
RSet<String> set = t.getSet("test");
|
||||
Set<String> putSet = new HashSet<String>();
|
||||
putSet.add("4");
|
||||
putSet.add("3");
|
||||
set.removeAll(putSet);
|
||||
assertThat(s).containsOnly("1", "3");
|
||||
assertThat(set).containsOnly("1");
|
||||
|
||||
t.commit();
|
||||
|
||||
assertThat(s).containsOnly("1");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testPutAll() {
|
||||
RSet<String> s = redisson.getSet("test");
|
||||
s.add("1");
|
||||
s.add("3");
|
||||
|
||||
RTransaction t = redisson.createTransaction(TransactionOptions.defaults());
|
||||
RSet<String> set = t.getSet("test");
|
||||
Set<String> putSet = new HashSet<String>();
|
||||
putSet.add("4");
|
||||
putSet.add("6");
|
||||
set.addAll(putSet);
|
||||
assertThat(s).containsOnly("1", "3");
|
||||
assertThat(set).containsOnly("1", "3", "4", "6");
|
||||
|
||||
t.commit();
|
||||
|
||||
assertThat(s).containsOnly("1", "3", "4", "6");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testKeySet() {
|
||||
RSet<String> s = redisson.getSet("test");
|
||||
s.add("1");
|
||||
s.add("3");
|
||||
|
||||
RTransaction t = redisson.createTransaction(TransactionOptions.defaults());
|
||||
RSet<String> set = t.getSet("test");
|
||||
set.remove("3");
|
||||
assertThat(set).containsOnly("1");
|
||||
|
||||
assertThat(s).containsOnly("1", "3");
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testAdd() {
|
||||
RSet<String> s = redisson.getSet("test");
|
||||
s.add("1");
|
||||
s.add("3");
|
||||
|
||||
RTransaction transaction = redisson.createTransaction(TransactionOptions.defaults());
|
||||
RSet<String> set = transaction.getSet("test");
|
||||
assertThat(set.add("4")).isTrue();
|
||||
assertThat(set.add("3")).isFalse();
|
||||
assertThat(set.contains("4")).isTrue();
|
||||
|
||||
assertThat(s.contains("4")).isFalse();
|
||||
|
||||
transaction.commit();
|
||||
|
||||
assertThat(s.size()).isEqualTo(3);
|
||||
assertThat(s.contains("1")).isTrue();
|
||||
assertThat(s.contains("3")).isTrue();
|
||||
assertThat(s.contains("4")).isTrue();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void testRemove() {
|
||||
RSet<String> s = redisson.getSet("test");
|
||||
s.add("1");
|
||||
s.add("3");
|
||||
|
||||
RTransaction transaction = redisson.createTransaction(TransactionOptions.defaults());
|
||||
RSet<String> set = transaction.getSet("test");
|
||||
assertThat(set.contains("1")).isTrue();
|
||||
assertThat(set.remove("3")).isTrue();
|
||||
assertThat(set.remove("3")).isFalse();
|
||||
assertThat(set.remove("3")).isFalse();
|
||||
|
||||
assertThat(s.contains("3")).isTrue();
|
||||
|
||||
transaction.commit();
|
||||
|
||||
assertThat(s.size()).isEqualTo(1);
|
||||
assertThat(s.contains("1")).isTrue();
|
||||
assertThat(s.contains("3")).isFalse();
|
||||
}
|
||||
|
||||
|
||||
}
|
Loading…
Reference in New Issue