Hibernate join one to many by java - java

I'm using Hibernate and I have two tables:
Table A:
#Table(name = "users")
public class User extends BaseEntity {
#OneToMany(fetch = FetchType.LAZY, cascade=CascadeType.ALL)
#JoinColumn(name = "book_id")
private List<Book>book;
}
and
Table B
#Table(name = "books")
public class Book extends BaseEntity {
#Column(name = "nameOfBook")
private String nameOfBook;
#Column(name = "userId")
private String userId;
}
I have to do the following query:
select *
from users, books
where books.nameOfBook == 'duck' and users.id == books.userId
My problem that I have not understood how to convert the SQL in Java like this:
Subquery<String>query = query.subquery(String.class);
Root<Supplier> psFrom = supplierQuery.from(User.class);
Path<String> expression = psFrom.get("id");
supplierQuery.select(expression);

Related

How to take data from crosstabs from Database related ManyToMany relationship using Spring Boot / Thymeleaf

There are two tables Books and Users. They are connected by a many-to-many relationship and have a crosstab between them with columns book_id, reader_id, start date, and end date. I need to take data from this table as User.login, Books.title, start_date and end_date. How can I get data from this table if I have the following entities:
Book Entity:
#Entity
#Table(name = "books")
public class Book {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#Column(name = "title")
private String title;
#Column(name = "publishing_year")
private Integer publishingYear;
#Column(name = "sell_cost")
private BigDecimal sellCost;
#Column(name = "rent_cost")
private BigDecimal rentCost;
#Column(name = "amount")
private Integer amount;
//=======Relationships=======
#ManyToMany(cascade = {CascadeType.DETACH,CascadeType.MERGE,CascadeType.PERSIST,CascadeType.REFRESH,CascadeType.REMOVE})
#JoinTable(name = "rented_books",
joinColumns = #JoinColumn(name = "book_id"),
inverseJoinColumns = #JoinColumn(name = "reader_id"))
private List<User> usersRented;
}
User Entity:
#Entity
#Table(name = "users")
public class User {
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(name = "username")
private String login;
#Column(name = "password")
private String password;
#Column(name = "name")
private String name;
#Column(name = "surname")
private String surname;
#Column(name = "wallet")
private Integer wallet;
#Column(name = "enabled")
private boolean enabled;
//=======Relationships=======
#ManyToMany(mappedBy = "usersRented")
private List<Book> rentedBooks;
Book Repository
#Repository
public interface BookRepository extends CrudRepository<Book,Long> {
#Query(value = "SELECT b.title,rb.start_date,rb.expiration_date FROM books b INNER JOIN rented_books rb ON rb.book_id = b.id INNER JOIN users u ON u.id = rb.reader_id WHERE u.id = ?1",nativeQuery = true)
Page<Book> findAllRentedBookByUser(Long id, Pageable pageable);
}
But this query doesn't work, throws this error:
java.sql.SQLException: Column 'id' not found.
Although in DBMS this query works fine
You are returning the Book entity so you need to select all fields for that entity. You can't return a partially filled entity, because JPA does not support partial entity population from native queries.
According to JPA specification:
3.10.16.1 Returning Managed Entities from Native Queries
When an entity is to be returned from a native query, the SQL
statement should select all of the columns that are mapped to the
entity object. This should include foreign key columns to related
entities. The results obtained when insufficient data is available are
undefined
Example of correct query:
#Repository
public interface BookRepository extends CrudRepository<Book,Long> {
#Query(value = "SELECT b.* FROM books b INNER JOIN rented_books rb ON rb.book_id = b.id INNER JOIN users u ON u.id = rb.reader_id WHERE u.id = ?1", nativeQuery = true)
Page<Book> findAllRentedBookByUser(Long id, Pageable pageable);
}
In case you want to return a custom column set you need to create an additional simple POJO class and define a mapping for it.
Example: How to fix convertion error in Nativequery in Spring-boot
1. Create a custom POJO class
import java.util.Date;
public class BookDetails {
private String title;
private Date start_date;
private Date expiration_date;
public BookDetails(String title, Date start_date, Date expiration_date) {
this.title = title;
this.start_date = start_date;
this.expiration_date = expiration_date;
}
public String getTitle() {
return this.title;
}
public Date getStart_date() {
return this.start_date;
}
public Date getExpiration_date() {
return this.expiration_date;
}
public void setTitle(String title) {
this.title = title;
}
public void setStart_date(Date start_date) {
this.start_date = start_date;
}
public void setExpiration_date(Date expiration_date) {
this.expiration_date = expiration_date;
}
}
2. Define #NamedNativeQuery and mapping for POJO class
import javax.persistence.*;
import java.math.BigDecimal;
import java.util.List;
#Entity
#Table(name = "books")
#NamedNativeQuery(name ="BookDetailsByUser",
query =
" SELECT b.title, rb.start_date, rb.expiration_date " +
" FROM books b INNER JOIN rented_books rb ON rb.book_id = b.id INNER JOIN users u ON u.id = rb.reader_id " +
" WHERE u.id = ?1 ",
resultSetMapping = "BookDetailsMapping"
)
#SqlResultSetMapping(name="BookDetailsMapping",
classes = {
#ConstructorResult(targetClass = BookDetails.class,
columns = {#ColumnResult(name = "title"),
#ColumnResult(name = "start_date"),
#ColumnResult(name = "expiration_date")
})
})
public class Book {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#Column(name = "title")
private String title;
#Column(name = "publishing_year")
private Integer publishingYear;
#Column(name = "sell_cost")
private BigDecimal sellCost;
#Column(name = "rent_cost")
private BigDecimal rentCost;
#Column(name = "amount")
private Integer amount;
//=======Relationships=======
#ManyToMany(cascade = {CascadeType.DETACH,CascadeType.MERGE,CascadeType.PERSIST,CascadeType.REFRESH,CascadeType.REMOVE})
#JoinTable(name = "rented_books",
joinColumns = #JoinColumn(name = "book_id"),
inverseJoinColumns = #JoinColumn(name = "reader_id"))
private List<User> usersRented;
}
3. Use named query in repository
#Repository
public interface BookRepository extends CrudRepository<Book,Long> {
#Query(name = "BookDetailsByUser", nativeQuery = true)
Page<BookDetails> findBookDetailsByUser(Long id, Pageable pageable);
}

