#CacheEvict not evicting any entries - spring

So I'm trying to implement a caching service using redis in spring.this is The code for the caching service.
#Service
public class PermissionCachingService {
#Autowired
PermissionRepository permissionRepository;
#CachePut(value = "Permission", key = "#permission.id")
public Permission save(Permission permission) {
return permissionRepository.save(permission);
}
#CacheEvict(value = "Permission", key = "#permission.id")
void delete(Permission permission) {
permissionRepository.delete(permission);
}
}
This is the object I'm trying to cache.
#Entity
public class Permission {
#Id
#NotNull
#GeneratedValue(strategy = GenerationType.IDENTITY)
Long id;
#NotNull
Boolean read = false;
#NotNull
Boolean write = false;
#NotNull
Boolean update = false;
#NotNull
#ManyToOne
Resource resource;
Long groupId;
//construct
//setters and getters
}
the save works fine for both saving/updating. however, the delete is doing nothing to the cache whatsoever. I tried evicting all entries, even hardcoding a key to delete, but it's doing nothing.
I tried using CacheManager, and it cleared the "Permission" cache successfully with the getCache("Permission").clear() method. However, evicting using a key didn't work, even though I used the same key as the one in the save. what's wrong with the code?

The delete method just wasn't public. After making it public, everything worked as intended.
#CacheEvict(value = "Permission", key = "#permission.id")
public void delete(Permission permission) {
permissionRepository.delete(permission);
}

Related

Spring Boot JPA update after fething ID is not working

#Autowired
LessonService lsnService;
#PutMapping(path = "/{id}")
public ResponseEntity<Object> updateLesson(#PathVariable("id") Long id, #Valid #RequestBody LessonDto dto) {
try {
lsnService.findById(id);
dto.setId(id);
lsnService.save(dto);
return ResponseEntity.ok(dto);
}
catch (Exception e) {
ApiErrorMessage errorMessage = new ApiErrorMessage();
errorMessage.setStatusCode(400L);
errorMessage.setMessage(e.getMessage());
errorMessage.setDescription("The server cannot or will not process the request due to an apparent client error");
return ResponseEntity.badRequest().body(errorMessage);
}
}
Here's my problem. When I remove lsnService.findById(id);, update is working.
If I didn't add that code if a user update with unexisting ID, it will save another data.
Another problem is when I remove dto.setId(id);, both method from lsnService; findById(id); and save(dto); are working! But as you can see, repo must update the entity but it won't!!!
So, I tried to put #Transactional in saving. And I even try putting Thread.sleep(5000); 5 secs delay between those two services. Like this,
lsnService.findById(id);
Thread.sleep(5000);
dto.setId(id);
lsnService.save(dto);
But it doesn't work either!
#Autowired
private LessonJpaRepository repo;
#Override
public LessonDto findById(Long id) {
// TODO Auto-generated method stub
Lesson lesson = repo.getOne(id);
LessonDto dto = new LessonDto(lesson);
return dto;
}
#Override
public void save(LessonDto dto) {
// TODO Auto-generated method stub
repo.save(dto.getEntity());
System.out.println(dto.getId()+dto.getTitle()+dto.getStructure()+dto.getExplanation());
}
And then, I check output of that dto. It's all there! repo is not saving it! It's so strange to me. Got any ideas?
public class LessonDto {
private Long id;
#NotNull(message = "Title must not be null")
#NotBlank(message = "Title must not be blank")
#ValidLessonTitle(message = "Title must begin with uppercase character")
private String title;
#NotNull(message = "Structure must not be null")
#NotBlank(message = "Structure must not be blank")
private String structure;
#NotNull(message = "Explanation must not be null")
#NotBlank(message = "Explanation must not be blank")
private String explanation;
public LessonDto() {
}
public LessonDto(Lesson lesson) {
this.id=lesson.getId();
this.title=lesson.getTitle();
this.structure=lesson.getStructure();
this.explanation=lesson.getExplanation();
}
#java.beans.Transient
public Lesson getEntity() {
Lesson lesson = new Lesson();
lesson.setId(this.id);
lesson.setTitle(this.title);
lesson.setStructure(this.structure);
lesson.setExplanation(this.explanation);
return lesson;
}
//getters and setters
}
This is the entity
#Entity
public class Lesson implements Serializable {
/**
*
*/
private static final long serialVersionUID = 2239534946567783017L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "title")
private String title;
#Column(name = "structure")
private String structure;
#Column(name = "explanation")
private String explanation;
//getters and setters
}
There are 2 ways to make that work
Update instance that is retured by findById with values from DTO
Dont use findById as it fetches entity pointer (at least) to the cache and this might be the origin of problems. Try to use existsById instead

