Not persisting entity with thymeleaf, spting data and r2dbc - spring

Im trying to create Skill entity through Thymeleaf form and post it:
controller's endpoint
#PostMapping("/add")
public String addSkill(#ModelAttribute Skill skill){
log.info( " orly? "+ (skill== null));
log.info(skill.getName() + " name was set");
log.info(skill.getId() + " i<--d");
log.info(skill.getLevel() + " level was set");
log.info(skill.getPriority() + " prior was set");
log.info(skill.getSkillGroupName() + " group id was set");
service.add(skill);
return TEMPLATE;
}
service's method
#Override
public Mono<Skill> add(Skill skill) {
log.debug("SKILL IS ______ "+ skill.getName() + " ____WAS SAVED");
return repository.save(skill);
}
repo
#Repository
public interface SkillRepository extends ReactiveCrudRepository<Skill, UUID> {
Mono<UUID> removeById(UUID id);
}
entity implements Persistable
#Data
#Table
#NoArgsConstructor
#AllArgsConstructor
public class Skill implements Persistable<UUID> {
#Id
private UUID id;
#Column("skill_name")
private String name;
private Level level;
private Priority priority;
#Column("skill_group_name")
private String skillGroupName;
#Override
public boolean isNew() {
boolean result = Objects.isNull(id);
this.id = result ? UUID.randomUUID() : this.id;
return result;
}
}
Main class is annotated with #EnableR2dbcRepositories.
When I submit form, I get the log, confirming that entity is not null, all fields but id aren't nulls. And that's all, service's method add(Skill skill) never produce logs, neither postgres shows tuple. Any ideas?

Related

How to create Unit Test for my Service Layer?

I would like to understand and see the easiest way to create a Unit Test for my Service Layer. I've been researching for days on the easiest way to do this, but unfortunately I still haven't found a solution for my specific code. I tried to write the test many times myself, but people told me that the code could be done much better. Thank you in advance.
My Entity:
#Entity
#Getter
#Setter
#AllArgsConstructor
#NoArgsConstructor
#ToString
#Builder
#Table
public class Appointment {
#Id
#SequenceGenerator(
name = "appointment_sequence",
sequenceName = "appointment_sequence",
allocationSize = 1
)
#GeneratedValue(
strategy = GenerationType.SEQUENCE,
generator = "appointment_sequence"
)
private Long appointmentId;
#Column(
name = "date_of_appointment",
nullable = false
)
private LocalDate dateOfAppointment;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(
name = "patient_id",
referencedColumnName = "patientId"
)
private Patient patient;
public void connectWithPatient(Patient patient) {
this.patient = patient;
}
}
My Repository:
public interface AppointmentRepository extends JpaRepository<Appointment, Long> {
}
And my Service:
#Service
public class AppointmentService implements AppointmentServiceInterface {
private final AppointmentRepository appointmentRepository;
private final PatientRepository patientRepository;
#Autowired
public AppointmentService(AppointmentRepository appointmentRepository, PatientRepository patientRepository) {
this.appointmentRepository = appointmentRepository;
this.patientRepository = patientRepository;
}
#Override
public List<Appointment> getAllAppointments() {
return appointmentRepository.findAll();
}
#Override
public ResponseEntity<Appointment> getAppointmentById(Long appointmentId) {
Appointment appointment = appointmentRepository.findById(appointmentId)
.orElseThrow(()-> new ResourceNotFoundException("Appointment with id " + appointmentId + " doesn't exist."));
return ResponseEntity.ok(appointment);
}
#Override
public Appointment createNewAppointment(Appointment appointment) {
return appointmentRepository.save(appointment);
}
#Override
public ResponseEntity<Appointment> updateAppointment(Long appointmentId, Appointment appointmentUpdatedDetails) {
Appointment updatedAppointment = appointmentRepository.findById(appointmentId)
.orElseThrow(()-> new ResourceNotFoundException("Appointment with id " + appointmentId + " doesn't exist."));
updatedAppointment.setDateOfAppointment(appointmentUpdatedDetails.getDateOfAppointment());
appointmentRepository.save(updatedAppointment);
return ResponseEntity.ok(updatedAppointment);
}
#Override
public ResponseEntity<Appointment> deleteAppointment(Long appointmentId) {
Appointment appointment = appointmentRepository.findById(appointmentId)
.orElseThrow(()-> new ResourceNotFoundException("Appointment with id " + appointmentId + " doesn't exist."));
appointmentRepository.delete(appointment);
return new ResponseEntity<>(HttpStatus.NO_CONTENT);
}
#Override
public Appointment makeAppointmentWithPatient(Long appointmentId, Long patientId) {
Appointment appointment = appointmentRepository.findById(appointmentId)
.orElseThrow(()-> new ResourceNotFoundException("Appointment with id " + appointmentId + " doesn't exist."));
Patient patient = patientRepository.findById(patientId)
.orElseThrow(()-> new ResourceNotFoundException("Patient with id " + patientId + " doesn't exist."));
appointment.connectWithPatient(patient);
return appointmentRepository.save(appointment);
}
}
Returning ResponseEntity is not something you would really like to do from the service layer - it's a place for the business logic and not for Web responses. So currently your service is bound to spring MVC.
On the positive side, it doesn't call the Database directly and instead has DAOs injected as dependencies.
So the real question is what would you like to test here? If you want to test the service itself, it can be unit-tested with a simple unit test, you don't need spring here:
Create mocks for the Repositories and inject them into the instance of the service which will be a "subject" of your test as if they're real DAOs.
Then specify expectations like "if someone calls findAll then return a list of entity objects. Again, there is no need to use any kind of database here
Verify the expected return result from the method of the service that you test with some assertion library
That's it.
Of course, if you'll return real objects rather than ResponseEntity from some methods your code will be way cleaner and hence the testing will be easier because you'll verify the real domain objects rather than web-mvc abstractions.

