How can I assert value created in void method? - java

I have class
public class CloneUserService {
private final UserRepository userRepository;
private final PersonRepository personRepository;
private final OrderRepository orderRepository;
public CloneUserService(UserRepository userRepository, PersonRepository personRepository, OrderRepository orderRepository) {
this.userRepository = userRepository;
this.personRepository = personRepository;
this.orderRepository = orderRepository;
}
public void createFromTemplate(String templateUserId) {
User templateUser = userRepository.getUserById(templateUserId);
Person templatePerson = personRepository.getPersonByUserId(templateUserId);
List<Order> templateOrders = orderRepository.getOrdersByUserId(templateUserId);
User newUser = cloneUserFromTemplate(templateUser);
newUser.setId("newId");
userRepository.save(newUser);
Person newPerson = clonePersonFromTemplate(templatePerson);
newPerson.setUser(newUser);
newPerson.setId("newId");
personRepository.save(newPerson);
for (Order templateOrder : templateOrders) {
Order newOrder = cloneOrderFromTemplate(templateOrder);
newOrder.setId("newId");
newOrder.setUSer(newUser);
orderRepository.save(newOrder);
}
}
private Order cloneOrderFromTemplate(Order templateOrder) {
//logic
return null;
}
private Person clonePersonFromTemplate(Person templatePerson) {
//logic
return null;
}
private User cloneUserFromTemplate(User templateUser) {
//logic
return null;
}
}
I need to test this method createFromTemplate.
I create this test. I create stabs for each repository and store saved object into this stub. And I add the additional method for getting this object for the assertion.
It works. But I have 2 problems:
My template object is mutable. It is not a big problem but it is a fact.
If I add new methods to repository interface I must implement it in stubs.
Mu question - How can I test cloned objects like theses from my example?
I don't use spring and H2DB or another in-memory database.
I have a MongoDB database.
If I use mockito I will not understand how to assert new objects in void method.
class CloneUserServiceTest {
private CloneUserService cloneUserService;
private UserRepositoryStub userRepository;
private PersonRepositoryStub personRepository;
private OrderRepositoryStub orderRepository;
#Before
public void setUp() {
User templateUser = new User();
Person templatePerson = new Person();
List<Order> templateOrders = Collections.singletonList(new Order());
userRepository = new UserRepositoryStub(templateUser);
personRepository = new PersonRepositoryStub(templatePerson);
orderRepository = new OrderRepositoryStub(templateOrders);
cloneUserService = new CloneUserService(userRepository, personRepository, orderRepository);
}
#Test
void createFromTemplate() {
cloneUserService.createFromTemplate("templateUserId");
User newUser = userRepository.getNewUser();
// assert newUser
Person newPerson = personRepository.getNewPerson();
// assert newPerson
Order newOrder = orderRepository.getNewOrder();
// assert newOrder
}
private static class UserRepositoryStub implements UserRepository {
private User templateUser;
private User newUser;
public UserRepositoryStub(User templateUser) {
this.templateUser = templateUser;
}
public User getUserById(String templateUserId) {
return templateUser;
}
public void save(User newUser) {
this.newUser = newUser;
}
public User getNewUser() {
return newUser;
}
}
private static class PersonRepositoryStub implements PersonRepository {
private Person templatePerson;
private Person newPerson;
public PersonRepositoryStub(Person templatePerson) {
this.templatePerson = templatePerson;
}
public Person getPersonByUserId(String templateUserId) {
return templatePerson;
}
public void save(Person newPerson) {
this.newPerson = newPerson;
}
public Person getNewPerson() {
return newPerson;
}
}
private static class OrderRepositoryStub implements OrderRepository {
private List<Order> templateOrders;
private Order newOrder;
public OrderRepositoryStub(List<Order> templateOrders) {
this.templateOrders = templateOrders;
}
public List<Order> getOrdersByUserId(String templateUserId) {
return templateOrders;
}
public void save(Order newOrder) {
this.newOrder = newOrder;
}
public Order getNewOrder() {
return newOrder;
}
}
}

