Saving entity with composite key get ConversionNotSupportedException - spring

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.

Related

Automatic JPA refresh ManyToOne objects with #Version feature

I'm getting an exception:
org.hibernate.TransientPropertyValueException:
object references an unsaved transient instance
- save the transient instance before flushing :
com.example.jpamapstruct.entity.Member.club ->
com.example.jpamapstruct.entity.Club
while saving the member entity:
#Transactional
public MemberDto save(MemberDto memberDto){
Member entity = memberMapper.toEntity(memberDto);
return memberMapper.toDto(repository.save(entity));
}
How to fix this case in a proper way?
Possible solution:
I can get and set a club object before saving a member but is it only one and the best approach in such scenario?
Member entity = memberMapper.toEntity(memberDto);
clubRepository.getReferencedById(memberDto.getClubId()).ifPresent(entity::setClub);
return memberMapper.toDto(repository.save(entity));
Questions:
Should I put this getReferencedById code explicity? I mean what if we have several child objects (unidirectional ManyToOne), for each we need to get data from DB.
Is there any way to handle this by JPA (Spring Data/JPA) "automatically"?
Maybe it is possible to hit DB only one time with f.e join fetch somehow for all childs (with using custom #Query or querydsl or criteria/specification)?
Next, hoow to handle collections (unidirectional manyToMany)? In my case set of events in member object. Also need to loop thru and get all objects one by one before saving member?
Where should I put such logic in a service or maybe better in a mapstuct mapper?
If so, how to use repositories in such mapper?
#Mapper(componentModel = "spring")
public interface MemberMapper extends EntityMapper<MemberDto, Member> {
#AfterMapping
default void afterMemberMapping(#MappingTarget Member m, MemberDto dto) {
var club = clubRepo.findById(m.getClub().getId())
m.setClub(club)
}
Source code:
#Entity
public class Club extends AbstractEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", nullable = false)
private Long id;
}
public class ClubDto extends AbstractDto {
private Long id;
}
#Entity
public class Member {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", unique = true, nullable = false)
private Long id;
// commented out as don't want to save child object as it should already exist
// #ManyToOne(cascade = CascadeType.ALL)
#ManyToOne
Club club;
#ManyToMany
#JoinTable(name = "member_events",
joinColumns = #JoinColumn(name = "member_id"),
inverseJoinColumns = #JoinColumn(name = "event_id")
)
List<Event> events = new ArrayList<>();
}
public class MemberDto {
private Long id;
private ClubDto club;
}
#MappedSuperclass
public abstract class AbstractEntity {
#Version
private Integer version;
}
public abstract class AbstractDto {
private Integer version;
}
//MemberMapper above

Hibernate Fetch #Formula annotated fields on demand

