Mapping to a composite unique (not primary) key in Hibernate - java

I have two classes - USER and ROLE. There are corresponding tables in the database, and a table called USER_ROLE that links the two.
USER has a primary key on column "id", and a composite unique key on columns "realm" and "realm_user_id". ROLE has a primary key on column "role_id"
USER_ROLE has 3 columns - role_id, realm, realm_user_id.
role_id references role_id in the ROLE table, and (realm,realm_user_id) reference the composite key in USER. I don't have access to the DB, so I can't update the schema.
There is a many-to-many relationship between users and roles. I want to be able to call getRoles() on User, and getUsers() on Role.
class User:
#Entity
#Table(name="USER")
public class User {
..other fields..
#ManyToMany(fetch = FetchType.LAZY, targetEntity=Role.class)
#JoinTable(name = "USER_ROLE",
joinColumns = {
#JoinColumn(name = "realm", nullable = false, updatable = false),
#JoinColumn(name = "realm_user_id", nullable = false, updatable = false)
},
inverseJoinColumns = { #JoinColumn(name = "role_id", nullable = false, updatable = false) })
private Set<Role> roles;
Class Role:
#ManyToMany(fetch = FetchType.LAZY,
cascade={CascadeType.PERSIST,CascadeType.MERGE},
targetEntity=User.class
)
#JoinTable(name = "USER_ROLE",
joinColumns = { #JoinColumn(name = "role_id", nullable = false, updatable = false) },
inverseJoinColumns={
#JoinColumn(name="realm",nullable=false, updatable=false),
#JoinColumn(name="realm_user_id",nullable=false, updatable=false)
})
private Set<User> users;
I'm getting the following error:
A Foreign key refering User from Role has the wrong number of column. should be 1
I tried creating a separate class containing realm and realm_user_id, referenced in the #IdClass annotation, but that failed as User already has a primary key on id. I tried using the mappedBy annotation, but that didn't work either.

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

Hibernate ManyToMany realation

I have a many to many User <-> Roles relation.
The User entity looks like this:
#Entity
#Table(name = "user",
uniqueConstraints = {#UniqueConstraint(columnNames = {"username", "email"})})
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", unique = true, nullable = false)
private Long id;
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(name = "user_roles",
joinColumns = #JoinColumn(name = "user_id", referencedColumnName = "id"),
inverseJoinColumns = #JoinColumn(name = "role_id", referencedColumnName = "id"))
private Set<Role> roles = new HashSet<>();
}
And the Roles entity is the following:
#Entity
#Table(name = "role")
public class Role {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id", updatable = true, nullable = false)
private Long id;
#Enumerated(EnumType.STRING)
#Column(length = 60, name = "roleName", updatable = true, nullable = false)
private RoleName roleName;
}
Everything works fine, except one thing. When I insert two users with the same role the roles table getting two records with the same role, but different IDs.
The question is, can I eliminate this behavior? Ideally the ROLES table should not contain duplicated roles.
Any advice would be appreciated. :)
I cannot find a problem in your entity mapping. The problem should be in the business logic where you try to save the user entity.
The problem you have described can happen if you set a not-yet-saved Role entity to a User. In other terms, your Role do not have an id field yet.
You need to get the Role from your persistent provider and set it to User.
If you are using spring-data, it can be like:
User user = ....
Role role = rolesRepository.findByName(roleName);
user.setRole(role);
// The persist User

JPA entity with composite primary and foreign keys

I have an entity with a composite primary key consisting of two fields, one of which is also part of a composite foreign key.
Background: I have entities Person,Area, and Session.
Person has many-to-many relationships with Area and Session, using join entities called PersonArea and PersonSession.
So, I have PersonSession, with primary key of (personId, sessionId).
PersonId and SessionId are themselves foreign keys to Person and Session.
PersonSession also has a field areaId.
I want (personId, areaId) to be a composite foreign key to PersonArea.
My code for PersonSession:
#Entity
#Table(name="person_session")
#IdClass(PersonSession.ID.class)
public class PersonSession {
#Id
private int personId ;
#Id
private int sessionId;
#ManyToOne
#JoinColumn(name = "personId", updatable = false, insertable = false, referencedColumnName = "id")
private Person person;
#ManyToOne
#JoinColumn(name = "sessionId", updatable = false, insertable = false, referencedColumnName = "id")
private Session session;
#ManyToOne//(cascade = CascadeType.ALL)
#JoinColumns({
#JoinColumn(name = "personId", updatable = false, insertable = false),
#JoinColumn(name = "areaId", updatable = false, insertable = false),
})
private PersonArea personArea;
}
Code for PersonSession.Id
public static class ID implements Serializable {
private int personId;
private int sessionId;
}
This seems OK, it creates all the correct relationships in the database. The problem comes when I try to insert PersonSession objects - the areaId column is always null, I think that is because it's defined a updatable=false, insertable=false.
However, if I try and make it updatable and insertable, I get an exception complaining the personId is a repeated column:
Caused by: org.hibernate.MappingException: Repeated column in mapping for entity: foo.bar.PersonSession column: personId (should be mapped with insert="false" update="false")
How can I have the required relationships AND have areaId updatable and insertable?
I should be able to do what I want with this:
#ManyToOne//(cascade = CascadeType.ALL)
#JoinColumns({
#JoinColumn(name = "personId"),
#JoinColumn(name = "areaId", updatable = false, insertable = false),
})
private PersonArea personArea;
But Hibernate does not support mixing updatable and non updatable Join Columns. The accepted answer to this question indicates that it might be supported at some time, but it seems the developers aren't very worried about that shortcoming.
I have how ditched Hibernate in favour of Eclipselink and it works!
I know I am late but I faced the same problem and I used #JoinColumnsOrFormulas to resolve it. Here is what you could do:
#JoinColumnsOrFormulas(value = {
#JoinColumnOrFormula(column = #JoinColumn(name="personId", referencedColumnName = "personId")),
#JoinColumnOrFormula(formula = #JoinFormula(value="areaId", referencedColumnName = "areaId"))})
private PersonArea personArea;

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.

