Spring Boot JPA 复合主键只查询部分主键
程序员文章站
2022-04-24 22:37:37
...
Spring Data JPA给了我们很强大的功能,我们只需要通过编写一个继承自JpaRepository的接口就能完成数据访问。最近使用过程中,碰到一个问题:
数据库的表中有多个主键,我们和数据库交互的实体(Entity)中只定义了其中部分主键,也就是数据库表中的字段多于Entity中定义的字段。当我们查询时发现,查询返回的结果和我们预想的完全不一致。
接下来我们来逐步验证并进行完善:
- 首先是建表,我们采用的是MySql数据库:
SET FOREIGN_KEY_CHECKS=0;
-- ----------------------------
-- Table structure for `order_info`
-- ----------------------------
DROP TABLE IF EXISTS `order_info`;
CREATE TABLE `order_info` (
`id` varchar(20) NOT NULL DEFAULT '',
`time` datetime NOT NULL DEFAULT '0000-00-00 00:00:00',
`flag` varchar(1) DEFAULT NULL,
`memo` varchar(100) CHARACTER SET utf8 DEFAULT NULL,
PRIMARY KEY (`id`,`time`)
) ENGINE=InnoDB DEFAULT CHARSET=latin1;
-- ----------------------------
-- Records of order_info
-- ----------------------------
INSERT INTO `order_info` VALUES ('1', '2017-07-12 10:18:35', '0', '1号订单');
INSERT INTO `order_info` VALUES ('1', '2017-07-12 10:19:21', '1', '1号订单子订单1');
INSERT INTO `order_info` VALUES ('1', '2017-07-12 10:19:59', '2', '1号订单子订单2');
INSERT INTO `order_info` VALUES ('2', '2017-07-12 10:19:14', '0', '2号订单');
INSERT INTO `order_info` VALUES ('3', '2017-07-12 10:19:17', '0', '3号订单');
设置了订单号(id)及下单时间(time)两个字段作为主键。
- Entity定义上,我们只引用了订单号(id)这一个主键
@Entity
@Table(name = "order_info")
public class OrderEntity implements Serializable {
@Id
private String id;
private String flag;
private String memo;
//省略get、set语句…
}
- 编写调用接口,根据订单编号获取订单信息
@RestController
@RequestMapping(value = "/order")
public class OrderController {
@Autowired
private OrderService orderService;
@RequestMapping(value ="/get/{id}")
@ResponseBody
public List<OrderEntity> getOrder(@PathVariable("id") String id) {
return orderService.getById(id);
}
}
- 启动工程后访问接口,发现返回的值和我们预期的值不一样,返回的3条记录竟然是一样的!!!
查看下Hibernate的运行日志:
Hibernate: select orderentit0_.id as id1_0_, orderentit0_.flag as flag2_0_, orderentit0_.memo as memo3_0_ from order_info orderentit0_ where orderentit0_.id=?
补全id后我们手动运行sql发现运行结果和数据库一致,并不是JPA返回给我们的结果。
- 分析下来,发现是JPA构建OrderEntity实体类时判断主键仅为id,将查询结果转换为实体时默认取第一条数据进行反序列化,导致返回的三条数据都是默认数据库返回结果中id=1的第一条数据。
对于这种情况,我们可以通过添加@IdClass注解的方式来完善我们的查询结果。@IdClass故名思意说明主键是一个集合类,类包含的全部字段均为主键。
所以我们目前有两种方案可以实施:
- 新建一个类,添加@IdClass注解,将表结构的全部主键都引入(id,time);
- 在现有类上添加@IdClass,将我们查询的所有字段都标记为主键。
这里我们选择了方案2。
@Entity
@Table(name = "order_info")
@IdClass(OrderEntity.class)
public class OrderEntity implements Serializable {
@Id
private String id;
private String flag;
private String memo;
//省略get、set语句…
}
添加@IdClass(OrderEntity.class)后,JPA认为我们实体类中所有字段均为主键,将查询结果反序列化的时候会判定全部字段来取出,而不仅仅判定id就取出。我们看下执行的结果:
添加后,我们得到的结果和预期的一致了。小伙伴们有需求的话,也可以自行尝试下第一种方案。