JPA ManyToMany where annotation - java

I'm using JPA / EclipseLink 2.5.2 and want to add an additional where clause to my ManyToMany Mapping. The SQL Code would look like this:
SELECT * FROM Friends f
INNER JOIN User u ON f.friend_id = u.id
WHERE f.verified=1;
So I managed to do the JoinMapping with:
#Entity
#NamedQueries({
#NamedQuery(name="User.findAll", query="SELECT u FROM User u"),
#NamedQuery(name="User.login", query="SELECT a FROM User a WHERE a.username = :name and a.password = :password"),
#NamedQuery(name="User.findId", query="SELECT a FROM User a WHERE a.id = :id"),
#NamedQuery(name="User.findName", query="SELECT a FROM User a WHERE a.username = :name")
})
public class User implements Serializable {
...
#ManyToMany
#JoinTable(
name="Friends"
, joinColumns={
#JoinColumn(name="user_id")
}
, inverseJoinColumns={
#JoinColumn(name="friend_id")
}
)
List<User> friends;
}
But I dont know how to add the WHERE f.verified=1
How can this be realized?
Thanks in advance,
Cassidy

As advised in my comment you cannot use #ManyToMany as you require an additional column in the join table to indicate whether verified or not.
You then need to use a #OneToMany with an additional Entity, say Friendship. We can use the verified column as a discriminator and use a simple class hierarchy to distinguish between Unconfirmed and Confirmed friends.
This will then look something like the below (haven't tested it fully).
Note, I tested this with Hibernate but there is an issue so will need to look at again. These posts suggests the issue may be Hibernate specific:
https://stackoverflow.com/a/1631055/1356423.
Why #OneToMany does not work with inheritance in Hibernate
So may be worth trying with EclipseLink.
#Entity
#Table(name = "users")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private int id;
#OneToMany(mappedBy = "user")
private Set<ConfirmedFriendship> confirmedFriendships;
#OneToMany(mappedBy = "user")
private Set<UnconfirmedFriendship> unconfirmedFriendships;
public List<User> getConfirmedFriends() {
return getFriends(confirmedFriendships);
}
public List<User> getUnconfirmedFriends() {
return getFriends(unconfirmedFriendships);
}
private List<User> getFriends(Set<? extends Friendship> friendships){
List<User> friends = new ArrayList<User>();
for(Friendship friendship : friendships) {
friends.add(friendship.getFriend());
}
return friends;
}
}
Base Entity for Friendship:
#Entity
#Table(name = "friendships")
#Inheritance(strategy = InheritanceType.SINGLE_TABLE)
#DiscriminatorColumn(name = "verified")
public abstract class Friendship {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private int id;
#ManyToOne
#JoinColumn(name = "user_id")
private User user;
#ManyToOne
#JoinColumn(name = "friend_id")
private User friend;
#Column(name = "verified")
private boolean verified;
public int getId() {
return id;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
public User getFriend() {
return friend;
}
public void setFriend(User friend) {
this.friend = friend;
}
public boolean isVerified() {
return verified;
}
public void setVerified(boolean verified) {
this.verified = verified;
}
}
Two subclassses which use the verified column as discriminator:
#Entity
#DiscriminatorValue(value = "1")
public class ConfirmedFriendship extends Friendship {
}
#Entity
#DiscriminatorValue(value = "0")
public class UnconfirmedFriendship extends Friendship {
}

I dont think there is JPA standard to include the "Where" option.
Personally I use NamedQuery to retrieve the entities. You can also use CriteriaQuery.
Well, In eclipseLink, you can use #AdditionalCriteria("this.verified=1")

Related

Fetch an entity including child entities with filter/condition

I have the following entities
RegisteredProgram
#Data
#NoArgsConstructor
#Entity
#EntityListeners(RegisteredProgramAuditListener.class)
public class RegisteredProgram extends Auditable<String> {
#OneToMany(mappedBy = "registeredProgram", cascade = CascadeType.ALL)
#JsonBackReference
private List<Trainer> trainerList;
#OneToMany(mappedBy = "registeredProgram", cascade = CascadeType.ALL)
#JsonBackReference
private List<Official> officialList;
}
Trainer
#Data
#NoArgsConstructor
#EntityListeners(TrainerAuditListener.class)
#Entity
public class Trainer extends Auditable<String> {
#ManyToOne
#JoinColumn(name = "REGISTERED_PROGRAM_ID", nullable = false)
#JsonManagedReference
private RegisteredProgram registeredProgram;
#Type(type = "yes_no")
private Boolean isDeleted = false;
}
Official
#Data
#NoArgsConstructor
#EntityListeners(OfficialAuditListener.class)
#Entity
public class Official extends Auditable<String> {
#ManyToOne
#JoinColumn(name = "REGISTERED_PROGRAM_ID", nullable = false)
#JsonManagedReference
private RegisteredProgram registeredProgram;
#Type(type = "yes_no")
private Boolean isDeleted = false;
}
Basically I have entities with many to one relationship with RegisteredProgram, (Trainer-RegisteredProgram, Official-RegisteredProgram). Now I have a service which fetches a registered program by id and I should only include all the Trainer and Official with isDeleted false. I have the service below:
Service
#Override
public RegisteredProgramRequestDto getRegisteredProgramDto(Long id) {
RegisteredProgram registeredProgram = registeredProgramRepository.getOne(id);
RegisteredProgramRequestDto registeredProgramRequestDto = programRegistrationMapper
.registeredProgramToRequestDto(registeredProgram);
registeredProgramRequestDto.setOfficialDtoList(
registeredProgramRequestDto.getOfficialDtoList()
.stream()
.filter(officialDto -> !officialDto.getIsDeleted())
.collect(Collectors.toList())
);
registeredProgramRequestDto.setTrainerDtoList(
registeredProgramRequestDto.getTrainerDtoList()
.stream()
.filter(trainerDto -> !trainerDto.getIsDeleted())
.collect(Collectors.toList())
);
return registeredProgramRequestDto;
}
My question is, is there any way that I can improve my service more efficiently?
Yes: The query to select only trainers and officials isDeleted is false is part of JPA. A #EntityGraph is also part of JPA but can be done a little easier through spring-data-jpa.
public interface RegisteredProgramRepository extends JpaRepository<RegisteredProgram, Long>{
#Query("select rp from RegisteredProgram rp join rp.officials rpos join rp.trainers rpts where rp.id = :id and rpos.isDeleted = false and rpts.isDeleted = false")
#EntityGraph(attributePaths = {"officials", "trainers"}, type = EntityGraphType.LOAD)
RegisteredProgram getByIdNotDeleted(#Param("id") Long id);
}
This does everything through JPA with a single query.

Problem with many to many relationship query JPA

I have 2 entites User and Role with many to many relationship. I want to get role_id for specific user_id.
#Entity
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#ManyToMany
private Set<Role> roles;
}
#Entity
#Table(name = "role")
public class Role {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
#ManyToMany(mappedBy = "roles")
private Set<User> users;
}
#Override
public List<Long> getUserRole(User user) {
return entityManager.createNativeQuery("??????").getResultList();
}
How should I write query in JPA?
First if you use createNativeQuery then you'll supply a native SQL query. If you want to use JPQL use the createQuery
To select the role_id by user id do
select role.id from Role role join role.users user where user.id=:p1
After that, you can do
entityManager.createQuery("query").setParameter("p1", user.getId()).getResultList();

updating an entity in many-to-many relationship

I have a Spring project that uses JPA with Hibernate and MySQL database. Database includes three tables: Users, Roles, and many-to-many join table UserRoles.
Below are the corresponding classes for the tables.
ApplicationUser.java
#Entity
#Table(name = "APPLICATION_USER")
public class ApplicationUser extends ExtAuditInfo {
public static final Long SYSTEM_USERID = 1000L;
#Id
#Column(name = "APPLICATION_USER_ID")
private long applicationUserId;
#Column(name = "LOGIN_NAME")
private String loginName;
#Column(name = "LAST_NAME")
private String lastName;
#Column(name = "FIRST_NAME")
private String firstName;
#Column(name = "MIDDLE_NAME")
private String middleName;
#OneToMany(mappedBy = "id.applicationUser", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
private List<UserRole> roles =new ArrayList<>();
//get and set methods
Role.java
#Entity
#Table(name = "ROLE")
#NamedQueries({
#NamedQuery(name = "Role.getRoleById", query = "select r from Role r where r.roleId =:roleId"))}
public class Role extends AuditInfo {
#Id
#Column(name="ROLE_ID")
private long roleId;
#Column(name="ACTIVE_FLAG")
private String activeFlag;
#Column(name="ROLE_NAME")
private String roleName;
#OneToMany(mappedBy = "id.role", fetch = FetchType.LAZY)
private List<UserRole> users = new ArrayList<>();
//get and set methods
UserRole.java
#Entity
#AssociationOverrides({
#AssociationOverride(name = "id.applicationUser",
joinColumns = #JoinColumn(name = "APPLICATION_USER_ID")),
#AssociationOverride(name = "id.role",
joinColumns = #JoinColumn(name = "ROLE_ID")) })
#Table(name = "USER_ROLE")
public class UserRole extends ExtAuditInfo implements Serializable{
#EmbeddedId
private UserRoleID id = new UserRoleID();
#Column(name="USER_ROLE_VER")
private long userRoleVer;
public UserRole(){
}
#Transient
public ApplicationUser getApplicationUser() {
return this.id.getApplicationUser();
}
public void setApplicationUser(ApplicationUser applicationUser) {
this.id.setApplicationUser(applicationUser);
}
public long getUserRoleVer() {
return this.userRoleVer;
}
public void setUserRoleVer(long userRoleVer) {
this.userRoleVer = userRoleVer;
}
#Transient
public Role getRole() { return this.id.getRole(); }
public void setRole(Role role) { this.id.setRole(role); }
}
UserRoleID.java
#Embeddable
public class UserRoleID implements Serializable {
#ManyToOne(cascade = CascadeType.ALL)
private ApplicationUser applicationUser;
#ManyToOne(cascade = CascadeType.ALL)
private Role role;
private static final long serialVersionUID = 1L;
public UserRoleID() {
}
public ApplicationUser getApplicationUser() {
return this.applicationUser;
}
public void setApplicationUser(ApplicationUser applicationUser) {
this.applicationUser = applicationUser;
}
public Role getRole() {
return this.role;
}
public void setRole(Role role) {
this.role = role;
}
}
Now, when I create a sample user with viewer role, the record is being inserted into the Application_user and User_Role tables, but when I try to update the role of the user it is adding a new role to the user instead of updating the existing role.
This is what I'm doing
#TransactionAttribute(TransactionAttributeType.REQUIRED)
public void updateRole(ApplicationUser appUser, long roleId){
EntityManager em=getEntityManager();
TypedQuery<Role> query = em.createNamedQuery("Role.getRoleById", Role.class);
query.setParameter("roleId",roleId);
Role r = query.getSingleResult();
UserRole userRole= appUser.getRole().get(0);
userRole.setRole(r);
em.merge(userRole);
em.flush();
em.detach(userRole);
}
Any idea, what to do to update the existing role instead of creating a new role in user_role table?
You are assigning new role to user, so a new record is added in user_role table, and old user_role entry is deleted. That's the right behavior.
So it's not you called "update the role of user".
Update:
You should delete role manually when many-to-many relationship.
appUser.getRoles().remove(userRole);
em.remove(userRole);
UserRole newUserRole = new UserRole();
newUserRole.setRole(r);
appUser.getRoles().add(newUserRole);

Fetch all children as a list with Spring Data JPA using projection

I want to use projections to fetch only specific fields from my database with Spring Data JPA.
This is my (shortened) data model:
#Entity
#Table(name = "data")
#Data
public class DataEntity {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
private String name;
private String description;
#LazyCollection(LazyCollectionOption.FALSE)
#Fetch(value = FetchMode.SUBSELECT)
#OneToMany(mappedBy = "data", fetch = FetchType.LAZY)
#Builder.Default
private List<OwnerEntity> owners = new ArrayList<>();
}
#Entity
#Table(name = "owner")
#Data
public class OwnerEntity {
#EmbeddedId
public OwnerId id = new OwnerId();
#Fetch(value = FetchMode.JOIN)
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name="userId", insertable = false, updatable = false)
private UserEntity user;
#ManyToOne
#JoinColumn(name="dataId", insertable = false, updatable = false)
private InterfaceEntity iface;
}
#Embeddable
#Data
public class OwnerId implements Serializable {
private Integer dataId;
private String userId;
}
#Entity
#Table(name = "users")
#Data
public class UserEntity {
#Id
private String id;
private String name;
private String mail;
}
This is my projection:
public interface DataProjection {
String getName();
String getDescription();
List<UserEntity> getOwners();
}
Finally, this is my DAO:
public interface DataDao extends CrudRepository<DataEntity, Integer> {
#Query("select d.name as name, " +
" d.description as description, " +
" o.user as owners " +
"from DataEntity d " +
"left join d.owners o " +
"order by d.name")
List<DataProjection> getData();
}
It generally works but it returns one row for each owner resulting in multiple same DataProjections with a list containing only one of the owners.
A similar problem was mentioned in this question but as mentioned in the solutions comments this would make it an open projection loading all columns.
Is there a solution other than mapping the resulting rows programmatically?

error creating alias while using hibernate inheritance table per concrete class

I have a model where I implemented table per concrete class. So I have an abstract class having the common properties across multiple tables. and I have the following entities.
#Entity
#Inheritance(strategy=InheritanceType.TABLE_PER_CLASS)
public abstract class BaseForm{
#Id
#GeneratedValue(strategy = GenerationType.TABLE)
protected Long id;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id=id;
}
#Column(name = "submission_date")
protected Date submissionDate;
public Date getSubmissionDate() {
return submissionDate;
}
public void setSubmissionDate(Date submissionDate) {
this.submissionDate=submissionDate;
}
}
#Entity
#Table(name = "form_a")
public class FormA extends BaseForm{
#OneToMany(cascade = CascadeType.ALL, mappedBy = "formA", fetch = FetchType.LAZY)
#Cascade(value = org.hibernate.annotations.CascadeType.DELETE_ORPHAN)
#Fetch(FetchMode.SUBSELECT)
#OrderBy("id")
protected List<UserForm> userForms = new ArrayList<UserForm>();
}
#Entity
#Table(name = "form_b")
public class FormB extends BaseForm{
#OneToMany(cascade = CascadeType.ALL, mappedBy = "formB", fetch = FetchType.LAZY)
#Cascade(value = org.hibernate.annotations.CascadeType.DELETE_ORPHAN)
#Fetch(FetchMode.SUBSELECT)
#OrderBy("id")
protected List<UserForm> userForms = new ArrayList<UserForm>();
}
#Entity
#Table(name = "user_form")
public class UserForm {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
protected Long id;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
#ManyToOne(optional = true)
protected FormA formA;
#ManyToOne(optional = true)
protected FormB formB;
#ManyToOne(optional = true)
protected User user;
}
But whenever I try to use createAlias on the polymorphic query selecting all the forms joining userForms to return the user information for each form. It raises an exception.
2016-01-04 12:21:54,158 ERROR [org.hibernate.util.JDBCExceptionReporter] Not unique table/alias: 'userforms1_'
DetachedCriteria baseCR = DetachedCriteria.forClass(BaseForm.class);
baseCR.createAlias("userForms", "userForms");
);
baseCR.add(Restrictions.disjunction()
.add(Restrictions.isNotNull("userForms.formA"))
.add(Restrictions.isNotNull("userForms.formB"))
);
baseCR.setResultTransformer(CriteriaSpecification.DISTINCT_ROOT_ENTITY);
baseCR.setProjection(Projections.rowCount());
List results= ht.findByCriteria(baseCR);
Here is the generated hibernate query
SELECT COUNT(*) AS y0_
FROM
(SELECT id,
submissionDate,
1 AS clazz_
FROM form_a
UNION
SELECT id,
submissionDate,
2 AS clazz_
FROM form_b
) this_
INNER JOIN user_form userforms1_
ON this_.id=userforms1_.formA_id
INNER JOIN user_form userforms1_
ON this_.id=userforms1_.formB_id
WHERE (userforms1_.formA IS NOT NULL
OR userforms1_.formB IS NOT NULL)
Any idea what would be the problem. and how to solve it?
You are accessing subclass fields with a superclass alias, which is conceptually wrong (although works in some situations, but obviously not always).
You should consider moving userForms to the base class or transforming your query to something like this (JPQL, just translate it to an equivalent Criteria):
select bf from BaseForm bf
where bf.id in (select fa.id from FormA fa join fa.userForms)
or bf.id in (select fb.id from FormB fb join fb.userForms)

Categories

Resources