For the request, we have two object attributes, Passenger and Payment.
If the payment is not saved successfully, we wish the passenger will not be saved as well, or this passenger will an orphan record.
Implementation
To use @Transactional, we need to annotate the main application with @EnableTransactionManagement
, and add @Transaction
with required methods.
Example code:
BookingService.java
package com.example.demo.service;
import com.example.demo.dto.BookingAcknowledgement;
import com.example.demo.dto.BookingRequest;
import com.example.demo.entity.Passenger;
import com.example.demo.entity.Payment;
import com.example.demo.repository.PassengerRepository;
import com.example.demo.repository.PaymentRepository;
import com.javatechie.tx.utils.PaymentUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.UUID;
@Service
public class BookingService {
@Autowired
private PassengerRepository passengerRepository;
@Autowired
private PaymentRepository paymentRepository;
@Transactional//(readOnly = false,isolation = Isolation.READ_COMMITTED,propagation = Propagation.REQUIRED)
public BookingAcknowledgement book(BookingRequest request) {
// save the passenger
Passenger passengerInfo = request.getPassenger();
passengerInfo = passengerRepository.save(passengerInfo);
// validate the payment
Payment paymentInfo = request.getPayment();
PaymentUtils.validateCreditLimit(paymentInfo.getAccountNo(), passengerInfo.getFare());
// save the payment
paymentInfo.setPassengerId(passengerInfo.getId());
paymentInfo.setAmount(passengerInfo.getFare());
paymentRepository.save(paymentInfo);
return new BookingAcknowledgement("SUCCESS",
passengerInfo.getFare(),
UUID.randomUUID().toString().split("-")[0],
passengerInfo);
}
}
DemoApplication.java
@SpringBootApplication
@RestController
@EnableTransactionManagement
public class DemoApplication {
@Autowired
private BookingService service;
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
@PostMapping("/book")
public BookingAcknowledgement book(@RequestBody BookingRequest request){
return service.book(request);
}
}
Passenger.java
package com.example.demo.entity;
import com.fasterxml.jackson.annotation.JsonFormat;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;
import java.util.Date;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Entity
@Table(name = "PASSENGER_INFOS")
public class Passenger {
@Id
@GeneratedValue
private Long id;
private String name;
private String email;
private String source;
private String destination;
@JsonFormat(shape = JsonFormat.Shape.STRING, pattern = "dd-MM-yyyy")
private Date travelDate;
private String pickupTime;
private String arrivalTime;
private double fare;
}
Payment.java
package com.example.demo.entity;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.hibernate.annotations.GenericGenerator;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Table;
@Data
@AllArgsConstructor
@NoArgsConstructor
@Entity
@Table(name = "PAYMENT_INFO")
public class Payment {
@Id
@GeneratedValue(generator = "uuid2")
@GenericGenerator(name = "uuid2", strategy = "org.hibernate.id.UUIDGenerator")
private String id;
private String accountNo;
private double amount;
private Long passengerId;
}
BookingRequest.java
package com.example.demo.dto;
import com.example.demo.entity.Passenger;
import com.example.demo.entity.Payment;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class BookingRequest {
private Passenger passenger;
private Payment payment;
}
BookingAcknowledgement.java
package com.example.demo.dto;
import com.example.demo.entity.Passenger;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class BookingAcknowledgement {
private String status;
private double totalFare;
// PNR is the abbreviation of Passenger Name Record and it is a digital certificate allowing passengers to do online check-in or manage their bookings in a short time.
private String pnrNo;
private Passenger passengerInfo;
}
PassengerRepository.java
package com.example.demo.repository;
import com.example.demo.entity.Passenger;
import org.springframework.data.jpa.repository.JpaRepository;
public interface PassengerRepository extends JpaRepository<Passenger,Long> {
}
PaymentRepository.java
package com.example.demo.repository;
import com.example.demo.entity.Payment;
import org.springframework.data.jpa.repository.JpaRepository;
public interface PaymentRepository extends JpaRepository<Payment,String> {
}
InsufficientAmountException.java
package com.example.demo.exception;
public class InsufficientAmountException extends RuntimeException {
public InsufficientAmountException(String msg){
super(msg);
}
}
PaymentUtils.java
package com.example.demo.utils;
import com.example.demo.exception.InsufficientAmountException;
import java.util.HashMap;
import java.util.Map;
public class PaymentUtils {
private static Map<String, Double> paymentMap = new HashMap<>();
static {
paymentMap.put("acc1", 12000.0);
paymentMap.put("acc2", 10000.0);
paymentMap.put("acc3", 5000.0);
paymentMap.put("acc4", 8000.0);
}
public static boolean validateCreditLimit(String accNo, double paidAmount) {
if (paidAmount > paymentMap.get(accNo)) {
throw new InsufficientAmountException("insufficient fund..!");
} else {
return true;
}
}
}
Finally, we have this directory structure
service
-- BookingService.java
entity
-- Passenger.java
-- Payment.java
dto
-- BookingRequest.java
-- BookingAcknowledgement.java
repository
-- PassengerRepository.java
-- PaymentRepository.java
error
-- InsufficientAmountException.java
utils
-- PaymentUtils.java
Test with Postman
// POST http://localhost:8120/book
{
"passenger":{
"id": "1",
"name": "phil",
"email": "source",
"source":"source",
"destination":"des",
"travelDate":"20-12-2021",
"pickupTime":"test",
"arrivalTime":"test",
"fare":"6000"
},
"payment":{
"id":"1",
"accountNo":"acc3"
}
}
You will have this error, because the fare exceeds the amount limit.
{
"timestamp": "2021-07-27T07:28:02.156+00:00",
"status": 500,
"error": "Internal Server Error",
"path": "/book"
}
If you have @Transactiona, the Passenger info will not be preserved. Otherwise, the Passenger info will preserved though the code throws an error.