How to create Predicate BooleanExpression for many to many relations in QueryDSL - spring

How can inner join be done on Many to Many relations using Predicate BooleanExpression?
I have 2 entities
public class A {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
#ManyToMany(fetch = FetchType.LAZY,
cascade = { CascadeType.DETACH, CascadeType.MERGE,
CascadeType.REFRESH, CascadeType.PERSIST})
#JoinTable(name = "a_b_maps",
joinColumns = #JoinColumn(name = "a_id", nullable =
false,referencedColumnName = "id"),
inverseJoinColumns = #JoinColumn(name = "b_id", nullable = false,
referencedColumnName = "id")
)
private Set<B> listOfB = new HashSet<B>();
}
public class B {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
#ManyToMany(fetch = FetchType.LAZY,
cascade = { CascadeType.DETACH, CascadeType.MERGE,
CascadeType.REFRESH, CascadeType.PERSIST})
#JoinTable(name = "a_b_maps",
joinColumns = #JoinColumn(name = "b_id", nullable =
false,referencedColumnName = "id"),
inverseJoinColumns = #JoinColumn(name = "a_id", nullable = false,
referencedColumnName = "id")
)
private Set<A> listOfA = new HashSet<A>();
}
A Base repo
#NoRepositoryBean
public interface BaseRepository<E, I extends Serializable>
extends JpaRepository<E, I> {
}
And a repository class for A
public interface Arepo extends BaseRepository<A, Integer>,
QueryDslPredicateExecutor<A> {
Page<A> findAll(Predicate predicate, Pageable pageRequest);
}
Now I want to use A Repo with Predicate query. I need to form a predicate where I can load A based on some given Bs
I tried
QA a = QA.a;
QB b = QB.b;
BooleanExpression boolQuery = null;
JPQLQuery<A> query = new JPAQuery<A>();
query.from(a).innerJoin(a.listOfB, b)
.where(b.id.in(someList));
Now I am able to form a JPQLQuery, but the repository expects a Predicate. How can I get Predicate from the JPQLQuery??
Or, how can the inner join be achieved using Predicate?

I am able to create a Predicate with the help of answer given here
https://stackoverflow.com/a/23092294/1969412.
SO, instead of using JPQLQuery, I am directly using
a.listOfB.any()
.id.in(list);
This is working like a charm.

Related

Springboot ExampleMatcher always returns no result

