Query syntax in URL when using Spring and #QuerydslPredicate - spring

How can I write the HTTP request URL in order to get a query similar to:
select *
from incidents i,
jira_issues ji
where i.incident_id = ji.incident_id
and ji.external_jira_issue_id = 'ABC-123'
and ji.jira_server_id = '1'
I have the following classes:
#Entity(name = "incidents")
public class IncidentEntity {
#OneToMany(
mappedBy = "incident",
cascade = CascadeType.ALL
)
#LazyCollection(LazyCollectionOption.FALSE)
private List<JiraIssueEntity> jiraIssues;
...
}
#Entity(name = "jira_issues")
public class JiraIssueEntity {
#EmbeddedId
#EqualsAndHashCode.Include
private JiraIssueId id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "incident_id")
#ToString.Exclude
private IncidentEntity incident;
...
}
#Embeddable
public class JiraIssueId implements Serializable {
#EqualsAndHashCode.Include
private String externalJiraIssueId;
#EqualsAndHashCode.Include
private String jiraServerId;
}
This is my API method signature:
#GetMapping("")
public Page<Incident> listIncidents(
#QuerydslPredicate(root = IncidentEntity.class) Predicate predicate
);
I know that I can send something like:
/incidents/?jiraIssues.id.externalJiraIssueId=ABC-123&jiraIssues.id.jiraServerId=1"
This translates to the following query:
select *
from incidents incidenten0_
where (exists(select 1
from jira_issues jiraissues1_
where incidenten0_.incident_id = jiraissues1_.incident_id
and (lower(jiraissues1_.external_jira_issue_id) like ? escape '!')))
and (exists(select 1
from jira_issues jiraissues2_
where incidenten0_.incident_id = jiraissues2_.incident_id
and (lower(jiraissues2_.jira_server_id) like ? escape '!')))
which is not so good.
I don't know how to:
Do equals and not contains (rows with externalJiraIssueId=ABC-1234 will return as well but I don't want that).
Check that same JiraIssue has externalJiraIssueId=ABC-123 and jiraIssues.id.jiraServerId=1 and not different JiraIssues that each matches one (something like jiraIssues.id=(ABC-123, 1)
Thank you.

regarding the first problem you can make your 'repository' interface extend QuerydslPredicateExecutor and QuerydslBinderCustomizer
then you can override the 'customize' method with something like this:
#Override
default void customize(QuerydslBindings bindings, #NotNull QIncidentEntity root)
{
bindings.bind(String.class)
.first((SingleValueBinding<StringPath, String>)
StringExpression::equalsIgnoreCase);
}
this will make the query check for equals (ignoring the case) and not contains.

Related

How to create a get request for many-to-one columns?

I currently have made a spring boot project which is for an event system. In the model for booking class, I have two objects one is an event and the other one is the user. Now I want to create a get request that allows me to get all bookings made by a single user and all the bookings for a single event respectively. I have managed to create the other requests which are getting all the bookings and getting a booking by the booking id.
Right now if I try to make create any sort of implementation it either gives me a null pointer error or tells me the table relation "booking" doesn't exist. Please let me know if it's possible to write such a get request. Thanks
Model:
#Id
#SequenceGenerator(
name = "booking_sequence",
sequenceName = "booking_sequence",
allocationSize = 1
)
#GeneratedValue(
strategy = GenerationType.SEQUENCE,
generator = "booking_sequence"
)
private Long id;
#ManyToOne
#JoinColumn(
name = "event_id",
referencedColumnName = "id"
)
private Event event;
#ManyToOne
#JoinColumn(
name = "user_id",
referencedColumnName = "id"
)
private User user;
private Integer tickets;
#Transient
private Integer amount;
Repository:
#Repository
public interface BookingRepository extends JpaRepository<Booking, Long > {
#Query
Optional<Booking> findBookingById(Long id);
}
Service:
#Autowired
public BookingService(BookingRepository bookingRepository) {
this.bookingRepository = bookingRepository;
}
public List<Booking> getBookingList() {
return bookingRepository.findAll();
}
public Booking getSingleBooking(Long bookingId) {
return bookingRepository.findBookingById(bookingId).orElseThrow();
}
Controller:
#GetMapping
public List<Booking> getBookings() {
return bookingService.getBookingList();
}
#GetMapping(path = "{bookingId}")
public Booking getSingleBooking(#PathVariable("bookingId") Long bookingId) {
return bookingService.getSingleBooking(bookingId);}
#GetMapping(path = "/user/{userId}")
public List<Booking> getUserBookings(#PathVariable("userId") Long userId) {
return bookingService.getBookingByUser(userId);}
#GetMapping(path = "/event/{eventId}")
public List<Booking> getEventBookings(#PathVariable("eventId") Long eventId) {
return bookingService.getBookingForEvent(eventId);}
you don't need the line
#Query
Optional<Booking> findBookingById(Long id);
the default repository implementation already gives you a findById so you can use it
And #Query can't be used like it used to, you need to pass the query you want, you can find out more here(https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#jpa.query-methods.at-query)
Or you can use this strategy to make your queries https://docs.spring.io/spring-data/jpa/docs/current/reference/html/#jpa.sample-app.finders.strategies
So it is possible to make such requests, all I did was use "nativeQuery" so that it would function the way I want it to. As mentioned I wanted to make two get-requests and here is how I wrote the queries for them.
Getting all user bookings of a specific user using its "ID":
#Query(value = "SELECT * from bookings, user where bookings.user_id = :id", nativeQuery = true)
List<Booking> findByUserid(Long id);
Getting all event bookings of a specific event using its "ID":
#Query(value = "SELECT * from bookings, user where bookings.event_id = :id", nativeQuery = true)
List<Booking> findByEventid(Long id);

