Criteria query, find all groups that I'm not member of - java

I have two entities, Group and GroupMember. Group is like it sounds a group that has name and some other properties. Members of the group are mapped with GroupMember entity, which has an entry with a User and a Group for every group the user is member of. They look like the following:
#Entity
#Table(name = EntityTokens.GROUP_TABLE)
public class Group
{
#Id
#Column(name = EntityTokens.GROUP_ID_COLUMN)
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long groupId;
...
// Group members
#OneToMany(orphanRemoval = true, fetch = FetchType.LAZY, mappedBy = "group", cascade = {CascadeType.ALL})
private Collection<GroupMember> groupMembers;
}
#Entity
#Table(name = EntityTokens.GROUP_MEMBER_TABLE)
public class GroupMember
{
#Id
#Column(name = EntityTokens.GROUP_MEMBER_ID_COLUMN)
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long memberId;
...
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = EntityTokens.GROUP_MEMBER_ID_COLUMN)
private Group group;
}
I'm trying to write a criteria query that returns all groups that have some predefined properties and that the current user is not part of. My query looks like this:
CriteriaQuery<Group> q = cb.createQuery(Group.class);
Root<Group> root = q.from(Group.class);
Join<Group, GroupMember> groups = root.join(Group_.groupMembers);
q.select(root);
q.where(cb.notEqual(groups.get(GroupMember_.user), user),
cb.equal(root.get(Group_.global), false),
cb.equal(root.get(Group_.personal), false),
cb.equal(root.get(Group_.privacy), GroupPrivacy.PUBLIC));
q.distinct(true);
The user here represents the current user. This query doesn't work because if there are other members that are part of the same group as me they will be included in the query result due to the join. How should the correct query look like? I'm not that familiar with the criteria query API yet.

join for a ToMany relationship is conceptually an "anyOf" operation, meaning if any of the many is true then the expression is true.
What you want is an "allOf", criteria does not have this, you need to use a sub select for this in SQL.
The JPQL would be,
Select g from Group g where not exists (select m from g.members m where m.user = :user)
The criteria would be the same, using a sub criteria query for the exists.

Related

How to get collection of property in Join JPA criteria API

I'm trying to get a list of property policy_type_id from ListAttribute<Article, PolicyType>, but I can't figure out how to do it.
I come up with an inefficient method was select whole Collection of PolicyType then filter it later
Root<ArticleVersion> a = cq.from(ArticleVersion.class);
Join<ArticleVersion, Article> join1 = a.join(ArticleVersion_.article, JoinType.INNER);
cq.where(getCondition(cb, join1));
cq.multiselect(join1.get(Article_.article_id), join1.get(Article_.policyTypes), a);
Sadly, hibernate generate an error query like this
select article1_.article_id as col_0_0_, . as col_1_0_, articlever0_.article_version_id as col_2_0_ . As you can see, there is a . in select that make query broken (which I believe select all)
#Entity
#Table(name = "PolicyType", schema = "SM_Request")
#Getter
#Setter
#NoArgsConstructor
public class PolicyType {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int policy_type_id;
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#JoinTable(name = "PolicyTypeArticle", schema = "SM_Request",
joinColumns = #JoinColumn(name = "policy_type_id"), inverseJoinColumns = #JoinColumn(name = "article_id"))
#JsonIgnore
private List<Article> articles;
}
After long searching, I think that hibernate doesn't support query tuple of primitive types and list of objects (which is kinda sad, compare to LINQ to query). I decided to break down my query into smaller parts. First, I select tuples of article_id and ArticleVersion. After that, I select a list of PolicyType which also contains article_id, and union 2 lists back.
By the time I wrote this, I have an idea that I could select all 3 joins together and transform data the way I want. But It really depend on many aspects, like how many join or which type of join you're using, how fast data in each table grown (JOIN queries vs multiple queries)

Operand should contain 2 column(s) when fetching data through JPQL using IN & MEMBER OF clause

