Java 关于数据库 Entity 如何设计

2022-07-28 09:50:43 +08:00
 hahaFck

喜欢写 sql ,所以不想用 hibernate 类似的完全托管访问数据库的框架,一般用 mybatis 之类的将查询结果转换成 entity 。

这种情况下如果类之间有关联关系,在设计 api 的时候大家一般如何做呢。

比如 User 关联 Department ,在 User 里面是用 departmentId 还是 Department 实体,

如果用 id 属性,在一些情况下需要 department 表的信息,就需要二次查询。

用实体 Department 的情况下(查询 User 时增加 left join department ),是很方便访问关联表的数据,但是会遇到下面两个问题:

  1. 如果 Department 又关联了 Organization 属性呢?Organization 又关联了其他的 Entity 呢, 这样一个查询 User 表的数据的 sql 会关联到很多其他的表,而且很多的 sql 语句都是重复使用的,比如查询 DepartmentOrganization 的列字段和直接查询 Department 实体时的字段是一样的,如果 department 表新增加了字段,这几处的 sql 都要改。

2.

6272 次点击
所在节点    程序员
62 条回复
wxf666
2022-07-28 21:02:36 +08:00
@LeegoYih 数据库新手说下看法,有不对的请指正


这是另外一种什么范式吗?


以前学过的数据库范式说,

1. 每个字段都必须是不可再分割的原子数据项,不能是集合、数组、另一行记录等

2. 其他字段必须完全依赖整个主键组(如:用户部门表『「用户 ID ,部门 ID 」,用户名,省份名』,用户名只依赖用户 ID ,省份名不依赖任何其他字段,故不符)

3. 非主键不依赖其他非主键(如:学生表『「学号」,学院 ID ,学院名称』,学院名称依赖学院 ID ,故不符)

然而实际出于性能、分库分表难 join 等原因,总会冗余一些字段


回到你的回复,你说 用户表『「用户 ID 」,……,部门 ID 』不妥,

1. 部门 ID 业务不依赖 用户 ID (是说违反第二范式?但感觉实际上是依赖的?用户所属部门?)

2. 或用户可所属多个部门(部门 IDs ?但这违反第一范式?)


总的看起来,感觉你像是在说违反了数据库范式

所以,若有性能(你 join 多一个表,肯定性能差些)、分库分表难 join 等问题,你会妥协地加入『部门 ID 』?
chenshun00
2022-07-28 21:57:47 +08:00
这个其实是领域的问题,OP 描述的很容易就让人陷入到面向表结构开发,需求才开始过,表结构就想好了,我要建三个表,巴拉巴拉。
ychost
2022-07-28 22:03:39 +08:00
ModeFirst or DBFirst
oneisall8955
2022-07-28 22:50:15 +08:00
怎么感觉你说的是 jpa ?
LeegoYih
2022-07-29 00:01:57 +08:00
@wxf666 按我的理解,数据库范式只是一种建议,而且其过于学院派,并不适合所有实际场景。
尤其是互联网行业,更倾向于实用派,例如 MySQL ,其作用仅为储存数据,计算基本上都放在应用服务,有些公司的设计规范中,甚至连外键都禁止使用。
shiyanfei5
2022-07-29 02:21:27 +08:00
假设有表 TA ,其有三个字段 A ,B ,C ,表中有 100W 数据。有表 TB ,其有三个字段 C D E ,表中有 800W 数据。
TA 和 TB 通过 C 字段关联后具有业务含义,TA 的 C 列是 TB 的 C 列的数据子集。
需求: 返回表 TA 的数据并实现分页(页大小 10 条), 且筛选 条件为 D 字段=某个值(可能为热点数据)。。
这个不走 sql 的 exists 或 join 语法怎么做呢。。

难点:
1.筛选 条件为 D 字段=某个值 为 热点数据,其对应的 C 可能非常多
2.因为要做分页,但是这是对结果分页
shiyanfei5
2022-07-29 02:21:56 +08:00
谁来看下这种场景不走 sql 咋解决呢
wxf666
2022-07-29 03:49:13 +08:00
@LeegoYih 数据库范式是有点理想化,

所以,若有性能(你 join 多一个表,肯定性能差些)、分库分表难 join 等实际问题,你会妥协地加入『部门 ID 』至『用户表』吗?
siweipancc
2022-07-29 09:03:34 +08:00
尽信书不如无书,网友同理
fox0001
2022-07-29 09:51:26 +08:00
首先,规范是规范,一切从实际出发吧。

一般,对于变化不大的关系,并且是一对多的,我会用外键这种方式。例如用户表会加上部门 ID ,做查询会方便很多,至于外键限制,一般不加。

entity 设计一般对应数据库,除了枚举类做转换。主要是清晰,新增或修改只关注自己的数据。

