I am learning Hibernate, and I have a question about basic HQL join syntax. I am following this tutorial. Say I have a Product and Category entity,
#Entity
#Table(name = "CATEGORY")
public class Category {
private long id;
private String name;
private Set<Product> products;
public Category() {
}
public Category(String name) {
this.name = name;
}
#Id
#Column(name = "CATEGORY_ID")
#GeneratedValue
public long getId() {
return id;
}
#OneToMany(mappedBy = "category", cascade = CascadeType.ALL)
public Set<Product> getProducts() {
return products;
}
// other getters and setters
}
#Entity
#Table(name = "PRODUCT")
public class Product {
private long id;
private String name;
private String description;
private float price;
private Category category;
public Product() {
}
public Product(String name, String description, float price,
Category category) {
this.name = name;
this.description = description;
this.price = price;
this.category = category;
}
#Id
#Column(name = "PRODUCT_ID")
#GeneratedValue
public long getId() {
return id;
}
#ManyToOne
#JoinColumn(name = "CATEGORY_ID")
public Category getCategory() {
return category;
}
// other getters and setters
}
So I have to join category and Product, I will something like this in sql
select * from Category A inner join Product B on A.id=B.category_id,
In HQL, it seems we drop the "on" condition, the HQL for the above query is
String hql = "from Product p inner join p.category";
Query query = session.createQuery(hql);
Why is on not required in HQL?
If you have an association (for an example #ManyToOne), you don't need on, Hibernate will add it to the SQL.
It was a problem prior to Hibernate 5.1, If you don't have an association. From Hibernate 5.1 you can use ad hoc joins:
How to join unrelated entities with JPA and Hibernate
Apart that, HQL also defines a with clause to qualify the join conditions:
Hibernate docs: Explicit joins
In my code i have a oneToMany relation between customer class and item class. This means that, a customer may have one or many items.
Here is the customer code:
#Entity
#Data
public class customer {
#Id
#GeneratedValue
int id;
String name;
String lastname;
#Embedded
Address address;
#OneToMany
#Column(name="ITEM_ID")
List<item> item;
}
and it's the item class:
#Entity
#Data
public class item {
#Id
#GeneratedValue
int id;
String name;
String Serialnumber;
int price;
#ManyToOne
customer customer;
}
Then i have made some tests to try my queries in the models.
insert into item(id,name,Serialnumber,price) values(1,'bike','123',200);
insert into item(id,name,Serialnumber,price) values(2,'car','123',200);
insert into customer(id,name,lastname,Country,City,Street,No,item_id)
values(1,'Salman','Lashkarara','Iran','Tehran','Shariati','12',1);
insert into customer(id,name,lastname,Country,City,Street,No,item_id)
values(2,'Saba','Lashkarara','Iran','Tehran','Shariati','12',2);
insert into customer(id,name,lastname,Country,City,Street,No,item_id)
values(3,'Saba','Lashkarara','Iran','Tehran','Shariati','12',1);
But when i run my code, i face with the following error:
Column "ITEM_ID" not found; SQL statement:
insert into customer(id,name,lastname,Country,City,Street,No,item_id) values(1,'Salman','Lashkarara','Iran','Tehran','Shariati','12',1)
Please pay especial attention, that it is a java mvc-spring application and i create my models using the code, so there is no database to check the field item_id.
As you can see i have already added the #Column(name="ITEM_ID") to define the column.
You have to use #JoinColumn for association columns:
#OneToMany
#JoinColumn(name="ITEM_ID")
List<item> item;
some other options
#OneToMany(cascade=CascadeType.All, fetch=FetchType.EAGER)
#JoinColumn(name="ITEM_ID")
List<item> item;
in Item class
#ManyToOne(mappedBy="item")
customer customer;
you could do this i also have user class and bcr class, one user have many bcr so below code will help you
bcr.java
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "user_who_enter_demand", nullable = false)
public User getUserByUserWhoEnterDemand() {
return this.userByUserWhoEnterDemand;
}
public void setUserByUserWhoEnterDemand(User userByUserWhoEnterDemand) {
this.userByUserWhoEnterDemand = userByUserWhoEnterDemand;
}
user.java
#OneToMany(fetch = FetchType.LAZY, mappedBy = "userByUserWhoEnterDemand")
public Set<BudgetControlRegister> getBudgetControlRegistersForUserWhoEnterDemand() {
return this.budgetControlRegistersForUserWhoEnterDemand;
}
public void setBudgetControlRegistersForUserWhoEnterDemand(Set<BudgetControlRegister> budgetControlRegistersForUserWhoEnterDemand) {
this.budgetControlRegistersForUserWhoEnterDemand = budgetControlRegistersForUserWhoEnterDemand;
}
You can't map a table column without table:
#Entity
#Table(name = "ITEM_TABLE")
public class item {
...
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name = "CUSTOMER_ID_ITEM_TABLE")
private customer customer;
...
}
#Entity
#Table(name = "CUSTOMER_TABLE")
public class customer {
...
#OneToMany
#Column(name="ITEM_ID")
List<item> item;
}
I have created the following entities to manage a persistent shopping cart:
ShoppingCart.java:
#Entity
public class ShoppingCart {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#PrivateOwned
#OneToMany(mappedBy = "cart", cascade = CascadeType.ALL)
#OrderBy("creationTimestamp")
private List<ShoppingCartItem> items;
public ShoppingCart() {}
// Getters and setters...
}
ShoppingCartItem.java:
#Entity
#IdClass(ShoppingCartItemId.class)
public class ShoppingCartItem {
#Id
#ManyToOne
private Item item;
#Id
#ManyToOne
private ShoppingCart cart;
private int quantity;
#Column(precision = 17, scale = 2)
private BigDecimal price;
#Temporal(TemporalType.TIMESTAMP)
private Date creationTimestamp;
protected ShoppingCartItem() {}
#PrePersist
protected void prePersist() {
creationTimestamp = new Date();
}
public ShoppingCartItem(ShoppingCart cart, Item item, int quantity) {
this.cart = cart;
this.item = item;
this.quantity = quantity;
this.price = item.getPrice();
}
// Getters and setters...
}
Item.java:
#Entity
public class Item {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToOne
private Brand brand;
private String model;
private String variant;
private String description;
#Column(precision = 17, scale = 2)
private BigDecimal price;
private int availability;
protected Item() {}
// Constructors, getters and setters...
}
When I issue the the following JPQL query:
SELECT c FROM ShoppingCart c JOIN FETCH c.items WHERE c.id = :id
I notice that all the ShoppingCartItems in the same ShoppingCart are retrieved as expected in a single query but the #ManyToOne private Item item; field is not in the join and a separate query for each ShoppingCartItem is issued to fetch that field when accessed.
Using EclipseLink, is there a way to have also the Items join fetched when join/batch fetching the ShoppingCartItems? How do I change the query and/or code?
If you are using EclipseLink you can take a look at the #BatchFetch and #JoinFetch annotations.
While the left join fetchs with aliases seems to be ignored, I've found this query hint that do the job:
Query query = entityManager.createQuery("SELECT c FROM ShoppingCart c WHERE c.id = :id");
query.setHint("eclipselink.left-join-fetch", "c.items.item.brand");
This is probably better than the annotation approach as it can be specified per single query.
UPDATE
Use of this hint broke #OrderBy("creationTimestamp") so ShoppingCartItems aren't returned in the order they were inserted anymore. This is probably due to a bug in EclipseLink but I think it doesn't really hurt so much since I actually need to have the items ordered only when showing the cart to the user and not, for example, when the user logs in and items in the anonymous cart must be transferred to the user cart.
I have following entities:
#Entity
public class Company {
....
#OneToMany
private List<Product> products = new Arraylist<>();
....
}
#Entity
public class Product {
....
#Column(name="product_key")
private String productKey; // same value as in ProductCategory
....
}
#Entity
public class ProductCategory{
....
#Column(name="product_key")
private String productKey // same value as in Product
#ManyToMany
#JoinTable (...)
private List<Category> categories = new ArrayList<>();
....
}
I want to write query which will return companies with their corresponding categories:
Company - List<Category>
I.e. I want aggregate categories of each Product company have.
Currently I end up with this HQL query, but it doesn't work:
SELECT DISTINCT c,
(SELECT pc.categories
FROM ProductCategories pc
LEFT JOIN c.products products
WHERE pc.productKey IN products.productKey)
FROM Company c
I tried to add virtual List<Categories> field to Company entity using #JoinFormula, but without success (#JoinFormula can not be used with List types, only with single values)
I think Your DB design is making the solution hard.
Since company & product has a one to many relationship, the owner of the relation should be product. Therefore you could keep a reference to the company inside each product.
If I were you, I would go with a design like this.
Company Class
#Entity
public class Company {
#OneToMany(mappedBy="company", cascade = CascadeType.ALL)
private List<Product> products = new ArrayList<Product>();
#Id
private int companyId;
}
Product Class
#Entity
public class Product {
#Id
#Column(name = "product_key")
private String productKey; // same value as in ProductCategory
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "company", nullable = false)
private Company company;
#ManyToMany(cascade = CascadeType.ALL, mappedBy="products")
private List<ProductCategory> categories = new ArrayList<ProductCategory>();
}
ProductCategory Class
#Entity
public class ProductCategory {
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(name="ProductWiseCategory", joinColumns=#JoinColumn(name="cat_name"),
inverseJoinColumns=#JoinColumn(name="product_key") )
private List<Product> products = new ArrayList<Product>();
I've done the necessary changes to my models outlined here. However, I don't know what to put on my join table entity.
Note that my join table has a surrogate key , and two extra columns (date and varchar).
What I've got so far is:
User.java
#Entity
#Table (name = "tbl_bo_gui_user")
#DynamicInsert
#DynamicUpdate
public class User implements Serializable {
private String id;
private String ntName;
private String email;
private Set<GroupUser> groupUsers = new HashSet<GroupUser>(0);
// Constructors and some getters setters omitted
#OneToMany(fetch = FetchType.LAZY, mappedBy = "pk.user", cascade=CascadeType.ALL)
public Set<GroupUser> getGroupUsers() {
return groupUsers;
}
public void setGroupUsers(Set<GroupUser> groupUsers) {
this.groupUsers = groupUsers;
}
}
Group.java
#Entity
#Table (name = "tbl_bo_gui_group")
#DynamicInsert
#DynamicUpdate
public class Group implements Serializable {
private String id;
private String groupName;
private String groupDesc;
private Set<GroupUser> groupUsers = new HashSet<GroupUser>(0);
// Constructors and some getters setters omitted
#OneToMany(fetch = FetchType.LAZY, mappedBy = "pk.group", cascade=CascadeType.ALL)
public Set<GroupUser> getGroupUsers() {
return groupUsers;
}
public void setGroupUsers(Set<GroupUser> groupUsers) {
this.groupUsers = groupUsers;
}
}
The problem is that I don't know what to put on my join table entity. Here it is.
GroupUser.java
#Entity
#Table (name = "tbl_bo_gui_group_user")
#DynamicInsert
#DynamicUpdate
#AssociationOverrides({
#AssociationOverride(name = "pk.user",
joinColumns = #JoinColumn(name = "id")),
#AssociationOverride(name = "pk.group",
joinColumns = #JoinColumn(name = "id")) })
public class GroupUser implements Serializable {
private String id;
private User userId;
private Group groupId;
private Date dateCreated;
private String createdBy;
// constructors and getters and setters for each property
// What now? ? No idea
}
user to group would be a Many-To-Many relation. Now, you are splitting that up into Two One-To-Many Relations. Therefore your Mapping Entity simple needs to complete the Many-To-Many relation, by using Many-To-One:
public class GroupUser implements Serializable {
private String id;
#ManyToOne
private User userId;
#ManyToOne
private Group groupId;
private Date dateCreated;
private String createdBy;
}
See also this example: Mapping many-to-many association table with extra column(s) (The Answer with 38 upvotes)