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

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.

Related

Mapping two columns in JPA

I have the following 3 tables
table: project
id
company_code
number
contract_type_code
account_type_code
other columns…
table: contract_type
id
company_code
code
name
table: account_type
id
company_code
code
name
The project table references contract_type and account_type tables through contract_type_code/company_code and account_type_code/company_code respectively.
The company_code and code columns are what make a contract_type and account_type unique.
I'm struggling with modelling and mapping this in JPA. I've tried with the #JoinColumn and #JoinColumns annotation and there's no way for me to make it work.
This is one of the ways I've been trying with no success:
public class Project implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private Long companyCode;
private Long number;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumns({
#JoinColumn(name = "contract_type_code", referencedColumnName = "code"),
#JoinColumn(name = "company_code", referencedColumnName = "company_code")
})
private ContractType contractType;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumns({
#JoinColumn(name = "account_type_code", referencedColumnName = "code"),
#JoinColumn(name = "company_code", referencedColumnName = "company_code")
})
private AccountType accountType;
This is the issue I'm getting with the mapping above:
Caused by: org.hibernate.MappingException: Unable to find column with logical name company_code in table contract_type
For this mapping:
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumns({
#JoinColumn(name = "contract_type_code", referencedColumnName = "code", insertable = false, updatable = false),
#JoinColumn(name = "company_code", referencedColumnName = "company_code2", insertable = false, updatable = false)
})
private ContractType contractType;
I get:
Caused by: org.hibernate.DuplicateMappingException: Table [account] contains physical column name [company_code] referred to by multiple logical column names: [company_code], [companyCode]
I assume you have companyCode fields in the AccountType and ContractType. Annotate them as below (first error suggests JPA can't find them):
#Column(name = "company_code")
In your Project class modify companyCode field as follows (to avoid the second error):
#Column(name = "company")
private Long companyCode;
and keep mapping with:
insertable = false, updatable = false
Hope this will help. If not please add AccountType and ContractType classes to your question. Maybe then it will be easier to sort it out

How to do One to One association in hibernate with foreign key and join table and unidirectional

I wish to have a one to one association with a join table in unidirectional way. -
Tables :
A (A_id, D_id, A_Data)
B (A_id, C_id) // Join table just contain relation between A and C
C (C_id, C_Data)
Class A {
.
.
#OneToOne(cascade = CascadeType.ALL)
#JoinTable(name = "B",
joinColumns = #JoinColumn(name = "A_id", referencedColumnName = "A_id"),
inverseJoinColumns = #JoinColumn(name = "C_id", referencedColumnName = "C_id"))
private C c;
}
I am using hibernate with jpa 2.0.
Entity D is not important in the model hence ignored.
I only wish to read data ,hence insert/update/delete use cases should not be concern, but one can suggest best practice in that case also.
This setup does not work. Can some one suggest how to do it in correct way?
It gives following exception
org.hibernate.MappingException: Unable to find column with logical name: A_id in org.hibernate.mapping.Table(A) and its related supertables and secondary tables
In order to get your desired schema:
// Given the following C entity
#Entity
public class C {
#Id
#Column(name = "C_ID")
private long id;
private String C_Data;
//...
}
// A Entity should be
#Entity
public class A {
#Id
#Column(name = "A_ID")
private long id;
private String A_Data;
#OneToOne(cascade = CascadeType.ALL )
#JoinTable(name = "B", joinColumns = #JoinColumn(name = "A_id"), inverseJoinColumns = #JoinColumn(name = "C_id", unique = true))
private C c;
//...
}
I've omitted referencedColumnName, so hibernate will map it to the entity primary key.
Note also that A_id column of B table will be the primary key.

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

How to resolve many-to-many relationship without creating a junction class in Java?

In my DB I have three table USER, PERMISSION and a junction table USER_PERMISSIONS. As you can see I have clear many-to-many relationship.
And here is the question. I'm using a Hibernate. Is it possible to use only two classes, User and Permissions without creating a junction class UserPermissions? I mean can I use some kind of annotations to include my junction table USER_PERMISSIONS?
Yes you can. You need to use the #JoinTable and #JoinColumn annotation. Example below:
#Entity
#Table(name = "USER")
public class User {
#Id
#Column(name = "ID")
private Long id;
#ManyToMany
#JoinTable(name = "USER_PERMISSIONS",
joinColumns = #JoinColumn(name = "USER_ID", referencedColumnName = "ID"),
inverseJoinColumns = #JoinColumn(name = "PERM_ID", referencedColumnName = "ID"))
private Collection<Permissions> permissions;
A full working example with bidirectionaly many to many can be found in here http://en.wikibooks.org/wiki/Java_Persistence/ManyToMany

Hibernate create foreign key to itself

I have a very strange problem with hibernate at the moment.
Somehow on a table, it create a foreign key which reference to itself. the column is also a primary key. This essentially prevent me from delete any row from that table.
In the log I could see a line:
DEBUG org.hibernate.SQL - alter table Device add index
FK79D00A76C682495E (id), add constraint FK79D00A76C682495E foreign key
(id) references Device (id)
Other table with similar table seems fine. and this seems to be true for both MySQL and Derby. I'm using hibernate 4.1.4
the annotated class is as follow.
#Entity(name = "Device")
public class Device extends DomainObject implements Searchable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
protected long id;
#Column(name = "Type")
#Enumerated(EnumType.STRING)
private DeviceTypeEnum type = DeviceTypeEnum.AccessControlDevice;
#Column(name = "Name", length = Constance.DATABASE_NAME_FIELD_LENGTH)
private String name;
#Column(name = "Description", length = Constance.DATABASE_DESCRIPTION_FIELD_LENGTH)
private String description;
#Column(name = "Identifier", length = Constance.DATABASE_IDENTIFIER_FIELD_LENGTH, unique = true)
private String identifier;
#ManyToMany
#JoinTable(name = "Device2Group", joinColumns = {#JoinColumn(name = "DeviceID")}, inverseJoinColumns = {#JoinColumn(name = "DeviceGroupID")})
private List<DeviceGroup> groups = new ArrayList<DeviceGroup>();
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "location")
private Location location;
#ManyToOne(cascade = {CascadeType.ALL})
#JoinColumn(name = "Address")
private Address address;
#ManyToOne
#JoinColumn(name = "Link", nullable = false)
private Link link;
}
It turns out that in one of the Entity Class "Location" which the Device entity references, it has a #ManyToMany mapped collection of Device, where it really should be #OneToMany.
After change the #ManyToMany to #OneToMany the constrain disappears.
I see no references to Device class in your code. So I am assuming that this class has been modified, but its table has not, because it has some data. (Why else should it have a foreign-key to itself?)
Try dropping this table in your database to make hibernate create it once more, or set p.hibernate.hbm2ddl.auto to create-drop.

Categories

Resources