I want to input boolean value in ChallengeDto

public class ChallengeDto {
private Long id;
private Category category;
private String title;
private String subTitle;
private boolean like;
private int totalScore;
private int requiredScore;
public ChallengeDto(Long id, Category category, String title, String subTitle, boolean like, int totalScore, int requiredScore) {
this.id = id;
this.category = category;
this.title = title;
this.subTitle = subTitle;
this.like = like;
this.totalScore = totalScore;
this.requiredScore = requiredScore;
}
}
I created challengeDto that include challenge's properties(id, category, title, subtitle, totalScore, requiredScore) and like property(can know that if i like challenge or not).
If I put like button, that information stored challengeLike table.
public class ChallengeLike {
#Id
#GeneratedValue
#Column(name = "challenge_like_id")
private Long id;
#ManyToOne(fetch = LAZY)
#JoinColumn(name = "user_id")
private User user;
#ManyToOne(fetch = LAZY)
#JoinColumn(name = "challenge_id")
private Challenge challenge;
private LocalDateTime createDate;
}
Now I'm trying to write a code to retrieve challengeDto that checks if I clicked like or not, but I'm having a problem... I can't think of what kind of code to make.
#Repository
#RequiredArgsConstructor
public class ChallengeDtoRepository {
private final EntityManager em;
#Transactional
public List<ChallengeDto> findChallenges(Long userId) {
return em.createQuery(
"select new " +
"com.example.candy.controller.challenge.ChallengeDto(c.id,c.category,c.title,c.subTitle,????,c.totalScore,c.requiredScore)" +
" from Challenge c" +
" left join ChallengeLike cl on c.id = cl.challenge.id" +
" and cl.user.id = : userId", ChallengeDto.class)
.setParameter("userId", userId)
.getResultList();
}
}
try to rename the field to likeDone or something different than like, it makes the code ambiguous.
However, just simply do:
cl.likeDone
which means:
return em.createQuery(
"select new " +
"com.example.random.demo.dto.ChallengeDto(c.id,c.category,c.title,c.subTitle,cl.likeDone,c.totalScore,c.requiredScore)" +
" from Challenge c" +
" left join ChallengeLike cl on c.id = cl.challenge.id" +
" where cl.user.id = : userId", ChallengeDto.class)
.setParameter("userId", userId)
.getResultList();
However, try to use JPA if you don't have any mandatory condition to use native query or jpql.
JPA implementation:
#Repository
public interface ChallengeLikeRepository extends JpaRepository<ChallengeLike, Long> {
List<ChallengeLike> findAllByUser_Id(long userId);
}
Just call the repository method from service layer and map to your required dto:
public List<ChallengeDto> findChallenges(Long userId) {
List<ChallengeLike> entities = this.repository.findAllByUser_Id(userId);
return entities.stream().map(this::mapToDto).collect(Collectors.toList());
}
The mapToDto() method converts the entity to corresponding ChallengeDto
private ChallengeDto mapToDto(ChallengeLike x) {
return ChallengeDto.builder()
.category(x.getChallenge().getCategory())
.id(x.getChallenge().getId())
.like(x.isLikeDone())
.requiredScore(x.getChallenge().getRequiredScore())
.subTitle(x.getChallenge().getSubTitle())
.title(x.getChallenge().getTitle())
.totalScore(x.getChallenge().getTotalScore())
.userId(x.getUser().getId())
.build();
}
For your convenience, some properties has been added or changed in some classes. The #Builder annotation has been added to the ChallengeDto class. The rest of the corresponding entity and other classes:
a) ChallengeLike.java
#Entity
#Data
public class ChallengeLike {
#Id
#GeneratedValue
#Column(name = "challenge_like_id")
private Long id;
#ManyToOne
#JoinColumn(name = "user_id")
#JsonIgnoreProperties("challengeLikes")
private User user;
#ManyToOne
#JoinColumn(name = "challenge_id")
#JsonIgnoreProperties("challengeLikes")
private Challenge challenge;
private boolean likeDone;
private LocalDateTime createDate;
}
b) Challenge.java
#Entity
#Data
public class Challenge {
#Id
private Long id;
private Category category;
private String title;
private String subTitle;
private int totalScore;
private int requiredScore;
#OneToMany(mappedBy = "challenge", cascade = CascadeType.ALL)
#JsonIgnoreProperties("challenge")
private List<ChallengeLike> challengeLikes = new ArrayList<>();
}
c) Category.java
public enum Category {
CAT_A,
CAT_B
}
Update
If you want to fetch Challenge entity instead of ChallengeLike and map that to ChallengeDto, first implement ChallangeRepository:
#Repository
public interface ChallengeRepository extends JpaRepository<Challenge, Long> {
}
Add the fetchType to EAGER in Challange Entity class:
#OneToMany(mappedBy = "challenge", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JsonIgnoreProperties("challenge")
private List<ChallengeLike> challengeLikes = new ArrayList<>();
And to map the Challenge to ChallengeDto, you can add another mothod as follows:
private ChallengeDto mapToDto(Challenge x) {
return ChallengeDto.builder()
.category(x.getCategory())
.id(x.getId())
.like(!x.getChallengeLikes().isEmpty() && x.getChallengeLikes().get(0).isLikeDone())
.requiredScore(x.getRequiredScore())
.subTitle(x.getSubTitle())
.title(x.getTitle())
.totalScore(x.getTotalScore())
.userId(x.getUserId()) // if you have user reference in Challenge, remove this otherwise.
.build();
}
finally, to incorporate everything properly, change the caller:
public List<ChallengeDto> findChallenges(Long userId) {
List<Challenge> entities = this.repository.findAll();
List<ChallengeDto> entitiesWithoutChallengeLikes = entities.stream()
.filter(x -> x.getChallengeLikes() == null
|| x.getChallengeLikes().isEmpty())
.map(this::mapToDto).collect(Collectors.toList());
List<ChallengeDto> entitiesInferredFromChallengeLikes = entities.stream()
.filter(x -> x.getChallengeLikes() != null && !x.getChallengeLikes().isEmpty())
.flatMap(x -> x.getChallengeLikes().stream())
.map(this::mapToDto)
.collect(Collectors.toList());
entitiesInferredFromChallengeLikes.addAll(entitiesWithoutChallengeLikes);
return entitiesInferredFromChallengeLikes;
}
Final Update
Well, I finally understood properly what you expected. Adopt the following changes to the previous solution and you will get exactly what you want.
Change the 2 occurrence of the following in the findChallanges method:
.map(this::mapToDto)
To:
.map(x -> mapToDto(x, userId))
And the two mapToDto functions will be changed to follows:
private ChallengeDto mapToDto(ChallengeLike x, long userId) {
return ChallengeDto.builder()
.category(x.getChallenge().getCategory())
.id(x.getChallenge().getId())
.like(x.getUser().getId() == userId && x.isLikeDone())
.requiredScore(x.getChallenge().getRequiredScore())
.subTitle(x.getChallenge().getSubTitle())
.title(x.getChallenge().getTitle())
.totalScore(x.getChallenge().getTotalScore())
.userId(x.getUser().getId())
.build();
}
private ChallengeDto mapToDto(Challenge x, long userId) {
return ChallengeDto.builder()
.category(x.getCategory())
.id(x.getId())
.like(false)
.requiredScore(x.getRequiredScore())
.subTitle(x.getSubTitle())
.title(x.getTitle())
.totalScore(x.getTotalScore())
.userId(userId)
.build();
}

