Spring batch item writer rest API - spring-boot

Is it possible to read data from DB, process it and in ItemWriter send to another system using RestAPI (REST TEMPLATE) in Spring batch project? All I can see is fetch data and write it in a csv file.

It is possible to create your own custom ItemWriter.
In your case, please add the spring-boot-starter-web dependency to either your pom.xml or build.gradle
Example:
package com.example.batch;
import lombok.extern.log4j.Log4j2;
import org.springframework.batch.item.ItemWriter;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.web.client.RestTemplate;
import java.util.List;
#Log4j2
public class RestItemWriter implements ItemWriter<String> {
#Autowired
RestTemplate restTemplate;
public RestItemWriter(RestTemplate restTemplate) {
this.restTemplate = restTemplate;
}
#Override
public void write(List<? extends String> items) throws Exception {
ResponseEntity<Users> users = restTemplate.getForEntity("https://jsonplaceholder.typicode.com/users/1", Users.class);
log.info("Status code is: " + users.getStatusCode());
}
}
package com.example.batch;
import com.fasterxml.jackson.annotation.JsonInclude;
import lombok.Getter;
import lombok.Setter;
#Getter
#Setter
#JsonInclude(JsonInclude.Include.NON_NULL)
public class Users {
public String id;
public String name;
public String username;
public String email;
public String phone;
}
More information about custom item writers here

Related

Reactive way of reading YAML with Jackson using Spring boot webflux

The yamlObjectMapper in configuration
#Bean
public ObjectMapper yamlObjectMapper() {
ObjectMapper yamlObjectMapper = new ObjectMapper(new YAMLFactory().disable(YAMLGenerator.Feature
.WRITE_DOC_START_MARKER));
yamlObjectMapper.findAndRegisterModules();
return yamlObjectMapper;
}
The Service to parse yaml file
#Service
public class CustomerService {
#Autowired
#Qualifier("yamlObjectMapper")
private ObjectMapper yamlObjectMapper;
public Customer get() {
try {
InputStream inputStream = ResourceUtils.getURL("classpath:/files/test.yaml").openStream();
return yamlObjectMapper.readValue(inputStream, Customer.class);
} catch (IOException ex) {
throw new IllegalStateException(ex);
}
}
#Data
public static class Customer {
private String name;
private String surname;
private String email;
}
}
I guess IO operations are blocking, how this can be done using reactive way?
I would rather use configuration binding since probably you need to read it once.
package com.vob.webflux.webfilter.controller;
import lombok.Getter;
import lombok.Setter;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.PropertySource;
import org.springframework.stereotype.Component;
#Component
#PropertySource(value = "classpath:config.yml", factory= YamlPropertySourceFactory.class)
#Getter
public class YamlFooProperties {
#Value("${test}")
private String test;
}
Factory
package com.vob.webflux.webfilter.controller;
import org.springframework.beans.factory.config.YamlPropertiesFactoryBean;
import org.springframework.core.env.PropertiesPropertySource;
import org.springframework.core.env.PropertySource;
import org.springframework.core.io.support.EncodedResource;
import org.springframework.core.io.support.PropertySourceFactory;
import java.io.IOException;
import java.util.Properties;
public class YamlPropertySourceFactory implements PropertySourceFactory {
#Override
public PropertySource<?> createPropertySource(String name, EncodedResource encodedResource)
throws IOException {
YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean();
factory.setResources(encodedResource.getResource());
Properties properties = factory.getObject();
return new PropertiesPropertySource(encodedResource.getResource().getFilename(), properties);
}
}
Source factory from

APPLICATION FAILED TO START error encountered on SpringBoot