In your scenario I would consider using mocking framework like Mockito.
Some main advantages:
Adding new methods to repository interface doesn't require implementing it in stubs
Supports exact-number-of-times and at-least-once verification
Allows flexible verification in order (e.g: verify in order what you want, not every single interaction)
Very nice and simple annotation syntax - #Mock, #InjectMocks, #Spy
Here is an example - maybe it will interest you:
// arrange
Warehouse mock = Mockito.mock(Warehouse.class);
//act
Order order = new Order(TALISKER, 50);
order.fill(warehouse); // fill will call remove() implicitly
// assert
Mockito.verify(warehouse).remove(TALISKER, 50); // verify that remove() method was actually called

Related

Repository bean not getting instantiated

I have this repository interface:
public interface ScoreCardRepository extends CrudRepository<ScoreCard, Long> {
#Query(value = "SELECT SUM(SCORE) FROM SCORE_CARD WHERE USER_ID = :userId", nativeQuery = true)
Integer getTotalScoreForUser(Long userId);
}
and this controller:
#RestController
#RequestMapping("/gamification")
public class GamificationController {
private final LeaderBoardServiceImpl leaderBoardService;
private final GameServiceImpl gameService;
#Autowired
public GamificationController(GameServiceImpl gameService, LeaderBoardServiceImpl leaderBoardService){
this.gameService = gameService;
this.leaderBoardService = leaderBoardService;
}
#GetMapping("/retrieve-stats")
ResponseEntity<GameStats> getUserStats(#RequestParam("user") String userId){
return ResponseEntity.ok(gameService.retrieveStatsForUser(Long.parseLong(userId)));
}
}
Now, when I call the /retrieve-stats and we go inside gameService.retrieveStatsForUser I get a null pointer exception
#Service
public class GameServiceImpl implements GameService {
private final ScoreCardRepository scoreCardRepository;
private final BadgeCardRepository badgeCardRepository;
#Autowired
public GameServiceImpl(ScoreCardRepository scoreCardRepository, BadgeCardRepository badgeCardRepository) {
this.scoreCardRepository = scoreCardRepository;
this.badgeCardRepository = badgeCardRepository;
}
#Override
public GameStats retrieveStatsForUser(Long userId) {
List<BadgeCard> badgeCardList = badgeCardRepository.findByUserIdOrderByBadgeTimestampDesc(userId);
--->>> int totalScore = scoreCardRepository.getTotalScoreForUser(userId); //NULL POINTER EXCEPTION
GameStats gameStats = new GameStats(userId, totalScore,
badgeCardList.stream().map(BadgeCard::getBadge).collect(Collectors.toList()));
return gameStats;
}
}
Does this mean that the scoreCardRepository bean doesn't get instantiated? That should happen in the #Autowired GamificationserviceImpl constructor right? the badgeCardRepository gets instantiated fine. What's happening?
I suggest another cause:
Integer getTotalScoreForUser(Long userId);
This method can return in Integer of null which causes a NPE during auto-boxing to primitive datatype int at
int totalScore = scoreCardRepository.getTotalScoreForUser(userId);

Unit Test for Redis cache in Java

