Home > AI > Backend > SpringBoot >

@Transactional

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.

Leave a Reply