JpaRepository - Inner Join - Subsequent selects - spring-boot

I'm facing a problem when I try to get an list of ServiceCup with its ServiceLanguage. When I try to manipulate the list of ServiceCup in my service layer hibernate is executing a second query and populate my ServiceCup with all ServiceLanguage again.
ServiceCup x ServiceLanguage x LanguageCup
ServiceCup:
#Data
#Entity
#Table(name = "csm_service")
public class ServiceCup extends BaseEntity implements Serializable {
private static final long serialVersionUID = 1L;
private String context;
// bi-directional many-to-one association to CsmServiceLanguage
#OneToMany(mappedBy = "service")
private List<ServiceLanguage> serviceLanguages;
}
ServiceLanguage:
#Data
#Entity
#Table(name = "csm_service_language")
public class ServiceLanguage extends BaseEntity implements Serializable {
private static final long serialVersionUID = 1L;
#Column(name = "translated_name")
private String translatedName;
// bi-directional many-to-one association to CsmLanguage
#ManyToOne
#JoinColumn(name = "csm_language_id_fk")
private LanguageCup language;
// bi-directional many-to-one association to CsmService
#ManyToOne
#JoinColumn(name = "csm_service_id_fk")
private ServiceCup service;
}
JpaRepository:
#Query(value = "select s, sl from ServiceCup s \n" + "INNER JOIN FETCH ServiceLanguage sl on s.id = sl.service \n"
+ "where sl.language.id = :languageId")
List<ServiceCup> findAllServicesByLanguageId(#Param("languageId") String languageId);
Query in repository layer:
select *all_fields* from csm_service servicecup0_ inner join csm_service_language servicelan1_ on (servicecup0_.id=servicelan1_.csm_service_id_fk) where servicelan1_.csm_language_id_fk=?
But in service layer execute a lot of queries to bring all the relations of ServiceCup. I want the ServiceCup objects populate but only with the results that are in the query.
How can I get a ServiceCup object with only the results of the query?
PS: In my method in service layer I have #Transactional(readOnly = true) but if I remove I can't get the objects related to ServiceCup.

I needed to use projection and not the entity to execute the query only once.
ServiceCupProj
public interface ServiceCupProj {
public Long getId();
public String getDescription();
public String getInternalname();
......
Repository
#Repository
public interface CupServiceRepository extends JpaRepository<ServiceCup, Long> {
#Query(value = "select servicecup0_.id as id, servicelan1_.translated_description as description, servicelan1_.translated_name as internalname \n"
+ "from csm_service servicecup0_ inner join csm_service_language servicelan1_ on (servicecup0_.id=servicelan1_.csm_service_id_fk) \n"
+ "where servicelan1_.csm_language_id_fk = :languageId ", nativeQuery = true)
List<ServiceCupProj> findAllServicesByLanguageId(#Param("languageId") String languageId);
......
And in the service layer I transform this ServiceCupProj in the entity that I need.

Related

Spring JPA one to many join

#Entity
#Getter #Setter
public class IndieApp {
#Id
#Column(name = "indie_app_id")
private Long id;
#Column(name = "name")
private String name;
#OneToMany(mappedBy = "indieApp")
private List<Genre> genres = new ArrayList<>();
}
#Entity
#Getter #Setter
public class Genre {
#Id
#Column(name = "genre_id")
private Long genreId;
#Column(name = "description")
private String description; //like "RPG", "Action"
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "indie_app_id")
private IndieApp indieApp;
}
#Data
public class RandomRecDto {
private final Long id;
private final String name;
private final String genres;
}
#Repository
#RequiredArgsConstructor
public class RandomRecRepository {
private final EntityManager em;
public List<RandomRecDto> findRandomApps() {
return em.createQuery(
"select new study.weba.studyJPA.dto.RandomRecDto(i.id, i.name, g.description)" +
" from IndieApp i join i.genres g", RandomRecDto.class)
.setMaxResults(12)
.getResultList();
}
}
Hello seniors!
When i put the dummy data as shown below,
indie_app_id
name
1
App1
2
App2
genre_id
description
indie_app_id
1
Action
1
2
RPG
1
3
FPS
2
4
Sport
2
i can get result like this.
randomRecDto = RandomRecDto(id=1, name=App1, genres=Action)
randomRecDto = RandomRecDto(id=1, name=App1, genres=RPG)
randomRecDto = RandomRecDto(id=2, name=App2, genres=FPS)
randomRecDto = RandomRecDto(id=2, name=App2, genres=Sport)
However, result that i want is like this.
randomRecDto = RandomRecDto(id=1, name=App1, genres=Action, RPG)
randomRecDto = RandomRecDto(id=2, name=App2, genres=FPS, Sport)
I want to get description by array.
What should I do?
Why don't you get IndieApp entities and then convert them into your RandomRecDto DTO? This would even simplify your Repository that would handle IndieApp instead of RandomRecDto:
public interface IndieAppRepository extends JpaRepository<IndieApp, Long> {
List<IndieApp> findTop12();
}
You may want to read more details about Spring Data in the following links:
Spring Data JPA
Spring Data Repositories
Limiting query results with Spring Data JPA
Defining Spring Data Query Methods
Spring Data Repositories Query Keywords
JpaResultMapper jpaResultMapper = new JpaResultMapper();
String sql = "SELECT i.indie_app_id, i.name, group_concat(g.description separator ',') FROM indie_app AS i" +
" JOIN genre AS g ON i.indie_app_id = g.indie_app_id" +
" group by i.indie_app_id, i.name";
Query nativeQuery = em.createNativeQuery(sql);
List<RandomRecDto> results = jpaResultMapper.list(nativeQuery, RandomRecDto.class);

How to retrieve only a specific field from child entity on #OneToOne relationship, not all fields?

When I use jpa's #OneToOne annotation, I want to get the userName field from the table, not all fields. What should I do instead?
#Setter
#Getter
#Entity
public class Menu implements Serializable {
private static final long serialVersionUID = 4462798713783196961L;
/**
* id
*/
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
...
#OneToOne
#JoinColumn(name = "createUserId",referencedColumnName = "userId")
private User createUser;
#Column(nullable = false)
private LocalDateTime createTime;
}
What do I need to do, can I get the userName field in the User object, but not all of it? Thank you in advance.
You can create a POJO with required fields. e.g. You only want id from Menu and userName from User:
public class CustomMenu {
private Long menuId;
private String userName;
public CustomMenu(Long menuId, String userName) {
this.menuId = menuId;
this.userName = userName;
}
// getters, setters
}
Then you can write a query with hql using the constructor in the CustomMenu with parameters new com.yourpackage.CustomMenu(m.id, m.createUser.userName) and join User entity (join m.createUser) :
TypedQuery<CustomMenu> query = entityManager.createQuery("select new com.yourpackage.CustomMenu(m.id, m.createUser.userName)"
+ "from com.yourpackage.Menu m join m.createUser", CustomMenu.class);
List<CustomMenu> menus = query.getResultList();
This generates one sql query with inner join fetching only required fields :
select menu0_.id as col_0_0_, user1_.user_name as col_1_0_ from menu menu0_ inner join user user1_ on menu0_.create_user_id=user1_.user_id

How to use #NamedEntityGraph with #EmbeddedId?

I'm trying to have Spring Data JPA issue one query using joins to eagerly get a graph of entities:
#Entity
#NamedEntityGraph(name = "PositionKey.all",
attributeNodes = {#NamedAttributeNode("positionKey.account"),
#NamedAttributeNode("positionKey.product")
})
#Data
public class Position {
#EmbeddedId
private PositionKey positionKey;
}
#Embeddable
#Data
public class PositionKey implements Serializable {
#ManyToOne
#JoinColumn(name = "accountId")
private Account account;
#ManyToOne
#JoinColumn(name = "productId")
private Product product;
}
Here's my Spring Data repo:
public interface PositionRepository extends JpaRepository<Position, PositionKey> {
#EntityGraph(value = "PositionKey.all", type = EntityGraphType.LOAD)
List<Position> findByPositionKeyAccountIn(Set<Account> accounts);
}
This produces the following exception:
java.lang.IllegalArgumentException: Unable to locate Attribute with the the given name [positionKey.account] on this ManagedType
I want all of the accounts and products to be retrieved in one join statement with the positions. How can I do this / reference the embedded ID properties?
I would suggest refactoring the entity this way if it possible
#Entity
#NamedEntityGraph(name = "PositionKey.all",
attributeNodes = {#NamedAttributeNode("account"),
#NamedAttributeNode("product")
})
#Data
public class Position {
#EmbeddedId
private PositionKey positionKey;
#MapsId("accountId")
#ManyToOne
#JoinColumn(name = "accountId")
private Account account;
#MapsId("productId")
#ManyToOne
#JoinColumn(name = "productId")
private Product product;
}
#Embeddable
#Data
public class PositionKey implements Serializable {
#Column(name = "accountId")
private Long accountId;
#Column(name = "productId")
private Long productId;
}
Such an EmbeddedId is much easier to use. For instance, when you are trying to get an entity by id, you do not need to create a complex key containing two entities.

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.

