【问题标题】:Hibernate One-to-One DTO-Entity populationHibernate 一对一 DTO 实体群体
【发布时间】:2016-12-06 17:42:28
【问题描述】:

我的数据库中有 2 个实体,具有一对一的方向映射: 用户和密码重置令牌。这背后的想法是在每次用户请求密码重置时创建新令牌并仅存储最新的令牌。

以下是我的实体:

@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,以便在我的应用程序中传递它们。 代码sn-ps:

@Getter @Setter
public class PasswordResetTokenModel {

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

}

UserModel 也用于 Spring Security

@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 个人口:

@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. 似乎我的 OneToOne 映射中的 Cascade.ALL 不起作用。如果 我在 Token 类中创建了单独的主键,一切正常 正如预期的那样,但将每个创建的令牌存储在数据库中(更像 OneToMany 关系),但是我想避免它,因为我需要存储 我的数据库中每个用户只有一个令牌
  2. 我不喜欢在填充器中使用 new,因为它会强制休眠在刷新会话时创建新对象。但是,我也不想再做一次选择来从数据库中获取这些数据,因为在之前提到的填充器我已经做了这个查询来获取它,我认为这是一个开销。

另外,我真的很想拥有 DTO,我不想删除 DTO 层。

所以,我的问题:

  1. 在 DTO 和实体之间处理填充的正确方法是什么?
  2. 我的解决方案是否还有其他改进(可能是架构方面的)?

非常感谢。

【问题讨论】:

  • 可能我没有正确理解您的应用程序,但是如果您在 Token 类中添加单独的主键,删除 @JoinColumn(nullable = false, name = "user_id") 并在您的 User 类中添加 @OneToOne (mappedBy = "user", fetch = FetchType.EAGER, cascade = CascadeType.ALL) PasswordResetToken token;,也许您会达到预期的效果
  • @lenach87,我不希望用户知道令牌,但其他方式。

标签: java spring hibernate dto one-to-one


【解决方案1】:

我不确定你为什么要让UserModel 扩展User,但我猜你这样做是因为你不想将所有属性从User 复制到UserModel。太糟糕了,因为这是在实体模型和数据传输模型之间进行清晰分离所需要的。

您收到该异常是因为您尝试持久化一个 PasswordResetToken,该 PasswordResetToken 引用了一个带有 id 的 User 对象,但 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(),但我不会那样做。实际上,我建议根本不要使用填充器,而是在您的服务中创建实体对象。

通常,您只有几种(主要是 1 种)实际创建新实体对象的方法,因此从 DTO 到实体的全部转换仅在极少数情况下相关。

大多数情况下,您要进行更新,为此,您应首先选择现有实体,然后将 DTO 中的允许更改的字段应用于实体对象。

为了向表示层提供 DTO,我建议使用 Blaze-Persistence Entity Views 以避免手动映射样板文件并提高选择查询的性能。

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 1970-01-01
    • 2021-06-16
    • 1970-01-01
    • 2021-04-20
    • 2018-11-06
    • 2019-05-01
    • 2018-04-29
    • 2015-01-30
    相关资源
    最近更新 更多