【问题标题】:JPA OneToOne and shared primary key need manual assignmentJPA OneToOne 和共享主键需要手动分配
【发布时间】:2022-11-10 15:31:57
【问题描述】:

我正在使用 Springboot 和 JPA 创建两个共享相同主键的表。

对于我写的第一张表:

public class UserAccount implements Serializable
 {
   @Id
   @GeneratedValue(strategy = GenerationType.IDENTITY)
   private Long id;
   @OneToOne(mappedBy ="user", cascade = {CascadeType.REMOVE, CascadeType.MERGE, 
                                         CascadeType.REFRESH}, fetch=FetchType.LAZY)
   @PrimaryKeyJoinColumn
   private UserLogin login;
 }  
 

对于我写的第二张表:

public class UserLogin implements Serializable
  {
    @Id
    private Long user_id;
    @OneToOne(cascade = {CascadeType.MERGE, CascadeType.REFRESH}, 
               fetch=FetchType.LAZY)
   @MapsId("user_id")
   @JoinColumn(name = "user_id", referencedColumnName = "id")
   @Setter(AccessLevel.NONE)
   private UserAccount user;
   public void setUser(UserAccount user)
    {
     this.user = user;
     this.user_id = user.getId();    
    }
  }

为简洁起见,省略了其他内容。该代码有效,因为我通过编写语句手动设置了 UserLogin 的 id

this.user_id = user.getId();

否则我得到错误:

休眠错误:必须在调用 save() 之前手动分配此类的 id:

我猜想可以手动管理 id,但我无法获得正确的配置。

更新: 我找到了解决方案,谢谢(请参阅接受的答案)。现在我只是在设置用户登录时摆脱 findById() 。

//these methods are defined within a dedicated @Service
@Transactional
public void createLoginInfo(UserAccount user)
{
 UserLogin userlogin=new UserLogin();       
 this.addLoginToUser(userlogin,user);
 loginService.save(userlogin);
}

@Transactional
public void addLoginToUser(UserLogin login, UserAccount account)
{
  //whit this commented line works
  //UserAccount acc= this.findById(account.getId());   
  login.setUser(account);      
  account.setLogin(login);
}

//In a transactional test method I first create the user then I call
userService.save(theuser);
userService.createLoginInfo(theuser);

【问题讨论】:

    标签: spring-boot jpa spring-data-jpa


    【解决方案1】:

    您有一个双向关系,但已将它与一些不能很好地协同工作的竞争选项映射。首先,在 UserAccount 中,不清楚为什么要生成一个 ID,但还要尝试将它与关系映射(特别是使用 PrimaryKeyJoinColumn)。如果您希望生成它,它也不能是引用中的外键值 - 并且您已经通过“mappedBy”设置将此关系设置为“另一端”。它应该只是:

    public class UserAccount implements Serializable {
       @Id
       @GeneratedValue(strategy = GenerationType.IDENTITY)
       private Long id;
       @OneToOne(mappedBy ="user", cascade = {CascadeType.REMOVE, CascadeType.MERGE, 
                                             CascadeType.REFRESH}, fetch=FetchType.LAZY)
       private UserLogin login;
     }
    

    用户登录应该只是:

    public class UserLogin implements Serializable {
       @Id
       private Long user_id;
       @OneToOne(cascade = {CascadeType.MERGE, CascadeType.REFRESH}, 
                   fetch=FetchType.LAZY)
       @MapsId("user_id")
       @JoinColumn(name = "user_id", referencedColumnName = "id")
       @Setter(AccessLevel.NONE)
       private UserAccount user;
       public void setUser(UserAccount user) {
         this.user = user;   
       }
    }
    

    请注意,因为您在用户关系上有 mapsId 注释,所以一旦分配了 ID,JPA 将为您设置 user_id 属性 - 无需自己手动设置。你可以,但如果你这样做,你必须确保它是以前分配的——这需要在用户帐户上保存/刷新。如果您实际上不使用 Long user_id 属性,您甚至不需要映射它;您可以将用户属性标记为 ID:

    public class UserLogin implements Serializable {
       @Id
       @OneToOne(cascade = {CascadeType.MERGE, CascadeType.REFRESH}, 
                   fetch=FetchType.LAZY)
       @JoinColumn(name = "user_id", referencedColumnName = "id")
       @Setter(AccessLevel.NONE)
       private UserAccount user;
       public void setUser(UserAccount user) {
         this.user = user;   
       }
    }
    

    然后,来自 UserAccount 的 Long ID 可用于查找 UesrAccounts 和 UserLogin 实例。

    【讨论】:

    • 我从 UserAccount 中删除了 @PrimaryKeyJoinColumn 并删除了 UserLogin 中 Id 的手动分配,但我收到了问题中报告的错误。
    • 抱歉,我提到了“分离错误”。
    • 究竟是什么分离错误?分离意味着您正在使用现有实体 - 可能是您从不同的上下文/事务中读取的 UserAccount 实例并将其关联到您的 UserLogin。使用级联设置标记登录-> 用户关系(有风险),或者确保使用与保存 userLogin 实例相同的事务来读取它。
    • 当我使用 setUser 时,即使我使用创建 UserAccount 的同一事务也会给出错误。
    • 什么错误?什么代码?您需要展示如何使用这些对象,因为这是现在的问题,而不是映射。
    【解决方案2】:

    尝试这个 :

    public class UserLogin implements Serializable
      {
        @Id
        private Long user_id;
    
       @OneToOne(fetch=FetchType.LAZY)
       @MapsId
       @JoinColumn(name = "user_id")
       private UserAccount user;
    
       public UserAccount getUser() {
         return user;
       }
       public void setUser(UserAccount user) {
         this.user = user;
       }
      }
    
    public class UserAccount implements Serializable
     {
       @Id
       @GeneratedValue(strategy = GenerationType.IDENTITY)
       private Long id;
     }
    

    持久化 UserLogin :

    EntityManager em;
    UserAccount user = em.find(UserAccount.class, 1L)
    UserLogin login = new UserLogin();
    login.setUser(user);
    em.persist(login);
    

    【讨论】:

    • 您的解决方案似乎有效。你知道为什么我不能使用: setLogin(UserLogin login, UserAccount account){ login.setUser(user)} 甚至所有方法都是事务性的吗?
    • 我不知道。也许您应该向我们展示代码。
    • 我已经更新了问题(底部)
    猜你喜欢
    • 2012-09-02
    • 2017-08-02
    • 1970-01-01
    • 2016-05-26
    • 2021-03-15
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多