How to get data from tables in spring data jpa?

I have two tables
#Entity
#Table(name = "TAX_CATEGORY")
public class TaxCategory {
#Id
#GeneratedValue
#Column(name = "ID", nullable = false)
private long id;
#Column(name = "CATEGORY", nullable = false)
private String category;
#Column(name = "TAX", nullable = false)
private Double tax;
#Entity
#Table(name = "PRODUCT")
public class Product {
#Id
#GeneratedValue
#Column(name = "ID", nullable = false)
private long id;
#Column(name = "PRICE", nullable = false)
private Double price;
#Column(name = "NAME", nullable = false)
private String name;
#OneToOne
#JoinColumn(name = "TAX_CATEGORY_ID")
private TaxCategory taxCategory;
Now I want to query
"Select p.name, p.price, t.tax from Product p, TaxCategory t join p.taxCategory.id=t.id"
So List it would return is
ProductName ProductPrice Tax
but I am not able to get this data from two tables. Single table data is working fine.
public interface CustomRepositoryCustom {
public void customMethod();
}
public interface CustomRepository
extends JpaRepository<Account, Long>, CustomRepositoryCustom { }
public class CustomRepositoryImpl implements CustomRepositoryCustom {
public void customMethod() {
Query nativeQuery = entityManager.createNativeQuery("Select p.name, p.price, t.tax from Product p, TaxCategory t join p.taxCategory.id=t.id");
return query.getResultList();
}
}
This throws exception that object is not managed bean. If I create custom object then also it gives similar type of issues.
Use the following JPA query to get the both tables data. Here used jpa query to fetch the product. From product object, can get the taxCategory.
public interface CustomRepository extends JpaRepository<Account, Long>, CustomRepositoryCustom {
Query("select product from Product as product join fetch product.taxCategory as taxCategory where taxCategory.id = :taxCategoryId")
public Product getProductByCategory(#Param Long taxCategoryId);
}
Instead of query method you can directly define JPA method to find products based on category Id as.
#Repository
#RepositoryRestResource
public interface ICountryRepository extends JpaRepository<Product , Long > {
List<Product> findByTaxCategory_Id(#Param Long Id);
}

Resources