Spring JPA - Is there a good way for inner join between entities or the only way is JPQL? - spring

I have the following Entities:
#Entity
public class Organisation {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
// ...
}
#Entity
public class Section{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
// ...
#ManyToOne
#JoinColumn(name = "organisation_id", nullable = false)
private Organisation organisation;
}
#Entity
public class SubSection {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
// ...
#ManyToOne
#JoinColumn(name = "section_id")
private Section section;
}
Now I want to find all SubSections by Organisation Id. Right now I am doing it using JPQL at the SubSectionRepository as above:
public interface SubSectionRepository extends JpaRepository<SubSection, Long>{
#Query(value = "SELECT ss.* FROM sub_section as ss INNER JOIN section as s ON ss.section_id = s.id WHERE s.organisation_id = ?1", nativeQuery = true)
List<SubSection> findByOrganisation(Long organisationId);
}
Is there any way that I can make INNER JOIN using the JpaRepository interface?

Did you try this way
subsectionRepository.findBySectionOrganizationId(long organizationId)

Update your method to
public interface SubSectionRepository extends JpaRepository<SubSection, Long> {
List<SubSection> findBySectionOrganisationId(Long organisationId);
}
Hibernate will generate something like this.
select subsec.*,
from sub_section subsec
left outer join section sec on subsection.section_id=sec.id
left outer join organisation org on section.organisation_id=org.id
where org.id=?
Don't worry about the left join term. It is actually a inner join because you have a where condition with id = on the most right table. Because of that, it effectively becomes a inner join. I.e if there is no record on the right table for that record, it will be ignored.

Related

Is that possible in spring boot that join column (foreign key) with id