I'm self-studying in Spring boot application and encountered this problem. Weird thing is that I only followed the tutorial but still encountered this
Parameter 0 of constructor in com.example.Project1.service.PersonService required a bean of type 'com.example.Project1.dao.PersonDao' that could not be found.
I have one controller, 2 DAO files, 1 Person for model and 1 for service class.
These are my codes:
PersonController:
package com.example.Project1.api;
import com.example.Project1.model.Person;
import com.example.Project1.service.PersonService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
#RequestMapping("api/v1/person")
#RestController
public class PersonController {
private final PersonService personService;
#Autowired
public PersonController(PersonService personService){
this.personService = personService;
}
#PostMapping
public void addPerson(#RequestBody Person person){
personService.addPerson(person);
}
}
FakePersonDataAccessService
package com.example.Project1.dao;
import com.example.Project1.model.Person;
import org.springframework.stereotype.Repository;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
#Repository("fakeDao")
public class FakePersonDataAccessService implements PersonDao {
private static List<Person> DB = new ArrayList<>();
#Override
public int insertPerson(UUID id, Person person){
DB.add(new Person(id, person.getName()));
return 1;
}
}
PersonDao
package com.example.Project1.dao;
import com.example.Project1.model.Person;
import java.util.UUID;
public interface PersonDao {
int insertPerson(UUID id, Person person);
default int insertPerson(Person person){
UUID id = UUID.randomUUID();
return insertPerson(id, person);
}
}
Person
package com.example.Project1.model;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.UUID;
public class Person {
private final UUID id;
private final String name;
public Person(#JsonProperty("id") UUID id, #JsonProperty("name") String name){
this.id = id;
this.name = name;
}
public UUID getId(){
return id;
}
public String getName(){
return name;
}
}
PersonService
package com.example.Project1.service;
import com.example.Project1.dao.PersonDao;
import com.example.Project1.model.Person;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;
#Service
public class PersonService {
private final PersonDao personDao;
#Autowired
public PersonService(#Qualifier("mongo") PersonDao personDao){
this.personDao = personDao;
}
public int addPerson(Person person){
return personDao.insertPerson(person);
}
}
I've tried googling the error but found no answer. Hope someone can enlighten me about the error encountered. Thank you~!
You only have "fakeDao" qualifier class that implements PersonDao but your injecting "mongo" qualifier. As another option, you can just removed the #Qualifier annotation in PersonService constructor since you only have one class that implements PersonDao

Detecting the insertion of data into the table and calling the method

