JPA Hibernate - Entity with #Loader and a function field in select, won't work properly - spring

#Entity
#Table(name="cad_paciente")
#Loader(namedQuery = "selectInicial")
#NamedNativeQuery(
name="selectInicial",
query="select p.*, fu_obter_lista_convenios_pac(p.id) as ds_convenio from cad_paciente p where p.id = ?", resultClass = Paciente.class,
resultSetMapping = "sqlResult")
#SqlResultSetMapping(
name="sqlResult",
entities={
#EntityResult(entityClass = Paciente.class, fields={
#FieldResult(name="ds_convenio",column="ds_convenio")})})
public class Paciente {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NotNull
#Column(name="id_empresa")
private Integer id_empresa;
...
#Transient
#Column(name="ds_convenio")
private String ds_convenio;
public String getDs_convenio() {
return ds_convenio;
}
public void setDs_convenio(String ds_convenio) {
this.ds_convenio = ds_convenio;
}
My Controller method "pacientes.findAll()" won't return "ds_convenio" field with the correct value, listing "null" always in my JSON return.
What do I have to do?

Try removing the annotation #Transient and provide the column as below :
#Column(name="ds_convenio")
private String ds_convenio;
#org.springframework.data.annotation.Transient specifically states to the spring framework that the Object Mapper you are using should not include this value when converting from Java Object to JSON. Also, it means that the value is not to be persisted into the database, which means you could not query over it.
Or if you want to keep it as transient itself but does not require the value to be serialized then register the object mapper as below :
#Bean
public ObjectMapper includeTransientObjectMapper() {
Hibernate5Module hibernate5Module = new Hibernate5Module();
hibernate5Module.disable(Hibernate5Module.Feature.USE_TRANSIENT_ANNOTATION);
ObjectMapper mapper = new ObjectMapper();
mapper.registerModule(hibernate5Module);
return mapper;
}
Or in your case since you want the result of the #NamedNativeQuer in which you aliased ds_convenio, using #FieldResult might be required to get the desired result as follows :
#Entity
#Table(name="cad_paciente")
#Loader(namedQuery = "selectInicial")
#NamedNativeQuery(name="selectInicial", query="select p.*, fu_obter_lista_convenios_pac(p.id) as ds_convenio from cad_paciente p where p.id = ?", resultClass = Paciente.class)
#SqlResultSetMapping(name="Results",
entities={
#EntityResult(entityClass=com.acme.Order.class, fields={
#FieldResult(name="id", column="id"),
#FieldResult(name="id_empresa", column="id_empresa"),
........
})
public class Paciente {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NotNull
#Column(name="id_empresa")
private Integer id_empresa;
...
#Transient
#Column(name="ds_convenio")
private String ds_convenio;
Read doc

Related

Infinite recursion with #JsonManagedReference and #JsonBackReference

I have an entity class that is self referencing itself. For example, a document can be linked to a parent document.
#Entity
#Table(name = "documents")
public class DocumentEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#JsonIgnore
#JsonManagedReference
#ManyToOne(fetch = FetchType.LAZY)
private DocumentEntity parentDocument;
#JsonBackReference
#OneToMany(mappedBy = "parentDocument", fetch = FetchType.LAZY)
private Set<DocumentEntity> documents;
#Column(nullable = false, unique = true)
private String documentId;
#Column(nullable = false)
private String fileName;
}
In my entry point / controller layer :
#GetMapping(
path = "/{fileId}",
produces = { MediaType.APPLICATION_JSON_VALUE, MediaType.APPLICATION_XML_VALUE }
)
public DocumentResponse getParentDocument(#PathVariable("fileId") String fileId) {
modelMapper = createModelMapper();
DocumentDto documentDto = documentService.getParentDocument(fileId);
DocumentResponse documentResponse = modelMapper.map(documentDto, DocumentResponse.class);
documentResponse.getDocuments().forEach(document -> System.out.println(document.getDocumentId()));
return documentResponse;
}
In my Service layer :
#Override
public DocumentDto getParentDocument(String documentId) {
DocumentDto documentDtoResponse = new DocumentDto();
ModelMapper modelMapper = new ModelMapper();
modelMapper.getConfiguration().setMatchingStrategy(MatchingStrategies.STRICT);
DocumentEntity storedDocumentEntity =
documentRepository.findByDocumentIdAndParentDocumentNull(documentId);
if(storedDocumentEntity.getDocumentId().isEmpty() || storedDocumentEntity.getDocumentId().isBlank()) {
throw new AppFileNotFoundException("Oops file not found");
}
documentDtoResponse = modelMapper.map(storedDocumentEntity, DocumentDto.class);
return documentDtoResponse;
}
In the repository:
Now I'm making a sql request in a repository interface that extends JpaRepository.
The application allow to have a parent document with child documents and child documents cannot have child documents.
#Repository
public interface DocumentRepository extends JpaRepository<DocumentEntity, Long> {
DocumentEntity findByDocumentIdAndParentDocumentNull(String documentId);
}
I also tried to implement the method using JPQL :
#Query("SELECT d FROM DocumentEntity d WHERE d.documentId = :documentId AND d.parentDocument IS NULL")
DocumentEntity findByDocumentIdAndParentDocumentNull(String documentId);
This query allow to get parent documents and child documents.
My code implementation separates response and database by using a DTO layer.
Issue:
My issue is that I obtain an infinite recursion. I think i'm using #JsonManagedReference and #JsonBackReference correctly. Even adding the same annotations to DTO pojo do not solve issue. If i add those annotation to response POJO, then I do not obtain child documents.
Could not write JSON: Infinite recursion (StackOverflowError); nested exception is com.fasterxml.jackson.databind.JsonMappingException
Inially I have a DTO class that also self refers to itself.
public class DocumentDto implements Serializable {
private String filePath;
private String mimeType;
private String documentType;
private DocumentDto parentDocument;
Set<DocumentDto> documents;
}
I created a second class without properties that are causing problems;
public class DocumentChildDto implements Serializable {
private String filePath;
private String mimeType;
private String documentType;
}
In the DocumentDto I simply replaced the DocumentDto with DocumentChildDto.
public class DocumentDto implements Serializable {
private String filePath;
private String mimeType;
private String documentType;
private DocumentChildDto parentDocument;
Set<DocumentChildDto> documents;
}
It's more a hack than a technical solution but it works fine. Here childDocumentDto object won't load the parentDocument.