I have the following entity and I'm trying to use ExampleMatcher for simple queries:
#Entity(name="UserAccount")
#Table(name = "useraccount", catalog = "useraccount")
#Data
public class UserAccount implements Serializable
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(nullable = false, unique = true)
private String username;
#Column(nullable = false, unique = true)
private String mail;
private String password;
private boolean isEnabled;
private Timestamp credentialExpire;
private boolean isAccountNonLocked;
private boolean isSuspended;
private Timestamp accountExpire;
#ManyToMany(cascade = {CascadeType.DETACH, CascadeType.MERGE, CascadeType.REFRESH}, fetch=FetchType.LAZY)
#JoinTable(name = "user_to_privileges", catalog = "useraccount",
joinColumns = {#JoinColumn(name = "user_id", referencedColumnName = "id", nullable = false)},
inverseJoinColumns = {#JoinColumn(name = "privilege_id", referencedColumnName = "id", nullable = false)})
private Set<Privilege> privileges= new HashSet<>();
#ManyToMany(cascade = {CascadeType.DETACH, CascadeType.MERGE, CascadeType.REFRESH}, fetch=FetchType.LAZY)
#JoinTable(name = "user_to_organizations", catalog = "useraccount",
joinColumns = {#JoinColumn(name = "user_id", referencedColumnName = "id", nullable = false)},
inverseJoinColumns = {#JoinColumn(name = "organization_id", referencedColumnName = "id", nullable = false)})
private Set<Organization> organizations= new HashSet<>();
#OneToOne(mappedBy ="user", cascade = {CascadeType.REMOVE, CascadeType.MERGE, CascadeType.REFRESH}, fetch=FetchType.LAZY)
#PrimaryKeyJoinColumn
#Setter(AccessLevel.NONE)
private UserRegister register;
#OneToMany(mappedBy ="tokenId.user", cascade = {CascadeType.REMOVE, CascadeType.MERGE, CascadeType.REFRESH}, fetch=FetchType.LAZY)
private Set<SecureToken> tokens = new HashSet<>();
#OneToOne(mappedBy ="user", cascade = {CascadeType.REMOVE, CascadeType.MERGE, CascadeType.REFRESH}, fetch=FetchType.LAZY)
#PrimaryKeyJoinColumn
private UserLogin login;
//all the methods omitted from brevity
}
I create the Example matcher as follows:
UserAccount account= new UserAccount();
account.setUsername("John");
ExampleMatcher matcher = ExampleMatcher.matching()
.withIgnoreCase()
.withStringMatcher(ExampleMatcher.StringMatcher.CONTAINING);
Example<UserAccount> regExample = Example.of(account, matcher);
List<UserAccount> out = repository.findAll(regExample);
Consider that a user with username "John" exists, but the output is always empty, no matter what parameter I fill.
Edit: this helps to find the solution: Are there any possible ways to ignore all paths of JPA Example Matcher. There is no way to automatically ignore primitive fields when not used?
Edit: Notice that I want to find all the UserAccount containing the specified strings in the selected fields. With other entities the configuration of ExampleMatcher works.

Delete just one side of a manytomany relationship Hibernate

I have two tables that have a manytomany relationship:
first one is ad ( represents all the products)
#Entity
#Table(name = "ad")
public class Ad {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#ManyToOne(cascade = CascadeType.PERSIST)
#JoinColumn(name = "admin_id")
private Admin admin;
#ManyToMany(mappedBy = "ads", fetch = FetchType.LAZY)
private List<Order> orders = new ArrayList<>();
Second one is order:
#Entity
#Table(name = "`order`")
public class Order {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#ManyToOne( cascade=CascadeType.
#JoinColumn(name = "buyer_id")
private Buyer buyer;
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(name = "order_ad", joinColumns = {
#JoinColumn(name = "order_id", referencedColumnName = "id", nullable = false, updatable = false) }, inverseJoinColumns = {
#JoinColumn(name = "ad_id", referencedColumnName = "id", nullable = false, updatable = false) })
private List<Ad> ads = new ArrayList<>();
when I delete order using its repository that is representing a cancellation so I don't want the ads to be deleted as well.
How can I do that?
PS: I can't find a replacement for the orphanRemoval of the onetomany relationship

JPA Repository - Getting duplicates in List caused by table joins

I am having a very difficult situation and yet the situation is very complex and hard to find similar case in stackoverflow.
I have the following entities
Store
#Data
#Entity
#Table(name = "store")
public class Store implements IModel {
#Id
#EqualsAndHashCode.Exclude
#GeneratedValue(strategy = GenerationType.AUTO)
private UUID id;
#Enumerated(EnumType.STRING)
#Column(name = "storestatus", nullable = false)
private StoreStatus storeStatus = StoreStatus.UNKNOWN;
#OneToOne
#JoinColumn(name = "storetypecode_id")
private StoreTypeCode storeTypeCode;
#OneToOne(cascade = CascadeType.ALL, mappedBy = "store")
private Address address;
#Setter(AccessLevel.NONE)
#EqualsAndHashCode.Exclude
#OneToMany(fetch = FetchType.EAGER, mappedBy = "store")
private Set<StoreTranslation> storeTranslationList = new HashSet<>();
public Store() {
}
StoreTypeCode
#Data
#Entity
#Table(name = "storetypecode")
public class StoreTypeCode implements IModel {
#Id
#EqualsAndHashCode.Exclude
#GeneratedValue(strategy = GenerationType.AUTO)
private UUID id;
#Column(name = "displaysort", nullable = false)
private Integer displaySort = 999;
#Setter(AccessLevel.NONE)
#EqualsAndHashCode.Exclude
#OneToMany(fetch = FetchType.EAGER, mappedBy = "storeTypeCode")
private Set<StoreTypeCodeTranslation> storeTypeCodeTranslationList = new HashSet<>();
public StoreTypeCode() {
}
}
And StoreCategory
#Data
#Entity
#Table(name = "storeitemcategory")
public class StoreItemCategory implements IModel {
#Id
#EqualsAndHashCode.Exclude
#GeneratedValue(strategy = GenerationType.AUTO)
private UUID id;
#ManyToOne
#JoinColumn(name = "store_id")
private Store store;
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(
name = "storeitemcategory_storeitem",
joinColumns = {#JoinColumn(name = "storeitemcategory_id", referencedColumnName = "id")},
inverseJoinColumns = {#JoinColumn(name = "storeitem_id", referencedColumnName = "id")})
private List<StoreItem> storeItems = new ArrayList<>();
#Setter(AccessLevel.NONE)
#EqualsAndHashCode.Exclude
#OneToMany(fetch = FetchType.EAGER, mappedBy = "storeItemCategory")
private Set<StoreItemCategoryTranslation> storeItemCategoryTranslationList = new HashSet<>();
public StoreItemCategory() {
}
public void addStoreItem(StoreItem storeItem) {
this.storeItems.add(storeItem);
}
}
With the above relationship, here is what I have.
Store A with storeTypeCode ("Cafe") and storeItemCategory ("Iced drinks")
StoreTypeCode has two translations 1) for English, 2) for Chinese.
Whenever I add an item to storeItems in StoreItemCategory, I get duplicates in the list. (And multiple duplicate records are inserted to 'storeitemcategory_storeitem' table.)
StoreItemCategory sic = storeItemCategoryRepository.findById(storeItemCategoryid).get();
sic.addStoreItem(new StoreItem(...));
sic = storeItemCategoryRepository.save(sic);
I suspect this has something to do with the way tables are joined for translations because when I run a query created from Spring for getting StoreItemCategory, I get multiple records of StoreItemCategory (one for English and one for Chinese from StoreTypeCode).
select
*
from
storeitemcategory storeitemc0_
left outer join
store store1_
on storeitemc0_.store_id=store1_.id
left outer join
storetranslation storetrans2_
on store1_.id=storetrans2_.store_id
left outer join
storetypecode storetypec3_
on store1_.storetypecode_id=storetypec3_.id
left outer join
storetypecodetranslation storetypec4_
on storetypec3_.id=storetypec4_.storetypecode_id
left outer join
address address5_
on store1_.id=address5_.store_id
left outer join
storeitemcategorytranslation storeitemc6_
on storeitemc0_.id=storeitemc6_.storeitemcategory_id
left outer join
storeitemcategory_storeitem storeitems7_
on storeitemc0_.id=storeitems7_.storeitemcategory_id
left outer join
storeitem storeitem8_
on storeitems7_.storeitem_id=storeitem8_.id
left outer join
store store9_
on storeitem8_.store_id=store9_.id
left outer join
storeitemtranslation storeitemt10_
on storeitem8_.id=storeitemt10_.storeitem_id
where
storeitemc0_.id=?
All my tables will have translations tables and I am not sure how to get-around with this without using set.
Does anyone have similar experience?

Spring boot domain class required for mapping table

I m new to Spring Boot. I have a table (Team) that has resources, am storing in a separate table (Resources) and have team_resource mapping table (with fields teamid, resourceid). My question is should I have a domain class for the mapping_table too ?
When I m inserting a new team (POST) with resources I create entry in all 3 tables. I m using facade/dao pattern for writing/ reading to the DB. I have to handle when the team is modified/ deleted. Should I have a domain class for the mapping_table?
There are multiple approaches you can handle it
Approach 1
Define #ManyToMany between your Team and Resources entity like this:
In Team Entity
#ManyToMany(fetch = FetchType.LAZY,
cascade = {
CascadeType.PERSIST,
CascadeType.MERGE
})
#JoinTable(name = "resources",
joinColumns = { #JoinColumn(name = "id") },
inverseJoinColumns = { #JoinColumn(name = "id") })
private Set<Resources> resources= new HashSet<>();
In your resources entity:
#ManyToMany(fetch = FetchType.LAZY,
cascade = {
CascadeType.PERSIST,
CascadeType.MERGE
},
mappedBy = "resources")
private Set<Team> teams= new HashSet<>();
Approach 2
#Entity
#Table(name = "team_resources")
public class TeamResources implements Serializable {
#EmbeddedId
private TeamResourcesId id;
#ManyToOne
#JoinColumn(name = "team_id", referencedColumnName = "id", insertable = false, updatable = false)
private Team team;
#ManyToOne
#JoinColumn(name = "resources_id", referencedColumnName = "id", insertable = false, updatable = false)
private Resources resources;
public TeamResources (Team u, Resources r) {
// create primary key
this.id = new TeamResourcesId (u.getUserId(), q.getQuestionId());
// initialize attributes
this.user = u;
this.question = q;
}
#Embeddable
public static class TeamResourcesId implements Serializable {
#Column(name = "team_id")
protected Long teamId;
#Column(name = "resources_id")
protected Long resourcesId;
public TeamResourcesId () {
}
public TeamResourcesId (Long teamId, Long resourcesId) {
this.teamId= teamId;
this.resourcesId= resourcesId;
}
//Getter , setter. equal and hash
}
so to answer your question, follow second approach and its good to not define bidirectional approach as it can lead to some run time problem if not handled properly.

Spring JpaRepository manyToMany bidirectional should save instead of update

if got a language table and a system table with a many-to-many relationship:
Language:
#JsonAutoDetect
#Entity
#Table(name = "language")
public class Language implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "language_id", nullable = false)
private int languageId;
#Column(name = "language_name", nullable = false)
private String languageName;
#Column(name = "language_isocode", nullable = false)
private String languageIsoCode;
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(name = "system_language", joinColumns = {#JoinColumn(name = "language_id", updatable = false)},
inverseJoinColumns = {
#JoinColumn(name = "system_id", updatable = false)}, uniqueConstraints = {
#UniqueConstraint(columnNames = {
"language_id",
"system_id"
})})
private List<System> systems;
public Language() {
}
// GETTER & SETTERS
// ....
}
System
#JsonAutoDetect
#Entity
#Table(name = "system")
public class System implements Serializable {
#Id
#Column(name = "system_id", nullable = false)
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer systemId;
#Column(name = "system_name", nullable = false, unique = true)
private String systemName;
#OneToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "university_id", nullable = false)
private University university;
#JoinColumn(name = "calender_id", nullable = false)
#OneToOne(fetch = FetchType.EAGER)
private Calendar calender;
#OneToMany(mappedBy = "system")
#LazyCollection(LazyCollectionOption.FALSE)
private List<SystemUserRole> systemUserRoleList;
#OneToMany(mappedBy = "system")
#LazyCollection(LazyCollectionOption.FALSE)
private List<Role> roleList;
#OneToOne(mappedBy = "system")
#LazyCollection(LazyCollectionOption.FALSE)
private CsmUserEntity csmUserEntity;
#ManyToMany(mappedBy = "systems")
#LazyCollection(LazyCollectionOption.FALSE)
private List<Language> languages;
public System() {
}
// GETTER & SETTERS
// ....
}
When im writing a first dataset (systemId=1, language_id=20) into the table, everything works fine. But when i try to write a second dataset with the same language_id but with other system_id (systemId=2, language_id=20), then the existing dataset gets updated. But i want to have a new dataset instead. What can i do?
Thanks in advance!

Resources