1. @MappedSuperclass

@MappedSuperclass
public abstract class CucdCriteriaBase {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    private String showName;

    @Enumerated(EnumType.STRING)
    private CompareCondition compareCondition;

    private String compareValue;

    //getter、setter

}

@Entity
@Table(name = "cucd_criteria_inspection")
public class CucdCriteriaInspection extends CucdCriteriaBase {

    private String type;

    //getter、setter
}

@MappedSuperclass 声明的父类不会对应数据库中的表,只有子类会跟数据库中的表对应

2. @OneToMany

2.1. 级联删除

@Entity
@Table(name = "cucd_criteria")
public class CucdCriteria {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String deviceType;

    private String company;

    private String description;

    @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
    @JoinColumn(name = "criteria_id")
    private List<CucdCriteriaBackup> backups;

    @OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
    @JoinColumn(name = "criteria_id")
    private List<CucdCriteriaMetrics> metrics;

    //getter、setter
}

2.2. 使用中间表关联

@Entity
@Table(name = "cucd_criteria_part")
public class CucdCriteriaPart {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    @OneToMany
    @JoinTable(name = "cucd_criteria_part_backup", joinColumns = @JoinColumn(name = "part_id"), inverseJoinColumns = @JoinColumn(name = "backup_id"))
    private List<CucdCriteriaBackup> backups;

    @OneToMany
    @JoinTable(name = "cucd_criteria_part_metrics", joinColumns = @JoinColumn(name = "part_id"), inverseJoinColumns = @JoinColumn(name = "metrics_id"))
    private List<CucdCriteriaMetrics> metrics;

    //getter、setter
}

3. @ElementCollection

两个实体存在一对多关系且多的实体完全属于一的实体时使用,用DDD术语描述就是两个实体属于一个Aggregate并且一的实体是Aggregate Root

@Entity
@Table(name = "cucd_formula")
public class CucdFormula {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Long id;

    private String name;

    @ElementCollection
    @CollectionTable(name = "cucd_formula_device_name", joinColumns = @JoinColumn(name = "formula_id"))
    @Column(name = "device_name")
    private Set<String> deviceNames;

    @ElementCollection
    @CollectionTable(name = "cucd_formula_notify_type", joinColumns = @JoinColumn(name = "formula_id"))
    @Enumerated(EnumType.STRING)
    @Column(name = "notify_type")
    private Set<NotifyType> notifyTypes;                        (1)

    @ElementCollection
    @CollectionTable(name = "cucd_formula_notifier", joinColumns = @JoinColumn(name = "formula_id"))
    private Set<Notifier> notifiers;                            (2)

    //getter、setter

    @Embeddable
    public static class Notifier {

        private Integer userId;

        private Integer orgId;

        //getter、setter
    }
}
  1. cucd_formula_notify_type 表有两列 formula_idnotify_type

  2. cucd_formula_notifier 表有三列 formula_iduser_idorg_id

使用Criteria API根据 @ElementCollection 声明的属性进行模糊查询

public class CucdFormulaService {
    public Page<CucdFormula> list(CucdFormula cucdFormula, Pageable pageable, String deviceName) {
        Specification<CucdFormula> specification = (root, query, criteriaBuilder) -> {
            List<Predicate> predicates = new ArrayList<>();
            if (StringUtils.hasText(cucdFormula.getName())) {
                predicates.add(criteriaBuilder.like(root.get(CucdFormula_.NAME), "%" + cucdFormula.getName() + "%"));
            }
            if (StringUtils.hasText(deviceName)) {
                Subquery<Long> subquery = criteriaBuilder.createQuery(CucdFormula.class).subquery(Long.class);
                Root<CucdFormula> subRoot = subquery.from(CucdFormula.class);
                subquery.select(criteriaBuilder.literal(1L))
                        .where(criteriaBuilder.equal(root.get(CucdFormula_.ID), subRoot.get(CucdFormula_.ID)),
                            criteriaBuilder.like(subRoot.join(CucdFormula_.DEVICE_NAMES), "%" + deviceName + "%"));
                predicates.add(criteriaBuilder.exists(subquery));
            }
            return criteriaBuilder.and(predicates.toArray(new Predicate[0]));
        };
        return cucdFormulaRepository.findAll(specification, pageable);
    }
}