I have a entity (declared with 2 way)(some not influencing code part are ommited for readability)
Entity version 1.
#Entity
public class Article {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "name")
private String name;
#Formula("(SELECT COUNT(w.id) FROM stock s LEFT JOIN warehouse w ON s.id=w.stock_id WHERE s.article_id = id)")
private int variants;
public int getVariants() {
return variants;
}
}
Entity version 2.
#Entity
public class Article {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "name")
private String name;
#Transient
private int variants;
#Access(AccessType.PROPERTY)
#Formula("(SELECT COUNT(w.id) FROM stock s LEFT JOIN warehouse w ON s.id=w.stock_id WHERE s.article_id = id)")
public int getVariants() {
return variants;
}
}
respective DTO and ArticleMapper - MapStruct
#JsonIgnoreProperties(ignoreUnknown = true)
public class ArticleDTOCommon {
private Long id;
private String name;
}
#JsonIgnoreProperties(ignoreUnknown = true)
public class ArticleDTO {
private Long id;
private String name;
private int variants;
}
#Mapper(componentModel = "spring", uses = {})
public interface ArticleMapper{
ArticleDTO toDto(Article article);
ArticleDTOCommon toDtoCommon(Article article);
}
I have a #Service layer on which how i know Hibernate creates it's proxy(for defining which field is fetch or not fetch) and transactions are occurs.
#Service
#Transactional
public class ArticleService {
#Transactional(readOnly = true)
public List<ArticleDTO> findAll() {
return articleRepository.findAll()
stream().map(articleMapper::toDto).collect(Collectors.toList());
}
#Transactional(readOnly = true)
public List<ArticleDTO> findAllCommon() {
return articleRepository.findAll()
stream().map(articleMapper::toDtoCommon).collect(Collectors.toList());
}
}
It works fine with fetching Related entity but
Problem is (fetching #Formula annotated field) when I am looking executed query on log it fetchs all time variants #Formula annotated query not depending on respective DTO.
But it must be ignored on toDtoCommon - i.e. It must not fetch variants field -> because when mapping Article to ArticleDtoCommon it not uses getVariant() field of Article. I have tried multiple ways as mentioned above.
I can solve it with writing native query(for findAllCommon() method) and map respectivelly with other way... But I want to know that how we can solve it with ORM way and where is problem.
Manupulating #Access type is not helping too.
Thanks is advance.

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.

Lazy loading with JPA on #ManyToOne

I have a project with Spring Boot, and I cannot make lazy loading work. I have 2 entities: Question and Answer. A question can have many answers.
What I want is, when I try to get an answer, to get only the answer without the question. And also, if I want both, to have this possibility.
What I did, is I added in application.yml: spring.jpa.open-in-view: true.
The Answer entity is like:
#Entity
#Table(name = "mst_ans_answer", schema = "lquest_sc")
public class Answer implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "lquest_sc.mst_ans_answer_ans_lqs_id_seq")
#SequenceGenerator( name = "lquest_sc.mst_ans_answer_ans_lqs_id_seq", sequenceName = "lquest_sc.mst_ans_answer_ans_lqs_id_seq")
#Column(name = "ans_lqs_id")
private int id;
#Column(name = "qst_lqs_id")
private int questionId;
#Column(name = "ans_text")
private String text;
#ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "qst_lqs_id", insertable=false, updatable=false)
#JsonIgnore
private Question question;
//getters and setters
}
The Question entity is:
#Entity
#Table(name = "mst_qst_question", schema = "lquest_sc")
public class Question implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "lquest_sc.mst_qst_question_qst_lqs_id_seq")
#SequenceGenerator(name = "lquest_sc.mst_qst_question_qst_lqs_id_seq", sequenceName = "lquest_sc.mst_qst_question_qst_lqs_id_seq")
#Column(name = "qst_lqs_id")
private int id;
#Column(name = "qst_title")
private String title;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "question")
#OrderBy("order asc")
private Set<Answer> answers = new HashSet<Answer>();
//getters and setters here
The call in the controller is:
#RequestMapping(value = "/questionId/{id}", method = RequestMethod.GET)
public List<Answer> listAll( #PathVariable("id") int id ){
List<Answer> answers = answerRepository.findByEnabledAndQuestionIdOrderByOrderAsc(1,id);
return answers;
}
and the repository is
public interface AnswerRepository extends JpaRepository<Answer, Long> {
List<Answer> findByEnabledAndQuestionIdOrderByOrderAsc(int enabled,int questionId);
}
The problem is that in the controller, when I try to evaluate
answers.get(0).getQuestion(), I receive the entity of Question, with the properties filled with null values and the error Method threw 'org.hibernate.LazyInitializationException' exception. Cannot evaluate Question_$$_jvst5b6_1.toString(). What am I doing wrong?
I don't know why spring.jpa.open-in-view = true is not working in your case. Maybe the OpenEntityManagerInViewInterceptordoes not get triggered or the thread has been left, when you are evaluating the Question. Or you have an older version which just does not support it.
Lazy loading works only inside of a transaction. A solution could be -
as #Pradeep already gave you the hint - to use #Tranactional inside a business logic class.
Even if you put the #Transactional inside your repository it will not work, because you have to put the annotation on top of the method where you are trying to evaluate answers.get(0).getQuestion().
Furthermore I advise you not to call your repository from the controller directly, but to use a service layer, where you put your business logic.
Example implementation
This is only a example implementation to show you how to structure your application and what the important keywords are. Also note, that you can either use #Inject or #Autowired. When you have implemented your logic, just inject the service into your controller and use it there.
AnswerService.java
public interface AnswerService {
List<Answer> findByEnabledAndQuestionIdOrderByOrderAsc(int enabled, Long id);
}
AnswerServiceImpl.java
#Service
public class AnswerServiceImpl implements AnswerService {
private AnswerRepository answerRepository;
#Inject
public AnswerServiceImpl(AnswerRepository answerRepository) {
this.answerRepository = answerRepository;
}
#Transactional
#Override
public List<Answer> findByEnabledAndQuestionIdOrderByOrderAsc(int enabled, Long id) {
List<Answer> answerList = findByEnabledAndQuestionIdOrderByOrderAsc(int enabled,int questionId);
// do your lazy loading here
// because you are still in the same transactional context
// return the list
return answerList;
}
}

Resources