Hibernate, Abstract classes and common Entity - java

I got an abstract class called AbstractUser and two subclasses Organisation and User. Organisation got the same as User but with additional features.
I have another class aswell called notes
Organisation can create notes
And user can save notes. So notes belong to one Organisation but can be saved by alot of user objects
Is it possible to properly setup the notes to both have a ManyToMany relation with user and a ManyToOne with organisation?
I'd pretty much like a relation below, where
note_user note_organisation
note_id user_id id(incremented) note_id organisation_id
1 4 1 1 2
1 3 2 2 2
2 3 3 3 3
This is what I've got so far.
Organisation:
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long OrganisationId;
#OneToMany(fetch = FetchType.LAZY, cascade = { CascadeType.ALL })
#JoinTable(name = "organisation_note", joinColumns = { #JoinColumn(name = "OrganisationId") }, inverseJoinColumns = { #JoinColumn(name = "note_id") })
private Set<Note> bookmarks = new HashSet<>();
user:
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private long userId;
#ManyToMany( fetch = FetchType.LAZY, cascade = { CascadeType.PERSIST } )
#JoinTable(
name = "user_note",
joinColumns = {#JoinColumn(name = "userId")},
inverseJoinColumns = {#JoinColumn(name = "note_id")})
private Set<Note> notes = new HashSet<>();
note:
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private long projectId;
//Is this even necessary? I never save users to notes
#ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL )
private Set<User> users = new HashSet<>();
Above works. But it's behaving very very strangely. I get duplicated rows all the time when I create a note or save a note with a user. This will also allow me to create two notes with an organisation object, but the third will give me an error saying duplicate key for primary, same primary as the first note created. Is there anything I can do make my code more consistent and reliable?
Saving objects:
session = HibernateUtil.getSessionFactory().getCurrentSession();
session.beginTransaction();
session.persist(entity);
try {
session.flush();
} catch (Exception e) {
System.out.println("Error flushing");
}
session.getTransaction().commit();

Related

Changing a OneToMany Bidirectional Relationship to ManyToMany Bidirectional

I want to convert the following mapping on courseDetails to manyToMany.
This is because I get an exception Found shared references to a collection: com.xyz.courseDetails and I assume this happens because the relation is not actually one to many in the database, since there are some course_detail tuples that has multiple courses.
#Entity
#Table(name = "courses")
public class Course
{
#Column(name = "course_detail_id")
private Long extendedCourseDetailId;
...
#OneToMany(fetch = FetchType.LAZY, targetEntity = CourseDetail.class, cascade = CascadeType.ALL)
#JoinColumn(name="id", referencedColumnName="course_detail_id")
private List<CourseDetail> courseDetails = new ArrayList<>();
}
Simply changing the annotation to ManyToMany does not work, JPA somehow couldn't find the related columns. Why? How can I do this?
What do you think of this :
Let's assume the entity CourseDetail has as ID :
public class CourseDetail
{
#Id
#Column(name = "cd_id")
private Long courseDetailId;
So this non tested code might help you.
where the table "course__course_detail" will be automatically created to hold the relationship with 2 columns : "course_id" and "coursedetail_id".
#Entity
#Table(name = "courses")
public class Course
{
#Id
#Column(name = "c_id")
private Long courseId;
// #Column(name = "course_detail_id") // I comment because I dont understand the purpose
// private Long extendedCourseDetailId;
...
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(name = "course__course_detail",
joinColumns = #JoinColumn(name = "course_id", referencedColumnName="c_id"),
inverseJoinColumns = #JoinColumn(name = "coursedetail_id", referencedColumnName="cd_id"),
)
private List<CourseDetail> courseDetails = new ArrayList<>();
}
PS: NOT TESTED
Feel free to tell me more in comments.

Hibernate querying on ghost column

I have two entities that used to be linked by a one to many relation but now they are linked by a many to many relation declared as follow :
SalesTeam entity :
#Entity
#Table(name = "SALES_TEAMS")
public class SalesTeam {
#Id
#Column(name = "ID")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToMany(fetch = FetchType.LAZY, cascade = {CascadeType.REFRESH, CascadeType.PERSIST})
#JoinTable(name = "WORKFLOW_FOR_SALESTEAM", inverseJoinColumns = {
#JoinColumn(name = "WFC_ID")
})
private List<WorkFlowCode> workFlowCodes = new ArrayList<>();
}
And the WorkFlowCode entity :
#Entity
#Table(name = "WORK_FLOW_CODE")
public class WorkFlowCode {
#Id
#Column(name = "ID")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#ManyToMany(fetch = FetchType.LAZY, cascade = {CascadeType.REFRESH, CascadeType.PERSIST})
#JoinTable(name = "WORKFLOW_FOR_SALESTEAM", inverseJoinColumns = {
#JoinColumn(name = "ST_ID")
})
private List<SalesTeam> salesteam = new ArrayList<>();
}
As I said the relation use to be one SalesTeam for several workflow codes but the requirement change and now it need to be a many to many relation. So I had a relation table and remove the former SALES_TEAM_ID column from the WORK_FLOW_CODE table. The problem is that now I always get an error when I try to get the WorkFlowCode from a SalesTeam. It appears that hibernate still adds the removed column to the query thus the relation had changed and nothing is left from the former relation description.
Here is the hibernate generated query :
select workflowco0_.SALES_TEAMS_ID as SALES_TE3_13_0_, workflowco0_.WFC_ID as WFC_ID4_16_0_, workflowco1_.ID as ID1_17_1_ from WORKFLOW_FOR_SALESTEAM workflowco0_ inner join WORK_FLOW_CODE workflowco1_ on workflowco0_.WFC_ID=workflowco1_.ID where workflowco0_.SALES_TEAMS_ID=?
As you can see the former SALES_TEAM_ID from WORK_FLOW_CODE table is still there.
How can I remove it ?
Thx

