This the same example, but this time using the AbstractEntryProcessor class:
public class ReadOnlyEntryProcessorTest1 {
public static void main(String[] args) throws InterruptedException {
Config config = new Config();
config.setProperty("hazelcast.initial.min.cluster.size", "2");
HazelcastInstance hazelcastInstance = Hazelcast.newHazelcastInstance(config);
// Take a lock so only 1 of the 2 nodes will execute the entry processor
ILock doItLock = hazelcastInstance.getLock("doItLock");
doItLock.lock();
IMap<Long, User> testMap;
try {
testMap = hazelcastInstance.getMap("testMap");
if (!testMap.containsKey(1L)) {
IntStream.rangeClosed(1, 10)
.mapToObj(ReadOnlyEntryProcessorTest1::createUser)
.forEach(user -> testMap.put(user.getId(), user));
System.out.println("Calling the entry processor");
Map<Long, Object> userNames = testMap.executeOnEntries(new ReadUserNamesEntryProcessor());
userNames.values().forEach(name -> System.out.println("name = " + name));
}
} finally {
doItLock.unlock();
}
}
private static User createUser(int i) {
User user = new User();
user.setId(i);
user.setName("Wim" + i);
return user;
}
private static class ReadUserNamesEntryProcessor extends AbstractEntryProcessor<Long, User> {
public ReadUserNamesEntryProcessor() {
}
@Override
public Object process(Map.Entry<Long, User> entry) {
String name = entry.getValue().getName();
System.out.println("Returning name from primary entry: " + name);
return name;
}
}
}
The printing of the names has now changed from this (not using an entry processor):
testMap.values().stream()
.map( User::getName )
.forEach( name -> System.out.println( "name = " + name ) );
to this (using an entry processor):
Map<Long, Object> userNames = testMap.executeOnEntries( new ReadUserNamesEntryProcessor() );
userNames.values().forEach( name -> System.out.println( "name = " + name ) );
The advantage here is that Hazelcast only has to serialize (and send over the network) the "name" String instead of the full User object.
If you would run the example, you will notice that the text "Returning name from primary entry" will be printed 20 times instead of the expected 10 times (There are 10 User objects in the cache). This is because AbstractEntryProcessor by default, also runs the processor on the backup entries.
In a read-only use case, this has no use at all (For starters, a EntryBackupProcessor cannot return a value anyway). So to have the best performance, we need call the super constructor with false to avoid that a backup processor is used.
This is now the code for our optimal EntryProcessor:
private static class ReadUserNamesEntryProcessor extends AbstractEntryProcessor<Long, User> {
public ReadUserNamesEntryProcessor() {
super(false);
}
@Override
public Object process(Map.Entry<Long, User> entry) {
String name = entry.getValue().getName();
System.out.println("Returning name from primary entry: " + name);
return name;
}
}
An alternative would be to implement the interface and return null yourself:
private static class ReadUserNamesEntryProcessor implements EntryProcessor<Long, User> {
public ReadUserNamesEntryProcessor() {
}
@Override
public Object process(Map.Entry<Long, User> entry) {
String name = entry.getValue().getName();
System.out.println("Returning name from primary entry: " + name);
return name;
}
@Override
public EntryBackupProcessor<Long, User> getBackupProcessor() {
return null;
}
}
It would be nice if Hazelcast provided a ReadOnlyEntryProcessor abstract class. It would be more explicit that remembering having to call the super with 'false'. Maybe it could even throw an Exception if you would try to call 'entry.setValue' from such an EntryProcessor.