I'm trying to create bidirectional OneToMany relationship,
where one foreign key is part of composite primary index.
I have currently this schema:
CREATE TABLE users
(
id INTEGER UNIQUE AUTO_INCREMENT,
PRIMARY KEY (id)
);
CREATE TABLE user_roles
(
name VARCHAR(16) NOT NULL,
userId INTEGER,
PRIMARY KEY (userId, name),
FOREIGN KEY (userId) REFERENCES users (id)
);
And I would like to represent it in hibernate.
My UserEntity is mapped by:
@Entity
@Table(name = "users")
public class UserEntity implements User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
protected Integer id;
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, mappedBy = "id.userId")
List<UserRoleEntity> roles;
@Override
public Integer getId() {
return id;
}
@Override
public List<String> getRoles() {
return roles.stream()
.map(UserRoleEntity::getName)
.collect(Collectors.toList());
}
public void setId(Integer id) {
this.id = id;
}
public void setRoles(List<UserRoleEntity> roles) {
this.roles = roles;
}
}
And My UserRoleEntity:
@Embeddable // I haven't yet implemented hashcode etc
class UserAndRoleNameUniqueId implements Serializable {
protected Integer userId;
protected String name;
public Integer getUserId() {
return userId;
}
public void setUserId(Integer userId) {
this.userId = userId;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
@Entity
@Table(name = "user_roles")
public class UserRoleEntity {
@EmbeddedId
protected UserAndRoleNameUniqueId id = new UserAndRoleNameUniqueId();
@ManyToOne
@MapsId("userId")
protected UserEntity user;
public void setUserId(Integer userId) {
this.id.userId = userId;
}
public Integer getUserId() {
return user.getId();
}
public void setUser(UserEntity user) {
this.user = user;
}
public String getName() {
return id.name;
}
public void setName(String name) {
this.id.name = name;
}
}
I'm saving UserEntity object by:
var userEntity = new UserEntity();
userEntity.setRoles(
user.getRoles().stream().map(role -> {
var userRoleEntity = new UserRoleEntity();
userRoleEntity.setName(role);
return userRoleEntity;
}).collect(Collectors.toList())
);
sessionFactory.getCurrentSession().save(userEntity);
return userEntity;
and I get Caused by: java.sql.SQLException: Unknown column 'userroleen_.user_id' in 'field list'
How can I fix it and achieve this mapping in a simple way?
To map the primary key of UserEntity
with the composite primary key column in UserRoleEntity
, you will have to map the userId
field of your UserRoleEntityId
.
First you would need to change the mapping in your UserEntity
, right now you have
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, mappedBy = "id.userId")
List<UserRoleEntity> roles;
You need to change it to this
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, mappedBy = "user")
List<UserRoleEntity> roles;
Reason is that user class is parent here and we need to specify the field in child entity which will drive the relation among two.
Now, mapping in your child table UserRoleEntity
is fine, it will map the userId
field in your composite primary key to the field userId
in UserEntity
table, so in essence your user entity primary key will be shared.
Now, you have to be beware of few things here
UserRoleEntity
entities generated by your code, will be considered equals, because both fields would be null.userId
and name
should be unique combination)UserEntity
table, you need to be aware that at the time of creating new user that will be available only after hibernate execute insert statement (unless it is manual generated key, which is not the case here), so name
property in UserRoleEntityId
must be unique at the time of adding new roles.Last but not the least
You should synch both of your entities in one operation in bidirectional relationship.
1 So You are setting role
to UserEntity
using a setter, but you should have a add
function or setter written in given manner
public void add(UserEntityRole role) {
roles.add(role);
role.setUser(this);
}
Otherwise, if client forgot to call setter on both entities and table constraint were not there, then it could save foreign key as null in table.
Footnotes
@ManyToOne
@MapsId("id")
protected UserEntity user;
With @MapsId
you reference the id field of the other entity. So if you put @MapsId
on UserRoleEntity then the id that you reference is the one from UserEntity
But the following does also seem bad
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, mappedBy = "id.userId")
List<UserRoleEntity> roles;
change that to
@OneToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER, mappedBy = "id")
List<UserRoleEntity> roles;
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.