I have an issue with detecting adding a new row to the table. I want to trigger a method from some service (Spring boot) when somebody executes an insert query on the database (Postgres)
Somebody told me I can use #Scheduled annotation and check if something was added using a repository. I have to make some changes instantly (by using another method). The scheduled method should run every 5 seconds to do this instantly. Of course, this is a really bad idea because it will kill the database someday and it's not efficient.
How can I do this better?
You can write concrete implementer of org.hibernate.integrator.spi.Integrator. and give it to hibernate.integrator_provider
From ServiceRegistry we can get EventListenerRegistry and then append listener of type EventType.POST_INSERT. More events here.
Main Reference Hibernate Integrator Ref
As per the query, I have also added how to call the service method from the listener class.
Here is how I have done it:
package com.example.samplejdbctemplatecall;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.extern.slf4j.Slf4j;
import org.hibernate.boot.Metadata;
import org.hibernate.engine.spi.SessionFactoryImplementor;
import org.hibernate.event.service.spi.EventListenerGroup;
import org.hibernate.event.service.spi.EventListenerRegistry;
import org.hibernate.event.spi.EventType;
import org.hibernate.event.spi.PostInsertEvent;
import org.hibernate.event.spi.PostInsertEventListener;
import org.hibernate.integrator.spi.Integrator;
import org.hibernate.jpa.boot.spi.IntegratorProvider;
import org.hibernate.persister.entity.EntityPersister;
import org.hibernate.service.spi.SessionFactoryServiceRegistry;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.autoconfigure.orm.jpa.HibernatePropertiesCustomizer;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Component;
import org.springframework.stereotype.Repository;
import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import java.util.Collections;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
#RequestMapping(path = "/entity-listener")
#RestController
public class SampleLogController {
private final SampleLogRepository sampleLogRepository;
private final SampleLogEntries sampleLogEntiries;
#Autowired
public SampleLogController(SampleLogRepository sampleLogRepository, SampleLogEntries sampleLogEntiries) {
this.sampleLogRepository = sampleLogRepository;
this.sampleLogEntiries = sampleLogEntiries;
}
// This is usually post method but for test purpose creating new log with uuid random and inserting
#GetMapping(path = "insert")
public SampleLog insertNewEntry() {
final String uuid = UUID.randomUUID().toString();
final SampleLog sampleLog = new SampleLog();
sampleLog.setMessage(uuid);
return sampleLogRepository.save(sampleLog);
}
#GetMapping(path = "list-recent-inserts")
public Map<Long, String> entries() {
return sampleLogEntiries.data();
}
}
#Slf4j
#Component
class HibernateConfig implements HibernatePropertiesCustomizer {
private final JpaEventListenerIntegrator jpaEventListenerIntegrator;
#Autowired
HibernateConfig(JpaEventListenerIntegrator jpaEventListenerIntegrator) {
this.jpaEventListenerIntegrator = jpaEventListenerIntegrator;
}
#Override
public void customize(Map<String, Object> hibernateProperties) {
log.warn("Called hibernate configuration");
hibernateProperties.put("hibernate.integrator_provider",
(IntegratorProvider) () -> Collections.singletonList(jpaEventListenerIntegrator));
}
}
#Configuration
class SampleConfiguration {
#Bean
SampleLogEntries sampleEntries() {
return new SampleLogEntries();
}
}
class SampleLogEntries {
private final ConcurrentMap<Long, String> map = new ConcurrentHashMap<>();
public void add(SampleLog sampleLog) {
this.map.put(sampleLog.getId(), sampleLog.getMessage());
}
public Map<Long, String> data() {
return Collections.unmodifiableMap(this.map);
}
}
#Repository
interface SampleLogRepository extends CrudRepository<SampleLog, Long> {
}
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
#Entity
class SampleLog {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String message;
}
#Service
#Slf4j
class JpaEventListenerIntegrator implements Integrator, PostInsertEventListener {
private final SampleLogEntries sampleLogEntiries;
#Autowired
JpaEventListenerIntegrator(SampleLogEntries sampleLogEntiries) {
this.sampleLogEntiries = sampleLogEntiries;
}
#Override
public void integrate(Metadata metadata, SessionFactoryImplementor sessionFactory, SessionFactoryServiceRegistry serviceRegistry) {
final EventListenerRegistry eventListenerRegistry = serviceRegistry.getService(EventListenerRegistry.class);
eventListenerRegistry
.appendListeners(EventType.POST_INSERT, this);
}
#Override
public void disintegrate(SessionFactoryImplementor sessionFactory, SessionFactoryServiceRegistry serviceRegistry) {
final EventListenerRegistry eventListenerRegistry = serviceRegistry.getService(EventListenerRegistry.class);
EventListenerGroup<PostInsertEventListener> eventListenerGroup = eventListenerRegistry
.getEventListenerGroup(EventType.POST_INSERT);
log.info("listener attached were: " + eventListenerGroup.getClass().getSimpleName());
log.error("disintegrate : " + getClass().getCanonicalName());
eventListenerGroup.clearListeners();
}
#Override
public void onPostInsert(PostInsertEvent event) {
log.info("Inserted : " + event.getEntity());
final Object entity = event.getEntity();
if (entity instanceof SampleLog) {
sampleLogEntiries.add((SampleLog) entity);
}
}
#Override
public boolean requiresPostCommitHanding(EntityPersister persister) {
return false;
}
}
The answer from #silentsudo is the best one if you only ever use JPA to update the table in question, and if you only have one process updating it.
The issue is that since you are being notified via the JPA interceptor, you won't be notified of any updates that happen outside of your JAP repository.
If you need these other notifications, then you can use Postgres' LISTEN/NOTIFY without polling by using an alternate postgresql JDBC driver, pgjdbc-ng, which implements async notifications.
With this method, you create a trigger in the database to send the notification, so you will be notified of other's updates as well. See https://www.openmakesoftware.com/postgresql-listen-notify-events-example

How to us a constructor with parameters in a method used by Spring Boot's #RestController annotation to create a request handler

