首页 > 解决方案 > 使用 Criteria API 加入实体,关系仅在拥有方定义

问题描述

我有来自两个模块的两个实体。
有一个device-core包含实体的模块Device

@Entity
@Table(name = "devices")
public class Device extends AbstractAuditingEntity implements Serializable
{
    @NotNull
    @EmbeddedId
    private DeviceIdentity identity;

    // other properties, getters, setters, equals, hashCode, toString omitted
}

然后是retail依赖device-core并包含实体的模块POS

@Entity
@Table(name = "pos")
public class POS extends AbstractAuditingEntity implements Serializable
{
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    private Integer id;

    @OneToOne
    @JoinColumns({
            @JoinColumn(name = "device_vendor", referencedColumnName = "vendor"),
            @JoinColumn(name = "device_model", referencedColumnName = "model"),
            @JoinColumn(name = "device_sn", referencedColumnName = "sn")
    })
    private Device device;

    // other properties, getters, setters, equals, hashCode, toString omitted
}

如您所见,在拥有方的实体上POS <-> Device定义了一对一的关系 ( )。但是,由于模块甚至不知道实体存在 ,因此没有提及侧面。POS
POSDevicedevice-corePOS

不幸的是,我无法添加posDeviceusing的关系@OneToOne(mappedBy="device"),因为这会在device-coreand之间产生循环依赖retail,更不用说模块必须在根本不存在模块device-core的环境中工作。retail

我还尝试扩展Device实体并创建ExtendedDevice包含从to@OneToOne()映射的实体,但这也不起作用,因为 Hibernate 将其注册为另一个实体并需要在旁边添加字段(我无法修改)或创建一个新的虚拟该表将仅包含一列(或 3,因为设备 ID 是复合键),我必须将设备 ID 复制到其中。它还会添加另一个完全不必要且毫无意义的连接。这不是可接受的黑客攻击。ExtendedDevicePOSdtypeDeviceExtendedDevice

用例:我需要列出Devices、左连接POS和(可选)在POS.
问题:虽然这可以使用 JQPL 完成,但我还没有找到使用 Criteria API 的方法。
这是一个问题,因为我需要根据请求参数动态添加谓词。
我设法想出的唯一解决方案是非常肮脏的 hack,包括像这样附加 JPQL:

String q = "SELECT d.identity, p.id, p.name, b.id, b.name, c.identity, c.name"
        + " FROM Device d"
        + " LEFT JOIN POS p ON d.identity = p.device" // how to do this using Criteria API?
        + " LEFT JOIN p.branch b"
        + " LEFT JOIN b.company c";

List<String> predicates = new ArrayList<>();
if (criteria.getVendor() != null)
    predicates.add("lower(d.identity.vendor) LIKE lower(concat('%', :vendor, '%'))");
if (criteria.getModel() != null)
    predicates.add("lower(d.identity.model) LIKE lower(concat('%', :model, '%'))");
if (criteria.getSerialNumber() != null)
    predicates.add("lower(d.identity.serialNumber) LIKE lower(concat('%', :serialNumber, '%'))");
if (criteria.getPosName() != null)
    predicates.add("lower(p.name) LIKE lower(concat('%', :posName, '%'))");
if (criteria.getBranchName() != null)
    predicates.add("lower(b.name) LIKE lower(concat('%', :branchName, '%'))");
if (criteria.getCompanyName() != null)
    predicates.add("lower(c.name) LIKE lower(concat('%', :companyName, '%'))");

if (predicates.size() > 0) {
    q += " WHERE " + String.join(" AND ", predicates);
}

TypedQuery<Object[]> emQ = em.createQuery(q, Object[].class);

if (criteria.getVendor() != null)
    emQ.setParameter("vendor", criteria.getVendor());
if (criteria.getModel() != null)
    emQ.setParameter("model", criteria.getModel());
if (criteria.getSerialNumber() != null)
    emQ.setParameter("serialNumber", criteria.getSerialNumber());
if (criteria.getPosName() != null)
    emQ.setParameter("posName", criteria.getPosName());
if (criteria.getBranchName() != null)
    emQ.setParameter("branchName", criteria.getBranchName());
if (criteria.getCompanyName() != null)
    emQ.setParameter("companyName", criteria.getCompanyName());

我讨厌它。你讨厌它。每个人都应该讨厌它。太可怕了。它虽然有效。

现在的问题是:是否有更清洁的方法来实现这个相当标准的用例?
还是 Criteria API 根本不适合用于高度模块化的项目?
我应该考虑其他选择吗?

谢谢你的帮助!

标签: javahibernatejpajpa-2.0

解决方案


推荐阅读