Hibernate OneToMany relationship inner join - java

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

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?

HQL many to many query

I have two tables with many to many relations.
products(id, description, price,image)----> products_category(idProducts, category _id)----> category(id, category_name).
Here is my enteties:
1. Products
#Entity
#Table(name = "products")
public class Products implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "idProducts")
private long id;
#Column(name = "description")
private String description;
#Column(name = "price")
private String price;
#Column(name = "image")
private byte [] image;
public Products() {
}
public Products(String description, String price, byte[] image) {}
#ManyToMany
#JoinTable(
name = "category_products",
joinColumns ={#JoinColumn (name = "Products_idProducts", referencedColumnName = "idProducts")},
inverseJoinColumns = {#JoinColumn(name = "category_id", referencedColumnName = "id")}
)
List<Category> categories = new ArrayList<>();
#ManyToMany
#JoinTable(
name = "users_product",
joinColumns ={#JoinColumn (name = "Products_idProducts", referencedColumnName = "idProducts")},
inverseJoinColumns = {#JoinColumn(name = "users_id", referencedColumnName = "id")}
)
List<Users> usersList = new ArrayList<>();
2.Category
#Entity
#Table(name = "category")
public class Category {
#Id
#Column(name = "id")
private long id;
public Category() {
}
public Category(String category_name) {
this.category_name = category_name;
}
#Column (name = "category_name")
private String category_name;
#ManyToMany(mappedBy = "categories")
private List<Products> products = new ArrayList<>();
I'm try to write query for controller, which return all the products by previosly selected category object with id? i tried many query, but all throws exceptions.
public List<Products> list (Category category) {
//category - object with needed id
Query query;
query = entityManager.createQuery("SELECT c FROM Category c left join c.categories WHERE c.category = :category", Products.class);
query.setParameter("category", category);
return (List<Products>) query.getResultList();
}
java.lang.IllegalArgumentException: org.hibernate.QueryException: could not resolve property: categories of: classes.Category [SELECT c FROM classes.Category c join c.categories WHERE c.category = :category]
org.hibernate.jpa.spi.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1750)
org.hibernate.jpa.spi.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1677)
org.hibernate.jpa.spi.AbstractEntityManagerImpl.convert(AbstractEntityManagerImpl.java:1683)
If you need to retrieve products, you need to do a query that select Product entity, not Category.
So:
return all the products by previosly selected category object with
id
You need to do:
Query query = entityManager.createQuery("SELECT p FROM Product p
JOIN p.categories c
WHERE c.id = :idCategory");
query.setParameter("idCategory", category.getId());
You use LEFT JOIN but this is not necessary in your case, because the unique condition of your query is find a category with a specific ID. This condition will ignore the LEFT part of the JOIN, forcing always a JOIN.
I think there are some errors in your code:
You SELECT c which mean Category but you cast the result list to List<Product>, should be SELECT c.products
Your WHERE c.category = :category clause is not correct because you don't have any category attribute in your Category class, should be WHERE c.id = :id and query.setParameter("id", category.getId());
Hope that help.

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: Multiple filters on an entity

I want to have multiple Hibernate filters on an entity, I have tried everything logical without luck and Google has come up short on this one, as has the Hibernate doc. I can't imagine that this is not possible. (Using Java 6 Hibernate 4.1.9.final)
Currently, I have this:
#Entity
#Table(name = "CATEGORY")
public class Category implements Serializable
{
private static final long serialVersionUID = 1L;
#Id
#Column(name = "CATEGORYID")
private int ID;
#Column(name = "CATEGORYNAME")
private String name;
#OneToMany(fetch = FetchType.EAGER)
#JoinColumn(name = "CATEGORYID")
#OrderBy("TESTCASEID desc")
#Filter(name = "TEST_RUN_ID_FILTER")
private Collection<TestCase> testCases;
...
}
#Entity
#Table(name = "TESTCASE_NEW")
#FilterDef(name = "TEST_RUN_ID_FILTER", defaultCondition = "TESTRUNID in (:IDS)", parameters = { #ParamDef(name = "IDS", type = "int") })
public class TestCase implements Serializable
{
private static final long serialVersionUID = 1L;
#Id
#Column(name = "TESTCASEID")
private int ID;
#Column(name = "TESTCASENAME")
private String name;
...
}
I want to add a second independent filter to the Testcase class. What I am after is something like this:
Select ...
From CATEGORY INNER JOIN TESTCASE on CATEGORY.CATEGORYID = TESTCASE.CATEGORYID
Where TESTCASE.TESTRUNID in (....)
and TESTCASE.TESTCASENAME like '%..%'
This is what I tried
I tried adding multiple #FilterDefs to TestCase like such, but that didn't compile:
#Entity
#Table(name = "TESTCASE_NEW")
#FilterDef(name = "TEST_RUN_ID_FILTER", defaultCondition = "TESTRUNID in (:IDS)",
parameters = { #ParamDef(name = "IDS", type = "int") })
#FilterDef(name = "TESTCASE_NAME_FILTER", defaultCondition = "TESTCASENAME like :TESTCASE_NAME",
parameters = { #ParamDef(name = "TESTCASE_NAME", type = "string") })
public class TestCase implements Serializable
{
private static final long serialVersionUID = 1L;
#Id
#Column(name = "TESTCASEID")
private int ID;
#Column(name = "TESTCASENAME")
private String name;
...
}
The Hibernate documentation led to to try something like this which complained the testrunid filter was non-existent
#Entity
#Table(name = "CATEGORY")
public class Category implements Serializable
{
private static final long serialVersionUID = 1L;
#Id
#Column(name = "CATEGORYID")
private int ID;
#Column(name = "CATEGORYNAME")
private String name;
#OneToMany(fetch = FetchType.EAGER)
#JoinColumn(name = "CATEGORYID")
#OrderBy("TESTCASEID desc")
private Collection<TestCase> testCases;
...
}
#Entity
#Table(name = "TESTCASE_NEW")
#FilterDef(name = "TESTCASE_FILTER", parameters = { #ParamDef(name = "IDS", type = "int"), #ParamDef(name = "TESTCASE_NAME", type = "string") })
#Filters({ #Filter(name = "TEST_RUN_ID_FILTER", condition = "TESTRUNID in (:IDS)"), #Filter(name = "TESTCASE_NAME_FILTER", condition = "TESTCASENAME like :TESTCASE_NAME") })
// #FilterDef(name = "TEST_RUN_ID_FILTER", defaultCondition = "TESTRUNID in (:IDS)", parameters = { #ParamDef(name =
// "IDS", type = "int") })
public class TestCase implements Serializable
{
private static final long serialVersionUID = 1L;
#Id
#Column(name = "TESTCASEID")
private int ID;
#Column(name = "TESTCASENAME")
private String name;
...
}
#SuppressWarnings("unchecked")
public List<Category> getCategories(List<Integer> testRunIDs, String category, String testCaseName)
{
Session session = getSession();
session.enableFilter("FILE_TYPE_FILTER");
if (testRunIDs != null && testRunIDs.size() != 0)
{
session.enableFilter("TEST_RUN_ID_FILTER").setParameterList("IDS", testRunIDs);
}
if (category != null && !category.equals("0") && !category.equals(""))
{
session.enableFilter("CATEGORY_FILTER").setParameter("CATEGORY", category);
}
/*
* Hibernate wants to do an (left) outer join be default.
* This bit of HQL is required to get it to do an inner join.
* The query tells Hibernate to do an inner join on the testCases property inside the Category object
*/
Query query = session.createQuery("select distinct c from Category c inner join c.testCases tc");
List<Category> result = query.list();
return result;
..
}
Your help is greatly appreciated
I've solved it actually, but thanks for the help. The solution (detailed below) is to wrap multiple #FilterDef annotations in a #FilterDefs annotation. Oddly enough I didn't find this anywhere or in the Hibernate doc, I saw this post (Multiple annotations of the same type on one element?), and thought hey maybee #FilterDefs exists and it does.
#Entity
#Table(name = "TESTCASE_NEW")
#FilterDefs({
#FilterDef(name = "TESTCASE_NAME_FILTER", defaultCondition = "TESTCASENAME like :TESTCASENAME", parameters = { #ParamDef(name = "TESTCASENAME", type = "string") }),
#FilterDef(name = "TEST_RUN_ID_FILTER", defaultCondition = "TESTRUNID in (:IDS)", parameters = { #ParamDef(name = "IDS", type = "int") })
})
public class TestCase implements Serializable
{
private static final long serialVersionUID = 1L;
#Id
#Column(name = "TESTCASEID")
private int ID;
#Column(name = "TESTCASENAME")
private String name;
...
}
#Entity
public class Category implements Serializable
{
private static final long serialVersionUID = 1L;
#Id
#Column(name = "CATEGORYID")
private int ID;
#Column(name = "CATEGORYNAME")
private String name;
#OneToMany(fetch = FetchType.EAGER)
#JoinColumn(name = "CATEGORYID")
#OrderBy("TESTCASEID desc")
#Filters({
#Filter(name = "TEST_RUN_ID_FILTER"),
#Filter(name = "TESTCASE_NAME_FILTER") })
private Collection<TestCase> testCases;
...
}
In the DAO, I just turn on the ones I need
public List<Category> getCategories(List<Integer> testRunIDs, String category, String testCaseName)
{
Session session = getSession();
if (testRunIDs != null && testRunIDs.size() != 0)
{
session.enableFilter("TEST_RUN_ID_FILTER").setParameterList("IDS", testRunIDs);
}
if (testCaseName != null)
{
session.enableFilter("TESTCASE_NAME_FILTER").setParameter("TESTCASENAME", testCaseName);
}
/*
* Hibernate wants to do an (left) outer join be default.
* This bit of HQL is required to get it to do an inner join.
* The query tells Hibernate to do an inner join on the testCases property inside the Category object
*/
Query query = session.createQuery("select distinct c from Category c inner join c.testCases tc");
List<Category> result = query.list();
return result;
}

Categories

Resources