手动将模型映射到数据库 - Spring Data JDBC
我有以下(大大简化的)域对象
public class Student {
private Long studentId;
private List<Appointment> appointments;
// Business logic
}
public class Appointment {
private TimeRange timeRange;
private LocalDate date;
// Business logic
}
聚合根是Student
,其中包含约会列表。 Appointment
是Student
的子实体。
现在假设,无论出于何种原因,域对象 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,因为它不是聚合根。在我的例子中,Appointment
与 Student
具有相同的生命周期,例如,如果 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 技术交流群。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论
评论(1)
AppointmentID
是代理键。重申一下,代理键不是从应用程序数据派生并且代理键的唯一意义是充当主键。 Wikipedia由此可见,我们可以一次又一次地丢弃并生成 ID相同必要时进行预约记录。
因此,您有以下三个选项可供选择:
从表中删除所有预约,并在坚持时重新填充新的代理人 ID。
将代理 ID 加载到域中并将其作为预约数据的一部分保存
构造一个哈希键来唯一地表示约会(从其字段),并将该密钥用作约会的唯一 ID
所有三种方法都是可以接受的,但您可以选择最适合您的用例的权衡。举个例子:
等等。
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. WikipediaIt 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:
Delete all appointments from the table and repopulate with new surrogate IDs whenever you persist.
Load the surrogate ID into the domain and hold them as part of appointment data
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:
And so on.