I have spent the whole weekend trying to debug this piece of code. I have a Spring RestController :
import com.tsakirogf.schedu.model.ContactMean;
import com.tsakirogf.schedu.model.DefaultContactMean;
import com.tsakirogf.schedu.model.human.Business;
import com.tsakirogf.schedu.services.BusinessService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*;
import java.util.Optional;
import java.util.Set;
#RestController
#RequestMapping("api/v1/business/")
public class BusinessController
{
#Autowired
BusinessService businessService;
#GetMapping(value = "businesss")
Iterable<Business> list()
{
Iterable<Business> retVal = businessService.findAll();
return retVal;
}
#RequestMapping(value = "business", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE)
Business create(#RequestBody Business business)
{
CollectionOfContactMethods collectionOfContact = business.getContact();
collectionOfContact.setBusiness(business);
Set<ContactMean> contactMeanSet = collectionOfContact.getContactMeans();
DefaultContactMean defaultContactMeanSet = collectionOfContact.getDefaultContactMean();
defaultContactMeanSet.getCollectionOfContactMethodsDefault().setId(collectionOfContact.getId());
for (ContactMean element : contactMeanSet)
{
element.setCollectionOfContactMethods(collectionOfContact);
}
collectionOfContact.setDefaultContactMean(defaultContactMeanSet);
business.setContact(collectionOfContact);
Business retval = businessService.save(business);
return retval;
}
#RequestMapping(value = "business/{id}", method = RequestMethod.GET )
Optional<Business> get(#PathVariable Long id)
{
return businessService.findById(id);
}
}
And the service :
public interface BusinessService extends CrudRepository<Business, Long>
{
}
This is the model :
#Table(name = "business")
public class Business
{
#Id
#Column(name = "business_id", nullable = false)
private Long id;
#JsonProperty("name")
private String name;
#Embedded
#JsonProperty("address")
private Address address;
#OneToMany(mappedBy = "business",
cascade = CascadeType.ALL,
fetch = FetchType.LAZY)
#JsonProperty("operatives")
#JsonIgnore
Set<Professional> operatives;
#OneToOne(mappedBy = "business",
cascade = CascadeType.ALL,
fetch = FetchType.LAZY,
optional = false)
#JsonBackReference
#JsonProperty("contact_numbers")
private CollectionOfContactMethods contact;
public Business()
{
}
// Getters and Setters
}
When I send a POST request like this :
Where I got the following
{
"timestamp": "2021-11-01T08:59:06.343+00:00",
"status": 500,
"error": "Internal Server Error",
"path": "/api/v1/business/business"
}
I debug and I am getting InvocationTargetException as seen below
This is the controller, right before save() which seems to throw :
And here is the catch :
I found this article posted in a similar event in StackOverflow but I don't think that's what is happening in this case since I have only H2 database for now.
This is application.properties file :
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=password
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect
spring.h2.console.enabled=true
spring.jpa.hibernate.hbm2ddl.auto=create
I would appreciate any ideas. Thanks for your time.
If you look at your last screenshot you see a message indicating that there is an id field that has no value.
In your entity you have the following declaration of your id field:
#Id
#Column(name = "business_id", nullable = false)
private Long id;
Which indicates to hibernate that it shouldn't generate a key or that there is no database assigned one. Which means you will manually need to set the value for id. If you don't you will run into this exception.
Now I assume that this was a mistake and that you actually wanted to have a sequence or auto-incremented id field. For this add the #GeneratedValue annotation to add this behavior.
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE))
#Column(name = "business_id", nullable = false)
private Long id;
This will instruct hibernate to use a sequence to generate the id upon inserting the entity. If your database supports identity columns you might want to use GenerationType.IDENTITY instead of GenerationType.SEQUENCE.
Related
Spring Boot, Hibernate, bidirectional One-To-Many. Strange behavior. Why is there two selects instead of an error?
I have a basic Spring boot application.
It simulates throwing dices.
I have two entity classes Dice and DiceBatch.
DiceBatch has List<Dice> dices;
Dice has DiceBatch diceBatch; as two sides of bidirectional ManyToOne, or OneToMany.
I use JpaRepository<DiceBatch, UUID> to get one instance of DiceBatch by callig a method of JpaRepository findById(UUID id)
I call this method inside DiceBatchService's method findDiceBatchById(UUID diceBatchId).
Method is marked as #Transactional.
When i do that Hibernate logs one SQL select:
/* select
d
from
DiceBatch d
where
d.id = ?1 */ select
dicebatch0_.dice_batch_id as dice_bat1_1_,
dicebatch0_.batch_creation_time as batch_cr2_1_,
dicebatch0_.batch_name as batch_na3_1_
from
dice_batch dicebatch0_
where
dicebatch0_.dice_batch_id=?
At this point everything is ok.
Method returns DiceBatch entity with lazily initialized List<Dice> dices.
This is important. Method is #Transactional when method returns I should leave transactionla context.
Lazy fields should stay lazy and should cause LazyInitializationException if I try to access them.
Now control goes back to the controller method of DiceBatchController findDiceBatchById(UUID diceBatchId)
And here something strange happens.
Hibernate logs another select
select
dices0_.dice_batch_id as dice_bat5_0_0_,
dices0_.dice_id as dice_id1_0_0_,
dices0_.dice_id as dice_id1_0_1_,
dices0_.dice_batch_id as dice_bat5_0_1_,
dices0_.sequential_number as sequenti2_0_1_,
dices0_.throw_result as throw_re3_0_1_,
dices0_.throw_time as throw_ti4_0_1_
from
dice dices0_
where
dices0_.dice_batch_id=?
...and response JSON contains DiceBatch with all Dice entities related to it.
So I have several question.
Why didn't I get LazyInitializationException?
How come the List<Dice> inside DiceBatch got initialized outside of Transactional context?
How Spring managed to build a complete entity of DiceBatch, including the content of the List<Dice> without any exceptions?
How to modify my code to avoid this strange implicit bahavior?
Here is all the relevant code.
package org.dice.model;
#Entity
#Builder
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
public class Dice {
#Id
#GenericGenerator(name = "UUID",
strategy = "org.hibernate.id.UUIDGenerator")
#GeneratedValue(strategy = javax.persistence.GenerationType.AUTO,
generator = "UUID")
#Column(name = "dice_id",
nullable = false)
private UUID id;
#Column(name = "throw_result",
nullable = false)
private Integer throwResult;
#Column(name = "throw_time",
nullable = false)
private LocalDateTime throwTime;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "dice_batch_id",
nullable = false,
foreignKey = #ForeignKey(name = "fk_dice_dice_batch_id_dice_batch_dice_batch_id")
)
#JsonBackReference
private DiceBatch diceBatch;
#Embedded
private SequentialNumber sequentialNumber;
}
package org.dice.model;
#Entity
#Builder
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
public class DiceBatch {
#Id
#GenericGenerator(name = "UUID",
strategy = "org.hibernate.id.UUIDGenerator")
#GeneratedValue(strategy = GenerationType.AUTO,
generator = "UUID")
#Column(name = "dice_batch_id",
nullable = false)
private UUID id;
#Column(name = "batch_name",
nullable = false)
private String batchName;
#Column(name = "batch_creation_time",
nullable = false)
private LocalDateTime batchCreationTime;
#OneToMany(
mappedBy = "diceBatch",
cascade = CascadeType.ALL,
orphanRemoval = true,
fetch = FetchType.LAZY)
#JsonManagedReference
private List<Dice> dices = new ArrayList<>();
public void addDice(Dice dice) {
dices.add(dice);
dice.setDiceBatch(this);
}
public void removeDice(Dice dice) {
dices.remove(dice);
dice.setDiceBatch(null);
}
}
package org.dice.repo;
#Repository
public interface DiceBatchRepo extends JpaRepository<DiceBatch, UUID> {}
package org.dice.service;
#Service
#RequiredArgsConstructor
public class DiceBatchService {
#Transactional
public DiceBatch findDiceBatchById(UUID diceBatchId) {
DiceBatch diceBatch = diceBatchRepo
.findById_my(diceBatchId)
.orElseThrow();
return diceBatch;
}
}
package org.dice.controller;
public class DiceBatchController {
#GetMapping(path = "/get/{diceBatchId}")
public ResponseEntity<DiceBatch> findDiceBatchById(
#PathVariable(name = "diceBatchId") UUID diceBatchId) {
log.info("<C>[/batch/get] endpoint reached.\n" +
"Dice Batch Id: {}\n",
diceBatchId);
return ResponseEntity.ok(diceBatchService.findDiceBatchById(diceBatchId));
}
}
I want to load all objects from a table without a lazy objects/children and list them on the page (Thymeleaf template), but I get a LazyInitializationException every time. I tried to convert Hibernate entity objects into a POJO that doesnt contains a lazy/unwanted object but with the same result. I also tried open-in-view parameter set to false...
Simple example:
Parent:
#Entity
public class DocumentDbe implements Serializable {
public DocumentDbe(){
}
#Id
#Column(name = "id", updatable = false, nullable = false)
private Long id;
#OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST)
private DocumentFileDbe documentFile;
....
}
Child:
#Entity
public class DocumentFileDbe implements Serializable {
public DocumentFileDbe(){}
#Id
#Column(name = "id", updatable = false, nullable = false)
private Long id;
#Column
#Lob
private byte[] documentData;
...
}
POJO:
public class DocumentDto implements Serializable {
public DocumentDto(){
}
public DocumentDto(DocumentDbe doc){
this.id = doc.getId();
}
....
}
Controller:
#GetMapping("/list")
String getList(Model model) {
List<DocumentDbe> docs;
List<DocumentDto> data = new ArrayList<>();
try (Session ses = sessionFactory.openSession()) {
docs = ses.createQuery("FROM DocumentDbe").list();
docs.forEach(doc -> {
data.add(new DocumentDto(doc));
});
}
model.addAttribute(MODEL_LIST_DATA, data);
return "list";
}
EDIT: Thrown exception:
org.thymeleaf.exceptions.TemplateInputException: An error happened during template parsing (template: "class path resource [templates/list.html]")] with root cause
org.hibernate.LazyInitializationException: could not initialize proxy - no Session
EDIT2:
In DocumentDbe is relation with another object (EAGER this time so I was not paying attention to it) , which has reference to DocumentDbe again.. chained relationship and LazyInitializationException is created...
EDIT3:
Although
This is modified and working controller, without POJO:
#GetMapping("/list")
String getList(Model model) {
List<DocumentDbe> docs;
try (Session ses = sessionFactory.openSession()) {
docs = ses.createQuery("FROM DocumentDbe ORDER BY id DESC").list();
docs.forEach(doc -> {
doc.setDocumentFile(null);
doc.getHistory().forEach(log ->{
log.setDocument(null);
});
});
}
model.addAttribute(MODEL_ADMIN_DATA, docs);
return "list";
}
In class DocumentDbe you have mark relation as Lazy. In default relation #ManyToOne and #OneToOne is as EAGER, so if you don't want Lazy, you have to change
#OneToOne(cascade = CascadeType.PERSIST)
If you want have #lob also as eager:
#Lob
#Basic( fetch = FetchType.EAGER )
I have created a controller which actually ends in an error in my testcase. There are few other controllers which I have made the same way and there the tests work. Currently I am looking for a solution but I am stuck since hours.
The following testcase fails because it results in an http error 500 instead of 200
#ActiveProfiles("Test")
#AutoConfigureMockMvc
#AutoConfigureTestDatabase(replace = AutoConfigureTestDatabase.Replace.NONE)
#Transactional
#SpringBootTest
. . .
#Test
public void whenCreateCustomer_ThenReturnIt() throws Exception {
String customerName = "foobar2";
MvcResult result = mvc.perform(post(REST_CUSTOMERS)
.header(HEADER_AUTH_KEY, authTokenAdminUser.getToken())
.contentType(MediaType.APPLICATION_JSON)
.content("{\n" +
" \"name\": \""+ customerName + "\"" +
"}")
)
.andExpect(status().isOk())
.andReturn();
String responseString = result.getResponse().getContentAsString();
CustomerEntity customer = objectMapper.readValue(responseString, CustomerEntity.class);
assertThat(customer).isNotNull();
assertThat(customer.getName()).isEqualTo(customerName);
assertThat(customer.getCreated()).isNotNull();
}
Here is the method under test. I have debugged it, and it looks good. The entity has been created and also it comes to the point where the ResponseEntity should return ok with the entity in its body. Also I have evaluated this return ResponseEntity.ok().body(createdCustomer.get()); in the debugger and it worked.
#Override
#PostMapping
public ResponseEntity<CustomerDTO> create(#RequestBody CustomerDTO dto) {
dto.setId(uidService.getNextUidScServer());
if (dto.getCreated() == null){
dto.setCreated(LocalDateTime.now());
}
Optional<CustomerDTO> createdCustomer = customerService.create(dto);
if (createdCustomer.isPresent()){
return ResponseEntity.ok().body(createdCustomer.get());
}
else{
return ResponseEntity.badRequest().build();
}
}
In the stacktrace I found this, which I think is my problem. But I have actually no idea how to solve it.
Async:
Async started = false
Async result = null
Resolved Exception:
Type = org.springframework.http.converter.HttpMessageNotWritableException
Here is the entity
#NoArgsConstructor
#AllArgsConstructor
#Data
#Entity
#Builder
#Table(name = "Customer")
public class CustomerEntity {
#Id
#Column(name = "uid")
private Long id;
#Column(name = "name")
private String name;
#Column(name = "created")
private LocalDateTime created;
#OneToMany(
mappedBy = "customer",
cascade = CascadeType.ALL,
orphanRemoval = true)
List<PenEntity> pen;
Here the dto
#Data
#AllArgsConstructor
#NoArgsConstructor
public class CustomerDTO {
private Long id;
private String name;
#JsonDeserialize(using = LocalDateDeserializer.class)
#JsonSerialize(using = LocalDateSerializer.class)
private LocalDateTime created;
#JsonIgnore
private List<PenEntity> pen;
}
From the error message it looks like there is a problem with the json mapping. Any idea?
For the message converter, and you are not using the correct Serializer. You must use the LocalDateTimeSerializer and not the LocalDateSerializer.
#Data
#AllArgsConstructor
#NoArgsConstructor
public class CustomerDTO {
private Long id;
private String name;
#JsonDeserialize(using = LocalDateTimeDeserializer.class)
#JsonSerialize(using = LocalDateTimeSerializer.class)
private LocalDateTime created;
#JsonIgnore
private List<PenEntity> pen;
}
You said you used breakpoints. For the future, if you have an exception you don"t understand, use a breakpoint in the constructors of this exception. Then, use the stack trace to identify which class throws the exception, and how you can fix it.
In my debugger, I saw this:
"Could not write JSON: class java.time.LocalDateTime cannot be cast to class java.time.LocalDate (java.time.LocalDateTime and java.time.LocalDate are in module java.base of loader 'bootstrap')"
You are also suppose to have getters, but as I understood, #Data from Lombok generates them. So you should be good.
I can not make Lazy Load work on Spring.
#Entity
public class Livro {
#JsonInclude(Include.NON_NULL)
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NotEmpty(message = "Campo nome é obrigatorio")
private String nome;
#JsonInclude(Include.NON_NULL)
#JsonFormat(pattern = "dd/mm/yyy")
#NotNull(message = "Campo publicacao é obrigatorio")
private Date publicacao;
#JsonInclude(Include.NON_NULL)
private String editora;
#JsonInclude(Include.NON_NULL)
private String resumo;
#OneToMany( mappedBy = "livro", fetch = FetchType.LAZY )
private List<Comentario> comentarios;
//Comentario.Java
#Entity
public class Comentario {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#JsonProperty("comentario")
private String texto;
private String usuario;
#JsonFormat(pattern = "dd/mm/yyy")
private Date data;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "LIVRO_ID")
#JsonIgnore
private Livro livro;
//LivrosRepository.java
package com.curso.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import com.curso.domain.Livro;
public interface LivrosRepository extends JpaRepository<Livro, Long> {
}
//ComentariosRepository.java
package com.curso.repository;
import org.springframework.data.jpa.repository.JpaRepository;
import com.curso.domain.Comentario;
public interface ComentariosRepository extends JpaRepository<Comentario, Long> {
}
//LivrosService.java
#Service
public class LivrosService {
#Autowired
private LivrosRepository livrosRepository;
#Autowired
private ComentariosRepository comentariosRepository;
// [...]
public List<Livro> listar() {
return livrosRepository.findAll();
}
}
When I make a request to list the books, the behavior I expect is that I list all the data in books, but without the comments, since I'm using the java annotation
fetch = FetchType.LAZY, but the behavior I have is the return of all the data in the workbook.
[
{
"id": 4,
"nome": "Teste2",
"publicacao": "01/01/2018",
"editora": "Polenta",
"comentarios": [
{
"id": 1,
"usuario": "tester",
"data": "26/03/2019",
"comentario": "Comentario 1"
}
]
}
]
Hibernate Session exists within method with #Transactional. Passing entity outside Service class is a bad practise because session is being closed after leaving your listar method. On the other hand your entity contains lazy initialised collections (List<Comentario> comentarios), which cannot be pulled once session is closed.
The good practise is to map entity onto transport object and return those transport objects from service (not raw entities).
First of all you should wrap your public List<Livro> listar() method with #Transactional. Hibernate Session will be alive during execution of this method. It means you can pull lazy initialized elements within this method.
Secondly you should define LivroDto class with all necessary fields and map your Livro entity onto this POJO within this method then return LivroDro from the service.
i really don't know what actually my problem is.
I have two models in my Project.
model-package
Ansprechpartner
Lieferant
Ansprechpartner.java
#Entity
#Table(name = "ANSPRECHPARTNER")
#EntityListeners(AuditingEntityListener.class)
#JsonIgnoreProperties(value = {"anlageAm", "updatedAt"}, allowGetters = true)
public class Ansprechpartner {
...
#NotNull
#ManyToOne
#JoinColumn(name = "lief_code", foreignKey=#ForeignKey(name = "APART_LIEF_FK"))
private Lieferanten liefCode;
public Lieferanten getLiefCode() {
return liefCode;
}
public void setLiefCode(Lieferanten liefCode) {
this.liefCode = liefCode;
}
...
}
Lieferant.java
#Entity
#Table(name = "LIEFERANTEN")
#EntityListeners(AuditingEntityListener.class)
#JsonIgnoreProperties(value = {"anlageAm"}, allowGetters = true)
public class Lieferanten {
...
#Id
private String code;
#OneToMany(mappedBy = "liefCode")
private Set<Ansprechpartner> apart;
public String getCode() {
return code;
}
public void setCode(String code) {
this.code = code;
}
public Set<Ansprechpartner> getApart() {
return apart;
}
public void setApart(Set<Ansprechpartner> apart) {
this.apart = apart;
}
...
}
My Controller:
#RestController
#RequestMapping("/apart")
public class AnsprechpartnerController {
...
#GetMapping("/all/{id}")
public Ansprechpartner getApartWithId(#PathVariable("id") long id) {
Ansprechpartner apart = apartRepository.findOne(id);
return apartRepository.findOne(id);
}
}
When i try to get the json data i get the following problem. Ansprechpartner gets data from Lieferant (because of that join). But then Lieferant again shows data from Ansprechpartner and so on.
Maybe better described with the following picture:
Image with explanation
EDIT:
I finally solved it with the #JsonIgnoreProperties annotation:
In my Ansprechpartner.java i did it this way:
#NotNull
#JsonIgnoreProperties("apart")
// #JsonManagedReference
#ManyToOne
#JoinColumn(
name = "lief_code",
foreignKey=#ForeignKey(name = "APART_LIEF_FK")
)
private Lieferanten liefCode;
And in my Lieferanten.java i did it this way:
// #JsonBackReference
#JsonIgnoreProperties("liefCode")
#OneToMany(mappedBy = "liefCode", fetch = FetchType.LAZY)
private Set<Ansprechpartner> apart;
To avoid infinite recursions you can use #JsonManagedReference & #JsonBackReference
Json Infinite Recursion is one of the most common problems when we serialize Java objects which having Bidirectional-Relationships.
#JsonManagedReference: a part with the annotation will be serialized normally.
#JsonBackReference: a part with the annotation will be omitted from serialization.
like:
#JsonBackReference
private Set<Ansprechpartner> apart;
You can check details in solution-2
Strange behaviour. Possibly you could try:
1) Make sure in the Lieferanten entity, in the equals / hashCode you do not use the Set<Ansprechpartner> apart.
2) You can explicitly detach the entities from the persistence context:
#NotNull
#ManyToOne
#JoinColumn(name = "lief_code"
, foreignKey=#ForeignKey(name = "APART_LIEF_FK")
, cascade={CascadeType.DETACH})
private Lieferanten liefCode;
and then in the controller:
#GetMapping("/all/{id}")
public Ansprechpartner getApartWithId(#PathVariable("id") long id) {
Ansprechpartner apart = apartRepository.findOne(id);
apartRepository.detach(apart);
return apart;
}
you would need to implement a bit -> link, in repository in order to have that available.
3) explicitly add lazy loading: #OneToMany(mappedBy = "liefCode", fetch = FetchType.LAZY).
The root cause is jackson trying to serialize object when object has Bidirectional-Relationships.
You can fixed it by this way
Short way
Better way :
Returning entities directly to view layer is not a good practice.
You should convert entities to DTOs (Data Transfer Object) and pass the DTOs to view