OneToMany return just specific rows - java

I have class with attribute
#Entity
public class Energy() {
#Id
private long id;
private Date date;
#OneToMany(fetch = FetchType.LAZY)
private List<Value> values;
}
and Value class have
private String obis;
private long value;
What is the fastest way to return just elements of list values where obis contains specific value?
Thanks!

It depends on the implementation you choose, for example with Criteria Api:
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Energy> cq = cb.createQuery(Energy.class);
Root<Energy> root = cq.from(Energy.class);
Join<Energy,Value> join = root.join(Energy_.values);
//If you dont use metamodel change by root.join("values")
cq.select(root);
cq.where(cb.equal(join.get(Value_.value),VALUE));
//If you dont use metamodel change by join.get("value")
List<Energy> result = entityManager.createQuery(cq).getResultList();

Related

How to filter parent object which has list of children and a child has list of grand children and by matching property of grand children

Object hierarchy is as follow.
CoverCategory has multiple Covers and a Cover has multiple SubCovers.
Let says that in coding
CoverCategory
#Entity
public class CoverCategory {
#Id
private Long id;
#OneToMany
List<Cover> coverList;
}
Cover
#Entity
public class Cover {
#Id
private Long id;
#ManyToOne
private CoverCategory coverCategory;
#OneToMany
private List<SubCover> subCoverList;
}
Sub Cover
#Entity
public class SubCover {
#Id
private Long id;
#ManyToOne
private Cover cover;
private String name;
}
Here what I am try to do is select all the cover categories which has sub cover name "subcover1"
I tried to write the code as follows
String name = "subcover1";
Session session = getCurrentSession();
CriteriaBuilder criteriaBuilder = session.getCriteriaBuilder();
CriteriaQuery<CoverCategory> creteriaQuery = criteriaBuilder.createQuery(CoverCategory.class);
Root<CoverCategory> root = creteriaQuery.from(CoverCategory.class);
creteriaQuery.select(root).where(criteriaBuilder.equal(root.get("coverList").get("subCoverList").get("name"),name));
Query<CoverCategory> query = session.createQuery(creteriaQuery);
List<CoverCategory> results = query.getResultList();
But it gives me following exception
org.springframework.dao.InvalidDataAccessApiUsageException: Illegal attempt to dereference path source [null.coverList] of basic type; nested exception is java.lang.IllegalStateException: Illegal attempt to dereference path source [null.coverList] of basic type
Can someone tell me how to do this properly. Thanks in advance.
Only need to be done in criteria builder
CriteriaBuilder criteriaBuilder = em.getCriteriaBuilder();
CriteriaQuery<CoverCategory> criteriaQuery = criteriaBuilder.createQuery(CoverCategory.class);
Root<CoverCategory> root = criteriaQuery.from(CoverCategory.class);
Join<CoverCategory,Cover> coverJoin = root.join("coverList");
Join<Cover, SubCover> subjoin = coverJoin.join("subCoverList");
criteriaBuilder.equal(subjoin.get("name"),name);
Query<CoverCategory> query = session.createQuery(criteriaQuery);
List<CoverCategory> results = query.getResultList();

JPA Criteria API Specification for Many to Many

