1

I'm using apache geode 1.13.0

The data class is simple, it contains 1 ArrayList and 1 HashMap

The data is stored correctly on geode but then repository.findById() returns the wrong array and map

repository.findAll() works well though

the data is

id | someList  | someMap
-- | --------- | ---------------------------------
1  | [1,2,3,4] | {"1":"a","2":"b","3":"c","4":"d"}
2  | [2,3,4]   | {"4":"d","2":"b","3":"c"}
3  | [3,4]     | {"4":"d","3":"c"}
4  | null      | {"4":"d"}
5  | null      | null

while the findById retrieves

Class1{id=1, someList=[3, 4], someMap={4=d}}
Class1{id=2, someList=[3, 4], someMap={4=d}}
Class1{id=3, someList=[3, 4], someMap={4=d}}
Class1{id=4, someList=null, someMap={4=d}}
Class1{id=5, someList=null, someMap=null}

the logic is in MyRunner.java

What could be the problem?

here's the class

....
@Region("TestRegion")
public class Class1 implements PdxSerializable {
  @Id
  Integer id;
  ArrayList<Integer> someList;
  HashMap<Integer, String> someMap;

  public Class1() {}

  public Class1(Integer id, ArrayList<Integer> someList, HashMap<Integer, String> someMap) {
    this.id = id;
    this.someList = someList;
    this.someMap = someMap;
  }

  @Override
  public String toString() {
    String ret;

    ret = "Class1";
    ret += "{id=";
    if (id == null) { ret += "null"; } else { ret += id.toString(); }
    ret += ", someList=";
    if (someList == null) { ret += "null"; } else { ret += someList.toString(); }
    ret += ", someMap=";
    if (someMap == null) { ret += "null"; } else { ret += someMap.toString(); }
    ret += "}";

    return ret;
  }

  @Override
  public void toData(PdxWriter out) throws PdxFieldAlreadyExistsException, PdxSerializationException {
    out.writeInt("id", id);
    out.writeObject("someList", someList);
    out.writeObject("someMap", someMap);
  }

  @Override
  public void fromData(PdxReader in) throws PdxFieldTypeMismatchException, PdxSerializationException {
    id = in.readInt("id");
    someList = (ArrayList<Integer>)in.readObject("someList");
    someMap = (HashMap<Integer, String>)in.readObject("someMap");
  }
}

the repository

@Repository
public interface GeodeRepository extends CrudRepository<Class1, Integer> {
}

geode config class

.....
@EnableEntityDefinedRegions(basePackageClasses = Class1.class, clientRegionShortcut = ClientRegionShortcut.CACHING_PROXY)
@EnableGemfireRepositories(basePackageClasses = GeodeRepository.class)
@Configuration
class GeodeConfiguration {
}

main

....
@SpringBootApplication
class SpringbootCmdTest {
  public static void main(String[] args) {
    SpringApplication.run(SpringbootCmdTest.class, args);
  }
}

MyRunner class

....
@Component
public class MyRunner implements CommandLineRunner {
  @Autowired
  private GeodeRepository repository;