JPA Failing to produce a proper SQL query when a parameter has a composite primary key

Today I came across a weird bug while trying to test a JPA update query and I'm wondering if this a SpringBoot bug.
I have the following entities
An Entry entity
#Entity
#Data
#AllArgsConstructor
#NoArgsConstructor(access = AccessLevel.PROTECTED, force = true)
public class Entry {
#Id
private String id;
#ManyToOne(targetEntity = User.class)
#JoinColumn(referencedColumnName = "username")
#NotNull
private final User username;
#Enumerated(EnumType.STRING)
#NotNull
private Type type;
#ManyToOne(targetEntity = Category.class)
#JoinColumns({#JoinColumn(referencedColumnName = "name"),#JoinColumn(referencedColumnName = "type"),#JoinColumn(referencedColumnName = "username")})
#NotNull
private Category category;
#Size(max = 45)
#NotBlank
private String description;
#NotNull
private Double amount;
#NotNull
private final Date createdAt;
private Timestamp lastUpdate;
#NotNull
private Boolean isDeleted;
public enum Type{
Income,Expense
}
}
A Category entity with a composite key
#Entity
#NoArgsConstructor(access = AccessLevel.PROTECTED, force = true)
#Setter
#Getter
#EqualsAndHashCode(of = {"id"})
#ToString(of = {"id"})
public class Category {
#EmbeddedId
private CategoryId id;
private final Timestamp createdAt = Timestamp.from(Instant.now());
#ManyToOne(targetEntity = User.class)
#JoinColumn(referencedColumnName = "username")
private final User user;
#OneToMany(cascade = CascadeType.ALL,mappedBy = "category")
private List<Entry> entries;
public Category(String name, Type type, User user){
this.id = new CategoryId(name,type,user.getUsername());
this.user = user;
}
}
A CategoryID that is the embeddable composite key of the Category entity
#Data
#Embeddable
#AllArgsConstructor
#NoArgsConstructor(access = AccessLevel.PROTECTED)
#EqualsAndHashCode(of = {"name","type","username"})
public class CategoryId implements Serializable {
private String name;
#Enumerated(EnumType.STRING)
private Type type;
private String username;
}
The following repository
#Repository
public interface EntryRepository extends JpaRepository<Entry, String> {
Optional<Entry> findEntryById(String id);
#Modifying(clearAutomatically = true, flushAutomatically = true)
#Query(value = "UPDATE Entry e SET e.username = :username, e.type = :type, e.category = :category, e.description = :description, e.amount = :amount, e.createdAt = :date, e.lastUpdate = :lastUpdate, e.isDeleted = :isDeleted WHERE e.id = :id")
void update(#Param("id") String id,
#Param("username") User username,
#Param("type") Entry.Type type,
#Param("category") Category category,
#Param("description") String description,
#Param("amount") Double amount,
#Param("date") Date date,
#Param("lastUpdate") Timestamp lastUpdate,
#Param("isDeleted") Boolean isDeleted);
}
And finally the following Unit Test
#Test
void update() {
//given
User testUser = userRepository.save(new User("testUser#test.com","000000000000000000000000000000000000000000000000000000000000"));
Category testCategory = categoryRepository.save(new Category("Test Category", Entry.Type.Income,testUser));
Entry testEntry = new Entry("testEntry",testUser, Entry.Type.Income,
testCategory, "test",
0.0, new Date(343), from(now()), false);
System.out.println(testCategory);
entryRepositoryUnderTest.save(testEntry);
//when
entryRepositoryUnderTest.update("testEntry",testUser,Expense,testCategory,"testUpdated",1.0,new Date(346), from(now()),true);
Optional<Entry> actual = entryRepositoryUnderTest.findEntryById("testEntry");
System.out.println(actual.get().getCategory());
//then
assertThat(actual.get().getUsername()).isEqualTo(testUser);
assertThat(actual.get().getType()).isEqualTo(Expense);
assertThat(actual.get().getCategory()).isEqualTo(testCategory);
assertThat(actual.get().getDescription()).isEqualTo("testUpdated");
assertThat(actual.get().getAmount()).isEqualTo(1.0);
assertThat(actual.get().getIsDeleted()).isEqualTo(true);
}
When I run the test it fails and I get the following error message:
could not execute update query; SQL [update entry set username_username=?, type=?,category_name=?=category_type=?, description=?, amount=?, created_at=?, last_update=?, is_deleted=? where id=?]; nested exception is org.hibernate.exception.DataException: could not execute update query
As you can see here when SpringBoot is trying to produce a SQL query statement from my #Query parameter it can not properly extract the Category field from the parameters and inject it's composite embeddable key into the SQL statement. It has no problem extracting the User parameter because the User is an entity with an id that is not composite.
Is this a SpringBoot bug or am I missing something?
EDIT:
This is the structure of the database

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.

NamedQuery and no entity mapping

I would like to achieve the following. I have a query and I would like to run it and return rows in a REST call.
I do not want to map the query to a physical table, how would I achieve this?
I use Spring Boot 1.5.2.
After some try and fixes, I got the following solution.
Create a POJO class, no #Entity annotation. You want to add packageScan instructions if it is not found.
public class ActivityReport1 {
#Column
private BigInteger id;
#Column
private String title;
//Only getters
public ActivityReport1(BigInteger id,
String title){
this.id = id;
this.title = title;
}
In a class which is annotated with #Entity create the resultset mapping
#SqlResultSetMappings({
#SqlResultSetMapping(name = "ActivityReport1Mapping",
classes = {
#ConstructorResult(targetClass = ActivityReport1.class, columns = {
#ColumnResult(name = "id"),
#ColumnResult(name = "title")
})
})
})
Add repository class
#Repository
#Transactional
public class IActivityReport1Repository {
#PersistenceContext
private EntityManager entityManager;
public List<ActivityReport1> getResults(String userLogin) {
Query query = entityManager.createNativeQuery(
"SELECT " +
"t.request_id as id, t.request_title as title " +
"FROM some_table t ", "ActivityReport1Mapping");
List<ActivityReport1> results = query.getResultList();
return results;
}
}
And finally, the service impl class.
#Service
#Transactional(propagation = Propagation.SUPPORTS, readOnly = true)
public class ActivityReport1ServiceImpl implements IActivityReport1Service {
private static final Logger _Logger = LoggerFactory.getLogger(ActivityReport1ServiceImpl.class);
#Autowired
private IActivityReport1Repository sessionFactory;
#Override
public List<ActivityReport1> runReport(String userLogin) {
List<ActivityReport1> reportRows = sessionFactory.getResults(userLogin);
return reportRows;
}
}
If you face with "Could not locate appropriate constructor", this means that on Java side it could not map db types to java types.
In my case I had to change id from Long to BigInteger and Timestamp to java.util.date.