Fetch all children as a list with Spring Data JPA using projection

I want to use projections to fetch only specific fields from my database with Spring Data JPA.
This is my (shortened) data model:
#Entity
#Table(name = "data")
#Data
public class DataEntity {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
private String name;
private String description;
#LazyCollection(LazyCollectionOption.FALSE)
#Fetch(value = FetchMode.SUBSELECT)
#OneToMany(mappedBy = "data", fetch = FetchType.LAZY)
#Builder.Default
private List<OwnerEntity> owners = new ArrayList<>();
}
#Entity
#Table(name = "owner")
#Data
public class OwnerEntity {
#EmbeddedId
public OwnerId id = new OwnerId();
#Fetch(value = FetchMode.JOIN)
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name="userId", insertable = false, updatable = false)
private UserEntity user;
#ManyToOne
#JoinColumn(name="dataId", insertable = false, updatable = false)
private InterfaceEntity iface;
}
#Embeddable
#Data
public class OwnerId implements Serializable {
private Integer dataId;
private String userId;
}
#Entity
#Table(name = "users")
#Data
public class UserEntity {
#Id
private String id;
private String name;
private String mail;
}
This is my projection:
public interface DataProjection {
String getName();
String getDescription();
List<UserEntity> getOwners();
}
Finally, this is my DAO:
public interface DataDao extends CrudRepository<DataEntity, Integer> {
#Query("select d.name as name, " +
" d.description as description, " +
" o.user as owners " +
"from DataEntity d " +
"left join d.owners o " +
"order by d.name")
List<DataProjection> getData();
}
It generally works but it returns one row for each owner resulting in multiple same DataProjections with a list containing only one of the owners.
A similar problem was mentioned in this question but as mentioned in the solutions comments this would make it an open projection loading all columns.
Is there a solution other than mapping the resulting rows programmatically?