Saving entity with composite key get ConversionNotSupportedException

I use spring boot 2 and some of my entities have composite key
When I try to save an entity, I get this error
Failed to convert request element:
org.springframework.beans.ConversionNotSupportedException: Failed to
convert property value of type 'java.lang.Integer' to required type
'com.lcm.model.SamplingsPK' for property 'sampling'; nested exception
is java.lang.IllegalStateException: Cannot convert value of type
'java.lang.Integer' to required type 'com.lcm.model.SamplingsPK' for
property 'sampling': no matching editors or conversion strategy found
I get my entity with that method
public Samples findById(Integer id, int year, String sampleLetter) {
Optional<Samples> optSamples = samplesRepository.findById(new SamplesPK(new SamplingsPK(year, id), sampleLetter));
if (optSamples.isPresent()) {
return optSamples.get();
}
return null;
}
Samples samples = samplesService.findById(idSeq, year, samplesLetter);
Compressions compressionTest = null;
if (samples.getTestSamples().getAbsorptionTest() != null) {
compressionTest = samples.getTestSamples().getCompressionTest();
} else {
compressionTest = new Compressions();
}
samplesService.save(samples);
My entity
#Entity
#IdClass(SamplesPK.class)
public class Samples extends BaseEntity{
#Id
private String sampleLetter;
#Embedded
private TestSamples testSamples;
#Id
#ManyToOne(optional=false)
#JoinColumns({
#JoinColumn(name = "sampling_id", referencedColumnName = "id"),
#JoinColumn(name = "sampling_year", referencedColumnName = "year")})
private Samplings sampling;
}
#Entity
#IdClass(SamplingsPK.class)
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
public class Samplings {
#Id
private Integer year;
#Id
#GeneratedValue
private Integer id;
#OneToMany(mappedBy = "sampling", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Samples> samples = new ArrayList<>();
}
public class SamplingsPK implements Serializable {
private int year;
private Integer id;
public SamplingsPK(int year, Integer id) {
this.id = id;
this.year = year;
}
}
public class SamplesPK implements Serializable {
private SamplingsPK sampling;
private String sampleLetter;
public SamplesPK(SamplingsPK sampling, String sampleLetter) {
this.sampling = sampling;
this.sampleLetter = sampleLetter;
}
}
edit
no problem to save sample, when I pass from sampling
The problem is that since the IDs are set manually and there's no #Version property on these entities then Spring Data has no good way of knowing if the entity is a brand new one or an existing one. In this case it decides it is an existing entity and attempts a merge instead of a persist. This is obviously a wrong conclusion.
You can read more about how Spring Data decides if an entity is new or not here.
The best solution I've found is to always let entity classes with manually set IDs implement Persistable interface. This solves the problem. I make this a rule for myself for any such case. Most of the time I do not have to implement Persistable because my entity either has an auto-generated key or my entity uses a "#Version" annotation. But this is special case.
So, as per the recommendation in the Spring official documentation, for example the Samplings class would become:
#Entity
#IdClass(SamplingsPK.class)
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
public class Samplings implements Persistable<SamplingsPK> {
#Transient
private boolean isNew = true;
#Id
private Integer year;
#Id
#GeneratedValue
private Integer id;
#OneToMany(mappedBy = "sampling", cascade = CascadeType.ALL, orphanRemoval = true)
private List<Samples> samples = new ArrayList<>();
#Override
public boolean isNew() {
return isNew;
}
#PrePersist
#PostLoad
void markNotNew() {
this.isNew = false;
}
#Override
public SamplingsPK getId() {
return new SamplingsPK(year, id);
}
}
This issue is tracked at https://jira.spring.io/browse/DATAJPA-1391 and has to do with the use of #Id #ManyToOne inside of Samples. As a workaround, you can try creating a constructor for Samplings that takes in its two primary keys, or maybe one that takes a java.lang.Integer? That's what worked for a single level of composite primary keys, but it might not work if you have multiple levels.
You also have year in SamplingsPK typed as an int rather than an Integer. This may cause problems with PK recognition, since special consideration is needed to handle autobox-able primitive classes and I doubt it was considered.
I noticed this too. It does not happen on my IDE on Windows but it happens on the Azure build server
I was on org.springframework.data:spring-data-jpa:jar:2.4.5:compile.
I upgraded the BOM to <spring-data-bom.version>2020.0.15</spring-data-bom.version> so I have org.springframework.data:spring-data-jpa:jar:2.4.15:compile
Once I did that it started working correctly.

