英文:
Spring JPA-I get error that object is not persisted even though i have persisted it
问题
import javax.persistence.*;
import java.io.Serializable;
import java.util.Set;
@Entity
public class User implements UserDetails, Serializable {
// ... (other fields and methods)
@OneToMany(mappedBy = "user", cascade = CascadeType.ALL, fetch = FetchType.EAGER)
@JsonIgnore
private Set<UserRole> userRoles = new HashSet<UserRole>();
// ... (other methods)
public Set<UserRole> getUserRoles() {
return userRoles;
}
public void setUserRoles(Set<UserRole> userRoles) {
this.userRoles = userRoles;
}
// ... (other methods)
}
@Entity
public class Role implements Serializable {
// ... (fields and methods)
@OneToMany(mappedBy = "role", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
@JsonIgnore
private Set<UserRole> userRoles = new HashSet<UserRole>();
// ... (other methods)
public Set<UserRole> getUserRoles() {
return userRoles;
}
public void setUserRoles(Set<UserRole> userRoles) {
this.userRoles = userRoles;
}
// ... (other methods)
}
@Entity
public class UserRole implements Serializable {
// ... (fields and methods)
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "roleId")
private Role role;
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "userId")
private User user;
// ... (other methods)
public Role getRole() {
return role;
}
public void setRole(Role role) {
this.role = role;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
// ... (other methods)
}
@Service
public class UserServiceImpl implements UserService {
// ... (other fields and methods)
@Transactional
@Override
public User CreateUser(User user, Set<UserRole> userRoles) {
// ... (other code)
for (UserRole userRole : userRoles) {
roleRepository.save(userRole.getRole());
// ... (other code)
}
// ... (other code)
}
}
@SpringBootApplication
public class BookStoreApplication implements CommandLineRunner {
// ... (other fields and methods)
@Override
public void run(String... args) throws Exception {
// ... (other code)
User user1 = new User();
// ... (other code)
Role role1 = new Role();
// ... (other code)
UserRole userRole1 = new UserRole(user1, role1);
// ... (other code)
Set<UserRole> userRoles1 = new HashSet<UserRole>();
userRoles1.add(userRole1);
// ... (other code)
userService.CreateUser(user1, userRoles1);
// ... (similar code for user2)
userService.CreateUser(user2, userRoles2);
// ... (other code)
}
}
英文:
I am using Spring boot-I have 3 classes User
,Role
and UserRole
.I have pesisted both role object and user object but i get error that role object is not persisted.The mappings- between User
and UserRole
is OneToMany ,between Role
and UserRole
OneToMany.In the UserServiceImpl
class i have persisted Role object roleRepository.save(userRole.getRole());
Error is-
Caused by: org.hibernate.TransientPropertyValueException: object references an unsaved transient instance - save the transient instance before flushing : com.bookstore.domain.security.UserRole.role -> com.bookstore.domain.security.Role
@Entity
public class User implements UserDetails,Serializable {
private static final long serialVersionUID=157954L;
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
@Column(name = "Id",nullable = false,updatable = false)
private Long id;
private String userName;
private String password;
private String firstName;
private String lastName;
private String email;
private String phone;
private boolean enabled;
@OneToMany(mappedBy = "user",cascade = CascadeType.ALL,fetch = FetchType.EAGER)
@JsonIgnore
private Set<UserRole> userRoles=new HashSet<UserRole>();
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public String getPhone() {
return phone;
}
public void setPhone(String phone) {
this.phone = phone;
}
public void setEnabled(boolean enabled) {
this.enabled = enabled;
}
public Set<UserRole> getUserRoles() {
return userRoles;
}
public void setUserRoles(Set<UserRole> userRoles) {
this.userRoles = userRoles;
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
Set<GrantedAuthority> authorities=new HashSet<GrantedAuthority>();
userRoles.forEach(userRole->{
authorities.add(new Authority(userRole.getRole().getRoleName()));
});
return authorities;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return enabled;
}
@Override
public String getUsername() {
return userName;
}
}
@Entity
public class Role implements Serializable{
private static final long serialVersionUID=68678L;
@Id
private Long roleId;
private String roleName;
@OneToMany(mappedBy = "role",cascade = CascadeType.ALL,fetch = FetchType.LAZY)
@JsonIgnore
private Set<UserRole> userRoles=new HashSet<UserRole>();
public Role() {
}
public Long getRoleId() {
return roleId;
}
public void setRoleId(Long roleId) {
this.roleId = roleId;
}
public String getRoleName() {
return roleName;
}
public void setRoleName(String roleName) {
this.roleName = roleName;
}
public Set<UserRole> getUserRoles() {
return userRoles;
}
public void setUserRoles(Set<UserRole> userRoles) {
this.userRoles = userRoles;
}
}
@Entity
public class UserRole implements Serializable {
private static final long serialVersionUID=456874L;
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
private Long userRoleId;
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "roleId")
private Role role;
@ManyToOne(fetch = FetchType.EAGER)
@JoinColumn(name = "userId")
private User user;
public UserRole(User user,Role role) {
this.role = role;
this.user = user;
}
public UserRole() {
super();
}
public Long getUserRoleId() {
return userRoleId;
}
public void setUserRoleId(Long userRoleId) {
this.userRoleId = userRoleId;
}
public Role getRole() {
return role;
}
public void setRole(Role role) {
this.role = role;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
}
@Service
public class UserServiceImpl implements UserService {
private static final Logger LOG=LoggerFactory.getLogger(UserServiceImpl.class);
@Autowired
UserRepository userRepository;
@Autowired
RoleRepository roleRepository;
@Transactional
@Override
public User CreateUser(User user, Set<UserRole> userRoles) {
User localUser=userRepository.findByUserName(user.getUserName());
if(localUser!=null) {
LOG.warn("Username {} already exists",user.getUserName());
}
else {
for(UserRole userRole:userRoles) {
roleRepository.save(userRole.getRole());
LOG.error("inside for {}",userRole.getRole().getRoleName());
}
user.getUserRoles().addAll(userRoles);
localUser=userRepository.save(user);
}
return localUser;
}
}
@SpringBootApplication
public class BookStoreApplication implements CommandLineRunner {
@Autowired
UserService userService;
public static void main(String[] args) {
SpringApplication.run(BookStoreApplication.class, args);
}
@Override
public void run(String... args) throws Exception {
User user1=new User();
user1.setUserName("test");
user1.setPassword(SecurityUtility.passwordEncoder().encode("test"));
user1.setEmail("test@test.com");
user1.setEnabled(true);
user1.setFirstName("testFirstName");
user1.setLastName("testLastName");
user1.setPhone("123456789");
Role role1=new Role();
role1.setRoleId((long)1);
role1.setRoleName("ROLE_USER");
UserRole userRole1=new
UserRole(user1,role1);
Set<UserRole> userRoles1=new HashSet<UserRole>();
userRoles1.add(userRole1);
userService.CreateUser(user1, userRoles1);
User user2=new User();
user2.setUserName("admin");
user2.setPassword(SecurityUtility.passwordEncoder().encode("admin"));
user2.setEmail("admin@admin.com");
user2.setEnabled(true);
user2.setFirstName("adminFirstName");
user2.setLastName("adminLastName");
user2.setPhone("223456789");
Role role2=new Role();
role2.setRoleId((long) 2);
role2.setRoleName("ROLE_ADMIN");
UserRole userRole2=new UserRole(user2,role2);
Set<UserRole> userRoles2=new HashSet<UserRole>();
userRoles2.add(userRole2);
userService.CreateUser(user2, userRoles2);
}
}
答案1
得分: 1
以下是翻译好的部分:
第一个问题(以及问题)是您遇到“瞬态状态错误”的原因,是因为您试图保存一个实体,而附加到它的实体尚未由Hibernate管理。
当您对实体调用“new”时,它处于瞬态状态。Hibernate 不知道如何处理它。它没有数据库ID,并且不在Hibernate的上下文中(无法进行相关查询等操作)。
要使实体受管理,您需要通过存储库保存它。即 roleRepo.save(role)
。然后您会注意到它有一个ID,并且现在由Hibernate管理。
上述的服务可能不会产生您期望的结果。您获取并保存了“Role”对象,然后未替换“UserRole”中的“Role”对象为从存储库返回的受管理对象。也许以下方式可以工作:
for (UserRole userRole : userRoles) {
// 角色现在已被管理。
Role managedRole = roleRepository.save(userRole.getRole());
// 用受管理的角色替换“UserRole”中的瞬态角色。
userRole.setRole(managedRole);
}
因此,当继续保存“User”时:
user.getUserRoles().addAll(userRoles);
localUser = userRepository.save(user);
“UserRoles”(仍然是瞬态的)至少有一个受管理的“Role”。Cascade.ALL
应该会按您的预期执行操作(不过我不确定!),并保存子“UserRole”。
第二个问题,虽然不会引起上述问题,但您可能需要考虑一下:
目前您的模型是这样的:
User
1:M UserRole
UserRole
M:1 Role
这里的建模似乎不太合适。
通常情况下,您会有一些角色实体/数据库条目,可以通过ManyToMany关系与用户相关联。
用户注册后,他们的 Set<Role> userRoles
中会分配“USER”角色,而不是为每个用户创建一个具有“USER”字段的新角色。
因此,用户通过“join表” UserRole
与角色建立关系。
Spring已经可以为您创建一个连接表。根据目前的代码,您实际上不需要“UserRole”实体,因为它只包含FK_User和FK_Role。
基本上,您需要的是:
User
M:M Role
也就是说,一个用户可以拥有多个角色。
只需使用@ManyToMany注释进行用户和角色之间的多对多关系。要添加一个角色,您可以搜索数据库以查找 Role findByRoleName
,然后将该受管理的实体添加到用户的角色中,然后持久化用户。
希望这对您有所帮助。
英文:
Couple of issues here.
The first (and the question) issue and the reason why you are getting a "Transient state error" is because you are trying to save an entity with entities attached to it that are NOT yet managed by hibernate.
Have a read of: Entity Lifecycle Model in Hibernate
Caused by: org.hibernate.TransientPropertyValueException:
object references an unsaved transient instance -
save the transient instance before flushing :
com.bookstore.domain.security.UserRole.role
->
com.bookstore.domain.security.Role
So you are somewhere trying to save a UserRole
with a Role
that is not yet managed.
When you call new
on an entity, it is in a Transient state. Hibernate doesn't know how to handle it. It doesn't have a database ID and is not part of the context for hibernate to manage (make the relevent queries etc).
To make an entity managed you need to save it via the repo.
I.e. roleRepo.save(role)
You will then notice it then has an Id and is now managed by hibernate.
@Service
public class UserServiceImpl implements UserService {
@Transactional
@Override
public User CreateUser(User user, Set<UserRole> userRoles) {
User localUser = userRepository.findByUserName(user.getUserName());
if (localUser != null) {
LOG.warn("Username {} already exists", user.getUserName());
} else {
// For each transient UserRole passed in, save the Role.
// Role is now managed.
for (UserRole userRole : userRoles) {
roleRepository.save(userRole.getRole());
LOG.error("inside for {}", userRole.getRole().getRoleName());
}
user.getUserRoles().addAll(userRoles);
localUser = userRepository.save(user);
}
return localUser;
}
}
This service above doesn't maybe do what you expect.
You are getting the Role
s and saving them.
You then don't replace the Role
in UserRole
with the managed one back from the repo.
Maybe this would work?
for(UserRole userRole:userRoles) {
//The role is now managed.
Role managedRole = roleRepository.save(userRole.getRole());
//Replace the transient role in the UserRole with a managed Role.
userRole.setRole(managedRole);
}
So when it goes on to save the User
:
user.getUserRoles().addAll(userRoles);
localUser = userRepository.save(user);
The UserRoles
(which are still transient) have a managed Role
at least.
The Cascade.ALL
should do what you expect (I am unsure mind!) and save the transient UserRoles because Cascade.ALL will save the children UserRole
s.
https://www.baeldung.com/jpa-cascade-types
=============================
The second issue, not causing the problem in question, but you may want to go have a think about:
At the moment you have:
User
1 : M UserRole
UserRole
M : 1 Role
1 User has many UserRoles.
1 Role has many UserRoles.
The modelling here just smells off.
Usually you'd have some Role entities/database entries that can be related to a user via ManyToMany relationship.
User signs up, is given the "USER" role in their Set<Role> userRoles
rather than creating a new Role for each user with "USER" as a field.
So a user has a relationship to a role via a "join table" UserRole
.
Spring can already create a join table for you. You do not really need the UserRole
entity in your code as it stands as it just holds a FK_User and FK_Role.
Basically, you want:
User
M:M Role
I.e. a user can have many roles.
Simply use the @ManyToMany annotation for a Many:Many relationship between user and roles.
To add a role you search the database for
Role findByRoleName
And add that managed entity to the user's roles and then persist the user.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论