2

I am using spring data JPA I am trying to update the entity using the JPARepository method save() to update the entity details. Code I have written to update the entity works fine in development as expected. But the same code does not work on the production server it does not give any error only the code written inside the map() does not work. Below is my code

public Long updateQrCodeUrlByBusinessDetails(Long businessId, String menuUrl, MENU_TYPE menuType) {
        return qrCodeRepo.findByBusinessId(businessId).businessQrCode.stream().map(qr -> {
            qr.setMenuUrl(menuUrl);
            qr.setMenuType(menuType);
            return qrCodeRepo.save(qr);
        }).count();

    }
Jatinder
  • 96
  • 2
  • 11
  • Does this answer your question? [Stream.peek() method in Java 8 vs Java 9](https://stackoverflow.com/questions/48221783/stream-peek-method-in-java-8-vs-java-9) – luk2302 May 24 '21 at 07:43

3 Answers3

6

The problem is that count

may choose to not execute the stream pipeline (either sequentially or in parallel) if it is capable of computing the count directly from the stream source. In such cases no source elements will be traversed and no intermediate operations will be evaluated. Behavioral parameters with side-effects, which are strongly discouraged except for harmless cases such as debugging, may be affected

And exactly that happens in your case. The solution is to neither use map or count at all since you do not need / use them for what they are supposed to be used for and instead use a single forEach instead:

public void updateQrCodeUrlByBusinessDetails(Long businessId, String menuUrl, MENU_TYPE menuType) {
    List<BusinessQRCode> businessQrCode = qrCodeRepo.findByBusinessId(businessId);
    businessQrCode.stream().forEach(qr -> {
        qr.setMenuUrl(menuUrl);
        qr.setMenuType(menuType);
        qrCodeRepo.save(qr);
    });
}

Note that you could just do the exact same thing with a for loop with less calls, same amount of lines and without unnecessarily using streams:

public void updateQrCodeUrlByBusinessDetails(Long businessId, String menuUrl, MENU_TYPE menuType) {
    List<BusinessQRCode> businessQrCode = qrCodeRepo.findByBusinessId(businessId);
    for (BusinessQRCode qr : businessQrCode) {
        qr.setMenuUrl(menuUrl);
        qr.setMenuType(menuType);
        qrCodeRepo.save(qr);
    };
}
luk2302
  • 55,258
  • 23
  • 97
  • 137
3

just my two cents.

You should always return Optional from query results. You may have it as:

In repository:

Optional<List<BusinessQRCode>> findByBusinessId(Long businessId);

In Service:

public void updateQrCodeUrlByBusinessDetails(Long businessId, String menuUrl, MENU_TYPE menuType) {

// Fetch iterate, if exist
qrCodeRepo.findByBusinessId(businessId).ifPresent(qrCodes_ -> {

  qrCodes_.forEach(code_-> {
     qr.setMenuUrl(menuUrl);
     qr.setMenuType(menuType);
  });

  qrCodeRepo.saveAll(qrCodes_);

});

Why use map when you are returning same qr. You can skip that part and also the collect function. We already have the list of qrCodes, just iterate them and set the values then save all at once.

Kumar Ashutosh
  • 1,121
  • 10
  • 33
  • Great I come to know the different ways to resolve the same issue but can you explain why does the map part code not work or what is the issue with the map while updating the entity details – Jatinder May 24 '21 at 12:56
  • See this test class https://gist.github.com/ashutosh049/ea9a4e1e62ad5506209e7a2aba4a4b12, where I was expecting some intermediate operation before the reduction/terminal operation i.e. count(). In this, I was expecting the new ids to be updated and some print statement from within map operation, but that all seems to be discarded, as java thought we are only interested in the terminal operation of count , which java can return even without executing map operation, just by seeing the stream input. This is the culprit here. You can see it clearly mentioned in java doc Stream#count – Kumar Ashutosh May 24 '21 at 14:11
  • I would suggest to go through the java versions of both dev and prod envs and see if there is any discrepancies. I'm sure there must be something, it should never happen that it works on local and not on other envs – Kumar Ashutosh May 24 '21 at 14:21
  • on both envs java, 1.8 is used but the difference is OpenJDK on production and oracle JDK for development – Jatinder May 24 '21 at 14:29
  • 1
    I fixed the issue by referring to the solutions provided in this question. – Jatinder May 24 '21 at 14:52
1

After making some changes in code it work as expected on development and production. I just change the object value in the map() and return after that collect the object to list using the Collectors.toList() method and again use forEach() loop and update the entity data. Now code works as expected.

public void updateQrCodeUrlByBusinessDetails(Long businessId, String menuUrl, MENU_TYPE menuType) {
        List<BusinessQRCode> businessQrCode = qrCodeRepo.findByBusinessId(businessId);
        businessQrCode.stream().map(qr -> {
            qr.setMenuUrl(menuUrl);
            qr.setMenuType(menuType);
            return qr;
        }).collect(Collectors.toList()).forEach(qr -> qrCodeRepo.save(qr));

    }
Jatinder
  • 96
  • 2
  • 11
  • 1
    Calling save from within a Stream.map() is a bad design. Map should be used purely for returning a stream consisting of the results of applying the given function to the elements of current stream. It should be idempotent and not produce any side effect. Use forEach instead – Gro May 24 '21 at 08:07