Drools - 如何访问 THEN 子句中的对象

Drools - How to access an object in THEN clause

我有一个场景,当输入中的数据针对一个事实具有不同的列,即转置数据,例如

EmployeeID | EmpDept1 | EmpDept2 | EmpDept3 | EmpDept4 | EmpDept5

现在我也有一个查找部门table,例如

DeptId | DeptName

我要进行查找,一种方法是创建 5 条规则,例如:

rule "Lookup dept-1 data"
when
    e: EmployeeData()
    d: DeptData(deptId == $e.EmpDept1)
then
    System.out.println(e.toString());
end

这里的问题是我必须创建 5 个规则(在我的例子中还有更多)。有没有一种方法可以访问 "THEN" 子句中的 DeptData,然后编写 JAVA 查找?

或者如果有其他好的方法,请告诉我。

谢谢!

所以基本上你有一个员工,他们的部门可以列在五个地方中的任何一个。解决这个问题的最好方法是在触发规则之前弄清楚部门所在的位置,然后修改传递给规则的对象。只要它们序列化正常,您就不会限制将哪种对象传递给规则;如果 EmployeeData 当前是来自数据库调用或其他东西的原始结果,请将其中您需要的部分转换为新的 POJO 并将其传递到规则中。

所以对于你的例子,我会做类似的事情:

class Employee {
  private Integer employeeId;
  private Integer departmentId; // assuming the only thing you need out of Department is id

  public static Employee fromEmployeeData(EmployeeData data) {
    Employee employee = new Employee();
    employee.setEmployeeId(data.getEmployeeId());

    if (data.getDept1() != null) {
        employee.setDepartmentId(data.getDept1());
    } else if (...) {} // other else-if omitted for brevity

    return employee;
  }

  // getters and setters here
}

public void invokeRules(EmployeeData data) {
  KieSession kieSession = ...; 

  Employee employee = Employee.fromEmployeeData(data); 

  kieSession.insert( employee ); 
  kieSession.fireAllRules();
}

然后你可以编写关闭部门 id 的规则:

rule "Employee is in department 6"
when
  Employee( departmentId == 6 )
then
  // do something
end

或者,您可以 从 when 子句中调用 Java 方法。假设我们创建了一个名为 EmployeeUtils 的 class,它具有从 EmployeeData 解析部门的方法:

class EmployeeUtils {

  public static Integer resolveDepartment(Employee e) {
    Integer deptId = null;

    if (e.getDept1Id() != null) {
      deptId = e.getDept1Id();
    } else if (...) {} // other else-if omitted for brevity

    return deptId;
  }
}

然后您可以像这样在左侧调用此方法:

rule "Employee in department 6"
when
  $employee: Employee()
  Integer(this == 6) from EmployeeUtils.resolveDepartment($employee)
then
  // do something
end

显然您也可以在右侧调用它:

rule "Example"
when
  $employee: Employee()
then
  System.out.println("Employee is in department " + EmployeeUtils.resolveDepartment($employee));
end

一般 不推荐 从左侧调用 Java 方法,因为这样的调用无法优化.当您像 Employee(department == 6) 那样进行过滤时,Drools 可以对断言进行大量优化以使您的规则触发得更快。但是,如果您正在调用 Java 方法,那对于 Drools 引擎来说就像一个黑匣子;它不知道 Java 方法将要做什么,所以它必须让它按原样评估,而不是做一些工作来让它更快。

此外,通常也不建议将任意数据传递到规则集中。 只需传入您需要的数据 规则即可触发。从另一个数据源转换时,例如。从 SQL 查询结果或从电子表格转换,解析您实际需要的其他数据部分,并将解析的输入对象传递到规则中。

我在一家将数据库结果直接传递到规则中的公司工作。逻辑是代码已编译,因此如果您需要对规则输入对象进行更改,则需要重新编译代码并发布新版本。但是数据库模式和规则可能在编译后的代码之外,因此它们可以在不需要重新编译的情况下进行更改,因此只需提供所有数据即可,而不必为新版本而烦恼。当公司是新公司并且我们传递的数据只是几个连接表时,这很有效;到我离开时,我们正在传递数百个数据表,在内存中总计达数百 KB,尽管规则只需要可能五个表中的几位数据。它既缓慢又浪费。不要走这条路。

像这样,您有一个 Employee 并且有一个 Department 外部数据——加入这两者是在(例如)SQL 查询中最佳完成的事情。这种关系连接实际上通常不需要对其做出任何决定。如果它确实需要决策(例如,如果部门代码在 Dept5 中,那么它的含义与在 Dept2 中的含义不同),那么多个规则 are 实际上是合适的。但是,当它只是一个相同逻辑的问题但可能在 5 个地方时?不要费心按照规则去做;没有任何好处。在 Java 中执行,然后将结果传递到规则中以进行断言。

我使用 "collect"

解决了这个问题
rule "Lookup dept data"
when
    e: EmployeeData()
    d1: ArrayList() from collect (DeptData(deptId == $e.EmpDept1))
    d2: ArrayList() from collect (DeptData(deptId == $e.EmpDept2))
    d3: ArrayList() from collect (DeptData(deptId == $e.EmpDept3))
    d4: ArrayList() from collect (DeptData(deptId == $e.EmpDept4))
    d5: ArrayList() from collect (DeptData(deptId == $e.EmpDept5))
then
    System.out.println("Employee == " + e);
    if (d1.size() > 0) System.out.println("Department 1 == " + d1.get(0));
    if (d2.size() > 0) System.out.println("Department 1 == " + d2.get(0));
    if (d3.size() > 0) System.out.println("Department 1 == " + d3.get(0));
    if (d4.size() > 0) System.out.println("Department 1 == " + d4.get(0));
    if (d5.size() > 0) System.out.println("Department 1 == " + d5.get(0));
end

此处的"collect"功能确保员工记录不重复,然后可以找到各个适用的部门。