Is Spring #Component annotation used correctly?

The purpose of this question is to find out if the codes are written with the right approach. Let's do CRUD operations on categories and posts in the blog website project. To keep the question short, I shared just create and update side.
(Technologies used in the project: spring-boot, mongodb)
Let's start to model Category:
#Document("category")
public class Category{
#Id
private String id;
#Indexed(unique = true, background = true)
private String name;
#Indexed(unique = true, background = true)
private String slug;
// getter and setter
Abstract BaseController class and IController Interface is created for fundamental level save, delete and update operations. I shared below controller side:
public interface IController<T>{
#PostMapping("/save")
ResponseEntity<BlogResponse> save(T object);
#GetMapping(value = "/find-all")
ResponseEntity<BlogResponse> findAll();
#GetMapping(value = "/delete-all")
ResponseEntity<BlogResponse> deleteAll();
}
public abstract class BaseController<T extends MongoRepository<S,String>, S> implements IController<S> {
#Autowired
private T repository;
#Autowired
private BlogResponse blogResponse;
#PostMapping(value = "/save", consumes = MediaType.APPLICATION_FORM_URLENCODED_VALUE)
public #ResponseBody ResponseEntity<BlogResponse> save(S object) {
try {
S model = (S) repository.save(object);
String modelName = object.getClass().getSimpleName().toLowerCase();
blogResponse.setMessage(modelName + " is saved successfully").putData(modelName, object);
} catch (DuplicateKeyException dke) {
return new ResponseEntity<BlogResponse>(blogResponse.setMessage("This data is already existing!!!"), HttpStatus.BAD_REQUEST);
} catch (Exception e) {
return new ResponseEntity<BlogResponse>(blogResponse.setMessage(e.getMessage()), HttpStatus.INTERNAL_SERVER_ERROR);
}
return new ResponseEntity<BlogResponse>(blogResponse, HttpStatus.OK);
}
// delete, findAll and other controllers
#RestController
#RequestMapping(value = "category")
#RequestScope
public class CategoryController extends BaseController<ICategoryRepository, Category>{
// More specific opretions like findSlug() can be write here.
}
And finally BlogResponce component is shared below;
#Component
#Scope("prototype")
public class BlogResponse{
private String message;
private Map<String, Object> data;
public String getMessage() {
return message;
}
public BlogResponse setMessage(String message) {
this.message = message;
return this;
}
public BlogResponse putData(String key, Object object){
if(data == null)
data = new HashMap<String,Object>();
data.put(key,object);
return this;
}
public Map<String,Object> getData(){
return data;
}
#Override
public String toString() {
return "BlogResponse{" +
"message='" + message + '\'' +
", data=" + data +
'}';
}
}
Question: I am new spring boot and I want to move forward by doing it right. BlogResponse is set bean by using #Component annotation. This doc said that other annotations like #Controller, #Service are specializations of #Component for more specific use cases. So I think, I cant use them. BlogResponse is set prototype scope for create new object at each injection. Also it's life end after response because of #RequestScope. Are this annotations using correcty? Maybe there is more effective way or approach. You can remark about other roughness if it existing.

Able to persist data using entitymanager.persist() but unable to get using entitymanager.find() in spring hibernate application

This is my customer entity
#Entity
public class Customer
{
#Id
private String account_no;
private String customer_name,father_name,gender, phone, email, aadhaar; // phone no is varchar because I'll do validation in angular.
private double salary;
// one customer can have one loan. Relation is unidirectional
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name="account_no")
private Loan loan;
public Customer() {}
#Override
public String toString() {
return "CustomerInfo [account_no=" + account_no + ", customer_name=" + customer_name + ", phone=" + phone
+ ", email=" + email + ", aadhaar=" + aadhaar + ", salary="
+ salary + ", loan=" + loan + "]";
}
}
Now this is my Loan entity
#Entity
public class Loan
{
#Id
#Column(name="account_no")
private String account_no; // making this as foreign key will not allow any holder to take loan again.
private String type;
private String issue_date;
private String last_payment;
private double loan_amount;
private double emi_amount;
private int emi_balance;
public Loan() {
}
#Override
public String toString() {
return "LoanInfo [account_no=" + account_no + ", type=" + type + ", loan_amount=" + loan_amount
+ ", emi_amount=" + emi_amount + ", emi_balance=" + emi_balance + ", issue_date=" + issue_date + "]";
}
}
Now the code of Dao layer
The findCustomer() function is returning null.
In dao findLoan() and addCustomer() is working fine.Even I've checked database , it is persisting data to database.
Database which I'm using is mysql and hibernate is used to implement the application. Spring mvc is used .
#Repository("bankDao")
#Transactional
public class BankDao {
#PersistenceContext
EntityManager em;
// It add customer details and loan details like emi while sanctioning any loan.
public void addCustomer(Customer customer) throws RevokeLoanException
{
try {
em.persist(customer);
}
catch(Exception e) {
throw new RevokeLoanException();
}
}
// for customer details
public Customer findCustomer(String accNo) throws LoanNotFoundException{
System.out.println(em.find(Customer.class, accNo));
Customer customer=em.find(Customer.class, accNo);
if(customer==null)
throw new LoanNotFoundException();
return customer;
}
// useful while paying emi
public Loan findLoan(String acc_no) throws LoanNotFoundException{
// TODO Auto-generated method stub
Loan loan=em.find(Loan.class, acc_no);
if(loan==null)
throw new LoanNotFoundException();
return loan;
}
Problem arising in controller . Controller is not working in the specific way for id=2020-04-30T23:22:00.210
Api call = http://localhost:8082/LoanBackEnd/loan/loanStatus/2020-04-30T23:22:00.210
#GetMapping("/loanStatus/{id}")
public ResponseEntity<Map<String, String>> loanStatus(#PathVariable String id) throws LoanNotFoundException{
System.out.println(id+" which got");
Map<String,String> response=bankService.loanStatus(id);
return new ResponseEntity<Map<String,String>>(response,HttpStatus.OK);
}
That print line is returning me 2020-04-30T23:22:00 in place of 2020-04-30T23:22:00.210

JHipster - Insert in the database with the GET method

I have to create an application with Jhipster but i never use it before.
When a user send a GET request to the address http://localhost:8080/api/newmesure/{mac-address}/{value}
I want to insert a new mesure in my database.
First i created 3 entity "Plantes", "Capteurs" and "Mesures" with this format :
Image here : https://i.stack.imgur.com/zJqia.png (I'm not allowed to post)
I activated the JPA Filtering to create a #Query to insert data in my database but i read that was not possible.
In /src/main/java/com/mycompany/myapp/web/rest/MesuresRessources.java :
/**
* REST controller for managing {#link com.mycompany.myapp.domain.Mesures}.
*/
#RestController
#RequestMapping("/api")
public class MesuresResource {
private final Logger log = LoggerFactory.getLogger(MesuresResource.class);
private static final String ENTITY_NAME = "mesures";
#Value("${jhipster.clientApp.name}")
private String applicationName;
private final MesuresService mesuresService;
private final MesuresQueryService mesuresQueryService;
public MesuresResource(MesuresService mesuresService, MesuresQueryService mesuresQueryService) {
this.mesuresService = mesuresService;
this.mesuresQueryService = mesuresQueryService;
}
#GetMapping("/newMesure/{mac}/{value}")
public String newMesure(#PathVariable String mac,#PathVariable int value) {
log.debug("Adresse MAC : "+mac);
log.debug("Valeur : "+value);
#Query("SELECT valeur FROM Mesures WHERE id = 1") //not working
Mesures getValeur(); //not working
return "Mesure ajoutée";
}
}
In /src/main/java/com/mycompany/myapp/domain/Mesures.java :
/**
* A Mesures.
*/
#Entity
#Table(name = "mesures")
public class Mesures implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "valeur")
private Integer valeur;
#ManyToOne(optional = false)
#NotNull
#JsonIgnoreProperties("macs")
private Capteurs mac;
// jhipster-needle-entity-add-field - JHipster will add fields here, do not remove
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public Integer getValeur() {
return valeur;
}
public Mesures valeur(Integer valeur) {
this.valeur = valeur;
return this;
}
public void setValeur(Integer valeur) {
this.valeur = valeur;
}
public Capteurs getMac() {
return mac;
}
public Mesures mac(Capteurs capteurs) {
this.mac = capteurs;
return this;
}
public void setMac(Capteurs capteurs) {
this.mac = capteurs;
}
// jhipster-needle-entity-add-getters-setters - JHipster will add getters and setters here, do not remove
#Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (!(o instanceof Mesures)) {
return false;
}
return id != null && id.equals(((Mesures) o).id);
}
#Override
public int hashCode() {
return 31;
}
#Override
public String toString() {
return "Mesures{" +
"id=" + getId() +
", valeur=" + getValeur() +
"}";
}
}
Louan
Learning java with JHipster is probably not a wise idea, it uses a very rich technology stack which might lose you unless you invest enough time to learn the basics.
There are many things wrong in your code and approach:
You can't use #Query annotation inside the body of method a of your REST controller, it must be used in your #Repository interface, this code can't compile. See https://www.baeldung.com/spring-data-jpa-query for a quick introduction
JPA filtering is not related to inserting into database
In HTTP/REST, GET method is supposed to be idempotent. For making changes in your database you should use POST or PUT methods. See What is idempotency in HTTP methods?
Your entity naming convention is not consistent: use singular for entity classes because each entity object represents one single instance of Mesure. Here you have Plantes (plural), Capteur (singular) and Mesures (plural). For table names, JHipster uses singular but plural is quite common too because a table holds many rows. Of course, this is just a convention and you or your team may decide to apply another (like a prefix for table names) but the key point is to be consistent.

