JPA CriteriaBuilder: fetch specific columns on joined entity - java

Given the following two entities:
public class Performer {
#Id
private Long id;
private String name;
#ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinTable(name = "performers_characters",
joinColumns = #JoinColumn(name = "performer_id", nullable = false, updatable = false),
inverseJoinColumns = #JoinColumn(name = "character_id", nullable = false, updatable = false))
private Set<Character> characters = Sets.newHashSet();
}
And:
public class Character {
#Id
#Column(nullable = false)
private Long id;
#Column(nullable = false)
private String name;
private Boolean alive;
private String placeOfBirth;
#ManyToMany(mappedBy = "characters")
private Set<Performer> performers = Sets.newHashSet();
}
I want to create query, using Criteria Builder, that will generate the equivalent SQL:
SELECT performer0_.id AS id1_11_0_,
character2_.id AS id1_0_1_,
performer0_.name AS name7_11_0_,
character2_.name AS name12_0_1_,
characters1_.character_id AS character_id2_12_0__
FROM performer performer0_
INNER JOIN performers_characters characters1_ ON performer0_.id=characters1_.performer_id
INNER JOIN character character2_ ON characters1_.character_id=character2_.id
So basically, everything from Performer should be selected, but from Character, only id and name should be selected.
Currently I'm stuck with the following:
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<Performer> baseCriteriaQuery = criteriaBuilder.createQuery(Performer.class);
Root<Performer> baseRoot = baseCriteriaQuery.from(Performer.class);
baseCriteriaQuery.select(baseRoot);
baseRoot.fetch("characters");
TypedQuery<Performer> baseTypedQuery = entityManager.createQuery(baseCriteriaQuery);
List<Performer> baseEntityList = baseTypedQuery.getResultList();
This work fine, except that from both Character and Performer, all columns are selected.

With JPA this would only be possible if you specify the fields that you don't want to load as lazy.
#Basic(fetch=FetchType.LAZY)
private String placeOfBirth;
#Basic(fetch=FetchType.LAZY)
private Boolean alive;
But the question is why do you want to do that? Both fields are small items and with LAZY you have more SQL calls to fetch the data.

Related

Hibernate get parent child lazy loading with addtional check

I have two tables Company and Employees, one-to-many mapping. Company table contains composite primary key.
I want to search from company table based on primary id but want to put an additional check on the child table.
I want to load only a particular type of employees which I will get in the request. How it can be done in Sprongboot JPA with findById("id");
class Company{
#Id
private String companyId;
#Id
private String stateId;
private String company Name;
#OneToMany(targetEntity = Employees.class, fetch = FetchType.LAZY, cascade = {
CascadeType.ALL }, mappedBy = "company")
private Set<Employees> empList;
}
class Employees{
#Id
private String id;
//foreign key
private String companyId;
//foreign key
private String stateId;
#ManyToOne
#JoinColumns({
#JoinColumn(name = "companyId", referencedColumnName = "companyId", insertable = false, updatable = false, nullable = true),
#JoinColumn(name = "stateId", referencedColumnName = "stateId", insertable = false, updatable = false, nullable = true) })
private Company company;
private int salary;
private String type;
}
Use Filter, which is an alternative of #Where where you can set dynamic value.
Here is the sample
#FilterDef(
name = "employeeTypeFilter",
parameters = #ParamDef(name = "type", type = "string")
)
#Filter(
name = "employeeTypeFilter",
condition = "type > : type"
)
public class Employees {
}
You can enable or disable filter from your code dynamically based on your requirement.
You can use #Where for fixed type
#Where(clause = "type = 'anyEmployeeType'")
private Set<Employees> empList;
For dynamically fetch you can query in Employees repository
List<Employees> findByTypeAndCompany(String type, Company company);

how to find id of foreign key in hibernate

