2016-08-01 75 views
0

我有我的數據庫中有2個實體與一對一的一個定向映射: 用戶和PasswordResetToken。這背後的想法是每次用戶請求重置密碼並僅存儲最新密碼時創建新的令牌。休眠一對一DTO實體人口

下面是我的實體:

@Entity 
@Table(name = "USERS") 
@Getter @Setter 
public class User implements Serializable { 

@Id 
@Column(name = "ID") 
@GeneratedValue(strategy = GenerationType.AUTO, generator = "usersSeq") 
@SequenceGenerator(name = "usersSeq", sequenceName = "SEQ_USERS", allocationSize = 1) 
private long id; 

@Column(name = "NAME") 
private String name; 

@Column(name = "PASSWORD") 
private String password; 

@Column(name = "EMAIL") 
private String email; 

@Column(name = "ROLE") 
private Integer role; 

} 
///... 
@Entity 
@Table(name = "PASSWORD_RESET_TOKENS") 
@Getter 
@Setter 
public class PasswordResetToken implements Serializable { 

    private static final int EXPIRATION = 24; 

    @Column(name = "TOKEN") 
    private String token; 

    @Id 
    @OneToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL) 
    @JoinColumn(nullable = false, name = "user_id") 
    private User user; 

    @Column(name = "EXPIRY_DATE") 
    private Instant expiryDate; 

    public PasswordResetToken() { 
    } 

    public void setExpiryDate(ZonedDateTime expiryDate) { 
     this.expiryDate = expiryDate.plus(EXPIRATION, ChronoUnit.HOURS).toInstant(); 
    } 
} 

另外,我對他們倆創造的DTO通過他們在我的應用程序。 代碼片段:

@Getter @Setter 
public class PasswordResetTokenModel { 

    private String token; 
    private ZonedDateTime expiryDate; 
    private UserModel user; 

} 

的usermodel也可用於春季安全

@Getter 
@Setter 
public class UserModel extends User { 

    public UserModel(String username, String password, Collection<? extends GrantedAuthority> authorities) { 
     super(username, password, authorities); 
    } 

    private long id; 
    private String name; 

    public String getEmail() { 
     return this.getUsername(); 
    } 
} 

人口,我創建了2個populators:

@Component 
public class UserPopulatorImpl implements UserPopulator { 

    @Autowired 
    UserDetailsService userDetailsService; 

    @Override 
    public UserModel populateToDTO(User user) { 
     UserModel userModel = new UserModel(user.getEmail(), user.getPassword(), userDetailsService.getAuthorities(user.getRole())); 
     userModel.setId(user.getId()); 
     return userModel; 
    } 

    @Override 
    public User populateToDAO(UserModel userModel) { 
     User user = new User(); 

     user.setEmail(userModel.getEmail()); 
     user.setName(userModel.getName()); 
     user.setPassword(userModel.getPassword()); 
     //TODO: change it! 
     user.setRole(1); 

     return user; 
    } 

} 
//... 
@Component 
public class PasswordResetTokenPopulatorImpl implements PasswordResetTokenPopulator { 

    @Autowired 
    UserPopulator userPopulator; 

    @Override 
    public PasswordResetTokenModel populateToDTO(PasswordResetToken passwordResetToken) { 
     PasswordResetTokenModel passwordResetTokenModel = new PasswordResetTokenModel(); 

     passwordResetTokenModel.setUser(userPopulator.populateToDTO(passwordResetToken.getUser())); 
     passwordResetTokenModel.setToken(passwordResetToken.getToken()); 
     passwordResetTokenModel.setExpiryDate(ZonedDateTime.ofInstant(passwordResetToken.getExpiryDate(), ZoneId.systemDefault())); 

     return passwordResetTokenModel; 
    } 

    @Override 
    public PasswordResetToken populateToDAO(PasswordResetTokenModel passwordResetTokenModel) { 
     PasswordResetToken passwordResetToken = new PasswordResetToken(); 

     passwordResetToken.setExpiryDate(passwordResetTokenModel.getExpiryDate()); 
     passwordResetToken.setUser(userPopulator.populateToDAO(passwordResetTokenModel.getUser())); 
     passwordResetToken.setToken(passwordResetTokenModel.getToken()); 

     return passwordResetToken; 
    } 
} 

我節省使用

對象
sessionFactory.getCurrentSession().saveOrUpdate(token); 

當我使用此代碼,我發現了以下異常

object references an unsaved transient instance - save the transient instance before flushing: com.demo.megaevents.entities.User 

目前有2個問題在此代碼:

  1. 好像Cascade.ALL我OneToOne映射不工作。如果 我在令牌類中創建單獨的主鍵一切都按預期工作幾乎 ,但存儲在DB中的每個創建的令牌(更像是 OneToMany關係),但是我想避免它,因爲我需要存儲 每個用戶只有一個令牌在我DB
  2. 我不喜歡在populator中使用new,因爲它迫使hibernate在刷新會話時創建新對象。然而,我也不想做另一個選擇從數據庫中獲取這些數據,因爲之前提到的populator我已經做了這個查詢來獲取它,我認爲這是一個開銷。

此外,我真的想擁有DTO,我不想刪除DTO層。

所以,我的問題:

  1. 什麼是處理DTO和實體之間人口的正確方法是什麼?
  2. 我的解決方案有沒有其他改進(可能是架構)?

非常感謝。

+0

也許我不是正確理解你的應用程序,但是如果你在Token類中添加單獨的主鍵,刪除'@JoinColumn(nullable = false,name =「user_id」)'並且添加你的用戶類'@OneToOne(mappedBy =「user」,fetch = FetchType.EAGER,cascade = CascadeType.ALL)PasswordResetToken標記;' – lenach87

+0

@ lenach87,我不希望用戶知道一個標記,但其他方式。 – Selik

回答

0

我不知道爲什麼你會讓UserModel擴展User,但我想你是這麼做的,因爲你不想將User的所有屬性複製到UserModel。太糟糕了,因爲這是實體模型和數據傳輸模型之間需要清晰分離所需要的。

由於您嘗試持續引用具有ID的User對象的PasswordResetToken,但User未與當前會話相關聯,您會得到該例外。你不必詢問用戶,但至少,像這樣的會議協會吧:

PasswordResetToken token = // wherever you get that from 
Session s = sessionFactory.getCurrentSession(); 
token.setUser(s.load(User.class, token.getUser().getId()); 
s.persist(token); 

級聯會導致User創建/插入通過SQL INSERT更新或UPDATE語句顯然不是你想要的。

如果你願意,你可以做Session.load()打電話給你,但我不會那樣做。實際上,我會建議不要使用poppop,而是在您的服務中創建實體對象。

通常你只有幾個(大多數是1)的方式實際上是創建一個新的實體對象,所以從DTO到實體的轉換的全部範圍將只在極少數情況下相關。

大多數時候你要做更新,爲此,你應該首先選擇現有的實體並應用字段,該字段允許從實體對象上的DTO更改

爲了提供具有DTO的表示層,我建議使用Blaze-Persistence Entity Views來避免手動映射樣板並提高選擇查詢的性能。