I'm using spring-data-jpa, and I'm trying to apply spring cache abstraction.
findByEmail() method caches well, however, user variable on retrieve() method at controller which spring-data-jpa provide DomainClassConverter always looks up DB.
In documentation, it calles findOne() to look up the resource, but the #Cacheable trigger won't work.
It seems like implementation class as SimpleJpaRepository just invoke CrudRepository instead of UserRepository which I created and put #Cacheable annotation.
Is there any way to apply #Cacheable to findOne() except for custom DomainClassConverter class ?
UserController.class
#RequestMapping(method = RequestMethod.GET, value = "/users/{user}")
public ResponseEntity retrieve(#PathVariable User user) {
logger.info("Retrieve: " + user);
return new ResponseEntity(user.toUserResponse(), HttpStatus.OK);
}
UserService.class
public interface UserRepository extends JpaRepository<User, Long>, JpaSpecificationExecutor<User> {
#Cacheable("Users")
User findOne(Long id);
#Cacheable("Users")
User findByEmail(String email);
User findByEmailAndPassword(String email, String password);
Long countByEmail(String email);
}
I've filed and fixed DATACMNS-620 for you.
This issue shouldn't actually occur with Spring Data Commons 1.10.0 M1 (Fowler) as we moved to the Spring Data REST RepositoryInvokerAPI which explicitly checked for custom overrides. I created a fix for the 1.9.x bugfix branch so that we fix this for users of the current release train Evans.
If you want to check this upgrade to Spring Data Commons 1.9.0.BUILD-SNAPSHOT or 1.10.0.BUILD-SNAPSHOT. If you're using Boot, you can simply set a property spring-data-releasetrain.version and set it to either Evans-BUILD-SNAPSHOT or Fowler-BUILD-SNAPSHOT.
Related
I have a parent transaction at controller layer, but I want to start a new transaction when I call a repository, to achieve this I tried annotating Repository interface as below
#Transactional(isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRES_NEW)
public interface EventRepo extends JpaRepository<Event, Integer>{ }
However this seems to not start a new transaction upon calls to EventRepo#save. Why?
Here is my service layer.
public interface IApplicationService {
void save(Event event);
}
#Service
public class ApplicationService implements IApplicationService {
#Autowired
private EventRepo eventRepo;
#Override
public void save(Event event) {
eventRepo.save(event);
}
}
It is in turn called from controller layer
#RequestMapping(value="/{indicator}", method=RequestMethod.POST)
#Transactional(isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRES_NEW)
#ResponseBody
public String processRequest(#PathVariable Integer indicator) {
Event event = new Event("Student1");
service.save(event);
if(indicator != 0) {
throw new RuntimeException();
}
return "Success";
}
However everything works perfectly if I annotate Service interface with #Transactional
#Transactional(isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRES_NEW)
public interface IApplicationService {
void save(Event event);
}
When I say working what is mean is, if I run the below curl commands I will see 2 rows in h2 db for Event entity
curl -X POST http://localhost:8080/1
curl -X POST http://localhost:8080/0
I understand it is good to control transactions at Service layer then repository or controller layer, constructing situation this way makes it easy to demonstrate the problem.
Spring boot starter version is 2.5.6
below dependencie have versions managed by springboot starter
spring-boot-starter-data-jpa
spring-boot-starter-web
lombok
h2
Here is a thread that suggests it should be ok to annotate Repository layer although discourages it.
#Transactional on a JpaRepository
In this Spring article we can read the following:
Additionally, we can get rid of the #Transactional annotation for the method as the CRUD methods of the Spring Data JPA repository implementation are already annotated with #Transactional.
To me, this means that whatever #Transactional annotation you add to your EventRepo will be overridden by the #Transactional annotation mentioned above in the CRUD methods. Having said that, I really doubt #Transactional annotation has any effect in JpaRepository methods. It would have in your own custom methods, but it seems to me that it has none in the inherited methods.
In order to apply your own transactional settings in EventRepo#save override the save method:
#Transactional(isolation = Isolation.READ_COMMITTED, propagation = Propagation.REQUIRES_NEW)
public interface EventRepo extends JpaRepository<Event, Integer>{
#Override
Event save(Event event);
}
Explanation
Spring ignores your #Transactional annotation because it cannot find the save method in the EventRepo proxy and applies the default transaction settings from the parent CrudRepository interface.
Further reading: How Does Spring #Transactional Really Work?
Spring Boot version - 2.4.4,
mongodb version - 4.4.4
In my project, I want to do entry in 2 different document of mongodb, but if one fails than it should do rollback. mongodb supports transaction after version 4.0 but only if you have at least one replica set.
In my case I don't have replica set and also cannot create it according to my project structure. I can't use transaction support of mongodb because no replica-set. So, I am using Spring Transaction.
According to spring docs, to use transaction in Spring Boot, you only need to use #transactional annotation and everything will work(i.e. rollback or commit).
I tried many things from many sources but it is not rollbacking transaction if one fail.
Demo code is here,
This is demo code, not actual project.
This is my service class.
#Service
public class UserService {
#Autowired
UserRepository userRepository;
#Autowired
UserDetailRepository userDetailRepository;
#Transactional(rollbackFor = Exception.class)
public ResponseEntity<JsonNode> createUser(SaveUserDetailRequest saveUserDetailRequest) {
try {
User _user = userRepository.save(new User(saveUserDetailRequest.getId(), saveUserDetailRequest.getFirstName(), saveUserDetailRequest.getLastName()));
UserDetail _user_detail = userDetailRepository.save(new UserDetail(saveUserDetailRequest.getPhone(), saveUserDetailRequest.getAddress()));
} catch (Exception m) {
System.out.print("Mongo Exception");
}
return new ResponseEntity<>(HttpStatus.OK);
}
}
Also tried below code but still not working,
#EnableTransactionManagement
#Configuration
#EnableMongoRepositories({ "com.test.transaction.repository" })
#ComponentScan({"com.test.transaction.service"})
public class Config extends AbstractMongoClientConfiguration{
private com.mongodb.MongoClient mongoClient;
#Bean
MongoTransactionManager transactionManager(MongoDbFactory dbFactory) {
return new MongoTransactionManager(dbFactory);
}
#Bean
public com.mongodb.MongoClient mongodbClient() {
mongoClient = new com.mongodb.MongoClient("mongodb://localhost:27017");
return mongoClient;
}
#Override
protected String getDatabaseName() {
return "test";
}
}
The transaction support in Spring is only there to make things easier, it doesn't replace the transaction support for the underlying datastore being used.
In this case, it will simply delegate the starting/committing of a transaction to MongoDB. WHen using a database it will eventually delegate to the database etc.
As this is the case, the pre-requisites for MongoDB still need to be honoured and you will still need a replica.
I'am using #EntityListeners(AuditingEntityListener.class) in Spring Boot JPA application and having some issue with inserting entity. I created #RestController for REST API, #Service for extra business logic and spring's JpaRepository interfaces to access DB.
So the issue is when I want to update some entity and create some another entity in #Service (BL layer). When creating new entity from service I get exception that attribute annotated with #CreatedBy could not be null. I see that AuditorAware is empty. But if comment out creating of new entity and make update of first entity only then AuditorAware is not null and works fine.
It's also working if I create this problematic entity form #Controller.
Does anyone have similar problem and know how to solve this.
I suppose the proxyMode of AuditorAware #Bean is wrong but I don't know how to fixed it.
I changed the implementation of AuditorAware from:
#Bean
public AuditorAware<String> auditorProvider() {
return () -> {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
return Optional.ofNullable(Objects.isNull(authentication) ? null : authentication.getName());
};
}
to:
#Bean
public AuditorAware<String> auditorProvider() {
return () -> {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
if (Objects.isNull(authentication) || !authentication.isAuthenticated() || !(authentication.getPrincipal() instanceof Principal)) {
return Optional.of("anonymous");
}
return Optional.of(authentication.getName());
};
}
which also populate created_by in test phase where there is no security context.
Sry, if my question isn't new but i couldn't find answer.I use Spring Data JPA and Spring Cache.
I have folowing repository
#CacheConfig(cacheNames = "Category")
#Cacheable
#Repository
public interface Repository extends CrudRepository<Category, Long> {
Category findByCategory(String Category);
}
And i want to cache default CrudRepository methods, like findAll() and etc.
It's work If i override them like this
#CacheConfig(cacheNames = "Category")
#Cacheable
#Repository
public interface Repository extends CrudRepository<Category, Long> {
Category findByCategory(String Category);
List<Category> findAll();
}
But it's not convenient override them every time for every repository.
Is there a way cache defaults spring jpa methods without override them or no such way?
Yes, we can do it. Basically, Spring uses Hibernate ORM as the implementation of JPA. Hibernate itself supports caching functionality with it and will integrate better than Spring Cache.
To enable L2 cache, add these properties to your project add the following properties.
spring.jpa.properties.hibernate.cache.use_second_level_cache=true
spring.jpa.properties.hibernate.cache.use_query_cache=true
spring.jpa.properties.hibernate.cache.region.factory_class=org.hibernate.cache.ehcache.EhCacheRegionFactory
and dependencies hibernate-ehcache
Once this is done, all your default methods of JPA like findOne(), findAll() will be cached.
If you add any custom methods, you can add like below:
#QueryHints({ #QueryHint(name = "org.hibernate.cacheable", value ="true") }) Category findByCategory(String Category);
To test where default methods are cached, you can use the following properties to see if SQL has been executed.
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
I am trying to do CRUD operations with My Entity bean. CRUDRepository provide standard methods to find, delete and save but there is no generic method available like saveOrUpdate(Entity entity) that in turn calls Hibernate or HibernateTemplate sessions saveorUpdate() methods.
The way CRUDRepository provides this functionality is to
use like this
#Modifying
#Query("UPDATE Space c SET c.owner = :name WHERE c.id = :id")
Integer setNameForId(#Param("name") String name, #Param("id")
but this is not generic and needs to be written for complete form fields.
Please let me know if there is any way or i can get session of Hibernate or object of Spring HibernateTemplate to solve this issue.
The implementation of the method
<S extends T> S save(S entity)
from interface
CrudRepository<T, ID extends Serializable> extends Repository<T, ID>
automatically does what you want. If the entity is new it will call persist on the entity manager, otherwise it will call merge
The code looks like this:
public <S extends T> S save(S entity) {
if (entityInformation.isNew(entity)) {
em.persist(entity);
return entity;
} else {
return em.merge(entity);
}
}
and can be found here. Note that SimpleJpaRepository is the class that automatically implements CrudRepository in Spring Data JPA.
Therefore, there is no need to supply a custom saveOrUpdate() method. Spring Data JPA has you covered.
The issue is "i am using thymeleaf UI template and The Bean that i am trying to persist that is Form bean not Entity bean and that's why Spring boot is not saving it. Now i have to convert the entire Form bean into Entity bean with changed values and try to persist it." I got the solution for the specific problem i faced in my project, The solution is use #ModelAttribute to convert the form bean to entity.
#ModelAttribute("spaceSummary")
public SpaceSummary getSpaceSummary(int id){
return this.spaceSummaryService.getSpaceSummary(id);
}
and
#RequestMapping(value="/mgr/editSpace", method = RequestMethod.POST)
public ModelAndView editSpace(#ModelAttribute("spaceSummary") SpaceSummary
spaceSummary, BindingResult result, RedirectAttributes redirect, ModelAndView model) {
}