I have two entities viz:
State
#Entity
#Table(name = "State")
public class StateEntity {
#Column(name = "id", length = 36, nullable = false, unique = true)
private String id;
#ManyToOne (fetch = FetchType.LAZY)
#JoinColumn(name = "InsurerId", nullable = false)
private InsurerEntity insurer;
#Column(name ="StateName", length = 50, nullable = false)
private String stateName;
//getters and setters
}
Insurer
#Entity
#Table(name = "Insurer")
public class InsurerEntity {
#Column(name = "InsurerId", length = 36, nullable = false, unique = true)
private String insurerId;
#Column(name = "InsurerName", length = 100, nullable = true)
private String insurerName;
#OneToMany(mappedBy = "state", fetch = FetchType.LAZY)
private List<StateEntity> stateEntityList;
//getters and setters
}
the insurer's id gets saved in state database and I want to retrieve it using hibernate query but I cant't seem to find the solution for that
How to write this query SELECT InsurerId FROM State; in Hibernate query using CriteriaBuilder, CriteriaQuery and Root..
If you want to select all Insurers's Ids for all states:
String selectionQuery = "SELECT s.insurer.insurerId FROM State s";
List<String> insurersIds = session.createQuery(selectionQuery).list();
If you want to select the Insurer's Id of a certain state:
String selectionQuery = "SELECT s.insurer.insurerId FROM State s WHERE s.id = :stateId";
String insurerId = (String) session.createQuery(selectionQuery).setParameter("stateId", stateId).getSingleResult(); //This should be placed in a try/catch block to handle org.hibernate.NonUniqueResultException
Edit:
You should update your Insurer entity as Prasad wrote in his answer.
for this you have to map both the class as in put #oneToMany annotation in class InsurerEntity as well
#OneToMany(fetch = FetchType.LAZY,mappedBy="StateEntity", cascade = CascadeType.ALL)
private List< StateEntity > StateEntitys;
and when you fetch states you will also get object of InsurerEntity in it from where you can access it with the getter

"MultipleBagFetchException: cannot simultaneously fetch multiple bags" when joining three depth table

org.springframework.dao.InvalidDataAccessApiUsageException:
org.hibernate.loader.MultipleBagFetchException: cannot simultaneously
fetch multiple bags: [Order.items, OrderItem.options];
Above is an exception i faced when i join three tables like below.
OrderItemOption.java
#Entity
public class OrderItemOption {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "item_option_id")
private Long id;
#Column(name = "item_id", nullable = false)
private Long itemId;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(
name = "item_id",
referencedColumnName = "item_id",
insertable = false,
updatable = false
)
private OrderItem orderItem;
}
OrderItem.java
#Entity
public class OrderItem {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "item_id")
private Long id;
#Column(name = "order_id", nullable = false)
private Long orderId;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(
name = "order_id",
referencedColumnName = "order_id",
insertable = false,
updatable = false,
nullable = false
)
private Order order;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "orderItem")
#OrderBy("item_option_id ASC")
private List<OrderItemOption> options;
}
Order.java
#Entity
public class Order {
#Id
#Column(name = "order_id", nullable = false)
private Long id;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "order")
#OrderBy("item_id ASC")
private List<OrderItem> items;
}
And here's my QueryDSL code to join them at one time.
final QOrder order = QOrder.order;
final QOrderItem item = QOrderItem.orderItem;
final QOrderItemOption option = QOrderItemOption.orderItemOption;
from(order)
.leftJoin(order.items, item).fetchJoin()
.leftJoin(item.options, option).fetchJoin()
.where(
order.id.eq(orderId)
.and(item.id.in(itemIds))
.and(option.id.in(optionIds))
)
.fetchOne())
What i'm trying to do is to get Order object which contains filtered relationship, so that i can access filtered children via order object.
and the type of relationship should be a List, not a Set.
for example, order.getItems().get(0).getOptions.get(0)
How can i achieve that goal?
To avoid above exception there are two possibilities:
Change List to Set
or
Use List but do not fetch two bags. This means don't use fetchJoin() on both collections.
Filtering:
Using where conditions collections will be not filtered. Collections will contain all associated objects. Joining in JPA is for creating conditions on root object - Order. It is not the same as in SQL.
It is possible to filter associated collections using JPA 2.1 JOIN ON feature. This allows additional conditions in ON clause
see for example QueryDSL Left Join with additional conditions in ON
If you really can not use Set instead of List:
Parent.class
#OneToMany(
mappedBy = "parent",
orphanRemoval = true,
cascade = { CascadeType.PERSIST, CascadeType.MERGE }
)
#OrderColumn(name = "position")
private List<Child> childs = new ArrayList<>();
Child.class
#ManyToOne(fetch = FetchType.LAZY)
private Parent parent;
And create a column in the Child's table named e.g "position"
ALTER TABLE child ADD COLUMN position integer NOT NULL default 0
And if you can not use other column in table, them you need to query the lists in sequence. Or use the id of the child and a custom getter.
#OrderColumn(name = "id_child", updatable = false, insertable = false)
public List<Child> getChilds() {
childs.removeAll(Collections.singleton(null));
return childs;
}