JPA Query from 3 column join table

I have a following Entities. Means that User can belong to many businesses and for each business this user can has separate permissions. So an existing user can be assigned to another business. The Business_User table looks like this:
USER_ID BUSINESS_ID AUTHORITY_NAME
6 1 ROLE_ANALYTICS
6 1 ROLE_SELF_ANALYTICS
7 1 ROLE_REVIEWS
8 1 ROLE_ANALYTICS
8 1 ROLE_SELF_ANALYTICS
8 1 ROLE_REVIEWS
6 2 ROLE_REVIEWS
6 2 ROLE_SELF_ANALYTICS
Question: I am querying Users for ONE business by trying to build list of user DTO objects, that DTO exposes List<Authority>, problem is that I can't figure how should I get these authorities for each user from the Business_User table. Been trying to do fancy stuff with lambdas but have no luck. I am using Spring Data for queries, maybe can solve it in the repository.
Thanks in advance.
EDIT: If I would go another route by adding new join table BUSINESS_USER_AUTHORITY, how would I have to describe it in UserBusiness class? I also would like the primary key to be over user_id and business_id. Note that name is PK in Authority table.
Something like this, but that does not create me join table at all.
Change UserBusiness class:
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(
name = "BUSINESS_USER_AUTHORITY",
joinColumns = {#JoinColumn(name = "business_user_id", referencedColumnName = "id")},
inverseJoinColumns = {#JoinColumn(name = "authority_name", referencedColumnName = "name")})
private Set<Authority> authorities = new HashSet<>();
User
#Entity
#Table(name = "USER")
public class User extends AbstractAuditingEntity implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#OneToMany(cascade = CascadeType.ALL)
#JoinColumns({
#JoinColumn(name = "user_id", referencedColumnName = "id")
})
private Set<BusinessUser> businessUsers = new HashSet<>();
}
BusinessUser
#Entity
#Table(name = "BUSINESS_USER")
#IdClass(BusinessUser.class)
public class BusinessUser implements Serializable {
#Id
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "user_id")
private User user;
#Id
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "business_id")
private Business business;
#Id
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "authority_name")
private Authority authority;
}
Business
#Entity
#Table(name = "BUSINESS")
public class Business implements Serializable {
#OneToMany(cascade = CascadeType.ALL)
#JoinColumns({
#JoinColumn(name = "business_id", referencedColumnName = "id")
})
private Set<BusinessUser> businessUsers = new HashSet<>();
}
Authority
#Entity
#Table(name = "AUTHORITY")
public class Authority implements Serializable {
#NotNull
#Size(min = 0, max = 50)
#Id
#Column(length = 50)
private String name;
}
I ended up writing a function like that, but if anyone has a solution how would the design look with another join table between BusinessUser <--> BusinessUserAuthority then I'd be glad to implement it.
private Function<Map.Entry<User, List<BusinessUser>>, UserManagementDTO> getPermissionForUser() {
return user -> {
UserManagementDTO dto = userManagementMapper.userToUserManagementDTO(user.getKey());
dto.setAuthorities(user.getValue().stream().map(BusinessUser::getAuthority).collect(Collectors.toSet()));
return dto;
};
}