Spring Joining table insert. Skip adding records to other table in join. Persist data only to main and link table

Help me in Entity design with the for following E-R.
Here I need to insert entries into REQUEST table and REQ_TYPE_MAP table. There shouldn't be any entry to TYPE_MAST table.
TYPE_MAST contains master data that are loaded once. I need to map the master data to a request.
But the below approach that I use had adding entries to TYPE_MAST table too. How can I avoid that.
Request Entity
#Entity
#Table(name="REQUEST")
public class Request implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Column(name="REQUEST_ID")
private long requestId;
#Column(name="CIVILID")
private Long civilId;
#Column(name="MOBILE_NO")
private Long mobileNo;
#ManyToMany(cascade ={ CascadeType.PERSIST, CascadeType.MERGE})
#JoinTable(name="REQ_TYPE_MAP",
joinColumns = #JoinColumn(name="REQUEST_ID"),
inverseJoinColumns = #JoinColumn(name="TYPE_ID")
)
private Set<MsaDisabScreenRequest> disabilities= new HashSet<MsaDisabScreenRequest>()
/**
getters & setters
equals() & hashCode()
**/
}
Type Mast Entity
#Entity
#Table(name="TYPE_MAST")
public class MsaDisabMaster implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Column(name="TYPE_ID")
private long disTypeId;
#Column(name="NAME")
private String name;
/**
getters & setters
equals() & hashCode()
**/
}
Request DTO
public class RequestDto implements Serializable {
private static final long serialVersionUID = 1L;
private long requestId;
private Long civilId;
private Long mobileNo;
private Set<MsaDisabScreenRequestDto> disabilities= new HashSet<MsaDisabScreenRequestDto>()
/**
getters & setters
equals() & hashCode()
**/
}
Type Mast Dto
public class MsaDisabMasterDto implements Serializable {
private static final long serialVersionUID = 1L;
private long disTypeId;
private String name;
/**
getters & setters
equals() & hashCode()
**/
}
Using spring JPA to persist.
#Service code relevant to save(let me know if you required more details).
Service
#SuppressWarnings("all")
public class ReqServiceImpl implements ReqService {
#Autowired
MsaRepository msaRepository;
#Autowired
private OrikaBeanMapper mapper;
#Override
#Transactional(rollbackOn = BusinessException.class)
public long saveScreeningRequest(RequestDto requestDto) throws BusinessException {
try {
// mapping
Request request = mapper.map(requestDto,Request.class);
Request res = reqRepository.save(request);
if (msaDisabScreenRes != null) {
return res.getRequestId();
} else {
//throw exception
}
} catch (DataAccessException e) {
//throw exception
}
}
}
First of all, since MsaDisabMaster is a permanent data, I recommend to make it immutable.
As MsaDisabMaster objects are already persisted then you must remove cascading from its reference in Request.
In your service you should 'attach' all MsaDisabMaster that are contained in RequestDto to the resulted Request. You can do this with getOne method of your MsaDisabMaster repository, something like this:
requestDto.getDisabilities()
.forEach(m -> request.getDisabilities().add(msaDisabMasterRepo.getOne(m.getDisTypeId())));
Then you can save request using save() method of the Request repository.
Please check this example:
https://www.mkyong.com/hibernate/hibernate-many-to-many-relationship-example-annotation/
This should work for you
#ManyToMany(fetch = FetchType.PERSIST, cascade = CascadeType.MERGE)
#JoinTable(name = "REQ_TYPE_MAP",joinColumns = {
#JoinColumn(name = "REQUEST_ID", nullable = false, updatable = false) },
inverseJoinColumns = { #JoinColumn(name = "TYPE_ID",
nullable = false, updatable = false) })