  @Override
  public void run(String... args) throws Exception {

    //repository.deleteAll(); // doesn't work for partitioned regions as of 2020-11-02 https://jira.spring.io/browse/DATAGEODE-265
    repository.deleteAll(repository.findAll());
    ArrayList<Integer> l = new ArrayList<>();
    HashMap<Integer, String> m = new HashMap<>();
    Class1 obj;
    Optional<Class1> o;

    l.clear(); l.add(1); l.add(2); l.add(3); l.add(4);
    m.clear(); m.put(1, "a"); m.put(2, "b"); m.put(3, "c"); m.put(4, "d");
    obj = new Class1(1, l, m);
    repository.save(obj);

    l.clear(); l.add(2); l.add(3); l.add(4);
    m.clear(); m.put(2, "b"); m.put(3, "c"); m.put(4, "d");
    obj = new Class1(2, l, m);
    repository.save(obj);

    l.clear(); l.add(3); l.add(4);
    m.clear(); m.put(3, "c"); m.put(4, "d");
    obj = new Class1(3, l, m);
    repository.save(obj);

    m.clear(); m.put(4, "d");
    obj = new Class1(4, null, m);
    repository.save(obj);

    obj = new Class1(5, null, null);
    repository.save(obj);

    System.out.println("\n-- findAll().foreach works -------------------------------");

    repository.findAll().forEach((item) ->System.out.println(item.toString()));

    System.out.println("\n-- optional directly to string. issue with the array and map. displays the last entry --");

    System.out.println(repository.findById(1).toString());
    System.out.println(repository.findById(2).toString());
    System.out.println(repository.findById(3).toString());
    System.out.println(repository.findById(4).toString());
    System.out.println(repository.findById(5).toString());

    System.out.println("\n-- first convert the optional to object. issue with the array and map. displays the last entry --");

    o = repository.findById(1);
    o.ifPresent(ob -> System.out.println(ob.toString()));
    o = repository.findById(2);
    o.ifPresent(ob -> System.out.println(ob.toString()));
    o = repository.findById(3);
    o.ifPresent(ob -> System.out.println(ob.toString()));
    o = repository.findById(4);
    o.ifPresent(ob -> System.out.println(ob.toString()));
    o = repository.findById(5);
    o.ifPresent(ob -> System.out.println(ob.toString()));

    System.out.println("\n-- findAllById().foreach does not work either -------------------------------");

    ArrayList<Integer> il = new ArrayList<>();

    il.add(1); il.add(2); il.add(3); il.add(4); il.add(5);
    repository.findAllById(il).forEach((item) ->System.out.println(item.toString()));

    System.out.println("\n---------------------------------");
  }
}

application.properties

spring.profiles.active = dev

logging.level.org.springframework.boot = INFO
logging.level.org.springframework.transaction = TRACE
logging.level.owa = DEBUG
logging.file.name = Test.log

spring.main.banner-mode=off
spring.profiles.include=common

spring.data.gemfire.pool.locators = localhost[2001]

pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
  <modelVersion>4.0.0</modelVersion>
  <parent>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-parent</artifactId>
    <version>2.3.4.RELEASE</version>
    <relativePath/><!-- lookup parent from repository -->
  </parent>
  <groupId>org.Test</groupId>
  <artifactId>SpringbootCmdTest</artifactId>
  <version>20.10.0</version>
  <name>SpringbootCmdTest</name>
  <description>Springboot Cmd Line For Testing</description>

  <properties>
    <java.version>1.8</java.version>
  </properties>

  <dependencies>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter</artifactId>
    </dependency>
    <dependency>
      <groupId>org.springframework.geode</groupId>
      <artifactId>spring-geode-starter</artifactId>
      <version>1.3.4.RELEASE</version>
    </dependency>
    <dependency>
      <groupId>org.apache.geode</groupId>
      <artifactId>geode-core</artifactId>
      <version>1.13.0</version>
    </dependency>
    <dependency>
      <groupId>org.springframework.boot</groupId>
      <artifactId>spring-boot-starter-test</artifactId>
      <scope>test</scope>
      <exclusions>
        <exclusion>
          <groupId>org.junit.vintage</groupId>
          <artifactId>junit-vintage-engine</artifactId>
        </exclusion>
      </exclusions>
    </dependency>
  </dependencies>

  <build>
    <plugins>
      <plugin>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-maven-plugin</artifactId>
      </plugin>
      <plugin>
        <groupId>org.codehaus.gmavenplus</groupId>
        <artifactId>gmavenplus-plugin</artifactId>
        <version>1.8.1</version>
        <executions>
          <execution>
            <goals>
              <goal>addSources</goal>
              <goal>addTestSources</goal>
              <goal>generateStubs</goal>
              <goal>compile</goal>
              <goal>generateTestStubs</goal>
              <goal>compileTests</goal>
              <goal>removeStubs</goal>
              <goal>removeTestStubs</goal>
            </goals>
          </execution>
        </executions>
      </plugin>
    </plugins>
  </build>