Why is #ManyToMany not working with non-primary key columns?

I have 2 entities - User and Role which have following relations: User has a manytomany relation to itself and a manytomany relation with the Role entity.
#Entity
public class UserEntity implements Serializable {
#Id
#Column(length = 12, columnDefinition = "BINARY(12)", name = "Id", unique = true)
private byte[] id;
#Column(name = "Login", unique = true, nullable = false)
private String login;
#ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinTable(name = "User_Role",
joinColumns = { #JoinColumn(name = "UserLogin", referencedColumnName = "Login") },
inverseJoinColumns = { #JoinColumn(name = "RoleId", referencedColumnName = "Id") })
private Set<RoleEntity> roles;
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinTable(name = "User_User",
joinColumns = { #JoinColumn(name = "UserParent") },
inverseJoinColumns = { #JoinColumn(name = "UserChild") })
private Collection<UserEntity> children;
...
}
and Role:
public class RoleEntity implements Serializable{
#Id
#Column(name = "Id", unique = true, nullable = false)
private String id;
...
}
The strange thing about the setup of DB is that the User_User relation is based on the binary Id keys
create table if not exists User_User (
UserParent binary,
UserChild binary
);
and the user-role is based on varchars
create table if not exists KNUser_UserRole (
UserLogin varchar,
RoleId varchar,
);
Now, when it runs, the user-user relationship work well. However, when I try to access the collection returned for roles, I get a ClassCastException:
java.lang.ClassCastException: **.entity.UserEntity cannot be cast to [B
at org.hibernate.type.descriptor.java.PrimitiveByteArrayTypeDescriptor.extractHashCode(PrimitiveByteArrayTypeDescriptor.java:41)
at org.hibernate.type.AbstractStandardBasicType.getHashCode(AbstractStandardBasicType.java:201)
at org.hibernate.type.AbstractStandardBasicType.getHashCode(AbstractStandardBasicType.java:205)
at org.hibernate.engine.spi.EntityKey.generateHashCode(EntityKey.java:114)
at org.hibernate.engine.spi.EntityKey.<init>(EntityKey.java:79)
at org.hibernate.internal.AbstractSessionImpl.generateEntityKey(AbstractSessionImpl.java:240)
at org.hibernate.engine.internal.StatefulPersistenceContext.getCollectionOwner(StatefulPersistenceContext.java:740)
at org.hibernate.loader.Loader.readCollectionElement(Loader.java:1181)
at org.hibernate.loader.Loader.readCollectionElements(Loader.java:800)
at org.hibernate.loader.Loader.getRowFromResultSet(Loader.java:651)
at org.hibernate.loader.Loader.doQuery(Loader.java:856)
at org.hibernate.loader.Loader.doQueryAndInitializeNonLazyCollections(Loader.java:289)
at org.hibernate.loader.Loader.doQueryAndInitializeNonLazyCollections(Loader.java:259)
at org.hibernate.loader.Loader.loadCollection(Loader.java:2175)
at org.hibernate.loader.collection.CollectionLoader.initialize(CollectionLoader.java:61)
at org.hibernate.persister.collection.AbstractCollectionPersister.initialize(AbstractCollectionPersister.java:622)
at org.hibernate.event.internal.DefaultInitializeCollectionEventListener.onInitializeCollection(DefaultInitializeCollectionEventListener.java:82)
at org.hibernate.internal.SessionImpl.initializeCollection(SessionImpl.java:1606)
at org.hibernate.collection.internal.AbstractPersistentCollection.initialize(AbstractPersistentCollection.java:379)
at org.hibernate.collection.internal.AbstractPersistentCollection.read(AbstractPersistentCollection.java:112)
at org.hibernate.collection.internal.PersistentSet.iterator(PersistentSet.java:180)
It looks like the UserEntity is being cast to some binary(?) thing. However, the first relation between users themselves works fine, but the one with another table is wrong.
I am using different columns of different types to join tables. Is it allowed to do it this way?
Another strange thing is that when I switch the #Id annotation to be on the login field, the roles work fine, no issue, but then of course the self-join PersistentBag key is the Login instead of Id, which breaks the relation and no results are retrieved. But the conversion from UserEntity to the "[B" is not done.
Also if I leave things as in example and change the Id type to String (and the DB to varchar) it also starts working (of course not consistently with the User_User table).
What am I doing wrong? What is the reason for getting the classcastexception in this case? Why it work when I change the byte[] to String? Please let me know if you have any ideas. I do not want to change the DB design cause this would lead to lots migration and compatibility issues for clients already using the DB.
Just a note: the #Id has to be on the Id binary field as otherwise I wouldn't be able to make a self-join (I was unable to point twice to a column not being a primary key see: Is Hibernate ManyToMany self-join possible for non-key columns? getting mappingException).
Cheers
Adam
the referred column in your join table must be unique entry, here if you put #Id on login field then it works fine,but when you change it to different other than #Id column you cant be sure about the entries will be unique.what you can do is,
#ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinTable(name = "User_Role",
joinColumns = { #JoinColumn(name = "UserLogin", referencedColumnName = "Id") },
inverseJoinColumns = { #JoinColumn(name = "RoleId", referencedColumnName = "Id") })
private Set<RoleEntity> roles;
I think it should work.

Hibernate - multiple many to many associations - cannot simultaneously fetch multiple bags

I'm trying to map two many to many associations in cascade. I have three classes: User, Usergroup and Permission. The first one has a many to many association to the second one while the second one has a many to many association to the third one.
I'm using hibernate 4.2.0
#Entity
#Table(name = "user")
public class User implements Serializable {
#ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL, targetEntity = org.weedea.bidupsys.user.logic.model.UserGroup.class)
#JoinTable(name = "UserGroupUser", joinColumns = { #JoinColumn(name = "userId") }, inverseJoinColumns = { #JoinColumn(name = "userGroupId") })
private List<UserGroup> userGroupList = null;
}
#Entity
#Table(name = "userGroup")
public class UserGroup implements Serializable {
#ManyToMany(fetch = FetchType.EAGER, targetEntity = org.weedea.bidupsys.user.logic.model.Permission.class, cascade = { CascadeType.ALL })
#JoinTable(name = "UserGroupPermission", joinColumns = { #JoinColumn(name = "userGroupId") }, inverseJoinColumns = { #JoinColumn(name = "permissionId") })
private List<Permission> permissionList = null;
}
With this configuration I get an error, because I try to load simultaneously two eager collections:
javax.servlet.ServletException: cannot simultaneously fetch multiple bags
javax.faces.webapp.FacesServlet.service(FacesServlet.java:229)
If I put fetch = FetchType.LAZY on the second collection, I get another error:
failed to lazily initialize a collection of role: org.weedea.bidupsys.user.logic.model.UserGroup.permissionList, could not initialize proxy - no Session
How could I map this two many to many associations? Thanks for help!
Short answer is you need to map them as java.util.Sets.
Here's a nice blog post explaining the issue: Hibernate Exception - Simultaneously Fetch Multiple Bags

Categories

Resources