The ACID properties (Atomicity, Consistency, Isolation, Durability) are fundamental principles in database management systems (DBMS) to ensure that transactions are processed reliably. Here's a real-time example for each property, along with a use case:
Use Case: Banking Transfer
In summary:
These properties are crucial for ensuring reliable and correct database transactions in systems like banking, e-commerce, reservation platforms, and more.
Here’s how you can achieve each of the ACID properties in Java with examples for each:
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class BankService {
private final AccountRepository accountRepository;
public BankService(AccountRepository accountRepository) {
this.accountRepository = accountRepository;
}
@Transactional
public void transferFunds(Long senderId, Long receiverId, double amount) {
// Deduct from sender's account
Account sender = accountRepository.findById(senderId).orElseThrow(() -> new RuntimeException("Sender not found"));
sender.setBalance(sender.getBalance() - amount);
accountRepository.save(sender);
// Simulate an error: Uncomment the line below to trigger rollback
if (amount > 1000) throw new RuntimeException("Simulated error");
// Add to receiver's account
Account receiver = accountRepository.findById(receiverId).orElseThrow(() -> new RuntimeException("Receiver not found"));
receiver.setBalance(receiver.getBalance() + amount);
accountRepository.save(receiver);
}
}
@Transactional ensures that if an exception occurs, the whole transaction will be rolled back, ensuring atomicity.
Consistency ensures that the database constraints and business logic are always maintained.
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class InventoryService {
private final ProductRepository productRepository;
public InventoryService(ProductRepository productRepository) {
this.productRepository = productRepository;
}
@Transactional
public void purchaseProduct(Long productId, int quantity) {
Product product = productRepository.findById(productId)
.orElseThrow(() -> new RuntimeException("Product not found"));
if (product.getStock() < quantity) {
throw new RuntimeException("Insufficient stock available");
}
product.setStock(product.getStock() - quantity);
productRepository.save(product);
}
}
The purchaseProduct
method ensures that if there is insufficient stock, a runtime exception is thrown, maintaining consistency. The @Transactional
annotation will ensure that any operation is atomic and consistent.
Isolation ensures that one transaction is not affected by another transaction, which is critical in multi-user environments.
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class ReservationService {
private final ReservationRepository reservationRepository;
public ReservationService(ReservationRepository reservationRepository) {
this.reservationRepository = reservationRepository;
}
@Transactional
public void bookSeat(Long seatId, Long customerId) {
// Fetch seat and customer details
Seat seat = reservationRepository.findSeatById(seatId);
if (seat.isBooked()) {
throw new RuntimeException("Seat is already booked");
}
seat.setBooked(true);
reservationRepository.save(seat);
// Log customer booking
reservationRepository.saveBookingLog(customerId, seatId);
}
}@Transactional
ensures that while the seat is being booked, the operation is isolated from other transactions that might also be booking seats simultaneously.
Once a transaction is committed, it should be saved permanently in the database even if the system crashes afterward.
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
@Service
public class OrderService {
private final OrderRepository orderRepository;
public OrderService(OrderRepository orderRepository) {
this.orderRepository = orderRepository;
}
@Transactional
public void placeOrder(Long customerId, List<Long> productIds) {
// Place the order and save to database
Order order = new Order();
order.setCustomerId(customerId);
order.setProductIds(productIds);
order.setStatus("PLACED");
orderRepository.save(order);
// After the commit, this data should persist even if the system crashes
}
}
With @Transactional
, the changes made to the database will persist even if the system crashes after the commit. Once the transaction is committed, it’s durable.
In real-time enterprise applications, these concepts are implemented using frameworks like Spring Transaction Management or JDBC (for lower-level control), making the system robust and reliable.