QueryDsl inner join - java

I have been struggling with query dsl joins. My problem is with unidirectional Many to One mapping. I am unable to fetch data from both tables as query dsl is unable to find many to one relationship entity as null. As I am using latest query dsl library and spring data.
#Entity
Public class Parent {
#Id
#GeneratedValue
private Integer id;
#ManyToOne
private Child c;
// Getters and setters.
}
#Entity
public class Child {
#Id
#GenereatedValue
private Integer id;
private String name;
// Getters and setters
}
// Query dsl method.
public List<Parent> getParentsByChildName(String name){
QParent qParent = QParent.parent ;
QChild qChild = QChild.child;
return queryFactory.select(Qparent).from(qChild).innerJoin(qParent). where (qChild.name.eq(name));
}

Related

Right way to create criteria for a spefic one-to-many relation with Hibernate

I have this model;
#Entity
class Product{
long id;
#OneToMany
private List<ProductFieldValue> fieldValues;
}
#Entity
class ProductFieldValue{
long id;
String value;
#ManyToOne
Product product;
#ManyToOne
ProductField field;
}
#Entity
class ProductField{
long id;
#Column(unique=true)
String name;
}
I'm trying to build a query where I can achieve these statements;
A product that has multiple field values with requested fields, such as;
(product.fieldValues.value = 'val1' && product.fieldValues.field.name = 'field1') && (product.fieldValues.value = 'val2' && product.fieldValues.field.name = 'field2')
I've tried to JOIN same association twice with Hibernate but appereantly that throws an exception.
What would be the best way to achive this using Criteria or HQL queries ?

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.

JPA ManyToMany join table query

Assuming theses Entities
#Entity
public class EntityNote implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#SequenceGenerator(name="SeqEntityNote", sequenceName="SeqEntityNote", allocationSize = 1)
#GeneratedValue(strategy=GenerationType.SEQUENCE, generator="SeqEntityNote")
private long id;
private Date date;
private String subject;
private String content;
#ManyToMany
private List<EntityTopic> listEntityTopic;
//setters/getters
#Entity
public class EntityTopic implements Serializable {
#Id
#SequenceGenerator(name="SeqEntityTopic", sequenceName="SeqEntityTopic", allocationSize = 1)
#GeneratedValue(strategy=GenerationType.SEQUENCE, generator="SeqEntityTopic")
private long id;
private String name;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
In my DB, a join table named "entity_note_list_entity_topic" records the ManyToMany relation.
This works correctly so far.
But I'd like to perform a count query like 'how many EntityNotes per EntitityTopic'
Unfortunatly I'm quite lost in this situation.
How this query can be written ?
Do I need other elements in my two entities ?
(In many examples I see a reverse relation using mappedBy attribute on ManyToMany.. Do I need this ?)
It will be the easiest if you make the many to many relation bidirectional. There are no serious extra costs involved, as it uses the same db structure, and the list are lazy loaded so if the relation is not being used the lists are not populated (you can hide the second direction by making accessors private).
Simply change:
#Entity
public class EntityTopic implements Serializable {
...
#ManyToMany(mappedBy="listEntityTopic")
private List<EntityNote> notes;
}
You can issue normal count jpql queries, for example:
SELECT count(n) from EntityTopic t INNER JOIN t.notes n where t.name =:name
so you don't neet to retrieve the notes and topics if don't need to.
But I also believe that your original mapping can also be queries with:
SELECT COUNT(n) FROM EntityNote n INNER JOIN n.listEntityTopic t WHERE t.name = :name
If you have the following code:
#Entity
public class EntityNote implements Serializable {
#ManyToMany(fetch = FetchType.LAZY)
private List<EntityTopic> topics;
}
#Entity
public class EntityTopic implements Serializable {
#ManyToMany(fetch = FetchType.LAZY)
private List<EntityNote> notes;
}
Then, topic.getNotes().size() will give you the number of notes associated with a topic. When using Hibernate as the JPA provider, a SELECT COUNT(...) query is issued for this instead of loading all the associated notes. If this does not work for you out-of-the-box, mark the collections as extra lazy using the instructions in this post.

Joining two table in spring data JPA and Querying two table data from repository

I am using Sprind JPA, Spring 3.1.2(in future 3.2.3), Hibernate 4.1 final.
I am new to Sprind Data JPA. I have tow Table Release_date_type and Cache_media which entities are as follows :
ReleaseAirDate.java
#Entity
#Table(name = "Release_date_type")
public class ReleaseDateType {
#Id
#GeneratedValue(strategy=GenerationType.TABLE)
private Integer release_date_type_id;
#Column
private Integer sort_order;
#Column
private String description;
#Column
private String data_source_type;
#Column(nullable = true)
private Integer media_Id;
#Column
private String source_system; with getters and setters..
and CacheMedia as
#Entity
#Table(name = "Cache_Media")
public class CacheMedia {
#Id
#GeneratedValue(strategy=GenerationType.TABLE)
private Integer id;
#Column(name="code")
private String code;
#Column(name="POSITION")
private Integer position;
#Column(name="DESCRIPTION")
private String media_Description; with setter and getters.
Now my repository interface is as follows :
public interface ReleaseDateTypeRepository extends CrudRepository<ReleaseDateType, Long>{ }
Now i want to write a method(Query) in ReleaseDateTypeRepository interface which can get all the data from Release_Date_Type table including appropriate media_description from Table 'Cache_Media' based on media_id of Release_date_type table.
So my select (SQL)query looks like
SELECT * from Release_Date_Type r left join Cache_Media c on r.media_id=c.id
I don't know how to map entities.
I tried so many thing but not luck.
Any help is appreciated.
Its not the answer for joining via Hibernate, but alternatively you can create a view with your join and map the view to your objects

Hibernate inheritance, Collections and #OrderedBy superclass attribute generates MySQL syntax Error

I' using Hibernate 3.6.1 to map three entities
#Entity
#Inheritance(strategy = InheritanceType.JOINED)
public class Entry {
private Long id;
private Date publishedAt;
#Id
public getId() {...}
...
}
#Entity
public class Category {
private Long id;
List<Podcast> podcasts;
#Id
public getId() {...}
#OneToMany(mappedBy = "category", cascade = {CascadeType.ALL}, fetch = FetchType.EAGER)
#OrderBy("publishedAt")
public List<Podcast> getPodcasts() {
return podcasts;
}
}
and
#Entity
public class Podcast extends Entry {
private Category category;
#ManyToOne(fetch = FetchType.EAGER)
public PodcastsCategory getCategory() {
return category;
}
}
If i try to fetch a Category instance, i get an Exception
com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Unknown column 'podcasts0_.Entry.publishedAt' in 'order clause'
What causes this exception? Whats wrong with this mapping?
It's caused by the following bug: HHH-3577 Wrong SQL in order by clause when using joined subclasses.
As a workaround you can remove #OrderBy and fetch = FetchType.EAGER on podcasts and load category using the following query instead of get():
SELECT DISTINCT c
FROM Category c LEFT JOIN FETCH c.podcasts p
WHERE c.id = ?
ORDER BY p.publishedAt
You could try the annotation #MappedSuperClass. See section 2.2.4.4. Inherit properties from superclasses of the hibernate documentation.

Categories