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返回给我们的结果。

jparespository 实体类弄联合主键_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就取出。我们看下执行的结果:

jparespository 实体类弄联合主键_复合主键_02


添加后,我们得到的结果和预期的一致了。小伙伴们有需求的话,也可以自行尝试下第一种方案。