4. 预期外的持久化

问题描述:有如下的 OrderService 以及对应的 OrderController,从 OrderController 调用 orderService.feedback() 方法时参数 orderFeedbackRequest.getProcessTime()null,因此 LocalDateTime.parse 抛出 NullPointerException 进行 catch 块执行,很明显因为发生了异常导致 orderSendRecordRepository.save(orderSendRecord); 没有执行,但最终数据库中 orderSendRecordstatushandler 仍然被修改

问题原因:项目保留了spring boot默认的 spring.jpa.open-in-view=true 的配置,因此 EntityManager 或者叫持久化上下文在整个请求期间都是打开的,因此 orderSendRecord 从未变成游离态,执行过 setStatussetHandler 之后 orderSendRecord 就变脏,最终执行 orderLogRepository.save(orderLog); 时刷新持久化上下文也将变脏的 orderSendRecord 更新到数据库

orderLogRepository.save(orderLog); 会开启事务,事务提交时会调用 entityManager.flush()
@Service
public class OrderService {

	private final Logger logger = LoggerFactory.getLogger(OrderService.class);

	private final OrderSendRecordRepository orderSendRecordRepository;

	private final OrderLogRepository orderLogRepository;

	public OrderService( OrderSendRecordRepository orderSendRecordRepository, OrderLogRepository orderLogRepository) {
		this.orderSendRecordRepository = orderSendRecordRepository;
		this.orderLogRepository = orderLogRepository;
	}


	public OrderFeedbackResponse feedback(OrderFeedbackRequest orderFeedbackRequest) {
		String workOrderNo = orderFeedbackRequest.getWorkOrderNo();
		Optional<OrderSendRecord> orderSendRecordOptional = orderSendRecordRepository.findByBusinessId(workOrderNo);
		if (!orderSendRecordOptional.isPresent()) {
			return OrderFeedbackResponse.failResponse("工单不存在");
		}
		OrderSendRecord orderSendRecord = orderSendRecordOptional.get();
		try {
			if(OrderSendRecordStatus.TO_HANDLE.equals(orderSendRecord.getStatus())) {
				orderSendRecord.setStatus(OrderSendRecordStatus.HANDLED);
				orderSendRecord.setHandler(orderFeedbackRequest.getProcessor());
				orderSendRecord.setHandleTime(LocalDateTime.parse(orderFeedbackRequest.getProcessTime(), DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss")));
				orderSendRecord.setHandleRemark(orderFeedbackRequest.getProcessContent());
				orderSendRecordRepository.save(orderSendRecord);
				saveLog(orderSendRecord.getOrder().getId(), OrderLogType.HANDLE, orderFeedbackRequest.getProcessor(),true, "");
				orderRepository.toComplete(orderSendRecord.getOrder().getId());
			}
			return OrderFeedbackResponse.successResponse();
		} catch (Exception ex) {
			logger.error("fail to feedback order, workOrderNo: " + workOrderNo, ex);
			saveLog(orderSendRecord.getId(), OrderLogType.HANDLE, orderFeedbackRequest.getProcessor(),false, "");
			return OrderFeedbackResponse.failResponse("回调失败");
		}
	}

	private void saveLog(Long orderId, OrderLogType type, String operator, boolean success, String remark) {
		OrderLog orderLog = new OrderLog();
		orderLog.setOrderId(orderId);
		orderLog.setType(type);
		orderLog.setOperator(operator);
		orderLog.setOperateTime(LocalDateTime.now());
		orderLog.setResult(success ? "成功" : "失败");
		orderLog.setRemark(remark);
		orderLogRepository.save(orderLog);
	}

}