Note: I already look at and tried some approaches on SO e.g. How to test Spring's declarative caching support on Spring Data repositories?, but as most of them old, I cannot make them work properly and I need a solution with the latest library versions. So, I would be appreciated if you have a look at the question and help.
#Service
#EnableCaching
#RequiredArgsConstructor
public class DemoServiceImpl implements DemoService {
private static final String CACHE_NAME = "demoCache";
private final LabelRepository labelRepository;
private final LabelTranslatableRepository translatableRepository;
private final LanguageService languageService;
#Override
public LabelDTO findByUuid(UUID uuid) {
final Label label = labelRepository.findByUuid(uuid)
.orElseThrow(() -> new EntityNotFoundException("Not found."));
final List<LabelTranslatable> translatableList = translatableRepository.findAllByEntityUuid(uuid);
return new LabelDTO(Pair.of(label.getUuid(), label.getKey()), translatableList);
}
}
I created the following Unit Test to test caching for the nethod above:
#EnableCaching
#ImportAutoConfiguration(classes = {
CacheAutoConfiguration.class,
RedisAutoConfiguration.class
})
#ExtendWith(MockitoExtension.class)
class TextLabelServiceImpl_deneme_Test {
#Autowired
private CacheManager cacheManager;
#InjectMocks
private LabelService labelService;
#Mock
private LabelRepository labelRepository;
#Mock
private LabelTranslatableRepository translatableRepository;
#Test
void test_Cache() {
UUID uuid = UUID.randomUUID();
final TextLabel textLabel = new TextLabel();
textLabel.setId(1);
textLabel.setKey("key1");
TextLabelTranslatable textLabelTranslatable = new TextLabelTranslatable();
textLabelTranslatable.setEntityUuid(uuid);
textLabelTranslatable.setLanguage(SupportedLanguage.fr);
textLabelTranslatable.setValue("value1");
final List<TextLabelTranslatable> translatableList = new ArrayList<>();
translatableList.add(textLabelTranslatable);
when(labelRepository.findByUuid(uuid)).thenReturn(Optional.of(textLabel));
when(translatableRepository.findAllByEntityUuid(uuid)).thenReturn(translatableList);
TextLabelDTO result1 = labelService.findByUuid(uuid);
TextLabelDTO result2 = labelService.findByUuid(uuid);
assertEquals(result1, result2);
Mockito.verify(translatableRepository, Mockito.times(1)).findAllByEntityUuid(uuid);
}
I am not sure if there is a missing part in my test, but at the last line (Mockito.verify()), it returns 2 instead of 1 that means caching not works. But it is working properly and there is a problem in my test I think. How should I complete the unit test to check the caching properly?
You need to annotate the service class method with #Cacheable. Try to follow the code in this tutorial. The following test code works as expected
#Import({CacheConfig.class, DemoServiceImpl.class})
#ExtendWith(SpringExtension.class)
#EnableCaching
#ImportAutoConfiguration(classes = {
CacheAutoConfiguration.class,
RedisAutoConfiguration.class
})
class DemoServiceImplTest {
#MockBean
private LabelRepository labelRepository;
#Autowired
private DemoServiceImpl demoService;
#Autowired
private CacheManager cacheManager;
#TestConfiguration
static class EmbeddedRedisConfiguration {
private final RedisServer redisServer;
public EmbeddedRedisConfiguration() {
this.redisServer = new RedisServer();
}
#PostConstruct
public void startRedis() {
redisServer.start();
}
#PreDestroy
public void stopRedis() {
this.redisServer.stop();
}
}
#Test
void givenRedisCaching_whenFindItemById_thenItemReturnedFromCache() {
UUID id = UUID.randomUUID();
Label aLabel = new Label(id, "label");
Mockito.when(labelRepository.findById(id)).thenReturn(Optional.of(aLabel));
Label labelCacheMiss = demoService.findByUuid(id);
Label labelCacheHit = demoService.findByUuid(id);
Mockito.verify(labelRepository, Mockito.times(1)).findById(id);
}
}
With this service class code:
#Service
#RequiredArgsConstructor
#EnableCaching
public class DemoServiceImpl {
public static final String CACHE_NAME = "demoCache";
private final LabelRepository labelRepository;
#Cacheable(value = CACHE_NAME)
public Label findByUuid(UUID uuid) {
return labelRepository.findById(uuid)
.orElseThrow(() -> new EntityNotFoundException("Not found."));
}
}
And this CacheConfig:
#Configuration
public class CacheConfig {
#Bean
public RedisCacheManagerBuilderCustomizer redisCacheManagerBuilderCustomizer() {
return (builder) -> builder
.withCacheConfiguration(DemoServiceImpl.CACHE_NAME,
RedisCacheConfiguration.defaultCacheConfig().entryTtl(Duration.ofMinutes(10)));
}
#Bean
public RedisCacheConfiguration cacheConfiguration() {
return RedisCacheConfiguration.defaultCacheConfig()
.entryTtl(Duration.ofMinutes(60))
.disableCachingNullValues()
.serializeValuesWith(
RedisSerializationContext.SerializationPair.fromSerializer(
new GenericJackson2JsonRedisSerializer()));
}
}

Problem trying to mock more than one Java Interface