How to delete entity from database

i am new on hibernate-spring tirple..
i just try to code simple register book.. i have following codes:
Student.java
#Entity(name = "STUDENTS")
#NamedQueries({
#NamedQuery(name = "getAllStudent", query = "SELECT k FROM STUDENTS k ORDER BY k.id DESC"),
#NamedQuery(name = "findByName", query = "SELECT k FROM STUDENTS k WHERE k.name LIKE :name")
})
public class Student {
#Column(name = "STUDENTNO", nullable = false)
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(name = "NAME", nullable = false)
private String name;
#Column(name = "SURNAME")
private String surname;
#Column(name = "AGE")
private String age;
// GET ve SET metods
StduentDAO.java
#Repository
#Transactional(readOnly = true)
public class StudentDAO implements IStudentDAO {
#PersistenceContext
EntityManager em;
#Override
public void deleteStudent(Student student) {
Student temp = em.getReference(Student.class, student.getId());
em.remove(temp);
System.out.println("### getting out from studentDAO deleteStudent method ###")
StudentController.java
#Component
#Scope(value = "request")
public class StudentController {
#Autowired
IStudentDAO studentDAO;
List<Student> allStudentList = new ArrayList();
Student student = new Student();
#PostConstruct
private void loadStudents() {
allStudentList = studentDAO.allStudent();
public void deleteStudent() {
studentDAO.deleteStudent(student);
System.out.println("### getting out from StudentController deleteStudent method ### ");
}
When I run deleteStudent() codes i am getting:
"### getting out from studentDAO deleteStudent method ###"
"### getting out from StudentController deleteStudent method ### "
i see these on output but nothing is deleting from database.. i searched a bit and i found this "every entitiy manager's methods open own session." that is why it says i should write my StudentDAO's deleteStudent methof like above..
i think i am missing something about transaciton but i have not recognized yet..
what should i do about this ?
Thanks..
#Transactional annotation create a transaction on your DBMS.
If you use (readOnly = true) you prevent operation on your DB (as INSERT/UPDATE/DELETE).
Remove readOnly = true so your delete method will work.

Resources