I am very new to Spring Boot. So please excuse my rookie mistakes. I want to run tasks asynchronously in the background while my Spring Boot application is running.
I have 3 entities:
EntityCustomer.java:
@Getter
@Table(name = "customer")
@Entity(name = "Customer")
public class EntityCustomer {
@Id
@GeneratedValue
@ColumnDefault("gen_random_uuid()")
@JdbcType(PostgreSQLUUIDJdbcType.class)
private UUID uuid;
@JsonManagedReference
@OneToMany(mappedBy = "customer")
private final List<EntityAccount> accounts = new ArrayList<>();
public EntityCustomer() {
}
}
EntityAccount.java:
@Getter
@Table(name = "account")
@Entity(name = "Account")
public class EntityAccount {
@Generated
@Id
@ColumnDefault("gen_random_uuid()")
private UUID uuid;
@Setter
@JsonBackReference
@ManyToOne(fetch = FetchType.LAZY, optional = false)
@JoinColumn(name = "customer_uuid", nullable = false)
@OnDelete(action = OnDeleteAction.CASCADE)
private EntityCustomer customer;
public EntityAccount() {
}
}
EntityTransferRequest.java
@Getter
@Table(name = "transfer_request")
@Entity(name = "TransferRequest")
public class EntityTransferRequest {
@Id
@GeneratedValue
@ColumnDefault("gen_random_uuid()")
@JdbcType(PostgreSQLUUIDJdbcType.class)
private UUID uuid;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "account_uuid", nullable = false)
private EntityAccount account;
@Setter
@Column(name = "amount")
private BigDecimal amount;
@Setter
@Enumerated(EnumType.STRING)
@Column(name = "status")
private Status status;
public enum Status {
PENDING,
PROCESSING,
COMPLETED,
FAILED
}
}
I need to process transfer requests one per customer at a time. So, say if there are 2 transfer requests from different accounts but accounts that are associated to the same customer; in that case one should be processed at a time. But all customers’ transfer requests should be processed asynchronously.
I tried creating a service, and mapping customers to executor services. Although I think this is a poor concept, this was what had become after researching on the internet for hours.
TransferService.java:
@Slf4j
@Service
public class TransferService {
private final TransferRequestRepository transferRequestRepository;
private final CustomerRepository customerRepository;
private final ConcurrentHashMap<UUID, ExecutorService> customerExecutors;
public TransferService(TransferRequestRepository transferRequestRepository, CustomerRepository customerRepository) {
this.transferRequestRepository = transferRequestRepository;
this.customerRepository = customerRepository;
this.customerExecutors = new ConcurrentHashMap<>();
}
@Scheduled(fixedDelay = 5000) // runs every 5 seconds
public void processTransfers() {
List<EntityTransferRequest> tasks = transferRequestRepository.findFirstTaskByStatusForAllCustomers(EntityTransferRequest.Status.PENDING);
List<CompletableFuture<Void>> futures = new ArrayList<>();
for (EntityTransferRequest task : tasks) {
CompletableFuture<Void> future = CompletableFuture.runAsync(() -> {
task.setStatus(EntityTransferRequest.Status.PROCESSING);
transferRequestRepository.save(task);
try {
Thread.sleep(10000L);
} catch (InterruptedException e) {
throw new RuntimeException(e);
} // for testing
}, customerExecutors.computeIfAbsent(task.getAccount().getCustomer().getUuid(), k -> Executors.newSingleThreadExecutor()));
futures.add(future);
}
CompletableFuture.allOf(futures.toArray(new CompletableFuture[0])).join();
}
}
TransferRequestRepository.java:
public interface TransferRequestRepository extends JpaRepository<EntityTransferRequest, UUID> {
@Query("SELECT tt FROM TransferRequest tt WHERE tt.uuid IN (SELECT MIN(tr.uuid) FROM TransferRequest tr WHERE tr.status = :status GROUP BY tr.account.customer.uuid)")
List<EntityTransferRequest> findFirstTaskByStatusForAllCustomers(@Param("status") EntityTransferRequest.Status status);
}
But this didn’t work as I expected. I got this error when I ran my application:
org.springframework.beans.factory.UnsatisfiedDependencyException: Error creating bean with name 'transferService' defined in file []: Unsatisfied dependency expressed through constructor parameter 0: Error creating bean with name 'transferRequestRepository' defined in com.example.untitled.repository.TransferRequestRepository defined in @EnableJpaRepositories declared on JpaRepositoriesRegistrar.EnableJpaRepositoriesConfiguration: Could not create query for public abstract java.util.List com.example.untitled.repository.TransferRequestRepository.findFirstTaskByStatusForAllCustomers(com.example.untitled.entity.EntityTransferRequest$Status); Reason: Validation failed for query for method public abstract java.util.List com.example.untitled.repository.TransferRequestRepository.findFirstTaskByStatusForAllCustomers(com.example.untitled.entity.EntityTransferRequest$Status)
I’m pretty sure there must be a much better way to implement this logic. I appreciate you taking time to help me solve this issue.
user25627638 is a new contributor to this site. Take care in asking for clarification, commenting, and answering.
Check out our Code of Conduct.