I am trying to fetch data of employee availability creating a query. I have used #ElementCollection annotation to map skills and daysAvailable, which stores data into other table.
TypedQuery<Employee> query = entityManager.createQuery("select e from Employee e where :day MEMBER OF e.daysAvailable and :list MEMBER OF e.skills ", Employee.class);//
query.setParameter("day", dayOfWeek);
query.setParameter("list", employeeRequestDTO.getSkills());
List<Employee> list = query.getResultList();
I am trying to pass a set in my query. If there is only one element in set then the query works but if more then 1, then it is not able to fetch. I have tried using e.skills in (:list) instead of
:list MEMBER OF e.skills. But still no luck!!
#Entity
public class Employee {
#Id
#GeneratedValue
private long id;
#Nationalized
private String name;
#ElementCollection(fetch = FetchType.EAGER)
#CollectionTable(name = "skills", joinColumns = #JoinColumn(name = "emp_id"))
#Enumerated(EnumType.STRING)
private Set<EmployeeSkill> skills;
#ElementCollection(fetch = FetchType.EAGER)
#CollectionTable(name = "days", joinColumns = #JoinColumn(name = "emp_id"))
#Enumerated(EnumType.STRING)
private Set<DayOfWeek> daysAvailable;
// Getters & Setters
This is the domain i have created and now want to fetch all employees who are available on a particular day and have particular skills. I have created query for this but it is showing error - java.sql.SQLException: Operand should contain 2 column(s)
This is the query generated-
select * from employee e where ( 'TUESDAY' in ( select d.days_available from days d where e.id=d.emp_id)) and (( 'PETTING','FEEDING' ) in (select s.skills from skills s where e.id=s.emp_id));
Any suggestions?
In JPQL, MEMBER OF takes a single argument and checks if that's contained in the provided collection.
Check out the Hibernate User Guide for more details.
Try this query instead:
select e
from Employee e
join e.skills s
where
:day MEMBER OF e.daysAvailable and
s in :list

Jpa select with relations

I have 4 tables in relations. A,B,C,D.
So I wrote select bellow:
select NEW org.example.ExtendsA(a,b.name,c.name,d.name)
from A a LEFT JOIN a.bItems b LEFT JOIN a.cItems c LEFT JOIN b.dItems d
order by b.name ASC;
A is unique but the relations are incomplette.
and I tried this:
select NEW org.example.ExtendsA(a,b.name,c.name,d.name)
from A a LEFT JOIN FETCH a.bItems b LEFT JOIN FETCH a.cItems c
LEFT JOIN FETCH b.dItems d order by b.name ASC;
A is not unique.
A object definition is:
#Entity
public class A implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "ID")
private Long id;
#NotNull
#Size(min = 1, max = 100)
#Column(name = "NAME")
private String name;
#OneToMany(mappedBy = "aId")
private List<B> bItems;
#OneToMany(mappedBy = "aId")
private List<C> cItems;
}
Some relations are empty but need A object with null relations.
Some A object has more than one relations between B and C and I want to select with all in one A object (distinct A).
Would you help me how to solve this issue? Maybe the approach is bad?
I use EclipeLink data provider.
This is a typical problem with loading OneToMany relathionships.
This is because of the nature of SQL result sets. Imagine a SQL result of a join of entity with it's other entities linked to it. On the left side, fields of this entity will be duplicated as many times as many related entities it has.
Unforunately, EclipseLink doesn't filter them out and you get many items of the same entity in your result. Although, EclipseLink is smart enough and each item will actually be the same Java object instance.
It's also the reason why you can't use setMaxResults in such queries.
You need to use distinct keyword that in this particular keys will not be mapped to a real SQL distinct, but will filter duplicated entities. Or, you can filter them manually.

How to get root by recursive oracle query in Hibernate

I'm a little curious, is there a way to get result of connect by sql query like root Entity with already mapped descendants.
So if I'm insert in base something like this:
insert into table test (id, parent_id, some_text) values
(1, null, 'a'),
(2, 1, 'b'),
(3, 1, 'c'),
(4, 2, 'd');
then by sql query
select *
from test t
start with t.id = 1
connect by prior t.id = t.parent_id
order siblings by t.some_text
I will get
id | parent_id | some_text
1 null a
2 1 b
4 2 d
3 1 c
and by entity
#Entity
#Table(name = "test")
public class Test {
#Id
#Column(name = "id")
#GeneratedValue(generator = "increment")
#GenericGenerator(name = "increment", strategy = "increment")
private BigInteger id;
#Column(name = "parent_id")
private BigInteger parent_id;
#Column(name = "some_text")
private String someText;
#OneToMany(mappedBy = "parent")
private Set<Test> descendants;
#ManyToOne
#JoinColumn(name = "parent_id")
private Test parent;
// getters and setters
}
it will back to me as list of Test. It possible to get root and full tree by recursive function, but it will get new query on iteration (it will very long if I have a big tree).
So is there a possible good way to get root of this tree with already mapped descendants by this query (maybe extend/implement some class/interface which will process mapping from jdbc to entity)?
you can use CONNECT_BY_ROOT unary operator.
See docs
select t.*, connect_by_root id
from test t
start with t.id = 1
connect by prior t.id = t.parent_id
order siblings by t.some_text
BTW: this has nothing to do with Hibernace. This is purely Oracle specific solution.