JPA join in spring boot application

I've read examples but have my personal question to you.
I have 2 tables:
Role:
id, name
User:
id, login, name, role_id
Role entity
#Entity
#Table(name = "role")
public class Role {
#Id
#Column(name = "id")
private long id;
#Column(name = "name", length = 45)
private String name;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "role")
private Set<User> user = new HashSet<>();
//getters and setters
User entity
#Entity
#Table(name = "user")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id",insertable = false, updatable = false)
private long id;
#Column(name = "login")
private String login;
#Column(name = "user_name")
private String userName;
#ManyToOne(fetch = FetchType.LAZY)
private Role role;
//getters and setters
And repository:
public interface UserRepository extends JpaRepository<User, Long> {
String Q_GET_ALL_USERS = "from User u left join Role r on u.role_id=r.id";
#Query(Q_GET_ALL_USERS)
Collection<User> getAllUsers();
This code is showing: Caused by: java.lang.IllegalArgumentException: org.hibernate.hql.internal.ast.QuerySyntaxException: Path expected for join! [from com.example.jpa.model.User u left join Role r on u.role_id=r.id]
How I understand entity can't contains 'id' (in my case in Role) for references and I should remove this field. But entity should have '#Id'.
In this case I should create new column in 'Role'? or I can use more beautiful decision?
I put all project to bb
To use join in HQL (JPQL) you don't need on clause
String Q_GET_ALL_USERS = "select u from User u left join u.role";
This query doesn't have any sence because of you don't use role in the where clause.
If you want to get users with a fetched role you can use join fetch
String Q_GET_ALL_USERS = "select u from User u left join fetch u.role";
Update
Your schema for User and Role is not commonly used. I advice to you make #ManyToMany association from user to roles and remove any user association from the Role
#Entity
#Table(name = "user")
public class User {
#ManyToMany(fetch = FetchType.LAZY)
private Set<Role> roles;
}
#Entity
#Table(name = "role")
public class Role {
#Id
#Column(name = "id")
private long id;
#Column(name = "name", length = 45)
private String name;
}
No, you should create a new column in User.
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "role_id")
private Role role;
Thank you all for answers. Right entities and query below (plus tables schema).
Tables (queries)
CREATE TABLE role (
id INT NOT NULL PRIMARY KEY,
name VARCHAR(45) NOT NULL
);
CREATE TABLE user (
id INT NOT NULL PRIMARY KEY IDENTITY,
login VARCHAR(45) NOT NULL,
user_name VARCHAR(45) NOT NULL,
role_id INT NOT NULL,
FOREIGN KEY (role_id) REFERENCES role (id)
);
Entities:
#Entity
#Table(name = "user")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id",insertable = false, updatable = false)
private long id;
#Column(name = "login")
private String login;
#Column(name = "user_name")
private String userName;
#ManyToOne(fetch = FetchType.LAZY)
private Role role;
//getters and setters
}
and
#Entity
#Table(name = "role")
public class Role {
#Id
#Column(name = "id")
private long id;
#Column(name = "name", length = 45)
private String name;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "role")
private Set<User> user = new HashSet<>();
//getters and setters
}
Repository
public interface UserRepository extends JpaRepository<User, Long> {
String Q_GET_ALL_USERS = "select u from User u left join u.role";
#Query(Q_GET_ALL_USERS)
Collection<User> getAllUsers();
}
#v-ladynev proposed alternative decision(use only #ManyToMany in User). More details you can find in comments under this answer.
When I check this decision I will update this answer (I hope I don't forget it :-))
Models
#Entity
#Table(name = "sys_std_user")
public class StdUser {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "class_id")
public int classId;
#Column(name = "user_name")
public String userName;
}
#Entity
#Table(name = "sys_std_profile")
public class StdProfile {
#Id
#Column(name = "pro_id")
public int proId;
#Column(name = "full_name")
public String fullName;
}
Controllers
#PersistenceUnit
private EntityManagerFactory emf;
#GetMapping("/join")
public List actionJoinTable() {
EntityManager em = emf.createEntityManager();
List arr_cust = em
.createQuery("SELECT u.classId, u.userName, p.fullName FROM StdUser u, StdProfile p WHERE u.classId=p.proId")
.getResultList();
return arr_cust;
}
Result:
[
[
1,
"Ram",
"Ram Pukar Chaudhary"
],
[
2,
"Raja",
"Raja Kishor Shah"
]
]