Lazy attribute is null inside transaction after creation

I have a small example with some get/post mappings and JpaRepository calls in Spring Boot.
Firstly I have two entity Classes:
#Entity
#Table(name = "stock")
public class Stock extends BaseEntity
{
#Column(name = "value")
public String value;
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}
#Entity
#Table(name = "stock_item")
public class StockItem extends BaseEntity
{
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "stock_id", insertable = false, updatable = false)
public Stock stock;
#Column(name = "stock_id")
public Long stockId;
#Column(name = "value")
public String value;
}
I have a many-to-one association from StockItem to Stock.
I insert a Stock and have a controller as below:
#Autowired
public Controller(StockItemRepository stockItemRepository) {
this.stockItemRepository = stockItemRepository;
}
#RequestMapping("/")
#Transactional(readOnly = true)
public String get() {
List<StockItem> stockItemList = stockItemRepository.getItemsById(1L);
System.out.println("TX MANAGER: " + TransactionSynchronizationManager.isActualTransactionActive());
for (StockItem stockItem : stockItemList) {
System.out.println(stockItem.getStock().getValue());
}
return "get";
}
#RequestMapping("/fromSave")
#Transactional
public String post() {
StockItem stockItem = new StockItem();
stockItem.setStockId(1L);
stockItemRepository.saveAndFlush(stockItem);
System.out.println("saveCalled");
return get();
}
and getItemsById in the repository is defined as follows:
#Query("FROM StockItem si " +
"JOIN FETCH si.stock stk " +
"WHERE si.stockId = :id")
List<StockItem> getItemsById(#Param("id") Long id);
From my understanding, when I call the post method:
it creates a new item
sets the id of the associated attribute
saves and ends the transaction
Heres where things get strange...
I call get after the post and make the above repository call, which has a join fetch and when I call stockitem.getStock().getValue() I get a null pointer when I expect a LazyInitializationException.
If I call the get() from the mapping, outside the class, it successfully loads the associated object.
I have even removed the #Transaction annotation from the get, as well as
the join-fetch from my query and again, if I call from outside of the class it works and from the post, it crashes with a NullPointerException.
I have put the get inside of a TransactionTemplate.execute() and I still get a NullPointerException when calling from inside the class.
So the main questions are:
Why am I getting a NullPointerException instead of LazyInitializationException?
What is the transaction magic behind having no transaction but successfully fetching a lazy attribute??
The problem here is that you are misusing JPA. As you are seemingly aware judging from the comments on the other answer you have mapped the stock_id column twice. Once as a many-to-one relationship
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "stock_id", insertable = false, updatable = false)
public Stock stock;
and once as a simple column
#Column(name = "stock_id")
public Long stockId;
When you set the simple column and flush the changes as in your post() method the following happens:
the value gets set in the simple column. The reference is still null.
the value gets stored in the database. The reference is still null.
The repository call will find the id of the StockItemin the Persistence Context and return that instance, i.e. the exact same used in the post method, with the reference still null.
What is the transaction magic behind having no transaction but successfully fetching a lazy attribute??
No magic involved here. fetch specifications are only used for object traversal. JPQL queries don't honor these.
The unasked question remains: how to fix the situation?
The obvious fix is to lose the simple column and just use entity references as intended by JPA.
You don't want to do that in order to avoid DB access somewhere. But as long as you only access the id of the referenced Stock it shouldn't get initialized. So it seems that this should be possible with just Lazy Fetching.
Alternatively, I'd suggest removing the many-to-one relationship and creating a repository for Stock and manually loading it when required.
#Entity
#Table(name = "stock_item")
public class StockItem extends BaseEntity
{
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "stock_id", insertable = false, updatable = false) //here is your problem
public Stock stock;
#Column(name = "stock_id")
public Long stockId; // why explicitly define a separate column for foreign key after mapping it above
#Column(name = "value")
public String value;
}
with insertable = false and updatable = false it won't insert in your DB and neither it will allow updation, so you are getting NullPointerException. You should atleast allow insertion in order to run the query based on the foreign key stock_id
UPDATE
Change your Entity class with property-based access:
#Entity
#Table(name = "stock_item")
public class StockItem extends BaseEntity
{
private Stock stock; // variables should always be private since you have getters and setters
private String value;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "stock_id", updatable = false)
public Stock getStock() {
return stock;
}
public void setStock(Stock stock) {
this.stock = stock;
}
#Basic
#Column(name = "value")
public String getValue() {
return value;
}
public void setValue(String value) {
this.value = value;
}
}