How does Spring's JPARepository and #Transactional behave together?

I have two methods (in a Spring boot application) that handle an entity. The entity has two fields, both boolean isDefault and isPdfGenerated. The first method (which is called from a controller) changes the isDefault flag when a new entity is created while the second one (called from a #Scheduled annotated method) changes the isPdfGenrated after it generates a pdf file for that entity.
My problem is that sometimes the second method finds entities with the isPdfGenerated flag set to false even though the file has been generated and saved in the database.
Both the methods have the #Transactional annotation and the repository interface for the entity extends JpARepository.
My guess is that the first method loads the entity from the database before the second method does but saves the entity after the second method does its job, thus overriding the isPdfGenerated flag.
Is this possible ? If the answer is yes, how should one handle such cases ? Shouldn't JPARepository handle the case when an entity gets updated from an external source ?
Bellow is some code to better illustrate the situation.
MyController:
#Controller
#RequestMapping("/customers")
public class MyController {
#Autowired
private EntityService entityService;
#RequestMapping(value = "/{id}/changeDefault", method = RequestMethod.POST)
public String changeDefault(#PathVariable("id") Long customerId, #ModelAttribute EntityForm entityForm, Model model) {
Entity newDefaultEntity = entityService.updateDefaultEntity(customerId, entityForm);
if (newDefaultEntity == null)
return "redirect:/customers/" + customerId;
return "redirect:/customers/" + customerId + "/entity/default;
}
}
EntityService:
import org.springframework.transaction.annotation.Transactional;
#Service
public class EntityService {
#Autowired
private EntityRepository entityRepository;
#Autowired
private CustomerRepository customerRepository;
#Transactional
public Entity updateDefaultEntity(Long customerId, submittedData) {
Customer customer = customerRepository.findById(customerId);
if(customer == null)
return customer; // I know there are better ways to do this
Entity currentDefaultEntity = entityRepository.findUniqueByCustomerAndDefaultFlag(customer, true);
if(currentDefaultEntity == null)
return null; // I know there are better ways to do this also
Entity newDefaultEntity = new Entity();
newDefaultEntity.setField1(submittedData.getField1());
newDefaultEntity.setField2(submittedData.getField2());
newDefaultEntity.setCustomer(customer);
oldDefaultEntity.setDefaultFlag(false);
newDefaultEntity.setDefaultFlag(true);
entityRepository.save(newDefaultEntity);
}
#Transactional
public void generatePdfDocument(Entity entity) {
Document pdfDocument = generateDocument(entity);
if(pdfDocument == null)
return;
documentRepository.save(pdfDocument);
entity.setPdfGeneratedFlag(true);
entityRepository.save(entity);
}
}
ScheduledTasks:
#Component
public class ScheduledTasks {
private static final int SECOND_IN_MILLISECONDS = 1000;
private static final int MINUTE_IN_SECONDS = 60;
#Autowired
private EntityRepository entityRepository;
#Autowired
private DocumentService documentService;
#Scheduled(fixedDelay = 20 * SECOND_IN_MILLISECONDS)
#Transactional
public void generateDocuments() {
List<Quotation> quotationList = entityRepository.findByPdfGeneratedFlag(false);
for(Entity entity : entitiesList) {
documentService.generatePdfDocument(entity);
}
}
}
DocumentService:
#Service
public class DocumentService {
#Autowired
private EntityRepository entityRepository;
#Autowired
private DocumentRepository documentRepository;
#Transactional
public void generatePdfDocument(Entity entity) {
Document pdfDocument = generateDocument(entity);
if(pdfDocument == null)
return;
documentRepository.save(pdfDocument);
entity.setPdfGeneratedFlag(true);
entityRepository.save(entity);
}
}
EntityRepository:
#Repository
public interface EntityRepository extends JpaRepository<Entity, Long> {
Entity findById(#Param("id") Long id);
List<Entity> findByPdfGeneratedFlag(#Param("is_pdf_generated") Boolean pdfGeneratedFlag);
Entity findUniqueByCustomerAndDefaultFlag(
#Param("customer") Customer customer,
#Param("defaultFlag") Boolean defaultFlag
);
}
DocumentRepository:
#Repository
public interface DocumentRepository extends JpaRepository<Document, Long> {
Document findById(#Param("id") Long id);
}
Entity:
#Entity
#Table(name = "entities")
#JsonIdentityInfo(generator = ObjectIdGenerators.IntSequenceGenerator.class, property = "id")
public class Entity {
private Long id;
private boolean defaultFlag;
private boolean pdfGeneratedFlag;
private String field1;
private String field2;
private Customer customer;
public Entity() { }
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
#Column(name = "is_default")
public boolean isDefaultFlag() {
return defaultFlag;
}
public void setDefaultFlag(boolean defaultFlag) {
this.defaultFlag = defaultFlag;
}
#Column(name = "is_pdf_generated")
public boolean isPdfGeneratedFlag() {
return pdfGeneratedFlag;
}
public void setPdfGeneratedFlag(boolean pdfGeneratedFlag) {
this.pdfGeneratedFlag = pdfGeneratedFlag;
}
#Column(name = "field_1")
public String getField1() {
return field1;
}
public void setField1(String field1) {
this.field1 = field1;
}
#Column(name = "field_2")
public String getField2() {
return field2;
}
public void setField2(String field2) {
this.field2 = field2;
}
#ManyToOne
#JoinColumn(name = "customer_id", referencedColumnName = "id", nullable = false)
public Customer getCustomer() {
return customer;
}
public void setCustomer(Customer customer) {
this.customer = customer;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Entity quotation = (Entity) o;
return id != null ? id.equals(entity.id) : entity.id == null;
}
#Override
public int hashCode() {
return id != null ? id.hashCode() : 0;
}
#Override
public String toString() {
return "Entity{" +
"id=" + id +
", pdfGeneratedFlag=" + pdfGeneratedFlag +
", defaultFlag=" + defaultFlag +
", field1=" + field1 +
", field2=" + field2 +
", customer=" + (customer == null ? null : customer.getId()) +
"}";
}
}
I have omitted the other classes because they are either POJOs ( EntityForm ) or the same as other domain model classes ( Document ).
If you're talking about a row on the database that is getting updated by another process after the first process has read it but before it has been updated, then you need to put in some sort of optimistic locking strategy.
This will be handled by the underlying ORM api (e.g. Hibernate or Eclipselink) rather than Spring Data (which will just handle an optimistic locking errors thrown by the ORM).
Have a look at this article. Bear in mind that if you want optimistic locking you need some way of determining a row's version. In JPA this is normally done using a column annotated with the #Version tag.
https://vladmihalcea.com/hibernate-locking-patterns-how-does-optimistic-lock-mode-work/

Resources