I bought this new book to try to learn Spring Boot quickly. It started out well, and I easily created a REST API. But then we added CrudRepository, and I'm seeing issues with the code as described in the book. Also, there is no code available to download because the author took it down from Oreily's git repo in order to fix some things...
The issue is that if I try to build the code as the book describes (without a default constructor) I get a Java error complaining that there is no default constructor. If I add a default constructor, it builds, but Spring uses it instead of the new constructor, that requires a parameter to be passed. So when I actually call the API, like if I call the /coffees endpoint, I get a java.lang.NullPointerException: null
So how is Spring supposed to know which constructor to use, and how could it pass in values for this parameter?
Here is the controller:
package com.bw.restdemo;
import java.util.ArrayList;
import java.util.List;
import java.util.Optional;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.DeleteMapping;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.PutMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
#RestController
#RequestMapping("/coffees")
class RestAPIDemoController {
private final CoffeeRepository coffeeRepository;
public RestAPIDemoController(CoffeeRepository coffeeRepository) {
this.coffeeRepository = coffeeRepository;
this.coffeeRepository.saveAll(List.of(
new Coffee("Cafe Cereza"),
new Coffee("Freedom Fuel"),
new Coffee("Cold Brew"),
new Coffee("Sumatra")
));
}
public RestAPIDemoController() {
this.coffeeRepository = null;
};
//#RequestMapping(value = "/coffees", method = RequestMethod.GET)
#GetMapping
Iterable<Coffee> getCoffees() {
return coffeeRepository.findAll();
}
#GetMapping("/{id}")
Optional<Coffee> getCoffeeById(#PathVariable String id) {
return coffeeRepository.findById(id);
}
#PostMapping
Coffee postCoffee(#RequestBody Coffee coffee) {
return coffeeRepository.save(coffee);
}
#PutMapping("/{id}")
ResponseEntity<Coffee> putCoffee(#PathVariable String id, #RequestBody Coffee coffee) {
return (!coffeeRepository.existsById(id))
? new ResponseEntity<>(coffeeRepository.save(coffee), HttpStatus.CREATED)
: new ResponseEntity<>(coffeeRepository.save(coffee), HttpStatus.OK);
}
#DeleteMapping("/{id}")
void deleteCoffee(#PathVariable String id) {
coffeeRepository.deleteById(id);
}
}
Here is where I'm defining the interface:
package com.bw.restdemo;
import org.springframework.data.repository.CrudRepository;
interface CoffeeRepository extends CrudRepository<Coffee, String> {
}
And here's the main class -- apologies for the class stuffed at the bottom.
package com.bw.restdemo;
import java.util.UUID;
import javax.persistence.Entity;
import javax.persistence.Id;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
#SpringBootApplication
public class RestDemoApplication {
public static void main(String[] args) {
SpringApplication.run(RestDemoApplication.class, args);
}
}
#Entity
class Coffee {
#Id
private String id;
private String name;
public Coffee(String id, String name) {
this.id = id;
this.name = name;
}
public void setId(String id) {
this.id = id;
}
public Coffee(String name) {
this(UUID.randomUUID().toString(), name);
}
public String getId() {
return id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
CoffeeRepository interface is missing #Repository Annotation.
Update:
Add #Repository Annotation at CoffeeRepository
Remove the default constructor from RestAPIDemoController.
package com.bw.restdemo;
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
#Repository
interface CoffeeRepository extends CrudRepository<Coffee, String> {
}
Explanation
In spring framework, #Component annotation marks a java class as a bean so the component-scanning mechanism can pick it up and pull it into the application context. As #Repository serves as a specialization of #Component , it also enable annotated classes to be discovered and registered with application context.
More at HowToDoInJava - #Repository annotation in Spring Boot

Is it a good idea to declare an injector and pass it as a state parameter when integrating typed Akka Actor with Spring

I could not find a neat way to integrate Spring with typed Actors.
Instead of using extensions I had declared an injector service
import akka.actor.typed.ActorSystem;
import com.akka.demo.a010.ASimpleSpringService;
import com.akka.demo.a010.AkkaSimpleBehaviour;
import lombok.Data;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
#Service
#Data
public class ActorInjector {
#Autowired
private ASimpleSpringService aSimpleSpringService;
#Autowired
private ActorInjector _self;
public ActorSystem<AkkaSimpleBehaviour.Command> createAkkaSimpleBehaviour(String actorName) {
return ActorSystem.create(AkkaSimpleBehaviour.create(_self), actorName);
}
}
This service autowires itself and pass that reference to a simple actor.
My Actor definition is as follows.
import akka.actor.typed.ActorRef;
import akka.actor.typed.Behavior;
import akka.actor.typed.javadsl.AbstractBehavior;
import akka.actor.typed.javadsl.ActorContext;
import akka.actor.typed.javadsl.Behaviors;
import akka.actor.typed.javadsl.Receive;
import com.akka.demo.a010.config.ActorInjector;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
#Slf4j
public class AkkaSimpleBehaviour extends AbstractBehavior<AkkaSimpleBehaviour.Command> {
private final List<String> messages = new ArrayList<>();
private ActorRef<List<String>> sender;
private ActorInjector actorInjector;
public interface Command extends Serializable {
}
#AllArgsConstructor
public static class TellMeSomething implements Command {
private static final long serialVersionUID = -7796709831949054890L;
#Getter
private final String message;
}
#AllArgsConstructor
public static class CollectTheResults implements Command {
private static final long serialVersionUID = 1643210899551075153L;
#Getter
private final ActorRef<List<String>> sender;
}
private AkkaSimpleBehaviour(ActorContext<Command> context, ActorInjector actorInjector) {
super(context);
this.actorInjector = actorInjector;
}
public static Behavior<Command> create(ActorInjector actorInjector) {
return Behaviors.setup(ctx -> new AkkaSimpleBehaviour(ctx,actorInjector));
}
#Override
public Receive<Command> createReceive() {
return newReceiveBuilder().onMessage(TellMeSomething.class, message -> {
messages.add(message.getMessage());
actorInjector.getASimpleSpringService().logSomething("*-*-*-*-*-*-*-*-*-*");
return Behaviors.same();
}).onMessage(CollectTheResults.class, message -> {
this.sender = message.getSender();
if (messages.size() == 4) {
this.sender.tell(messages);
}
return Behaviors.same();
}).build();
}
}
After passing the injector service I can get my autowired dependencies from that service like :
actorInjector.getASimpleSpringService().logSomething("*-*-*-*-*-*-*-*-*-*");
My ASimpleService is just a dummy service which logs an output.
import org.springframework.stereotype.Service;
import lombok.extern.slf4j.Slf4j;
#Service
#Slf4j
public class ASimpleSpringService {
public void logSomething(String message){
log.info(message);
}
}
Then in a simple RestController I am using the system as follows:
import akka.actor.typed.ActorSystem;
import akka.actor.typed.javadsl.AskPattern;
import com.akka.demo.a010.config.ActorInjector;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
import java.time.Duration;
import java.util.List;
import java.util.concurrent.CompletionStage;
import java.util.concurrent.ExecutionException;
#RestController
#Slf4j
public class MyRestController {
#Autowired
private ActorInjector actorInjector;
#GetMapping("/hello-akka")
public void greetings() {
ActorSystem<AkkaSimpleBehaviour.Command> exampleActor = actorInjector.createAkkaSimpleBehaviour("anActor");
exampleActor.tell(new AkkaSimpleBehaviour.TellMeSomething("hello"));
exampleActor.tell(new AkkaSimpleBehaviour.TellMeSomething("Who are you"));
exampleActor.tell(new AkkaSimpleBehaviour.TellMeSomething("create a child"));
exampleActor.tell(new AkkaSimpleBehaviour.TellMeSomething("Here is some message"));
CompletionStage<List<String>> result = AskPattern.ask(exampleActor, AkkaSimpleBehaviour.CollectTheResults::new, Duration.ofSeconds(10), exampleActor.scheduler());
result.whenComplete((reply, failure) -> {
if(reply != null){
log.info("The system responds in time");
} else {
log.error("The system does not respond in time");
exampleActor.terminate();
throw new RuntimeException("The system does not respond in time");
}
exampleActor.terminate();
});
try{
List<String> messages = result.toCompletableFuture().get();
messages.forEach(log::info);
} catch (InterruptedException | ExecutionException e){
e.printStackTrace();
}
}
}
My question is : I am planning to put all of my services into my ActorInjector service and these services will be injected into my actors. I am not familiar with Akka states and its side-effects however I know that it may be a bad idea to store all of these singleton services as actor states. Is it a good idea to store these services as an actor parameter ? What kind of side effects can I experience by doing this way? Can you point me the way?

Resources