OneToOne relation with nullable to true and relation using primary keys

I have two entities with a OneToOne relation:
#Entity
#Table(name = "APPLICATION_DEVICE")
public class ApplicationDevice implements Serializable {
[...]
#Id
public ApplicationDeviceKey getApplicationDeviceKey()
{
return applicationDeviceKey;
}
#NotFound(action = NotFoundAction.IGNORE)
#OneToOne(cascade=CascadeType.ALL)
#JoinColumns( {
#JoinColumn(name = "applicationId", referencedColumnName = "applicationId",insertable=false,updatable=false, nullable = true),
#JoinColumn(name = "deviceId", referencedColumnName = "deviceId",insertable=false,updatable=false, nullable = true), }
)
public ApplicationDevicePushInfo getDevicePushInfo() {
return devicePushInfo;
}
and the other entity:
#Entity
#Table(name = "APPLICATION_DEVICE_PUSHINFO")
public class ApplicationDevicePushInfo implements Serializable {
[...]
#Id
public ApplicationDeviceKey getApplicationDeviceKey()
{
return applicationDeviceKey;
}
#OneToOne
#JoinColumns( {
#JoinColumn(name = "applicationId", referencedColumnName = "applicationId",insertable=false,updatable=false, nullable = false),
#JoinColumn(name = "deviceId", referencedColumnName = "deviceId",insertable=false,updatable=false, nullable = false)}
)
public ApplicationDevice getApplicationDevice() {
return applicationDevice;
}
The second entity could be null, and when I try to store my first entity I get an:
Cannot add or update a child row: a foreign key constraint fails (`malcom_dev`.`application_device`, CONSTRAINT `FK16A0C0451DC7C799` FOREIGN KEY (`applicationId`, `deviceId`) REFERENCES `APPLICATION_DEVICE_PUSHINFO` (`applicationId`, `deviceId`))
By default nullable is to true, so I thought it would be possible this relation.
There are another way to have to entities with a oneToOne relation without create new columns or tables, and allow nullable the second entity ?
This is not Hibernate but database exception. Looks like there is foreign key constraint on the table.
It will work after you remove foreign key constraint.

Categories

Resources