I have got three classes as mentioned below. I am trying to create a specification to filter data where there is a match in the linked table.
public class Album {
private Long id;
private List<AlbumTag> albumTags;
}
public class Tag {
private Long id;
private String category;
}
public class AlbumTag{
private Long id;
private Album album;
private Tag tag;
}
In the schema given above what I am trying to find is a list of all albums from Album table with the link in AlbumTag. The SQL that I want to achieve, doesn't have to be same, is below
select *
from Album A
where (A.Id in (select [AT].AlbumId
from AlbumTag [AT]))
What I have tried so far which is not working, of course, is below
public class AlbumWithTagSpecification implements Specification<Album> {
#Override
public Predicate toPredicate(Root<Album> root, CriteriaQuery<?> cq, CriteriaBuilder cb) {
final Subquery<Long> personQuery = cq.subquery(Long.class);
final Root<Album> album = personQuery.from(Album.class);
final Join<Album, AlbumTag> albumTags = album.join("albumTags");
personQuery.select((albumTags.get("album")).get("id"));
personQuery.where(cb.equal(album.get("id"), (albumTags.get("album")).get("id")));
return cb.in(root.get("id")).value(personQuery);
}
}
Using spring boot and spring data JPA, you can prefer entity relationship to fetch the data.
1.Annotate the domain class with the entity relationship which given below:
#Entity
#Table(name="Album")
public class Album {
#Id
#Column(name="id")
private Long id;
#OneToMany(targetEntity = AlbumTag.class, mappedBy = "album")
private List<AlbumTag> albumTags;
//getter and setter
}
#Entity
#Table(name="Tag")
public class Tag {
#Id
#Column(name="id")
private Long id;
#Column(name="category")
private String category;
//getter and setter
}
#Entity
#Table(name="AlbumTag")
public class AlbumTag{
#Id
#Column(name="id")
private Long id;
#ManyToOne(optional = false, targetEntity = Album.class)
#JoinColumn(name = "id", referencedColumnName="id", insertable = false, updatable = false)
private Album album;
#ManyToOne(optional = false, targetEntity = Tag.class)
#JoinColumn(name = "id", referencedColumnName="id", insertable = false, updatable = false)
private Tag tag;
//getter and setter
}
2.use the spring data to fetch the details using the below:
Album album = ablumRepository.findOne(1); // get the complete details about individual album.
List<AlbumTag> albumTags = ablum.getAlbumTags(); // get the all related albumTags details for particular album.
I hope this will help you to solve it.
Subqueries in JPA only really work with CriteriaBuilder.exists() so i would try:
public Predicate toPredicate(Root<Album> root, CriteriaQuery<?> cq, CriteriaBuilder cb) {
final Subquery<Long> subQuery = cq.subquery(Long.class);
final Root<AlbumTag> albumTag = subQuery.from(AlbumTag.class);
// it doesn't really matter what we select
subQuery.select(cb.literal(1));
subQuery.where(cb.equal(root.get("id"), (albumTag.get("album")).get("id")));
return cb.exists(subQuery);
}
which is equivalent to
select *
from Album A
where exists(
select 1 from AlbumTag AT
where AT.AlbumId = A.Id
)
Well, I wouldn't go for in operation in this case - it just complicates the query and the specification. The problem you described is actually matter of joining records from Table A with related records from Table B so the query in your case would be like:
SELECT a from Album a join AlbumTag at on a.id = at.albumId - as you needed it will return all albums that have album tags. Inner join explained
So in your case I would create this "factory" method that would create for you this specification.
public static Specification<Album> withTags() {
return new Specification<Album>() {
#Override
public Predicate toPredicate(Root<Album> root, CriteriaQuery<?> query, CriteriaBuilder cb) {
return root.join("albumTags").getOn();
}
};
}
Also I would suggest you to have a look at static metamodel library from hibernate - link to introduction. It generates for you static model from your entity classes that helps you avoid creating queries/specifications using hardcoded strings.
creteria query for join tables
CriteriaQuery<Album> query = cb.createQuery(Album.class);
Root<Album> album = query.from(Teacher.class);
Join<Album, AlbumTag> tag = teacher.join("id");
query.select(tag).where(cb.equal(album.get("album")));
List<Album> results = em.createQuery(query).getResultList();
for (Album al : results) {
System.out.println("album-->+al.get(name));
}
This looks like a classic many to many example. The three classes you have map directly to the tables you would expect in the database. JPA is an Object Relational Mapping (ORM) library which means we can structure the classes in a more OO style and map to the underlying relational database.
The AlbumTag class can be omitted and the #ManyToMany relationship added to both Album and Tag.
public class Album {
private Long id;
#ManyToMany
#JoinTable(name="AlbumTag",
joinColumns=
#JoinColumn(name="album", referencedColumnName="id"),
inverseJoinColumns=
#JoinColumn(name="tag", referencedColumnName="id"))
private List<Tag> tags;
}
public class Tag {
private Long id;
private String category;
#ManyToMany(mappedBy="tags")
private List<Album> albums;
}
To find albums by Tag you would first retrieve the Tag from the repository using something like findById(1l); or findByCategory("Rock"); and then simply call getAlbums() on the Tag object.
Note: One slight difference here is that the AlbumTag table would have only two columns (album and tag). The extra id column on AlbumTag is unnecessary since the combination of album and tag would be a unique id and you would never need to find by id in this table anyway.
Since you are using spring-data-jpa you should really take advantage of the features it provides.
My first question is related to your entity classes. I do not understand why is it necesary to store a list of album tags in the album class. Since you have a join table this information is reduntant.
Secondly you should adnotate your entity clases:
#Entity
public class Album {
#Id
#Column
private Long id;
}
#Entity
public class Tag {
#Id
#Column
private Long id;
#Column
private String category;
}
#Entity
#Table
public class AlbumTag{
#Id
#Column
private Long id;
#ManyToOne
#JoinColumn
private Album album;
#ManyToOne
#JoinColumn
private Tag tag;
}
Next you should create repositories for your entity classes.
interface AlbumRepository extends JpaRepository<Album, Long>{
#Query
("select DISTINCT(a) from AlbumTag at "+
"join at.album a "
"where at.tag is not null")
List<Album> findAlbumWithTag();
}
Then simply call the repository function which will return a list of albums which have at least one tag.

Hibernate criteria join table issue

I have 3 entities as you can see below. I want to write a query that fetches products. In this query the parameter is a list of optionValues id.
now my question is how to join these entities?
Product:
public class Product{
//other col
#OneToMany(mappedBy = "product")
private Set<Attribute> attributeSet = new HashSet<>();
}
Attribute:
public class Attribute{
#OneToOne
#JoinColumn(name = "OPTION_VALUE_ID")
private OptionValue optionValue;
#ManyToOne
#JoinColumn(name="PRODUCT_ID",referencedColumnName="id")
private Product product;
}
optionValue:
public class OptionValue{
#Column(name = "id")
private Long id;
#Column(name = "value",updatable = true)
private String value;
}
I wrote a query but I think my code is not a good solution.
Criteria aCriteria = null;
if (!optionValueList.isEmpty()) {
aCriteria = currentSession().createCriteria(Attribute.class, "attribute");
aCriteria.createAlias("attribute.optionValue", "optionValue");
aCriteria.add(Restrictions.in("optionValue.id", optionValueList));
attributes = aCriteria.list();
}
PagingData<Product> pagingData = new PagingData<>();
Criteria criteria = currentSession().createCriteria(Product.class, "product");
if (!attributes.isEmpty()) {
for (Attribute attribute:attributes){
longList.add(attribute.getId());
}
criteria.createAlias("product.attributeSet", "attribute");
criteria.add(Restrictions.in("attribute.id", longList));
criteria.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
}
The general idea is to start with creating criteria of objects you want to return, and travel further by adding criteria which is joined. So I start with Parent class, add qualifiers and end up with most nested element, OptionValue.
Code below is untested, but you should get the idea:
Criteria criteria = currentSession()
.createCriteria(Product.class)
.createCriteria("attributeSet", "join_between_product_and_attribute");
if (!attributes.isEmpty()) {
Set<String> attributeIds = new HashSet<>();
for (Attribute attribute : attributeList) {
attributeIds.add(attribute.getId());
}
criteria.add(Restrictions.in("id", attributeIds));
}
criteria = criteria.createCriteria("optionValue", "join_between_attribute_optionvalue");
if (!optionValueList.isEmpty()) {
criteria.add(Restrictions.in("id", optionValueList));
}
an even easier solution would be to use a CriteriaQuery. i did not test the following code, but i think it should work correctly. it requires hibernate 5, but also works with some modifications in hibernate 4:
CriteriaBuilder cb = sessionFactory.getCriteriaBuilder();
CriteriaQuery<Product> query = cb.createQuery(Product.class);
Root<Product> r = query.from(Product.class);
In<Object> in = cb.in(r.join("attributeSet ").join("optionValue").get("id"));
for(Object optionValue : optionValueList){
in.value(optionValue);
}
query.select(r).where(in);
return sessionFactory.getCurrentSession().createQuery(query).getResultList();
i am assuming, that you can access the optionValueList since you posted it in your question.
For the solution with EntityManager i am assuming you already were able to instantiate one.
CriteriaBuilder cb = entityManager.getCriteriaBuilder();
CriteriaQuery<Product> query = cb.createQuery(Product.class);
Root<Product> r = query.from(Product.class);
In<Object> in = cb.in(r.join("attributeSet ").join("optionValue").get("id"));
for(Object optionValue : optionValueList){
in.value(optionValue);
}
query.select(r).where(in);
return entityManager.createQuery(query).getResultList();
if you have an EntityManagerFactory, replace the first entityManager with it and the second one with entityManagerFactory.createEntityManager()

JPA Criteria API join

Help me plz with one moment. I read about 10 articles already, but don't understand join moment. I have 2 tables:
public class News implements Serializable {
#Id
#GeneratedValue (generator = "increment")
#GenericGenerator (name = "increment", strategy = "increment")
private int id;
#Column
private String name;
#Column
private Date created;
#Column
private String data;
#ManyToOne (cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#JoinColumn (name = "category_id")
private NewsCategoryDict category;
// getters, setters
}
and
public class NewsCategoryDict implements Serializable {
#Id
#GeneratedValue (generator = "increment")
#GenericGenerator (name = "increment", strategy = "increment")
private int id;
#Column
private String name;
#OneToMany (mappedBy = "category", cascade = CascadeType.ALL)
private List<News> news = new ArrayList<>();
}
I want a query works like
SELECT * FROM news, categorynews WHERE news.category_id = categorynews.id;
And then get the result in jsp with
<div id="list_news">
<c:forEach items="${news}" var="news">
<h5>${news.id} : ${news.name} - ${news.created} ; ${news.data} (${news.category.name})</h5>
</c:forEach>
</div>
And I just can't understand this JOIN with Criteria API. Can you help me ?
Try to use this snippet, but get a error
public List<News> getAll() {
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<News> cq = cb.createQuery(News.class);
Root<News> rootFromNews = cq.from(News.class);
Join<NewsCategoryDict, News> join = rootFromNews.join("category");
cq.select(join);
return em.createQuery(cq).getResultList();
}
PropertyNotFoundException: Property 'created' not found on type ru.r1k0.spring.model.NewsCategoryDict
Assuming you want to return an instance of News associated to an instance of NewsCategoryDict, your criteria query should look as follows:
public List<News> getAll() {
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<News> cq = cb.createQuery(News.class);
Root<News> rootFromNews = cq.from(News.class);
Join<News, NewsCategoryDict> join = rootFromNews.join("category"); // #1
cq.select(rootFromNews); // #2
return em.createQuery(cq).getResultList();
}
The modified lines are marked with #1 and #2 comments.
The query should return all News which have a matching NewsCategoryDict; but News records which are not associated to aNewsCategoryDict record will not be returned.
Your error has nothing to do with the join! What is actually happening is that in the JSP fragment you are trying to access ${news.created} which does not exist in the NewsCategoryDict. I believe the error is in the JSP fragment, not in the Criteria query.
The way I understand it is that you want to list the News object, but in the query you are selecting the NewsCategoryDict and this is why at the end you end up with missing attribute because the NewsCategoryDict does not contain ${news.created}

Query ElementCollection of Enum by using JPA Criteria API

I'm working of a web application for a car dealer. I have a Car class with a field which contain a set of security enums.
public class Car {
#Id
#GeneratedValue
private Long id;
#NotNull(message = "{year}")
#Min(value = 1950)
#Max(value = 2020)
#Column(nullable = false)
private int year;
#NotNull()
#Column(nullable = false)
private String make;
#NotNull()
#Column(nullable = false)
private String model;
#NotNull()
#Min(value = 0)
#Max(value = 1000000)
#Column(nullable = false)
private int kilometres;
#Column(nullable = false)
private int price;
#NotNull()
#Enumerated(EnumType.STRING)
private Gearbox gearbox;
#ElementCollection(fetch = FetchType.EAGER)
#Enumerated(EnumType.STRING)
#CollectionTable(name="SECURITY")
#Column(name="TYPE")
private Set<Security> securityList = new HashSet<Security>();
#NotNull()
#Column(nullable = false)
private String description;
#OneToMany(cascade = { CascadeType.ALL }, fetch = FetchType.LAZY, orphanRemoval = true)
private List<Picture> pictureList = new ArrayList<Picture>();
// Getters and setters + help methods..
The Security enum is like:
public enum Security {
ABS("abs"),
AIRBAG("airbag"),
ANTISPIN("antispin"),
CENTRAL_LOCKING("centralLocking"),
REMOTE_ALARM("remoteAlarm"),
FOUR_WHEEL("fourWheel"),
PARKING_ASSISTANCE("parkingAssistance"),
SERVICE_MANUAL("serviceManual"),
STABILITY_CONTROL("stabilityControl"),
XENON_LIGHT("xenonLight");
private String label;
private Security(String label) {
}
public String getLabel() {
return label;
}
}
In the web application, I will create a search page, where the users is able to define required Securitiy parts and a manufacturer pattern (make field in Car class) . For instance, a user might search for Cars which have a make pattern according to "Volkswagen" and Security with at least ABS and REMOTE_ALARM.
My problem is that I am not sure how to create the query using the criteria API. I guess it should start like:
public List<Car> searchCars(String makePattern, Set<Security> requiredSecuirtySet) {
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Car> cq = cb.createQuery(Car.class);
Root<Car> _car = cq.from(Car.class);
// Give me some help here please =)
return em.createQuery(cq).getResultList();
}
Can you please help me? I also have a meta model over the Car class.
Best regards and thanks in advance!
You can use collections as parameters so maybe this will work:
TypedQuery<Car> q = em.createQuery("select c from Car c where c.make = :make and c.securityList in :secutiryList", Car.class);
q.setParameter("make", makePattern);
q.setParameter("securityList", requiredSecuirtySet);
return q.getResultList();
I haven't tested this so I'm not sure it will work. It is based on this question. I also haven't worked with the criteria API so I didn't know how to 'translate' it.
Here's a shot at the query with the criteria API:
public List<Car> searchCars(String makePattern,
Set<Security> requiredSecuirtySet)
{
CriteriaBuilder builder = em.getCriteriaBuilder();
CriteriaQuery<Car> query = builder.createQuery(Car.class);
Root<Car> car = query.from(Car.class);
query.select(car).where(
builder.equal(car.get("make"), makePattern),
car.get("securityList").in(requiredSecuirtySet));
return em.createQuery(query).getResultList();
}
Thanks siebz0r!
I was modifying your code a little bit since your code returns all Cars that has 1 or more security (and not all), i.e. returns all cars which has a securityList that contain at least a subset of the securityList.
Here is my code:
public List<Car> searchCars(String makePattern, Set<Security> requiredSecuirtySet) {
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<Car> cq = cb.createQuery(Car.class);
Root<Car> car = cq.from(Car.class);
Predicate criteria = cb.conjunction();
for (Security security : carQueryData.getSecurityCriteria()) {
criteria = cb.and(criteria, car.get(Car_.securityList).in(security) );
}
// Add more predicates, for instance:
// for (Equipment equipment : carQueryData.getEquipmentsCriteria()) {
// criteria = cb.and(criteria, car.get(Car_.equipmentList).in(equipment) );
// }
Predicate makePredicate = cb.equal(car.get(Car_.make), makePattern);
cq.select(car).where(makePredicate, criteria);
return em.createQuery(cq).getResultList();
}
Best regards

Categories