hibernate select query from child table

I am pretty new to hibernate. I have a two tables having one to many relationship. Two tables are:
public class Pashmina implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO, generator = "sq_pashmina_id")
#SequenceGenerator(name = "sq_pashmina_id", sequenceName = "sq_pashmina_id")
#Column(name = "PASHMINA_ID")
private int pashminaId;
#Column(name = "PASHMINA_NAME")
private String pashminaName;
#Column(name = "PRICE")
private double price;
#Column(name = "ADDED_AT", insertable = false)
#Temporal(TemporalType.TIMESTAMP)
private Date addedAt;
#Column(name = "CATEGORY")
private String category;
#Column(name = "ENABLED", insertable = false)
private Character enabled;
#OneToMany(mappedBy = "colourId", fetch = FetchType.EAGER)
private Set<PashminaColour> pashminaColor = new HashSet<PashminaColour>();
#OneToMany(mappedBy = "imageId", fetch = FetchType.EAGER)
private Set<Image> images = new HashSet<Image>();
#OneToMany(mappedBy = "descriptionId", fetch = FetchType.EAGER)
private Set<Description> descriptions = new HashSet<Description>();
//getter and setter method
This is a parent class and it have one to many relationship with Image table
public class Image implements Serializable {
#Id
#Column(name = "IMAGE_ID")
private int imageId;
#Column(name = "IMAGE_NAME")
private String imageName;
#JoinColumn(name = "PASHMINA_ID", referencedColumnName = "PASHMINA_ID")
#ManyToOne
private Pashmina pashmina;
Now I want to select a imagenames from Image class using its parent class's id (ie: pashminaId)
eg:
SELECT IMAGE_NAME FROM TBL_IMAGE WHERE PASHMINA_ID = 'some_digit';
How can i pass pashminaId in image class as there is no pashminaId it only have an Object creation of Parent class Pashmina.
So, How can i achieve this in hibernate?
Let me know if you don't understand me.
Thanks! Hoping for positive response.
As Hibernate treats objects instead of SQL tables, you can simply access the pashmina object from your image object, and in your query you will be treating java objects/POJOs, so you can acccess it via Image.pashmina.pashminaId.
So you can write the following query:
String hql = "select I.imageName FROM Image I WHERE I.pashmina.pashminaId = 10";
Query query = session.createQuery(hql);
List results = query.list();

How to create a namedquery of manytomany entity?

Brand
public class Brand implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Basic(optional = false)
#Column(name = "BrandID", nullable = false)
private Integer brandID;
#Basic(optional = false)
#Column(name = "BrandName", nullable = false, length = 100)
private String brandName;
#Basic(optional = false)
#Column(name = "Description", nullable = false, length = 1000)
private String description;
#Column(name = "Is_Visible")
private Boolean isVisible;
#JoinTable(name = "brandcategory", joinColumns = {
#JoinColumn(name = "BrandID", referencedColumnName = "BrandID")}, inverseJoinColumns = {
#JoinColumn(name = "CategoryID", referencedColumnName = "CategoryID")})
#ManyToMany(fetch = FetchType.EAGER)
private Collection<Category> categoryCollection;
#OneToMany(mappedBy = "brand", fetch = FetchType.EAGER)
private Collection<Product> productCollection;
I want to retrive the Brand IDs from table brandcategory whoes categoryID = :categoryID
how can i createnamed query for it in entity brand?
this does not work:
#NamedQuery(name = "Brand.getBrandListByCategory",
query = "SELECT b FROM Brand b WHERE b.brandID =
(SELECT bc.brandID
FROM b.brandctegory bc
WHERE bc.category.categoryID = :categoryID)")
If I understand correctly, you want all the brands belonging to a category. Why don't you simply make the association bidirectional. You could then just do:
Category category = em.find(Category.class, categoryId);
return category.getBrands();
If it's unidirectional, then you'll need a query, but it's much simpler that the one you tried:
select b from Brand b inner join b.categoryCollection category
where category.id = :categoryId;
Your query doesn't make sense: it uses a non-existing association (b.brandcategory). Remember that JPQL uses entities, their persistent fields and associations to other entities. And nothing else. Tables don't exist in JPQL.
AFAIK, you cant go out of a entity boundary, when creating queries in entity class.
Instead use .createNativeQuery() method of the entity manager, to create complex and mixed queries.

Categories

Resources