Spring data JPA derived query for multiple #OneToMany entities and inner entity localization

I am trying to do a simple task with Spring Data JPA derived queries and am unable to get the desired results from the query. Basically I have a Book which can have one or many Chapters with localization support for the Book as well as the Chapter. I want to create a query which would fetch a language specific book (with chapters) based on the Locale. Here are my four entities.
#Entity
#Getter
#Setter
public class Book {
#Id
#Column(name = "ID")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private int noOfPages;
/**
* Both mappings below are unidirectional #OneToMany
*/
#OneToMany(cascade = CascadeType.ALL)
#JoinColumn(name = "BOOK_ID", referencedColumnName = "ID")
private List<BookTranslation> bookTranslations;
#OneToMany(cascade = CascadeType.ALL)
#JoinColumn(name = "BOOK_ID", referencedColumnName = "ID")
private List<Chapter> chapters;
/**
* Constructor for JPA
*/
protected Book() {
}
public Book(int noOfPages, List<BookTranslation> bookTranslations, List<Chapter> chapters) {
this.noOfPages = noOfPages;
this.bookTranslations = bookTranslations;
this.chapters = chapters;
}
}
#Entity
#Getter
#Setter
public class BookTranslation {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Enumerated(EnumType.STRING)
private Language language;
private String name;
/**
* Constructor for JPA
*/
protected BookTranslation() {
}
public BookTranslation(Language language, String name) {
this.language = language;
this.name = name;
}
}
#Entity
#Getter
#Setter
public class Chapter {
#Id
#Column(name = "ID")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private int chapterNumber;
#OneToMany(cascade = CascadeType.ALL)
#JoinColumn(name = "CHAPTER_ID", referencedColumnName = "ID")
private List<ChapterTranslation> chapterTranslations;
/**
* Constructor for JPA
*/
protected Chapter() {
}
public Chapter(int chapterNumber, List<ChapterTranslation> chapterTranslations) {
this.chapterNumber = chapterNumber;
this.chapterTranslations = chapterTranslations;
}
}
#Entity
#Getter
#Setter
public class ChapterTranslation {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Enumerated(EnumType.STRING)
private Language language;
private String title;
/**
* Constructor for JPA
*/
protected ChapterTranslation() {
}
public ChapterTranslation(Language language, String title) {
this.language = language;
this.title = title;
}
}
public enum Language {
EN, FR
}
Below is the sample code, I am using to persist these entities. Ignore the #GetMapping please, this is just a sample.
#GetMapping("/persist-book")
public void persistBook() {
ChapterTranslation enChapter = new ChapterTranslation(Language.EN, "What is Java persistence?");
ChapterTranslation frChapter = new ChapterTranslation(Language.FR, "Qu'est-ce que la persistance Java?");
List<ChapterTranslation> chapterOneTranslation = new ArrayList<>();
chapterOneTranslation.add(enChapter);
chapterOneTranslation.add(frChapter);
Chapter chapterOne = new Chapter(1, chapterOneTranslation);
List<Chapter> chapters = new ArrayList<>();
chapters.add(chapterOne);
BookTranslation enBook = new BookTranslation(Language.EN, "JPA WikiBook in English");
BookTranslation frBook = new BookTranslation(Language.FR, "JPA WikiBook in French");
List<BookTranslation> bookTranslations = new ArrayList<>();
bookTranslations.add(enBook);
bookTranslations.add(frBook);
Book book = new Book(500, bookTranslations, chapters);
bookRepository.save(book);
}
My BookRepository looks as follows:
public interface BookRepository extends CrudRepository<Book, Long> {
List<Book> findBooksByBookTranslations_LanguageAndChapters_ChapterTranslations_Language(Language lang1, Language lang2);
}
Sample code I am using to retrieve the result.
#GetMapping("/english-book")
public List<Book> retrieveEnglishBook() {
return bookRepository.findBooksByBookTranslations_LanguageAndChapters_ChapterTranslations_Language(
Language.EN, Language.EN
);
}
My expected output is as attached in the image below.
One thing that I noticed from the Hibernate logs is that Hibernate makes a total of four select queries and the first query output is exactly what I need. However, since this a method name based query I don't suppose I can control that.
EDIT 1: Before trying out the answer, I was getting all books with all their locales returned, after changing my query to the one given in the accepted answer I was able to get the Book with the selected locale.
Please note: I also had to change all collections from using a List to a Set, more on this can be read about in the accepted answers link.
What you describe as a desired result is a single database result.
I guess what you mean by that is you expect to get all the books but only with the translations in a single language.
You don't describe what you actually get, so assume you are getting the book with all available translations.
Your desired result is beyond the capabilities of derived queries.
The different predicates of a derived queries all limit the root entities to be returned Book in your case. They should still have all references in tact.
You could achieve your goal with an annotated query like this:
public interface BookRepository extends CrudRepository<Book, Long> {
#Query("SELECT b FROM Book b
JOIN FETCH b.bookTranslations as bt
JOIN FETCH b.chapter as c
JOIN FETCH c.chapterTranslation as ct
WHERE bt.language = :lang
AND ct.language = :lang")
List<Book> findBooksByLanguage(Language lang);
}
See also How to filter child collection in JPQL query?
Side note: query derivation should only be used when the resulting method name is VERY similar to what you would have named the method anyway.

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;
}
}

Hibernate join two entities

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

Resources