Lazy Load in Hibernate - spring

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.

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

When does the hibernate session gets closed

I have created the following entities.
#Entity
public class Student {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
#OneToMany(mappedBy = "student")
private List<Book> books;
}
#Entity
public class Book {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
#ManyToOne
#JoinColumn(name = "STUDENT_ID")
private Student student;
}
My controller looks like this
#RestController
public class Controller {
MyService myService;
public Controller(MyService myService) {
this.myService = myService;
}
#GetMapping("student")
public List<Book> getBooksForStudent(Long id) {
return myService.getBooks(id);
}
}
The service is as follows.
public class MyService {
#Autowired
private StudentRepo studentRepo;
public List<Book> getStudent(Long id) {
Optional<Student> studentOptional = studentRepo.findById(id);
return studentOptional.map(Student::getBooks).orElseThrow(IllegalArgumentException::new);
}
}
I am getting the list of books as expected. But as I'm having lazy loaded list for books I should be getting a LazyInitializationException. I have not added transnational to the method and I'm returning the list of books from the entity itself without mapping it to a DTO. Why is the hibernate session not getting closed after the end of the method?
#RestController is transactional by default. Spring boot automatically registers an OpenEntityManagerInViewInterceptor when you use a web application/you use JPA. Refer #RestController methods seem to be Transactional by default, Why?

How can I add a tenant condition to Spring Data JPA Default and Dervied Queries

I have a Springboot Application with Repositories having Spring Data JPA Queries like findOne, findAll and also derived ones like findByID or findByName etc.
What I want to achieve is multitenancy. All entities have an "account_id" column which holds the tenant.
How do I add a filter like "account_id" to all the queries metioned above without using derived queries that contains those name slike findIdAndAccountid (which would be findone)
#Repository
public interface CategoryRepository extends JpaRepository<Category, Long> {
Category findByName(String name);
}
Here's the corresponding entity
#Entity
#Table(name = "unit")
#Data
public class Unit {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
private String name;
#Column(name = "account_id")
private Long account_id;
}
I know most people use schemas as tenant separation but that's impossible for me. Is there a way (I didn't find one) to add such a tenant filter condition on those queries without writing NamedQueries or using DerivedQueries. An elegeant solution like annotate the repository or entity or maybe the queries that all queries should add the additional filter "account_id"?
You can add Where clause on your Entity classes (Didnt had time to test )
#Entity
#Table(name = "unit")
#Data
#Where(clause = "account_id= :account_id")
public class Unit {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
private String name;
#Column(name = "account_id")
private Long account_id;
}
Update and Solution
1. Create a Filter & FilterDef on the entity like so
#FilterDef(name="accountFilter", parameters=#ParamDef( name="accountId", type="long" ) )
#Filters( {
#Filter(name="accountFilter", condition=":accountId = account_id")
} )
public class Category {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private Long id;
private String name;
#Column(name = "account_id")
private Long account_id;
}
enable filtering in the controller by autowiring entitymanager, writing a method to enable the filter and activate the filter in #ModelAttribute for each request
#RestController
#RequestMapping(path = "/categories",produces = MediaType.APPLICATION_JSON_VALUE )
public class CategoryController {
private final CategoryRepository repository;
#Autowired
private EntityManager entityManager;
CategoryController(CategoryRepository repository) {
this.repository = repository;
}
private void activateFilter() {
Session session = entityManager.unwrap(Session.class);
Filter filter = session.enableFilter("accountFilter");
filter.setParameter("accountId", Long.valueOf(TenantContext.getCurrentTenant()));
}
#ModelAttribute
public void initFilter() {
activateFilter();
}
... your rest methods here
}

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 loading doesn't work for OneToMany relationship on Spring Boot

I've been working with spring boot data jpa + spring + mysql + thymeleaf and have encountered a problem.
Is a #OneToMany (fetch = FetchType.LAZY) relationship for a list, the list loads hibernate like #OneToMany (fetch = FetchType.EAGER) outside. But if I use the annotation #ManyToOne this does work perfectly.
Any idea why it happens this behavior?
By the way, I want to keep the spring.jpa.open-in-view = true property. I debug an application Author - Book (One to Many) developed in IntelliJ IDEA.
Here's the Book class.
#Entity
public class Book implements Serializable {
#Id
#GeneratedValue
private Integer id;
#NotNull
private String name;
#NotNull
#JoinColumn(name = "author_id")
#ManyToOne(fetch = FetchType.LAZY)
private Author author;
/* getters and setters */
}
Here the class Author
#Entity
public class Author implements Serializable {
#Id
#GeneratedValue
private Integer id;
#NotNull
private String name;
#OneToMany(mappedBy = "author", fetch = FetchType.LAZY)
private List<Book> bookList;
/* getters and setteres */
}
The controller for debug.
#Controller
#RequestMapping("/")
public class HomeController {
#Autowired
private AuthorRepository authorRepository;
#Autowired
private BookRepository bookRepository;
#GetMapping
private ModelAndView index() {
List<Author> authorList = authorRepository.findAll();
return new ModelAndView("home"); // first breakpoint
}
#GetMapping("/books")
private ModelAndView viewBooks() {
List<Book> bookList = bookRepository.findAll();
return new ModelAndView("books"); // second breakpoint
}
}
Here's the result.
First breakpoint result
Second breakpoint result
Everything seems to be defined correctly, and after a research, I have done Spring data does use lazy loading plus you defined it on your associations.
I believe that when you use the debugging views in order to tell what inside, then you actually do the fetching.

Resources