欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页

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条记录竟然是一样的!!!
    Spring Boot JPA 复合主键只查询部分主键

查看下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返回给我们的结果。
Spring Boot JPA 复合主键只查询部分主键

  • 分析下来,发现是JPA构建OrderEntity实体类时判断主键仅为id,将查询结果转换为实体时默认取第一条数据进行反序列化,导致返回的三条数据都是默认数据库返回结果中id=1的第一条数据。

对于这种情况,我们可以通过添加@IdClass注解的方式来完善我们的查询结果。@IdClass故名思意说明主键是一个集合类,类包含的全部字段均为主键。

所以我们目前有两种方案可以实施:

  1. 新建一个类,添加@IdClass注解,将表结构的全部主键都引入(id,time);
  2. 在现有类上添加@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就取出。我们看下执行的结果:
Spring Boot JPA 复合主键只查询部分主键
添加后,我们得到的结果和预期的一致了。小伙伴们有需求的话,也可以自行尝试下第一种方案。


最后附上源码下载链接。

https://github.com/Shallow-Xu/union_primary_key