Mapping a read-only database with a many-to-many relation without a join table

I have a question similar to #ManyToMany without join table (legacy database) with an additional issue.
I have two tables A and B
A with a multiple column primary key (ID and ID2)
B with a multiple column primary key (ID and ID3)
An row in A can reference several rows in B (B.ID = A.ID) and a row in B can be referenced by several rows in A.
EDIT: the database is a read-only legacy database that I cannot change. I do not need to map the relationships with JPA (I could just do it in my program logic with additional selects) but it would be nice.
It is basically a many-to-many relationship without a join table. Since, as for the linked question, I just have to read the tables, I tried with two one-to-many relationships in both classes.
The additional problem that I have is that both IDs used for the join are not the primary key.
I have the following classes:
#Entity
#Table( name = "A" )
#IdClass( PrimaryKeysA.class )
public class A {
#Id
#Column( name = "ID", insertable = false, updatable = false, columnDefinition = "char" )
private String id;
#Id
#Column( name = "ID2", insertable = false, updatable = false )
private int id2;
#OneToMany( cascade = CascadeType.ALL )
#JoinColumn( name = "ID", columnDefinition = "char", referencedColumnName = "ID" )
private Set< B > setOfBs;
}
#Entity
#Table( name = "B" )
#IdClass( PrimaryKeysB.class )
public class B {
#Id
#Column( name = "ID", insertable = false, updatable = false, columnDefinition = "char" )
private String id;
#Id
#Column( name = "ID3", insertable = false, updatable = false )
private int id3;
#OneToMany( cascade = CascadeType.ALL )
#JoinColumn( name = "ID", columnDefinition = "char", referencedColumnName = "ID" )
private Set< A > setOfAs;
}
Hibernate generates the following error:
Exception while preparing the app : referencedColumnNames(ID) of package.B referencing package.A not mapped to a single property
I don't really get the message: B.id is referencing a single property in A (A.id).
EDIT: as requested:
public class PrimaryKeysA implements Serializable {
private static final long serialVersionUID = 1L;
private int id1;
private int id2;
// getters/setters/equals/hashcode
}
PrimaryKeysB is similar with id3 instead of id2. Both classes A and B are simplified (anonymized) examples.
You could create a view that would act as join table:
CREATE VIEW AJOINB AS
SELECT A.ID as AID, A.ID2 as AID2, B.ID as BID, B.ID3 as BID3
FROM A JOIN B ON A.ID = B.ID
And then map it in JPA as a ManyToMany with AJOINB as join table.
If A.ID2 and B.ID3 were unique by themselves, you wouldn't even need to map A.ID and B.ID in your JPA beans.
Can you share some sample records from you tables?
The problem is very clear. For any one-many relationship, on the "one" side, there should be only one record that can be uniquely identified. Here, i think, since id is not unique there are multiple entries.
You may try to use #JoinColumns and add both the columns to uniquely identify the entity on the "one" side.
#OneToMany
#JoinColumns({
#JoinColumn(name="yourID1", referencedColumnName="yourID1"),
#JoinColumn(name="yourid2", referencedColumnName="yourid2")
})
I'm assuming that you have the following data.
table A:
id2 c1 id
100 content1 1000
101 content2 1001
table B:
id3 s1 id
100 content1 1000
101 content2 1000
102 content3 1001
103 content4 1001
Here id2 and id3 are unique. A.id is unique but b.id is not; a typical OneToMany scenario.
If I map A to B using A.id and B.id, then this becomes one-to-many where A(100) can refer to B(100, 101) as the id is 1000
This will work fine i think. But if you have to map from B to A using the same columns (as stated in the question) it will not work as the one side (B) has duplicates.
Am I understanding your question correctly?

Categories