至于查询两次的问题,都是要查两次的,只是手工查,还是框架自动查。例如用户表含有部门 ID 列,想要获得部门信息,肯定要再查一次数据库。
fox0001
2022-07-29 09:56:15 +08:00
不好意思,补充一下,如果使用 join ,把用户信息和部门信息一并查出来,这种我没用过。因为我用 Hibernate 比较多。

我们偏向于根据 id 做查询,也方便做缓存。比如你的例子,用户信息与部门信息,部门的信息一般变化小,可以缓存起来,不用每次都查数据库。
LeegoYih
2022-07-29 10:29:26 +08:00
@wxf666 不会,多 join 一张表会有一点开销,但是不至于差;分库分表不允许使用 join 。

示例:user 、department 、user_department_rel 各 100 万条,共 300 万条数据,两张表联查和三张表联查的开销几乎无差别。

两张表联查结果:

test>
select *
from user u
join department d on u.department_id = d.id
where u.id = 100000
[2022-07-29 10:24:46] 1 row retrieved starting from 1 in 116 ms (execution: 76 ms, fetching: 40 ms)

test>
select *
from user u
join department d on u.department_id = d.id
where u.id > 100000
limit 10000,10
[2022-07-29 10:24:47] 10 rows retrieved starting from 1 in 127 ms (execution: 89 ms, fetching: 38 ms)

三张表联查结果:

test>
select *
from user u
join user_department_rel udr on u.id = udr.user_id
join department d on d.id = udr.department_id
where u.id = 100000
[2022-07-29 10:24:47] 1 row retrieved starting from 1 in 125 ms (execution: 76 ms, fetching: 49 ms)

test>
select *
from user u
join user_department_rel udr on u.id = udr.user_id
join department d on d.id = udr.department_id
where u.id > 100000
limit 10000,10
[2022-07-29 10:24:48] 10 rows retrieved starting from 1 in 138 ms (execution: 96 ms, fetching: 42 ms)
levon
2022-07-29 10:30:12 +08:00
micean
2022-07-29 10:39:37 +08:00
类似这样,解决好 N+1 即可

select(

User.user_id, User.department_id,

select(Department.department_id, Department.department_name)
.from(Department)
.where(Department.department_id.equals(User.department_id))
.as("department")

).from(User)

返回同样的 List<User>,不需要去写这个 Query 那个 Query

我自己弄了一个用来写后台查询很方便
uselessVisitor
2022-07-29 10:40:24 +08:00
什么业务需要一次性全都关联在一个对象里?按理来说详情页是单独查
nothingistrue
2022-07-29 12:34:40 +08:00
带关联的 Entity 是纯对象层面的设计,不用 hibernate 这种全 ORM ,你能设计个蛋蛋。你下面的关联问题,光用 mybatis 压根就不会有。光用 mybatis 你压根设计不出“User 关联 Department”。当你基于 mybatis 加了好多框架层代码把 “User 关联 Department” 设计出来的时候,那么恭喜你,你造了个新的 Hibernate 。
wxf666
2022-07-29 12:37:12 +08:00
@LeegoYih 这数据量小,可能都缓存至内存了也说不定

有试过每张表的 B+树三 /四 /五层高(视行记录长度的不同,可能分别能容纳几千万、几百亿、几十兆行记录)时,俩 /仨表 join 的耗时差异吗?
nothingistrue
2022-07-29 12:42:18 +08:00
当你回到全 ORM 的基础上的时候,才会有 User 是关联 Department ,还是关联 departmentId 的选择。然而这个并没有选择,对象层面没有外键关联,只能是 User 关联 Department ,User.departmentId 只是 User 的一个属性,不是 Department 的外键。

这个实际上能做得选择是,User 要不要关联 Department ,这个选择取决于业务和性能,而不是技术细节。业务上 User 跟 Department 是必须在一个事务当中的,那么就得关联。业务上二者没有紧密的事务结合,或者说虽然是紧密结合但是因为性能不得不放弃事务一致性而改为最终一致性,那么就不能关联,这时候可能需要 User.departmentId 来维持业务(而非技术)上的弱关联关系。
tvp100
2022-07-29 15:21:38 +08:00
请问如果多次查询,不用 JOIN ,怎么分页?
MonkeyJon
2022-07-29 15:51:28 +08:00
@tvp100 查询用户的话直接分页用户表就好了,部门和组织分别取,然后塞进用户对象里

这是一个专为移动设备优化的页面(即为了让你能够在 Google 搜索结果里秒开这个页面),如果你希望参与 V2EX 社区的讨论,你可以继续到 V2EX 上打开本讨论主题的完整版本。

https://www.v2ex.com/t/869153

V2EX 是创意工作者们的社区,是一个分享自己正在做的有趣事物、交流想法,可以遇见新朋友甚至新机会的地方。

V2EX is a community of developers, designers and creative people.

© 2021 V2EX