Why does Hibernate put inner join in the case of one-to-one relationship?

I have two entities:
UnsubscribedPartner for unsubscribed from mailing partners
#Entity
#Table(schema = "mailing", name = "unsubscribed_partner")
public class UnsubscribedPartner {
#Id
#Column(name = "partner_id")
private int partnerId;
#Column(name = "unsubscription_date")
private Date date;
#OneToOne(targetEntity = Partner.class, fetch = FetchType.EAGER)
#JoinColumn(name = "partner_id")
private Partner partner;
//GET, SET
}
Partner partner's class
#Entity
#Table(schema = "partner", name = "partner")
public class Partner {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private int id;
#Column(name = "email")
private String email;
#OneToOne(fetch = FetchType.EAGER, mappedBy = "partner")
private UnsubscribedPartner unsubscribedPartner;
//GET, SET
}
I constructed the following criteria query:
String email;
//...
Criteria criteria = getSession().createCriteria(Partner.class);
if(!(email == null)){
criteria.add(Restrictions.eq("email", email));
}
Criteria unsubscribedCrieria = criteria.createCriteria("unsubscribedPartner", "unsbcr");
unsubscribedCrieria.add(Restrictions.isNull("unsbcr.reason"));
But the result SQL query is
select
count(*) as y0_
from
partner.partner this_
inner join
mailing.unsubscribed_partner unsbcr1_
on this_.id=unsbcr1_.partner_id
where
unsbcr1_.unsubscription_reason_id is null
Inner join is not appropriate here, because the unsubscribed_partner tables may not any partner from the partner table, therefore I need LEFT OUTER JOIN instead. How can I fix that?
The documentation states that createCriteria(String, String) is functionally equivalent to createCriteria(String, String, int) using CriteriaSpecification.INNER_JOIN for the joinType.
So, try with createCriteria("unsubscribedPartner", "unsbcr", CriteriaSpecification.LEFT_JOIN) instead.

Hibernate OneToMany relationship inner join

I'm trying to simply filter out results based on the ID property of a child class in a OneToMany relationship, but Hibernate (4.1.9.Final) is generating a Left Outer Join instead of an inner join and thus returning results I don't want.
Model:
Parent:
#Entity
#Table(name = "CATEGORY")
public class Category
{
#Id
#Column(name = "CATEGORYID")
private int ID;
#Column(name = "CATEGORYNAME")
private String name;
#OneToMany(fetch = FetchType.EAGER)
#JoinColumn(name = "CATEGORYID")
#Filter(name = "TEST_RUN_ID_FILTER")
private Collection<TestCase> testCases
...
}
Child:
#Entity
#Table(name = "TESTCASE_NEW")
#FilterDef(name = "TEST_RUN_ID_FILTER", defaultCondition = "TESTRUNID in (:IDS)", parameters = { #ParamDef(name = "IDS", type = "int") })
public class TestCase
{
#Id
#Column(name = "TESTCASEID")
private int ID;
#Column(name = "TESTCASENAME")
private String name;
#Column(name = "STATUS")
private String status;
#Column(name = "TESTRUNID")
private int testRunId;
...
}
DAO:
public List<Category> getAllCategoriesForTestRuns(List<Integer> testRunIDs)
{
Session session = getSession();
session.enableFilter("TEST_RUN_ID_FILTER")
.setParameterList("IDS", testRunIDs);
Query query = session.createQuery("FROM " + Category.class.getSimpleName());
List<Category> result = query.list();
return resul2t;
}
How can I tell Hibernate to use an inner join ?
You can try something as follows:
select c from Category c inner join c.testCases tc

Categories

Resources