In other similar questions on how to catch EntityNotFoundException the main tip was to use simpler methods that return null like getOne
As of spring-boot v 2.7 + both getOne() and getById() are marked as deprecated and documentation says to use getReferenceById instead.
The Problem as mentioned in some other question, that you can't simply catch javax.persistence.EntityNotFoundException.
This is never be caught:
fun getDonation(donationId: Long): DonationDto? {
return try {
val entity: DonationEntity = donationJpaRepository.getById(donationId)
mapper.toDonationDto(entity)
} catch (e : EntityNotFoundException) {
null
}
}
Apart from downgrading the version and moving on with my life, how do I get to catch the exception or handle not found entities?
Seems like you can catch
JpaObjectRetrievalFailureException in this case.
From the documentation to JpaObjectRetrievalFailureException
JPA-specific subclass of ObjectRetrievalFailureException. Converts JPA's EntityNotFoundException.
return try {
val dto = service.getStuff(id)
ResponseEntity(dto, HttpStatus.OK)
} catch (e: JpaObjectRetrievalFailureException) {
ResponseEntity("Nothing with id $id.", HttpStatus.NOT_FOUND)
}
You can only love the javadoc of JpaRepository.getReferenceById(ID id)
Depending on how the JPA persistence provider is implemented this is very likely to always return an instance and throw an EntityNotFoundException on first access. Some of them will reject invalid identifiers immediately.
If you care about your mental health, use CrudRepository.findById() which guarantees that an empty optional will be returned if it cannot find the entity. Or better, just use the EntityManager and throw away the leaky abstraction of spring jpa.
If you need to load more than one entity, catching that expection might not give enough information to the client about what entity failed to be loaded. I know that in this case it's just one entity, but you'll probably have scenarios where you need to load more than one entity and having a consistent approach helps to maintain the code in the long term.
Related
I'm trying to migrate my project to Quarkus Reactive with Hibernate Reactive Panache and I'm not sure how to deal with caching.
My original method looked like this
#Transactional
#CacheResult(cacheName = "subject-cache")
public Subject getSubject(#CacheKey String subjectId) throws Exception {
return subjectRepository.findByIdentifier(subjectId);
}
The Subject is loaded from the cache, if available, by the cache key "subjectId".
Migrating to Mutiny would look like this
#CacheResult(cacheName = "subject-cache")
public Uni<Subject> getSubject(#CacheKey String subjectId) {
return subjectRepository.findByIdentifier(subjectId);
}
However, it can't be right to store the Uni object in the cache.
There is also the option to inject the cache as a bean, however, the fallback function does not support to return an Uni:
#Inject
#CacheName("subject-cache")
Cache cache;
//does not work, cache.get function requires return type Subject, not Uni<Subject>
public Uni<Subject> getSubject(String subjectId) {
return cache.get(subjectId, s -> subjectRepository.findByIdentifier(subjectId));
}
//This works, needs blocking call to repo, to return response wrapped in new Uni
public Uni<Subject> getSubject(String subjectId) {
return cache.get(subjectId, s -> subjectRepository.findByIdentifier(subjectId).await().indefinitely());
}
Can the #CacheResult annotations be used with Uni / Multi and everything is handled under the hood correctly?
Your example with a #CacheResult on a method that returns Uni should actually work. The implementation will automatically "strip" the Uni type and only store the Subject in the cache.
The problem with caching Unis is that depending on how this Uni is created, multiple subscriptions can trigger some code multiple times. To avoid this you have to memoize the Uni like this:
#CacheResult(cacheName = "subject-cache")
public Uni<Subject> getSubject(#CacheKey String subjectId) {
return subjectRepository.findByIdentifier(subjectId)
.memoize().indefinitely();
}
This will ensure that every subscription to the cached Uni will always return the same value (item or failure) without re-executing anything of the original Uni flow.
You can imagine I have some service, say it will be money service. Also assume I have one method, that perform actual transfer (Quite mundane example, I know). And I have to return true if transaction ended up successfully, and false, if it is not. So, here is the think that I do not actually grasp - how do I track the result of transaction in Spring Framework? (May be even for just simple logging purposes) Example of my transfer method is present below. Appreciate any help.
#Transactional
public boolean transferMoneyFromOneAccountToAnother(MoneyTransferForm moneyTransferForm) {
final UserBankAccount sourceBankAccount = bankAccountRepository.findBankAccountByIdentifier(
moneyTransferForm.getSourceAccountIdentifier()
);
final UserBankAccount targetBankAccount = bankAccountRepository.findBankAccountByIdentifier(
moneyTransferForm.getTargetAccountIdentifier()
);
subtractMoneyFromSourceAccount(moneyTransferForm, sourceBankAccount);
appendMoneyToTargetAccount(moneyTransferForm, targetBankAccount);
bankAccountRepository.updateUserBankAccount(sourceBankAccount);
bankAccountRepository.updateUserBankAccount(targetBankAccount);
}
I can think of two ways to do it:
You can simply enclose your method call with try/catch block and if there are no exception then your transaction was committed successfully.
try{
transferMoneyFromOneAccountToAnother()
logger.info("Transacton Done Successfully");
}catch(Exception ex){
//transaction failed
logger.error("Transaction failed")
}
You can have a method which is annotated with #TransactionalEventListener and listening to your custom event. You can check these links for more understanding of how it works:
https://www.baeldung.com/spring-events
#TransactionalEventListener annotated method not invoked in #Transactional test
I am reading up on AsyncCassandraOperations to perform async inserts to improve performance based on another post here. But I am unable to find a lot of help on google or spring data documentation.
Previously I was using Cassandra Repository for all data extraction and insert/updates which I found to be super slow. As per recommendation I am now using AsyncCassandraOperations for the insert operation alone, but it wont let me. I encounter required a bean of type 'org.springframework.data.cassandra.core.AsyncCassandraOperations' error.
What would be the correct way to use AsyncCassandraOperations please?
#Autowired private MyRepository repository_name;
#Autowired private AsyncCassandraOperations acops;
public void persist(List<POJO> l_POJO)
{
System.out.println("Enter Persist: "+new java.util.Date());
List<l_POJO> l_POJO_stale = repository_name.findBycol1AndStale("sample",false);
l_POJO_stale.forEach(s -> s.setStale(true));
l_POJO_stale.forEach(s -> acops.update(s));
try
{
acops.insert(l_POJO);
}
catch (Exception e)
{
System.out.println("Error in persisting new data");
}
}
Don't know whether spring boot is used, if so the AsyncCassandraOperations(AsyncCassandraTemplate is the implementation class) should be created automatically.
If the error shows you need an AsyncCassandraOperations bean, the straight way is to create one as shown below.
#Bean
AsyncCassandraTemplate asyncCassandraTemplate(Session session) {
return new AsyncCassandraTemplate(session);
}
Since you are using Spring data Repository interface, you can alse use the ReactiveCrudRepository interface to update or insert entity objects to Cassandra, which is shown in this spring data example project , as an alternative way to using the AsyncCassandraTemplate class.
In the case of using ReactiveCrudRepository and regarding what you want to do, your code needs the following changes.
change the return type of WRRepository.findByCol1AndCol2AndCol3(String, boolean, String) from List<WRpojo> to Flux<WRpojo> , in order to fully utilize the reactive functionality.
change the return type of persist(List<WRpojo>) from boolean to Mono<Void> , making the result non-blocking too.
change your persist(List<WRpojo>) to the following.
public Mono<Void> persist(List<WRpojo> l_wr) {
Flux<WRpojo> l_old_wr = objWRRepository.findByCol1AndCol2AndCol3("1", false, "2").doOnNext(s -> s.setStale(true));
return objWRRepository.saveAll(l_old_wr).thenMany(objWRRepository.saveAll(l_wr)).then();
}
In reactive programming, basically we don't block any code, this means that somewhere the returned Mono<Void> should be subscribed somewhere downstream, if you do want to block and wait for all operations complete, you can call block() on Mono<Void> , which is not recommended.
My question is regarding spring-retry.
Assume a simple sample code where I have a the Service layer and Controller class.,
This is the testService Interface
public interface testService{
#Retryable(value = { KnownExceptiomn.class }, backoff = #Backoff(delay = 1000), maxAttempts = 2)
Address getAddress(String emailAddress);
}
This is the implementation of the service
public class testServiceImpl{
public Address getAddress(String emailAddress){
//addressRepository is a crud repository
return addressRepository.getAddressFromEmail(emailAddress);
}
}
And the controller is
#GetMapping("path/{emailId}")
public ResponseEntity<?> getAddress(#PathVariable("emailId") final String email){
final Address address;
try{
address= testService.getAddress(String emailAddress);
if(address != null) return new ResponseEntity<Address>(address,HttpStatus.OK);
return new ResponseEntity<String>("Invalid Email", HttpStatus.BAD_REQUEST);
}catch(KnownException e){
return errorMessage("Error");
}
}
As seen the #Retryble is in the Service interface. However I have not implemented an #Recover method. My thought here was since i dont really have any secondary DB and If the DB is down there really isn't a recovery option I did not add a #Recovery method. Instead the exception was caught in the controller and handled.
My questions are:
Is the above approach wrong. If so, how to do it the right way?
Is it always necessary to have a recovery method? If so what would be recovery in these kind of scenarios like DB being down and no alternative source of Data.
Is it wrong to catch exceptions in controller and handle
them accordingly? (I was told the service lay should handle all the
exceptions in some discussions).
Everywhere I've see is some sort of recovery method but couldn't find a solid example with proper recovery handler for this type of scenario if there is.
#Recovery is optional; if there is none, after retries are exhausted, the last exception is thrown to the caller, who has to handle the exception.
It's perfectly normal to not have a #Recovery and handle the exception in the caller, by whatever means you like.
#Vipin Menon: I think you are referring to #Recover annotation, so the answer is No. #Recover annotation is provided just to give the programmer flexibility to create a recovery method to return a default (fallback) response if retry attempts also fails.
In a nutshell, don't create a recovery method using #Recover annotation if you don't need any default behavior when retry attempts fail. Simply use #Retryable annotation on the actual service method.
I am upgrading a working project from Spring2+Hibernate3 to Spring3+Hibernate4. Since HibernateTemplate and HibernateDAOSupport have been retired, I did the following
Before (simplified)
public List<Object> loadTable(final Class<?> cls)
{
Session s = getSession(); // was calling the old Spring getSession
Criteria c = s.createCriteria(cls);
List<Object> objects = c.list();
if (objects == null)
{
objects = new ArrayList<Object>();
}
closeSession(s);
return objects;
}
Now (simplified)
#Transactional(propagation=Propagation.REQUIRED)
public List<Object> loadTable(final Class<?> cls)
{
Session s = sessionFactory.getCurrentSession();
Criteria c = s.createCriteria(cls);
List<Object> objects = c.list();
if (objects == null)
{
objects = new ArrayList<Object>();
}
return objects;
}
I also added the transaction annotation declaration to Spring XML and removed this from Hibernate properties
"hibernate.current_session_context_class", "org.hibernate.context.ThreadLocalSessionContext"
The #Transactional annotation seems to have worked as I see this in the stacktrace
at com.database.spring.DatabaseDAOImpl$$EnhancerByCGLIB$$7d20ef95.loadTable(<generated>)
During initialization, the changes outlined above seem to work for a few calls to the loadTable function but when it gets around to loading an entity with a parent, I get the "collection with cascade="all-delete-orphan" was no longer referenced" error. Since I have not touched any other code that sets/gets parents or children and am only trying to fix the DAO method, and the query is only doing a sql SELECT, can anyone see why the code got broken?
The problem seems similar to Spring transaction management breaks hibernate cascade
This is unlikely problem of Spring, but rather issue with your entity handling / definition. When you are using deleteOrphans on a relation, the underlying PersistentSet MUST NOT be removed from the entity itself. You are allowed only to modify the set instance itself. So if you are trying to do anything clever within your entity setters, that is the cause.
Also as far as I remember there are some issues when you have deleteOrphans on both sides of the relation and/or load/manipulate both sides within one session.
Btw. I don't think "hibernate.current_session_context_class", "org.hibernate.context.ThreadLocalSessionContext" is necessary. In our project, this is the only configuration we have:
#Bean
public LocalSessionFactoryBuilder sessionFactoryBuilder() {
return ((LocalSessionFactoryBuilder) new LocalSessionFactoryBuilder(
dataSourceConfig.dataSource()).scanPackages(ENTITY_PACKAGES).
setProperty("hibernate.id.new_generator_mappings", "true").
setProperty("hibernate.dialect", dataSourceConfig.dialect()).
setProperty("javax.persistence.validation.mode", "none"));
}
#Bean
public SessionFactory sessionFactory() {
return sessionFactoryBuilder().buildSessionFactory();
}
The issue was with Session Management. The same block of transactional code was being called by other modules that were doing their own session handling. To add to our woes, some of the calling modules were Spring beans while others were written in direct Hibernate API style. This disorganization was sufficient work to keep us away from moving up to Hibernate 4 immediately.
Moral of the lesson (how do you like that English?): Use a consistent DAO implementation across the entire project and stick to a clearly defined session and transaction management strategy.