</project>

gfsh query

gfsh>query --query="select * from /TestRegion"
Result : true
Limit  : 100
Rows   : 5

id | someList  | someMap
-- | --------- | ---------------------------------
1  | [1,2,3,4] | {"1":"a","2":"b","3":"c","4":"d"}
2  | [2,3,4]   | {"4":"d","2":"b","3":"c"}
3  | [3,4]     | {"4":"d","3":"c"}
4  | null      | {"4":"d"}
5  | null      | null

output

-- findAll().foreach works -------------------------------
Class1{id=1, someList=[1, 2, 3, 4], someMap={1=a, 2=b, 3=c, 4=d}}
Class1{id=2, someList=[2, 3, 4], someMap={4=d, 2=b, 3=c}}
Class1{id=3, someList=[3, 4], someMap={4=d, 3=c}}
Class1{id=4, someList=null, someMap={4=d}}
Class1{id=5, someList=null, someMap=null}

-- optional directly to string. issue with the array and map. displays the last entry --
Optional[Class1{id=1, someList=[3, 4], someMap={4=d}}]
Optional[Class1{id=2, someList=[3, 4], someMap={4=d}}]
Optional[Class1{id=3, someList=[3, 4], someMap={4=d}}]
Optional[Class1{id=4, someList=null, someMap={4=d}}]
Optional[Class1{id=5, someList=null, someMap=null}]

-- first convert the optional to object. issue with the array and map. displays the last entry --
Class1{id=1, someList=[3, 4], someMap={4=d}}
Class1{id=2, someList=[3, 4], someMap={4=d}}
Class1{id=3, someList=[3, 4], someMap={4=d}}
Class1{id=4, someList=null, someMap={4=d}}
Class1{id=5, someList=null, someMap=null}

-- findAllById().foreach does not work either -------------------------------
Class1{id=1, someList=[3, 4], someMap={4=d}}
Class1{id=2, someList=[3, 4], someMap={4=d}}
Class1{id=3, someList=[3, 4], someMap={4=d}}
Class1{id=4, someList=null, someMap={4=d}}
Class1{id=5, someList=null, someMap=null}
claudiu
  • 11
  • 3
  • In addition to Patrick's comment below and his response to DATAGEODE-388, I also wanted to share additional information that I provided in a comment on DATAGEODE-388. See here: https://jira.spring.io/browse/DATAGEODE-388?focusedCommentId=191856&page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel#comment-191856 – John Blum Nov 30 '20 at 21:49
  • Also note: https://issues.apache.org/jira/browse/GEODE-8733. – John Blum Nov 30 '20 at 21:50

1 Answers1

1

Note: This behavior is not exclusive to Spring and can be reproduced using Apache Geode alone.

Because you have clientRegionShortcut set to ClientRegionShortcut.CACHING_PROXY, your data is stored both on the server and on the client in a local region. When you access data by id, the local region is checked first, and if the entry is found, it is returned, if it isn't found, it will call out to the server for it.

The reason you're seeing only the most recent values of your list and map is because the local region is holding on to m and l by reference, so when you reuse them and update their contents, the values stored in the local region reflect the change as well, even without saving them. You see the correct values for findAll() and your query because those delegate to the server directly (which doesn't hold the values by reference because it's a separate machine/process) rather than the local region.

There are a couple ways you can get the behavior you expect:

Option 1. Change ClientRegionShortcut.CACHING_PROXY to ClientRegionShortcut.PROXY so the values won't be stored locally and will instead be retrieved from the server every time.

Option 2. You can create a new ArrayList and HashMap every time you want to add an entry instead of reusing the same objects. For example, replacing l.clear() and m.clear() with l = new ArrayList<>() and m = new HashMap<>().

Patrick
  • 63
  • 3