I want to join column without object reference. is that possible?
I want to do foreign key without object reference like that
#Data
#Entity
#Table(name = "HRM_EMPLOYEE_SALARY_INCREMENT")
public class EmployeeSalaryIncrement implements Serializable {
private static final long serialVersionUID = 9132875688068247271L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="ID")
private Integer id;
#Column(name = "REFERENCE_NO")
private String referenceNo;
#ManyToOne
#JoinColumn(name = "AUTHORITY", referencedColumnName = "id")
private Integer authority;
#ManyToOne
#JoinColumn(name = "PART_TWO_REGISTER_ID")
private Integer partTwoRegisterId;
#Column(name = "PART_TWO_ORDER_NO")
private String partTwoOrderNo;
#Column(name = "REMARKS")
private String remarks;
#Column(name = "HRM_TYPE")
private Integer hrmType;
}
If I found solve this problem, it will helpful for me.
Joining is not needed in this case. If you only need the foreign key value, then simply add the column as a #Column like any other:
#Data
#Entity
#Table(name = "HRM_EMPLOYEE_SALARY_INCREMENT")
public class EmployeeSalaryIncrement implements Serializable {
private static final long serialVersionUID = 9132875688068247271L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="ID")
private Integer id;
#Column(name = "AUTHORITY")
private Integer authority;
// other fields
// ...
}
No, I don't think that you can join columns between two entities without adding the reference of one to the related entity. You will have to create one entity class corresponding to each of your relational database table and add the reference of one to the other to establish relation.
However, I understand that you may not need all the attributes from your related table based upon your use case, and only wish to select one column from it. You can do that either by only adding required attributes in your joined table entity class (if you are sure you won't need other attributes for that table anywhere else).
Or you can use custom queries using JPQL in your repository class which selects only the required attributes from the tables that you have joined.
I will show you an example of the second way:
//Say, this is your entity class where you wish to join other table to fetch only one attribute from the joined table-
#Entity
#Table(name = "TABLE1", schema = "SCHEMA1")
public class Table1 {
#Id
#Column(name = "ID")
private String id;
#Column(name = "TABLE2_COLUMN")
private String table2Column;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "TABLE2_COLUMN1")
private Table2 table2; //refrence of the joined table entity object
}
// And this is the joined table entity class
#Entity
#Table(name = "TABLE2", schema = "SCHEMA1")
public class Table2 {
#Id
#Column(name = "ID")
private String id;
#Column(name = "TABLE2_COLUMN1")
private String table2Column1;
#Column(name = "TABLE2_COLUMN2")
private String table2Column2; // The column which we want to select from the joined table
}
In your repository class -
#Repository
public interface Table1Repository extends JpaRepository<Table1, String> {
#Query("SELECT t1 FROM Table1 t1 WHERE t1.id = :id")
public List<Table1> getTable1Rows(#Param("id") String id);
#Query("SELECT t1.table2.table2Column2 FROM Table1 t1 WHERE t1.id = :id")
public String getTable2Column2(#Param("id") String id);
}
Based upon the response from Markus Pscheidt below, I agree when he said there's no need to join the entities if you only need the attribute which is a foreign key. As foreign key is already present as an attribute in your entity (or table) you are working with.
If you need to fetch any other column apart from foreign key, then you may use JPQL to fetch the exact column that you wish to select.

Spring Data Hibernate: N+1 problem and pagination

TL;DR -- I cannot find a way to solve the N+1 problem while doing pagination at the same time.
My Entities:
#Entity
public class Invoice {
#Id
#JsonView(InvoiceView.ShortView.class)
#GeneratedValue(strategy = GenerationType.AUTO)
private UUID id;
// other stuff
#OneToMany(targetEntity = InvoiceItem.class)
#JoinColumn(name = "invoice")
private List<InvoiceItem> items;
}
#Entity
public class InvoiceItem {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private UUID id;
// other stuff
}
My repository:
#EntityGraph(attributePaths = {"vendor","items"})
#Query(value = "select i from Invoice i where i.status=:status")
Page<Invoice> getInvoicesWithItemsByStatus(#Param("status") Status status, Pageable pageSpec);
or
#Query(value = "select i from Invoice i join fetch i.items join fetch i.vendor where i.status=:status",
countQuery = "select count(i) from Invoice i where i.status=:status")
Page<Invoice> getInvoicesWithItemsByStatus(#Param("status") Status status, Pageable pageSpec);
Both of these produce this Hibernate warning:
HHH000104: firstResult/maxResults specified with collection fetch; applying in memory!
and they do just that: fetch everything and give back the page requested. Every time a new page is requested, naturally.
Your problem and how to solve it is described in this article by Vlad Mihalcea
https://vladmihalcea.com/fix-hibernate-hhh000104-entity-fetch-pagination-warning-message/
Basically, you would need to write a native query.

findBy not working with inherited properties

I have the following model and repository:
#Entity
#Table(name = "db_user", uniqueConstraints = { #UniqueConstraint(columnNames = "email") })
public class User {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "seq_user")
#SequenceGenerator(name = "seq_user", sequenceName = "seq_user")
#Column(name = "id")
private Long id;
// ...
}
#Entity
#Table(name = "movie")
public class Movie extends AbstractItem {
// Id column inherited from AbstractItem
// ...
}
#Entity
#Table(name = "movie_user")
public class MovieOwnership extends AbstractOwnership {
#ManyToOne
private Movie movie;
// ...
}
#MappedSuperclass
public abstract class AbstractOwnership{
#Id
#SequenceGenerator(name = "seq_default", sequenceName = "seq_default")
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "seq_default")
#Column(name = "id")
private Long id;
#ManyToOne
private User owner;
// ...
}
public interface MovieOwnershipRepository extends QueryDslJpaRepository<MovieOwnership, Long> {
List<MovieOwnership> findByOwnerId(Long ownerId);
MovieOwnership findByOwnerIdAndMovie(Long ownerId, Movie movieId);
List<MovieOwnership> findByOwnerIdAndMovieIdIn(Long ownerId, Set<Long> movieIds);
}
I'm trying to use Spring's findBy requests to fetch MovieOwnerships by owner or movie, using the id field of both entities. I'm able to work directly with the owner's id, but using MovieId in my requests seems broken (I can use the whole Movie object though). In the code above, the first two findBy are fine but the last one throws this exception:
Caused by: java.lang.IllegalArgumentException: Unable to locate
Attribute with the the given name [movieId] on this ManagedType
[carrm.app.data.AbstractOwnership]
It compiles if I try with another property from Movie (like findByMovieTitle), but I can't make it work on the id.
Any idea how to solve this?
I tried the same with JpaRepository instead of QueryDslJpaRepository.
The SQL is generated correctly:
select movieowner0_.id as id1_1_, movieowner0_.owner_id as owner_id2_1_, movieowner0_.movie_id as movie_id3_1_
from movie_ownership movieowner0_
left outer join user user1_ on movieowner0_.owner_id=user1_.id
left outer join movie movie2_ on movieowner0_.movie_id=movie2_.id
where user1_.id=? and (movie2_.id in (?))
So it must be a QueryDslJpaRepository implementation bug.
I would suggest you use JpaRepository instead.

Selecting latest workout set by user

I'm trying to select the latest added workout set associated with a given user.
Users has sessions and sessions has sets. My entities are defined as below.
#Entity(name = "users") // Postgres doesn't like the table name "user"
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#OneToMany(mappedBy = "user")
private Set<Session> sessions;
...
#Entity
public class Session {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#ManyToOne
private User user;
#OneToMany(mappedBy = "session")
private Set<WorkoutSet> sets;
...
#Entity
public class WorkoutSet {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#OneToOne
private User user;
#ManyToOne
private Session session;
private LocalDateTime timestamp = LocalDateTime.now();
...
The following sql query seems to do the trick
select w
from workout_set w
inner join session s
on w.session_id = s.id
where s.user_id = 1
order by w.timestamp DESC
limit 1
But when I'm trying to do something like the below
#Repository
public interface WorkoutSetRepository extends CrudRepository<WorkoutSet, Long> {
#Query("select w from WorkoutSet w inner join Session s on w.session_id = s.id where s.user = :user order by w.timestamp")
List<WorkoutSet> findLastSet(User user, Pageable limit);
I get...
org.springframework.data.mapping.PropertyReferenceException: No property user found for type WorkoutSet!
Any clues about how to do the query right? I'm very open to alternative ways as well because I'd rather avoid writing jpql if possible.
Try this:
#Repository
public interface WorkoutSetRepository extends CrudRepository<WorkoutSet, Long> {
#Query("select w from WorkoutSet w inner join w.session s where s.user = :user order by w.timestamp")
List<WorkoutSet> findLastSet(#Param("user") TestUser user);
}
Note the difference in the join clause. This works with hibernate 5.0.11.

Spring Data/JPS (N+1) SELECTs with OneToOne associations

In my Spring Data application I ran into (N+1) selects issue.
I have a following Spring Data entities:
#Entity
#Table(name = "card_categories")
public class CardCategory extends BaseEntity implements Serializable {
#Id
#SequenceGenerator(name = "card_categories_id_seq", sequenceName = "card_categories_id_seq", allocationSize = 1)
#GeneratedValue(strategy = GenerationType.AUTO, generator = "card_categories_id_seq")
private Long id;
private String name;
...
}
#Entity
#Table(name = "levels")
public class Level extends BaseEntity implements Serializable {
#Id
#SequenceGenerator(name = "levels_id_seq", sequenceName = "levels_id_seq", allocationSize = 1)
#GeneratedValue(strategy = GenerationType.AUTO, generator = "levels_id_seq")
private Long id;
private String name;
...
}
#Entity
#Table(name = "card_categories_levels")
public class CardCategoryLevel extends BaseEntity implements Serializable {
#Id
#SequenceGenerator(name = "card_categories_levels_id_seq", sequenceName = "card_categories_levels_id_seq", allocationSize = 1)
#GeneratedValue(strategy = GenerationType.AUTO, generator = "card_categories_levels_id_seq")
private Long id;
#OneToOne
#JoinColumn(name = "card_category_id")
private CardCategory cardCategory;
#OneToOne
#JoinColumn(name = "level_id")
private Level level;
...
}
and empty Spring Data repository:
#Repository
public interface CardCategoryLevelRepository extends JpaRepository<CardCategoryLevel, Long> {
}
When I try to fetch all CardCategoryLevel entities by cardCategoryLevelRepository.findAll() method it produces 3 SELECTs for an each row in my card_categories_levels table.
In order to use one single JOIN instead of N+1 SELECTs I have reimplemented my CardCategoryLevelRepository:
#Repository
public interface CardCategoryLevelRepository extends JpaRepository<CardCategoryLevel, Long> {
#Query(value = "SELECT ccl FROM CardCategoryLevel ccl LEFT JOIN FETCH ccl.cardCategory cc LEFT JOIN FETCH ccl.level l where cc = :cardCategory and l = :level")
CardCategoryLevel findByCardCategoryAndLevel(#Param("cardCategory") CardCategory cardCategory, #Param("level") Level level);
#Override
#Query(value = "SELECT ccl FROM CardCategoryLevel ccl LEFT JOIN FETCH ccl.cardCategory LEFT JOIN FETCH ccl.level")
List<CardCategoryLevel> findAll();
}
but I'm not sure I did it in a right optimal way.
Please validate my approach and tell it is optimal workaround for (N+1) SELECTs issue with OneToOne associations in Spring Data or no and what is the best way to solve it.
Should I leave it as is or may be move to some other abstraction.. for example like QueryDSL or something like that ?
Thanks to Xtreme Biker I have re-implemented my solution with entity graphs and QueryDSL

Resources