Spring/JPA: composite Key find returns empty elements [{}]

I have build my data model using JPA and am using Hibernate's EntityManager to access the data. I am using this configuration for other classes and have had no problems.
The issue is that I created an entity with a composite primary key (the two keys are foreign keys) , adding elements works perfectly I checked it in database but I am not able to retrieve the populated row from database.
For example if I query "FROM Referentiel" to return a list of all referentiels in the table, I get this [{},{}] my list.size() has the proper number of elements (2), but the elements are null.
The entity:
#Entity
#Table(name = "Et_referentiel")
public class Referentiel implements Serializable {
/**
*
*/
private static final long serialVersionUID = 1L;
#Id
#ManyToOne
#JoinColumn(name = "id_projet")
private Projet projet;
#Id
#ManyToOne
#JoinColumn(name = "id_ressource")
private Ressource ressource;
#Column(name = "unite", nullable = false)
private String unite;
}
here is my controller getList method:
#PostMapping(value = "/list", consumes = { MediaType.APPLICATION_JSON_UTF8_VALUE })
public List<Referentiel> listReferentiel(#RequestBody Long idProjet) {
List<Referentiel> referentiel = referentielService.listReferentiel(idProjet);
return referentiel;
}
and here is my dao methods:
#Autowired
private EntityManager em;
#Override
public void ajouterReferentiel(Referentiel ref) {
em.persist(ref);
em.flush();
}
#SuppressWarnings("unchecked")
#Override
public List<Referentiel> listReferentiel(Long idProjet) {
Query query = em.createQuery("Select r from Referentiel r where r.projet.idProjet=:arg1");
query.setParameter("arg1", idProjet);
em.flush();
List<Referentiel> resultList = query.getResultList();
return resultList;
}
Any help is greatly appreciated.
Try creating a class representing your composite key:
public class ReferentielId implements Serializable {
private static final long serialVersionUID = 0L;
private Long projet; // Same type than idProjet, same name than inside Referentiel
private Long ressource; // Same type than idRessource (I guess), same name than inside Referentiel
// Constructors, getters, setters...
}
And assign it to your entity having that composite key.
#Entity
#IdClass(ReferentielId.class) // <- here
#Table(name = "Et_referentiel")
public class Referentiel implements Serializable {
// ...
}
Notice that it is required to have a class representing your composite keys, even if that does not help in your problem.

Resources