手动将模型映射到数据库 - Spring Data JDBC

发布于 2025-01-14 12:16:03 字数 2911 浏览 1 评论 0原文

我有以下(大大简化的)域对象

public class Student {
  private Long studentId;
  private List<Appointment> appointments;
  
  // Business logic
}
public class Appointment {
  private TimeRange timeRange;
  private LocalDate date;

  // Business logic
}

聚合根是Student,其中包含约会列表。 AppointmentStudent 的子实体。

现在假设,无论出于何种原因,域对象 Student 没有完美映射到我的数据库模型。例如,为了更好地执行业务逻辑,从数据库构造的实体会经历一些转换。需要进行这样的转换,因为我的 Appointment 类中有一个自定义 TimeRange 类,该类无法由 Data JDBC 自动映射。

因此,我想引入一个由 Spring Data JDBC 使用的间接寻址:

@Table("student")
public class StudentEntity {
  @Id
  private Long studentId;
  
  @MappedCollection(idColumn = "student_id")
  private Set<AppointmentEntity> appointments;

  public StudentEntity(Long studentId, Set<AppointmentEntity> appointments) {
    this.studentId = studentId;
    this.appointments = appointments;
  }
}
@Table("appointment")
public class AppointmentEntity {
  @Id
  private Long appointmentId;

  private LocalTime rangeStart;
  private LocalTime rangeEnd;
  private LocalDate date;
}

在我的存储库实现中,我执行以下操作:

@Repository
public class StudentRepositoryImpl implement StudentRepository {
  private final StudenDao studentDao;

  public StudentRepositoryImpl(StudentDao studentDao) {
    this.studentDao = studentDao;
  }

  public Student findStudent(Long id) {
    Optional<StudentEntity> studentEntity = studentDao.findStudentEntityById(id);
    return studentEntity.map(this::toStudent).orElse(null);
  }

  public void saveStudent(Student student) {
    // ???
  }

  private toAppointment(AppointmentEntity appointmentEntity) {
    TimeRange timeRange = new TimeRange(appointmentEntity.rangeStart, appointmentEntity.rangeEnd);
    return new Appointment(timeRange, appointmentEntity.getDate());
  }

  private toStudent(StudentEntity studentEntity) {
    List<Appointment> appointments = studentEntity.appointments.map(this::toAppointment);
    return new Student(studentEntity.getStudentId(), appointments);
  }
}

Database -> > 的流程实体-> Domain 工作正常,但另一个方向呢?假设我执行了一些操作,并且 Student 域对象的 appointments 字段发生了变化。我想再次将其保存到数据库中。

我必须将 Student 转换为 StudentEntity,因此也将 Appointment 转换为 AppointmentEntity。但Appointment 在域上下文中没有 ID,因为它不是聚合根。在我的例子中,AppointmentStudent 具有相同的生命周期,例如,如果 Student 取消注册,则会被丢弃。因此将其放入单独的聚合中是没有意义的。

所以我的主要问题是:如果您的域对象没有 1:1 映射到数据库结构,那么持久化域对象(包括其子实体)的最佳方法是什么?

I have the following (vastly simplified) domain object

public class Student {
  private Long studentId;
  private List<Appointment> appointments;
  
  // Business logic
}
public class Appointment {
  private TimeRange timeRange;
  private LocalDate date;

  // Business logic
}

The aggregate root is Student which contains a list of appointments. An Appointment is a sub-entity of Student.

Now let's say, for whatever reason, the domain object Student does not perfectly map to my database model. For example, to better perform the business logic, the entity constructed from the database undergoes some transformation. One such transformation is needed because I have a custom TimeRange class in my Appointment class which cannot be automatically mapped by Data JDBC.

Therefore I wanted to introduce an indirection which is to be used by Spring Data JDBC:

@Table("student")
public class StudentEntity {
  @Id
  private Long studentId;
  
  @MappedCollection(idColumn = "student_id")
  private Set<AppointmentEntity> appointments;

  public StudentEntity(Long studentId, Set<AppointmentEntity> appointments) {
    this.studentId = studentId;
    this.appointments = appointments;
  }
}
@Table("appointment")
public class AppointmentEntity {
  @Id
  private Long appointmentId;

  private LocalTime rangeStart;
  private LocalTime rangeEnd;
  private LocalDate date;
}

In my repository implementation I do the following

@Repository
public class StudentRepositoryImpl implement StudentRepository {
  private final StudenDao studentDao;

  public StudentRepositoryImpl(StudentDao studentDao) {
    this.studentDao = studentDao;
  }

  public Student findStudent(Long id) {
    Optional<StudentEntity> studentEntity = studentDao.findStudentEntityById(id);
    return studentEntity.map(this::toStudent).orElse(null);
  }

  public void saveStudent(Student student) {
    // ???
  }

  private toAppointment(AppointmentEntity appointmentEntity) {
    TimeRange timeRange = new TimeRange(appointmentEntity.rangeStart, appointmentEntity.rangeEnd);
    return new Appointment(timeRange, appointmentEntity.getDate());
  }

  private toStudent(StudentEntity studentEntity) {
    List<Appointment> appointments = studentEntity.appointments.map(this::toAppointment);
    return new Student(studentEntity.getStudentId(), appointments);
  }
}

The flow from Database -> Entity -> Domain works fine but what about the other direction? Say I perform some actions and the appointments field of a Student domain object changes. I want to save it to the database again.

I would have to convert Student into StudentEntity, and thus also Appointment to AppointmentEntity. But an Appointment does not have an ID in the domain context as it is not an aggregate root. In my case an Appointment has the same lifecycle as a Student and is discarded if a Student unregisters, for example. So it would not make sense to put it into a separate aggregate.

So my main question is: What is the best way to persist a domain object, including its sub-entities, if your domain objects do not 1:1 map to the database structure?

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

鸢与 2025-01-21 12:16:03

AppointmentID 是代理键。重申一下,代理键不是从应用程序数据派生并且代理键的唯一意义是充当主键。 Wikipedia

由此可见,我们可以一次又一次地丢弃并生成 ID相同必要时进行预约记录。

因此,您有以下三个选项可供选择:

  1. 从表中删除所有预约,并在坚持时重新填充新的代理人 ID。

  2. 将代理 ID 加载到域中并将其作为预约数据的一部分保存

  3. 构造一个哈希键来唯一地表示约会(从其字段),并将该密钥用作约会的唯一 ID

所有三种方法都是可以接受的,但您可以选择最适合您的用例的权衡。举个例子:

  • 如果一个学生可以有超过 100 个约会,那么擦除数据并重新填充整个列表将是一种浪费。
  • 如果您无法为每个约会构建唯一的哈希键,则将代理键加载到域中会更容易,即使约会是子实体也是如此。

等等。

AppointmentID is a Surrogate key. Reiterating, The surrogate key is not derived from application data and The only significance of the surrogate key is to act as the primary key. Wikipedia

It follows that we can discard and generate the IDs again and again for the same appointment record when necessary.

So you have three options to choose from:

  1. Delete all appointments from the table and repopulate with new surrogate IDs whenever you persist.

  2. Load the surrogate ID into the domain and hold them as part of appointment data

  3. Construct a hash key to represent an appointment uniquely (from its fields) and use the key as the appointment's unique ID

All three approaches are acceptable, but you can pick the trade-off that best fits your use case. As examples:

  • If a student can have more than 100 appointments, wiping the data and repopulating the entire list would be wasteful.
  • If you cannot construct a unique hash key for each appointment, loading the surrogate key into the domain would be easier to do, even though appointments are sub-entities.

And so on.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文