I've been writting some tests for a legacy web app on java using DeltaSpike, Weld, Hibernate and others frameworks.
I have faced some problem with my first test class that injects many Mocked interfaces on a Service.
Let me show some code:
Service to be tested
public class DistribuicaoProcessoManualServiceImpl extends DistribuicaoService implements DistribuicaoProcessoManualService {
#Inject
#WebServiceTJDBEntityManager EntityManager wstjEM;
private ForoTJComarcaMPRepository foroMPRepository;
private AssuntoTJNaturezaMPRepository naturezaMPRepository;
private VaraTJVaraMPRepository varaMPRepository;
private ClasseTJTipoMPRepository tipoMPRepository;
private ProcessoRepository processoRepository;
private UsuarioRepository usuarioRepository;
private UsuarioDaSessao usuarioDaSessao;
/**
* #deprecated CDI eyes only
*/
public DistribuicaoProcessoManualServiceImpl() {
this(null, null, null, null, null, null, null);
}
#Inject
public DistribuicaoProcessoManualServiceImpl(
ForoTJComarcaMPRepository foroMPRepository,
AssuntoTJNaturezaMPRepository naturezaMPRepository,
ClasseTJTipoMPRepository tipoMPRepository,
ProcessoRepository processoRepository,
UsuarioRepository usuarioRepository,
UsuarioDaSessao usuarioDaSessao,
VaraTJVaraMPRepository varaMPRepository) {
this.foroMPRepository = foroMPRepository;
this.naturezaMPRepository = naturezaMPRepository;
this.tipoMPRepository = tipoMPRepository;
this.processoRepository = processoRepository;
this.usuarioRepository = usuarioRepository;
this.usuarioDaSessao = usuarioDaSessao;
this.varaMPRepository = varaMPRepository;
}
#Override
#Transactional(readOnly = false, qualifier = {WebServiceTJDBEntityManager.class})
public Processo vinculaOuPreparaParaEspecializacao(Processo processo) {
//Service do some validations here with Guava's Preconditions;
Usuario usuario = usuarioRepository.procurarPorLogin(usuarioDaSessao.getUsuario().getLogin());
processo.atualizaInformacoesDeProcuradoriaEUsuario(usuario);
ProcessoDoTJMapeamentoParaMP processoDePara = compoeMapeamento(processo);
VaraTJVaraMP varaMP = varaMPRepository.procurarPorIdVaraTJForoEPorProcuradoria(processoDePara);
ForoTJComarcaMP comarcaMP = foroMPRepository.procurarPorIdForoEPorProcuradoria(processoDePara);
AssuntoTJNaturezaMP naturezaMP = naturezaMPRepository.procurarPorAssuntoEPorProcuradoria(processoDePara);
ClasseTJTipoMP tipoMP = tipoMPRepository.procurarPorCodigoEPorProcuradoria(processoDePara);
processoDePara
.comComarcaMP(comarcaMP)
.comVaraMP(varaMP)
.comNaturezaMP(naturezaMP)
.comTipoMP(tipoMP);
processoRepository.inclui(processo);
return processo;
}
}
The extended Class:
public abstract class DistribuicaoService {
public ProcessoDoTJMapeamentoParaMP compoeMapeamento(Processo processo) {
return new ProcessoDoTJMapeamentoParaMP(processo);
}
}
The mapper/builder/aggregator:
public class ProcessoDoTJMapeamentoParaMP implements Serializable {
private static final long serialVersionUID = 1L;
private final Processo processo;
public ProcessoDoTJMapeamentoParaMP(final Processo processo) {
this.processo = processo;
this.comCamaraMP( OrgaoJulgadorTJCamaraMP.semEfeito() )
.comRelatorMP( RelatorTJRelatorMP.semEfeito() )
.comOrigemMP( new OrgaoJulgadorTJOrigemMP() )
.comTipoDeRemessaMP();
}
public ProcessoDoTJMapeamentoParaMP comComarcaMP(ComarcaMP comarcaMP) {
processo.getDadosOrigem().setCodigoComarcaMP(comarcaMP.getIdComarca());
return this;
}
public ProcessoDoTJMapeamentoParaMP comVaraMP(VaraMP varaMP) {
processo.getDadosOrigem().setCodigoVaraMP(varaMP.getIdVaraMP());
return this;
}
public ProcessoDoTJMapeamentoParaMP comNaturezaMP(AssuntoTJNaturezaMP naturezaMP) {
processo.getAssuntoPrincipal().setCodigoNaturezaMP(naturezaMP.getIdNatureza());
return this;
}
public ProcessoDoTJMapeamentoParaMP comTipoMP(ClasseTJTipoMP tipoMP) {
processo.getDadosProcesso().getClasse().setCodigoTipoMP(tipoMP.getIdTipo());
return this;
}
ProcessoDoTJMapeamentoParaMP comCamaraMP(OrgaoJulgadorTJCamaraMP camaraMP) {
processo.getDadosDistribuicao().getMapeamento().setCodigoCamaraMP(camaraMP.getIdCamara());
return this;
}
ProcessoDoTJMapeamentoParaMP comRelatorMP(RelatorTJRelatorMP relatorMP) {
processo.getDadosDistribuicao().getMapeamento().setCodigoRelatorMP(relatorMP.getIdRelatorMP());
return this;
}
ProcessoDoTJMapeamentoParaMP comOrigemMP(OrgaoJulgadorTJOrigemMP origemMP) {
processo.getDadosDistribuicao().getMapeamento().setCodigoOrigemMP(origemMP.getIdOrigem());
return this;
}
ProcessoDoTJMapeamentoParaMP comTipoDeRemessaMP() {
processo.setTipoRemessa(TipoDeRemessa.fromTipoDeProcesso(this.processo.getProcessoVirtualTJ()));
return this;
}
public Procuradoria getProcuradoria() {
return processo.getProcuradoria();
}
public Integer getCodigoForoTJ() {
return processo.getDadosOrigem().getCodigoForoPrimeiraInstancia();
}
public Integer getCodigoVaraTJ() {
return processo.getDadosOrigem().getCodigoVaraPrimeiraInstancia();
}
public List<Parte> getPartes() {
return processo.getPartes();
}
public String getNomeAssuntoTJ() {
return processo.getAssuntoPrincipal().getDescricaoAssunto();
}
public Integer getCodigoClasseTJ() {
return processo.getDadosProcesso().getClasse().getCodigoClasse();
}
public String getNomeOrgaoJulgadorTJ() {
return processo.getDadosDistribuicao().getOrgaoJulgador().getNomeOrgaoJulgador();
}
public String getNomeRelatorTJ() {
return processo.getDadosDistribuicao().getRelator().getNomeRelator();
}
public Integer getIdAssuntoTJ() {
return processo.getAssuntoPrincipal().getCodigoAssunto();
}
public Movimentacao getUltimoMovimento() {
return null;
}
Class for Test:
#SuppressWarnings("deprecation")
#RunWith(MockitoJUnitRunner.class)
public class DistribuicaoProcessoManualServiceTest {
#Mock private ForoTJComarcaMPRepository foroMPRepository;
#Mock private AssuntoTJNaturezaMPRepository naturezaMPRepository;
#Mock private VaraTJVaraMPRepository varaMPRepository;
#Mock private ClasseTJTipoMPRepository tipoMPRepository;
#Mock private ProcessoRepository processoRepository;
#Mock private UsuarioRepository usuarioRepository;
#Mock private Usuario usuario;
#Mock private Procuradoria procuradoria;
#Mock private VaraTJVaraMP varaMP;
#Mock private ForoTJComarcaMP comarcaMP;
#Mock private AssuntoTJNaturezaMP naturezaMP;
#Mock private ClasseTJTipoMP tipoMP;
#Mock private UsuarioDaSessao usuarioDaSessao;
#InjectMocks
private DistribuicaoProcessoManualServiceImpl service = new DistribuicaoProcessoManualServiceImpl();
private Processo processo;
#Before
public void setUpMethod() {
initMocks(this);
//Others Builders for populate "processo" here;
processo = ProcessoBuilder()
.com(hoje)
.com(PARECER)
.com(FISICO)
.com(dadosProcesso)
.com(dadosOrigem)
.com(distribuicao)
.com(fila)
.cria();
ProcessoDoTJMapeamentoParaMP mapeamento = new ProcessoDoTJMapeamentoParaMP(processo);
when(usuario.getLogin()).thenReturn("luizrodriguesj");
when(usuario.getProcuradoria()).thenReturn(procuradoria);
when(usuarioDaSessao.getUsuario()).thenReturn(usuario);
when(usuarioRepository.procurarPorLogin(anyString())).thenReturn(usuario);
when(comarcaMP.getIdComarca()).thenReturn(1);
when(foroMPRepository.procurarPorIdForoEPorProcuradoria(mapeamento)).thenReturn(comarcaMP);
when(varaMP.getIdVaraMP()).thenReturn(1);
when(varaMPRepository.procurarPorIdVaraTJForoEPorProcuradoria(mapeamento)).thenReturn(varaMP);
when(naturezaMP.getIdNatureza()).thenReturn(1);
when(naturezaMPRepository.procurarPorAssuntoEPorProcuradoria(mapeamento)).thenReturn(naturezaMP);
when(tipoMP.getIdTipo()).thenReturn(1);
when(tipoMPRepository.procurarPorCodigoEPorProcuradoria(mapeamento)).thenReturn(tipoMP);
}
Method with problem:
#Test(expected = ApplicationException.class)
public void naoPermitirDistribuicaoDeProcessoSemUmMapeamentoDeVaraCorrespondente() {
service.vinculaOuPreparaParaEspecializacao(processo);
}
The interface that it was possible to mock:
public interface UsuarioRepository extends GenericDAO<Usuario, Long> {
boolean isAutenticavel(Usuario usuario);
Usuario procurarPorLogin(String login);
List<Usuario> lista(UsuarioPesquisa pesquisa, Paginacao paginacao);
}
One of Interfaces that not obey instruction mock and return null:
public interface ForoTJComarcaMPRepository extends GenericDAO<ForoTJComarcaMP, Long> {
ForoTJComarcaMP procurarPorIdForoEPorProcuradoria(ProcessoDoTJMapeamentoParaMP processoDePara);
List<ForoTJComarcaMP> lista(DominioMPPesquisa pesquisa, Paginacao paginacao);
ForoTJComarcaMP procuraPorForoTJ(ForoTJComarcaMP foroTJComarcaMP);
ForoTJComarcaMP procuraPorEntidade(ForoTJComarcaMP clone);
List<ForoTJComarcaMP> listaMapeamentoDistinctPor(Procuradoria procuradoria);
}
After others methods in this class that runs without any problem, this last one just fails because a NullPointerException (NPE) that occurs in:
VaraTJVaraMP varaMP = varaMPRepository.procurarPorIdVaraTJForoEPorProcuradoria(processoDePara);
AssuntoTJNaturezaMP naturezaMP = naturezaMPRepository.procurarPorAssuntoEPorProcuradoria(processoDePara);
ClasseTJTipoMP tipoMP = tipoMPRepository.procurarPorCodigoEPorProcuradoria(processoDePara);
ForoTJComarcaMP comarcaMP = foroMPRepository.procurarPorIdForoEPorProcuradoria(processoDePara);
....
// The references "comarcaMP", "varaMP", "naturezaMP", "tipoMP" are null here and next step throws a NPE.
processoDePara
.comComarcaMP(comarcaMP)
Other Mocked Interface declared work without problem, like this one:
Usuario usuario = usuarioRepository.procurarPorLogin(usuarioDaSessao.getUsuario().getLogin());
Where the "usuario" reference is setted as declared on setup method in Test Class.
Sorry for any mistake or lack of more informations.
Thanks and best regards!

Architecture pattern for "microservice" with hard logic (Spring boot)

i've got a microservice which implements some optimization function by calling many times another microservice (the second one calculates so called target function value and the first micriservice changes paramters of this tagrget function)
It leads to necessity of writing some logic in Rest Controller layer. To be clear some simplified code will be represented below
#RestController
public class OptimizerController {
private OptimizationService service;
private RestTemplate restTemplate;
#GetMapping("/run_opt")
public DailyOptResponse doOpt(){
Data iniData = service.prepareData(null);
Result r = restTemplate.postForObject(http://calc-service/plain_calc", iniData, Result.class);
double dt = service.assessResult(r);
while(dt > 0.1){
Data newData = service.preapreData(r);
r = restTemplate.postForObject(http://calc-service/plain_calc", newData , Result.class);
dt = service.assessResult(r);
}
return service.prepareResponce(r);
}
As i saw in examples all people are striving to keep rest controller as simple as possible and move all logic to service layer. But what if i have to call some other microservices from service layer? Should i keep logic of data formin in service layer and return it to controller layer, use RestTemplate object in service layer or something else?
Thank you for your help
It is straightforward.
The whole logic is in the service layer (including other services).
Simple example:
Controller:
#RestController
#RequestMapping("/api/users")
public class UserController {
private final UserManager userManager;
#Autowired
public UserController(UserManager userManager) {
super();
this.userManager = userManager;
}
#GetMapping()
public List<UserResource> getUsers() {
return userManager.getUsers();
}
#GetMapping("/{userId}")
public UserResource getUser(#PathVariable Integer userId) {
return userManager.getUser(userId);
}
#PutMapping
public void updateUser(#RequestBody UserResource resource) {
userManager.updateUser(resource);
}
}
Service:
#Service
public class UserManager {
private static final Logger log = LoggerFactory.getLogger(UserManager.class);
private final UserRepository userRepository;
private final UserResourceAssembler userResourceAssembler;
private final PictureManager pictureManager;
#Autowired
public UserManager(
UserRepository userRepository,
UserResourceAssembler userResourceAssembler,
PictureManager pictureManager
) {
super();
this.userRepository = userRepository;
this.userResourceAssembler = userResourceAssembler;
this.pictureManager= pictureManager;
}
public UserResource getUser(Integer userId) {
User user = userRepository.findById(userId).orElseThrow(() -> new NotFoundException("User with ID " + userId + " not found!"));
return userResourceAssembler.toResource(user);
}
public List<UserResource> getUsers() {
return userResourceAssembler.toResources(userRepository.findAll());
}
public void updateUser(UserResource resource) {
User user = userRepository.findById(resource.getId()).orElseThrow(() -> new NotFoundException("User with ID " + resource.getId() + " not found!"));
PictureResource pictureResource = pictureManager.savePicture(user);
user = userResourceAssembler.fromResource(user, resource);
user = userRepository.save(user);
log.debug("User {} updated.", user);
}
}
Service 2:
#Service
public class PictureManager {
private static final Logger log = LoggerFactory.getLogger(PictureManager.class);
private final RestTemplate restTemplate;
#Autowired
public PictureManager(RestTemplate restTemplate) {
super();
this.restTemplate = restTemplate;
}
public PictureResource savePicture(User user) {
//do some logic with user
ResponseEntity<PictureResource> response = restTemplate.exchange(
"url",
HttpMethod.POST,
requestEntity,
PictureResource.class);
return response.getBody();
}
}
Repository:
public interface UserRepository extends JpaRepository<User, Integer> {
User findByUsername(String username);
}

Spring Data concurrency

I want to get data from H2 database concurrently. I wanted to do it through parallel stream, but I am getting wrong results with parallel stream. With normal stream it returns right result (only one SELECT returns result others returns null). I am not able to find where is the problem. I look like in concurrent access to repository. Can you help?
Entity:
#Entity
public class Car {
#Id
private String carType;
public String getCarType() {
return carType;
}
public void setCarType(String carType) {
this.carType = carType;
}
}
Repository:
#org.springframework.stereotype.Repository
public interface CarsRepository extends Repository<Car, String> {
void save(Car car);
Car findOneByCarType(String carType);
}
Service:
#Service
public class CarsServiceImpl implements CarsService {
#Autowired
private CarsRepository carsRepository;
#Override
public void save(Car car) {
carsRepository.save(car);
}
#Override
public List<Car> getCars(List<String> carsTypes) {
return carsTypes.parallelStream()
.map(carType -> Optional.ofNullable(carsRepository.findOneByCarType(carType)))
.filter(Optional::isPresent)
.map(Optional::get)
.collect(Collectors.toList());
}
}
Test:
#RunWith(SpringJUnit4ClassRunner.class)
#Transactional
#SpringApplicationConfiguration(classes = Application.class)
public class CarsServiceTest {
#Autowired
private CarsService carsService;
#Test
public void getCars() {
List<String> carsTypes = Arrays.asList("audi", "ford", "opel");
carsTypes.forEach(
carType -> {
Car car = new Car();
car.setCarType(carType);
carsService.save(car);
}
);
List<Car> cars = carsService.getCars(carsTypes);
Assert.assertEquals(carsTypes.size(), cars.size());
}
}

Categories