learn.lianglianglee.com/专栏/领域驱动设计实践(完)/087 对象关系映射(上).md.html
2022-05-11 19:04:14 +08:00

815 lines
46 KiB
HTML
Raw Permalink Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

<!DOCTYPE html>
<!-- saved from url=(0046)https://kaiiiz.github.io/hexo-theme-book-demo/ -->
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1.0, user-scalable=no">
<link rel="icon" href="/static/favicon.png">
<title>087 对象关系映射(上).md.html</title>
<!-- Spectre.css framework -->
<link rel="stylesheet" href="/static/index.css">
<!-- theme css & js -->
<meta name="generator" content="Hexo 4.2.0">
</head>
<body>
<div class="book-container">
<div class="book-sidebar">
<div class="book-brand">
<a href="/">
<img src="/static/favicon.png">
<span>技术文章摘抄</span>
</a>
</div>
<div class="book-menu uncollapsible">
<ul class="uncollapsible">
<li><a href="/" class="current-tab">首页</a></li>
</ul>
<ul class="uncollapsible">
<li><a href="../">上一级</a></li>
</ul>
<ul class="uncollapsible">
<li>
<a href="/专栏/领域驱动设计实践(完)/001 「战略篇」访谈 DDD 和微服务是什么关系?.md.html">001 「战略篇」访谈 DDD 和微服务是什么关系?.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/002 「战略篇」开篇词:领域驱动设计,重焕青春的设计经典.md.html">002 「战略篇」开篇词:领域驱动设计,重焕青春的设计经典.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/003 领域驱动设计概览.md.html">003 领域驱动设计概览.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/004 深入分析软件的复杂度.md.html">004 深入分析软件的复杂度.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/005 控制软件复杂度的原则.md.html">005 控制软件复杂度的原则.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/006 领域驱动设计对软件复杂度的应对(上).md.html">006 领域驱动设计对软件复杂度的应对(上).md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/007 领域驱动设计对软件复杂度的应对(下).md.html">007 领域驱动设计对软件复杂度的应对(下).md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/008 软件开发团队的沟通与协作.md.html">008 软件开发团队的沟通与协作.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/009 运用领域场景分析提炼领域知识(上).md.html">009 运用领域场景分析提炼领域知识(上).md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/010 运用领域场景分析提炼领域知识(下).md.html">010 运用领域场景分析提炼领域知识(下).md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/011 建立统一语言.md.html">011 建立统一语言.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/012 理解限界上下文.md.html">012 理解限界上下文.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/013 限界上下文的控制力(上).md.html">013 限界上下文的控制力(上).md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/014 限界上下文的控制力(下).md.html">014 限界上下文的控制力(下).md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/015 识别限界上下文(上).md.html">015 识别限界上下文(上).md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/016 识别限界上下文(下).md.html">016 识别限界上下文(下).md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/017 理解上下文映射.md.html">017 理解上下文映射.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/018 上下文映射的团队协作模式.md.html">018 上下文映射的团队协作模式.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/019 上下文映射的通信集成模式.md.html">019 上下文映射的通信集成模式.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/020 辨别限界上下文的协作关系(上).md.html">020 辨别限界上下文的协作关系(上).md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/021 辨别限界上下文的协作关系(下).md.html">021 辨别限界上下文的协作关系(下).md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/022 认识分层架构.md.html">022 认识分层架构.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/023 分层架构的演化.md.html">023 分层架构的演化.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/024 领域驱动架构的演进.md.html">024 领域驱动架构的演进.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/025 案例 层次的职责与协作关系(图文篇).md.html">025 案例 层次的职责与协作关系(图文篇).md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/026 限界上下文与架构.md.html">026 限界上下文与架构.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/027 限界上下文对架构的影响.md.html">027 限界上下文对架构的影响.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/028 领域驱动设计的代码模型.md.html">028 领域驱动设计的代码模型.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/029 代码模型的架构决策.md.html">029 代码模型的架构决策.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/030 实践 先启阶段的需求分析.md.html">030 实践 先启阶段的需求分析.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/031 实践 先启阶段的领域场景分析(上).md.html">031 实践 先启阶段的领域场景分析(上).md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/032 实践 先启阶段的领域场景分析(下).md.html">032 实践 先启阶段的领域场景分析(下).md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/033 实践 识别限界上下文.md.html">033 实践 识别限界上下文.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/034 实践 确定限界上下文的协作关系.md.html">034 实践 确定限界上下文的协作关系.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/035 实践 EAS 的整体架构.md.html">035 实践 EAS 的整体架构.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/036 「战术篇」访谈DDD 能帮开发团队提高设计水平吗?.md.html">036 「战术篇」访谈DDD 能帮开发团队提高设计水平吗?.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/037 「战术篇」开篇词:领域驱动设计的不确定性.md.html">037 「战术篇」开篇词:领域驱动设计的不确定性.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/038 什么是模型.md.html">038 什么是模型.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/039 数据分析模型.md.html">039 数据分析模型.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/040 数据设计模型.md.html">040 数据设计模型.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/041 数据模型与对象模型.md.html">041 数据模型与对象模型.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/042 数据实现模型.md.html">042 数据实现模型.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/043 案例 培训管理系统.md.html">043 案例 培训管理系统.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/044 服务资源模型.md.html">044 服务资源模型.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/045 服务行为模型.md.html">045 服务行为模型.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/046 服务设计模型.md.html">046 服务设计模型.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/047 领域模型驱动设计.md.html">047 领域模型驱动设计.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/048 领域实现模型.md.html">048 领域实现模型.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/049 理解领域模型.md.html">049 理解领域模型.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/050 领域模型与结构范式.md.html">050 领域模型与结构范式.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/051 领域模型与对象范式(上).md.html">051 领域模型与对象范式(上).md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/052 领域模型与对象范式(中).md.html">052 领域模型与对象范式(中).md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/053 领域模型与对象范式(下).md.html">053 领域模型与对象范式(下).md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/054 领域模型与函数范式.md.html">054 领域模型与函数范式.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/055 领域驱动分层架构与对象模型.md.html">055 领域驱动分层架构与对象模型.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/056 统一语言与领域分析模型.md.html">056 统一语言与领域分析模型.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/057 精炼领域分析模型.md.html">057 精炼领域分析模型.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/058 彩色 UML 与彩色建模.md.html">058 彩色 UML 与彩色建模.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/059 四色建模法.md.html">059 四色建模法.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/060 案例 订单核心流程的四色建模.md.html">060 案例 订单核心流程的四色建模.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/061 事件风暴与业务全景探索.md.html">061 事件风暴与业务全景探索.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/062 事件风暴与领域分析建模.md.html">062 事件风暴与领域分析建模.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/063 案例 订单核心流程的事件风暴.md.html">063 案例 订单核心流程的事件风暴.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/064 表达领域设计模型.md.html">064 表达领域设计模型.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/065 实体.md.html">065 实体.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/066 值对象.md.html">066 值对象.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/067 对象图与聚合.md.html">067 对象图与聚合.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/068 聚合设计原则.md.html">068 聚合设计原则.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/069 聚合之间的关系.md.html">069 聚合之间的关系.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/070 聚合的设计过程.md.html">070 聚合的设计过程.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/071 案例 培训领域模型的聚合设计.md.html">071 案例 培训领域模型的聚合设计.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/072 领域模型对象的生命周期-工厂.md.html">072 领域模型对象的生命周期-工厂.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/073 领域模型对象的生命周期-资源库.md.html">073 领域模型对象的生命周期-资源库.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/074 领域服务.md.html">074 领域服务.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/075 案例 领域设计模型的价值.md.html">075 案例 领域设计模型的价值.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/076 应用服务.md.html">076 应用服务.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/077 场景的设计驱动力.md.html">077 场景的设计驱动力.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/078 案例 薪资管理系统的场景驱动设计.md.html">078 案例 薪资管理系统的场景驱动设计.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/079 场景驱动设计与 DCI 模式.md.html">079 场景驱动设计与 DCI 模式.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/080 领域事件.md.html">080 领域事件.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/081 发布者—订阅者模式.md.html">081 发布者—订阅者模式.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/082 事件溯源模式.md.html">082 事件溯源模式.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/083 测试优先的领域实现建模.md.html">083 测试优先的领域实现建模.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/084 深入理解简单设计.md.html">084 深入理解简单设计.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/085 案例 薪资管理系统的测试驱动开发(上).md.html">085 案例 薪资管理系统的测试驱动开发(上).md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/086 案例 薪资管理系统的测试驱动开发(下).md.html">086 案例 薪资管理系统的测试驱动开发(下).md.html</a>
</li>
<li>
<a class="current-tab" href="/专栏/领域驱动设计实践(完)/087 对象关系映射(上).md.html">087 对象关系映射(上).md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/088 对象关系映射(下).md.html">088 对象关系映射(下).md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/089 领域模型与数据模型.md.html">089 领域模型与数据模型.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/090 领域驱动设计对持久化的影响.md.html">090 领域驱动设计对持久化的影响.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/091 领域驱动设计体系.md.html">091 领域驱动设计体系.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/092 子领域与限界上下文.md.html">092 子领域与限界上下文.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/093 限界上下文的边界与协作.md.html">093 限界上下文的边界与协作.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/094 限界上下文之间的分布式通信.md.html">094 限界上下文之间的分布式通信.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/095 命令查询职责分离.md.html">095 命令查询职责分离.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/096 分布式柔性事务.md.html">096 分布式柔性事务.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/097 设计概念的统一语言.md.html">097 设计概念的统一语言.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/098 模型对象.md.html">098 模型对象.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/099 领域驱动设计参考过程模型.md.html">099 领域驱动设计参考过程模型.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/100 领域驱动设计的精髓.md.html">100 领域驱动设计的精髓.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/101 实践 员工上下文的领域建模.md.html">101 实践 员工上下文的领域建模.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/102 实践 考勤上下文的领域建模.md.html">102 实践 考勤上下文的领域建模.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/103 实践 项目上下文的领域建模.md.html">103 实践 项目上下文的领域建模.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/104 实践 培训上下文的业务需求.md.html">104 实践 培训上下文的业务需求.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/105 实践 培训上下文的领域分析建模.md.html">105 实践 培训上下文的领域分析建模.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/106 实践 培训上下文的领域设计建模.md.html">106 实践 培训上下文的领域设计建模.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/107 实践 培训上下文的领域实现建模.md.html">107 实践 培训上下文的领域实现建模.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/108 实践 EAS 系统的代码模型.md.html">108 实践 EAS 系统的代码模型.md.html</a>
</li>
<li>
<a href="/专栏/领域驱动设计实践(完)/109 后记:如何学习领域驱动设计.md.html">109 后记:如何学习领域驱动设计.md.html</a>
</li>
</ul>
</div>
</div>
<div class="sidebar-toggle" onclick="sidebar_toggle()" onmouseover="add_inner()" onmouseleave="remove_inner()">
<div class="sidebar-toggle-inner"></div>
</div>
<script>
function add_inner() {
let inner = document.querySelector('.sidebar-toggle-inner')
inner.classList.add('show')
}
function remove_inner() {
let inner = document.querySelector('.sidebar-toggle-inner')
inner.classList.remove('show')
}
function sidebar_toggle() {
let sidebar_toggle = document.querySelector('.sidebar-toggle')
let sidebar = document.querySelector('.book-sidebar')
let content = document.querySelector('.off-canvas-content')
if (sidebar_toggle.classList.contains('extend')) { // show
sidebar_toggle.classList.remove('extend')
sidebar.classList.remove('hide')
content.classList.remove('extend')
} else { // hide
sidebar_toggle.classList.add('extend')
sidebar.classList.add('hide')
content.classList.add('extend')
}
}
function open_sidebar() {
let sidebar = document.querySelector('.book-sidebar')
let overlay = document.querySelector('.off-canvas-overlay')
sidebar.classList.add('show')
overlay.classList.add('show')
}
function hide_canvas() {
let sidebar = document.querySelector('.book-sidebar')
let overlay = document.querySelector('.off-canvas-overlay')
sidebar.classList.remove('show')
overlay.classList.remove('show')
}
</script>
<div class="off-canvas-content">
<div class="columns">
<div class="column col-12 col-lg-12">
<div class="book-navbar">
<!-- For Responsive Layout -->
<header class="navbar">
<section class="navbar-section">
<a onclick="open_sidebar()">
<i class="icon icon-menu"></i>
</a>
</section>
</header>
</div>
<div class="book-content" style="max-width: 960px; margin: 0 auto;
overflow-x: auto;
overflow-y: hidden;">
<div class="book-post">
<p id="tip" align="center"></p>
<div><h1>087 对象关系映射(上)</h1>
<h3>领域模型的持久化</h3>
<p>领域驱动设计强调对领域建模来应对业务复杂度,通过分层架构来隔离业务复杂度与技术复杂度,这就使得我们在考虑领域逻辑时,尽量规避对领域模型持久化的考虑,引入抽象的资源库正是为了解决这一问题。领域驱动设计的驱动力是领域逻辑而非数据库样式,因此是先有领域模型,然后再根据领域模型定义数据模型,此为领域模型驱动设计与数据模型驱动设计的根本区别。</p>
<h3>对象关系映射</h3>
<p>领域模型是面向对象的数据模型是面向关系表的。倘若采用领域模型驱动设计领域模型一方面充分地表达了系统的领域逻辑同时它还将映射为数据模型成为操作数据库的持久化对象。这就是采用面向对象设计编写基础设施层的持久化功能时无法绕过的对象关系映射Object Relationship MappingORM</p>
<h4>对象关系阻抗不匹配</h4>
<p>如果持久化的数据库为关系数据库,就会出现所谓“对象关系阻抗不匹配”的问题。这种阻抗不匹配主要体现为以下三个方面:</p>
<ul>
<li>类型的阻抗不匹配:例如不同关系型数据库对浮点数的不同表示,字符串类型在数据库的最大长度约束等,又例如 Java 等语言的枚举内建类型本质上仍然属于基础类型,关系数据库中却没有对应的类型来匹配。</li>
<li>样式的阻抗不匹配:领域模型与数据模型不具备一一对应的关系。领域模型是一个具有嵌套层次的对象图结构,数据模型在关系数据库中却是扁平的关系结构,要让数据库能够表示领域模型,就只能通过关系来变通地映射实现。</li>
<li>对象模式的阻抗不匹配:面向对象的封装、继承与多态无法在关系数据库得到直观体现。通过封装可以定义一个高内聚的类来表达一个细粒度的基本概念,但数据表往往不这么设计;数据表只有组合关系,无法表达对象之间的继承关系;既然无法实现继承关系,就无法满足 Liskov 替换原则,自然也就无法满足多态。</li>
</ul>
<p>ORM 框架正是为了解决这些阻抗不匹配问题应运而生,这个问题如此的重要,因此 Java 语言甚至定义了持久化的规范,用以指导面向对象的语言要素与关系数据表之间的映射,如 SUN 在 JDK 5 中引入的 JPAJava Persistence API作为 JCP 组织发布的 Java EE 标准,就起到了在 Java 社区指导 ORM 技术实现的规范。</p>
<h3>JPA 的应对之道</h3>
<p>顾名思义ORM 框架的目的是在对象与关系之间建立一种映射。为了满足这一目标往往通过配置文件或者在领域模型中声明元数据来表现这种映射关系。JPA 作为一种规范,它全面地考虑了各种阻抗不匹配的情形,然后规定了标准的映射元数据,如 @Entity、@Table 和 @Column 等 Java 标注。一旦领域模型声明了这些标注具体的JPA框架如 Hibernate 等就可以通过反射识别这些元数据,获得对象与关系之间的映射信息,从而实现领域模型的持久化。</p>
<h4>类型的阻抗不匹配</h4>
<p>针对类型的阻抗不匹配JPA 元数据通过 @Column 标注的属性来指定长度、精度还有对 null 的支持;通过 Lob 标注来表示字节数组;通过 @ElementCollection 等标注来表达集合。至于枚举、日期和 ID 等特殊类型JPA 也针对性地给出了元数据定义。</p>
<p><strong>枚举类型</strong></p>
<p>关系数据库的内建类型没有枚举类型。如果领域模型的字段被定义为自定义的枚举,通常会在数据库中将相应的列定义为 smallint 类型,然后通过 @Enumerated 表示枚举的含义,例如:</p>
<pre><code class="language-java">public enum EmployeeType {
Hourly, Salaried, Commission
}
public class Employee {
@Enumerated
@Column(columnDefinition = &quot;smallint&quot;)
private EmployeeType employeeType;
}
</code></pre>
<p>使用 smallint 表示枚举类型虽然能够体现值的有序性,但在管理和运维数据库时,查询得到的枚举值却是没有任何业务含义的数字,这不利于对数据的理解。这时,可以将这样的列定义为 VARCHAR而在领域模型中声明为</p>
<pre><code class="language-java">public enum Gender {
Male, Female
}
public class Employee {
@Enumerated(EnumType.STRING)
private Gender gender;
}
</code></pre>
<p>通过在字段上标注 @Enumerated(EnumType.STRING),可以将枚举类型转换为字符串。注意,数据库的字符串应与枚举类型的字符串值以及大小写保持一致。</p>
<p><strong>日期类型</strong></p>
<p>针对 Java 的日期和时间类型进行映射,处理要相对复杂一些。因为 Java 定义了多种日期和时间类型,包括:</p>
<ul>
<li>用以表达数据库日期类型的 java.sql.Date 类和表达数据库时间类型的 java.sql.Timestamp 类</li>
<li>Java 库用以表达日期、时间与时间戳类型的 java.util.Date 类或 java.util.Calendar 类</li>
<li>Java 8 引入的新日期类型 java.time.LocalDate 类与新时间类型 java.time.LocalDateTime 类</li>
</ul>
<p>当领域模型对象的日期或时间字段被定义为 java.sql.Date 或 java.sql.Timestamp 类型时,由于数据库支持这一类型,因此无需做任何特别的配置。通过 columnDefinition 属性值,甚至可以设置默认值,例如:</p>
<pre><code class="language-java">@Column(name = &quot;START_DATE&quot;, columnDefinition = &quot;DATE DEFAULT CURRENT_DATE&quot;)
private java.sql.Date startDate;
</code></pre>
<p>如果字段被定义为 java.util.Date 或 java.util.Calendar 类型JPA 定义了 @Temporal 标注将其映射为日期、时间或时间戳,例如:</p>
<pre><code class="language-java">@Temporal(TemporalType.DATE)
private java.util.Calendar birthday;
@Temporal(TemporalType.TIME)
private java.util.Date birthday;
@Temporal(TemporalType.TIMESTAMP)
private java.util.Date birthday;
</code></pre>
<p>如果字段被定义为 Java 8 新引入的 LocalDate 或 LocalDateTime 类型时,情况稍显复杂,需要取决于 JPA 的版本。JPA 2.2 版本已经支持 Java 8 日期时间 API 中除 java.time.Duration 外的其他日期和时间类型。因此,若选择了这个版本的 JPA无需再为 JDK 8 的日期或时间类型做任何设置,与诸如 String、int 等类型一视同仁。</p>
<p>如果 JPA 的版本是 2.1 及以下版本,由于这些版本发布在 Java 8 之前,因此无法直接支持这两种类型,需要为其定义 AttributeConverter。例如为 LocalDate 定义转换器:</p>
<pre><code class="language-java">import javax.persistence.AttributeConverter;
import javax.persistence.Converter;
import java.sql.Date;
import java.time.LocalDate;
@Converter(autoApply = true)
public class LocalDateAttributeConverter implements AttributeConverter&lt;LocalDate, Date&gt; {
@Override
public Date convertToDatabaseColumn(LocalDate locDate) {
return locDate == null ? null : Date.valueOf(locDate);
}
@Override
public LocalDate convertToEntityAttribute(Date sqlDate) {
return sqlDate == null ? null : sqlDate.toLocalDate();
}
}
</code></pre>
<p><strong>主键类型</strong></p>
<p>在关系数据库中每个表的主键都是至为关键的列通过它可以标注每一行记录的唯一性。主键还是建立表关联的关键列通过主键与外键的关系可以间接支持领域模型对象之间的导航同时也保证了关系数据库的完整性。无论是单一主键还是联合主键主键作为身份标识Identity只要能够确保它在同一张表中的唯一性原则上可以定义为各种类型如 BigInt、VARCHAR 等。在数据表定义中,只要某个列被声明为 PRIMARY KEY在领域模型对象的定义中就可以使用 JPA 提供的 @Id 标注。这个标注还可以和 @Column 标注组合使用:</p>
<pre><code class="language-java">@Id
@Column(name = &quot;employeeId&quot;)
private int id;
</code></pre>
<p>主流的关系数据库都支持主键的自动生成JPA 提供了 @GeneratedValue 标注说明了该主键是自动生成的。该标注还定义了 strategy 属性用以指定自动生成的策略。JPA 还定义了 @SequenceGenerator 与 @TableGenerator 等特殊的 ID 生成器。</p>
<p>在建立领域模型时我们强调从领域逻辑出发考虑领域类的定义。尤其对于实体类而言ID 代表的是实体对象的身份标识。它与数据表的主键有相似之处,例如都要求唯一性,但二者的本质完全不同:前者代表业务含义,后者代表技术含义。前者用于对实体对象生命周期的管理与跟踪,后者用于标记每一行在数据表中的唯一性。因此,领域驱动设计往往建议定义 Identity 值对象作为实体的身份标识。一方面,值对象类型可以清晰表达该身份标识的业务含义;另一方面值对象类型的封装也有利于应对未来主键类型可能的变化。</p>
<p>Identity 值对象的定义体现了面向对象的封装思想JPA 定义了一个特殊的标注 @EmbeddedId 来建立数据表主键与身份标识值对象之间的映射。例如,为 Employee 实体对象定义了 EmployeeId 值对象,则 Employee 的定义为:</p>
<pre><code class="language-java">@Entity
@Table(name=&quot;employees&quot;)
public class Employee extends AbstractEntity&lt;EmployeeId&gt; implements AggregateRoot&lt;Employee&gt; {
@EmbeddedId
private EmployeeId employeeId;
}
</code></pre>
<p>JPA 对主键类有两个要求:相等性比较与序列化支持。这就需要 EmployeeId 实现 Serializable 接口,并重写 Object 的 equals() 与 hashcode() 方法,同时在类定义之上声明 Embeddable 标注:</p>
<pre><code class="language-java">@Embeddable
public class EmployeeId implements Identity&lt;String&gt;, Serializable {
@Column(name = &quot;id&quot;)
private String value;
private static Random random;
static {
random = new Random();
}
// 必须提供默认的构造函数
public EmployeeId() {
}
private EmployeeId(String value) {
this.value = value;
}
@Override
public String value() {
return this.value;
}
public static EmployeeId of(String value) {
return new EmployeeId(value);
}
public static Identity&lt;String&gt; next() {
return new EmployeeId(String.format(&quot;%s%s%s&quot;,
composePrefix(),
composeTimestamp(),
composeRandomNumber()));
}
@Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
EmployeeId that = (EmployeeId) o;
return value.equals(that.value);
}
@Override
public int hashCode() {
return Objects.hash(value);
}
}
</code></pre>
<p>使用时,可以直接传入 EmployeeId 对象作为主键查询条件:</p>
<pre><code class="language-java">Optional&lt;Employee&gt; optEmployee = employeeRepo.findById(EmployeeId.of(&quot;emp200109101000001&quot;));
</code></pre>
<h4>样式的阻抗不匹配</h4>
<p>样式Schema的阻抗不匹配实则就是对象图与关系表之间的不匹配。要做到二者的匹配就需要做到图结构与表结构之间的互相转换。在领域模型的对象图中一个实体组合了另一个实体由于两个实体都有各自的身份标识因此在数据库中可以通过主外键关系建立关联。这些关联关系分别体现为一对一、一对多或者多对一、多对多。</p>
<p>例如在领域模型中HourlyEmployee 聚合根实体与 TimeCard 实体之间的关系可以定义为:</p>
<pre><code class="language-java">@Entity
@Table(name=&quot;employees&quot;)
public class HourlyEmployee extends AbstractEntity&lt;EmployeeId&gt; implements AggregateRoot&lt;HourlyEmployee&gt; {
@OneToMany
@JoinColumn(name = &quot;employeeId&quot;, nullable = false)
private List&lt;TimeCard&gt; timeCards = new ArrayList&lt;&gt;();
}
@Entity
@Table(name = &quot;timecards&quot;)
public class TimeCard {
private static final int MAXIMUM_REGULAR_HOURS = 8;
@Id
@GeneratedValue
private String id;
private LocalDate workDay;
private int workHours;
public TimeCard() {
}
}
</code></pre>
<p>在数据模型中timecards 表则通过外键 employeeId 建立与 employees 表之间的关联:</p>
<pre><code class="language-sql">CREATE TABLE employees(
id VARCHAR(50) NOT NULL,
......
PRIMARY KEY(id)
);
CREATE TABLE timecards(
id INT NOT NULL AUTO_INCREMENT,
employeeId VARCHAR(50) NOT NULL,
workDay DATE NOT NULL,
workHours INT NOT NULL,
PRIMARY KEY(id)
);
</code></pre>
<p>如果对象图的组合关系发生在一个实体和值对象之间,并形成一对多的关联。由于值对象没有唯一的身份标识,它的数据模型也没有主键,而是将实体表的主键作为外键,由此来表达彼此之间的归属关系。这时,领域模型仍然通过集合来表达一对多的关联,但使用的标注却并非 @OneToMany而是 @ElementCollection。例如领域模型中的 SalariedEmployee 聚合根实体与 Absence 值对象之间的关系可以定义为:</p>
<pre><code class="language-java">@Embeddable
public class Absence {
private LocalDate leaveDate;
@Enumerated(EnumType.STRING)
private LeaveReason leaveReason;
public Absence() {
}
public Address(String country, String province, String city, String street, String zip) {
this.country = country;
this.province = province;
this.city = city;
this.street = street;
this.zip = zip;
}
}
@Entity
@Table(name=&quot;employees&quot;)
public class SalariedEmployee extends AbstractEntity&lt;EmployeeId&gt; implements AggregateRoot&lt;SalariedEmployee&gt; {
private static final int WORK_DAYS_OF_MONTH = 22;
@EmbeddedId
private EmployeeId employeeId;
@Embedded
private Salary salaryOfMonth;
@ElementCollection
@CollectionTable(name = &quot;absences&quot;, joinColumns = @JoinColumn(name = &quot;employeeId&quot;))
private List&lt;Absence&gt; absences = new ArrayList&lt;&gt;();
public SalariedEmployee() {
}
}
</code></pre>
<p>@ElementCollection 说明了字段 absences 是 SalariedEmployee 实体的字段元素,类型为集合;@CollectionTable 标记了关联的数据表以及关联的外键。其数据模型如下 SQL 语句所示:</p>
<pre><code class="language-sql">CREATE TABLE employees(
id VARCHAR(50) NOT NULL,
......
PRIMARY KEY(id)
);
CREATE TABLE absences(
employeeId VARCHAR(50) NOT NULL,
leaveDate DATE NOT NULL,
leaveReason VARCHAR(20) NOT NULL
);
</code></pre>
<p>数据表 absences 没有自己的主键employeeId 列是 employees 表的主键。注意,在 Absence 值对象的定义中,无需再定义 employeeId 字段,因为 Absence 值对象并不能脱离 SalariedEmployee 聚合根单独存在。</p>
</div>
</div>
<div>
<div style="float: left">
<a href="/专栏/领域驱动设计实践(完)/086 案例 薪资管理系统的测试驱动开发(下).md.html">上一页</a>
</div>
<div style="float: right">
<a href="/专栏/领域驱动设计实践(完)/088 对象关系映射(下).md.html">下一页</a>
</div>
</div>
</div>
</div>
</div>
</div>
<a class="off-canvas-overlay" onclick="hide_canvas()"></a>
</div>
<script defer src="https://static.cloudflareinsights.com/beacon.min.js/v652eace1692a40cfa3763df669d7439c1639079717194" integrity="sha512-Gi7xpJR8tSkrpF7aordPZQlW2DLtzUlZcumS8dMQjwDHEnw9I7ZLyiOj/6tZStRBGtGgN6ceN6cMH8z7etPGlw==" data-cf-beacon='{"rayId":"70997ec828113cfa","version":"2021.12.0","r":1,"token":"1f5d475227ce4f0089a7cff1ab17c0f5","si":100}' crossorigin="anonymous"></script>
</body>
<!-- Global site tag (gtag.js) - Google Analytics -->
<script async src="https://www.googletagmanager.com/gtag/js?id=G-NPSEEVD756"></script>
<script>
window.dataLayer = window.dataLayer || [];
function gtag() {
dataLayer.push(arguments);
}
gtag('js', new Date());
gtag('config', 'G-NPSEEVD756');
var path = window.location.pathname
var cookie = getCookie("lastPath");
console.log(path)
if (path.replace("/", "") === "") {
if (cookie.replace("/", "") !== "") {
console.log(cookie)
document.getElementById("tip").innerHTML = "<a href='" + cookie + "'>跳转到上次进度</a>"
}
} else {
setCookie("lastPath", path)
}
function setCookie(cname, cvalue) {
var d = new Date();
d.setTime(d.getTime() + (180 * 24 * 60 * 60 * 1000));
var expires = "expires=" + d.toGMTString();
document.cookie = cname + "=" + cvalue + "; " + expires + ";path = /";
}
function getCookie(cname) {
var name = cname + "=";
var ca = document.cookie.split(';');
for (var i = 0; i < ca.length; i++) {
var c = ca[i].trim();
if (c.indexOf(name) === 0) return c.substring(name.length, c.length);
}
return "";
}
</script>
</html>