1

我正在使用 Tapestry5 和 Hibernate。我正在尝试构建一个使用从 URL 生成的动态限制的条件查询。我的 URL 上下文的设计类似于键/值对。

例子

www.mywebsite.com/make/ford/model/focus/year/2009

我解码参数如下

private Map<String, String> queryParameters;
private List<Vehicle> vehicles;

    void onActivate(EventContext context) {
            //Count is 6 - make/ford/model/focus/year/2009
            int count = context.getCount();

            if (count > 0) {
                int i;
                for (i = 0; (i + 1) < count; i += 2) {
                    String name = context.get(String.class, i);
                    String value = context.get(String.class, i + 1);

                    example "make"
                    System.out.println("name " + name);

                    example "ford"
                    System.out.println("value " + value);

                    this.queryParameters.put(name, value);
                }
            }  

            this.vehicles = this.session.createCriteria(Vehicle.class)
...add dynamic restrictions. 
        }

我希望有人可以帮助我弄清楚如何将限制列表动态添加到我的查询中。我确信这已经完成,所以如果有人知道一个帖子,那也会有帮助。谢谢

4

4 回答 4

2

就像另一个答案所说的那样,但这里更详细地说明了。我认为您问题的症结确实是“告诉我如何添加限制”。无论如何,这就是我的解释。

您需要将每个限制解码为自己的字段。

您需要知道每个字段的 Java 实体属性名称。

然后构建这两个东西的 Map,键是已知的静态 Java 实体属性名称,值是 URL 解码数据(可能带有类型转换)。

private Map<String, Object> queryParameters;
private List<Vehicle> vehicles;

void onActivate(EventContext context) {
        //Count is 6 - make/ford/model/focus/year/2009
        int count = context.getCount();

        queryParameters = new HashMap<String,Object>();
        if (count > 0) {
            int i;
            for (i = 0; (i + 1) < count; i += 2) {
                String name = context.get(String.class, i);
                String value = context.get(String.class, i + 1);

                Object sqlValue = value;
                if("foobar".equals(name)) {
                    // sometime you don't want a String type for SQL compasition
                    //  so convert it
                    sqlValue = UtilityClass.doTypeConversionForFoobar(value);
                } else if("search".equals(name) ||
                          "model".equals(name) ||
                          "year".equals(name)) {
                    // no-op this is valid 'name'
                } else if("make".equals(name)) {
                    // this is a suggestion depends on your project conf
                    name = "vehicleMake.name";
                } else {
                    continue;  // ignore values we did not expect
                }
                // FIXME: You should validate all 'name' values 
                // to be valid and/or convert to Java property names here

                System.out.println("name " + name);
                System.out.println("value " + value);

                this.queryParameters.put(name, sqlValue);
            }
        }  

        Criteria crit = this.session.createCriteria(Vehicle.class)
        for(Map.Entry<String,Object> e : this.queryParameters.entrySet()) {
            String n = e.getKey();
            Object v = e.getValue();
            // Sometimes you don't want a direct compare 'Restructions.eq()'
            if("search".equals(n))
                crit.add(Restrictions.like(n, "%" + v + "%"));
            else  // Most of the time you do
                crit.add(Restrictions.eq(n, v));
        }

        this.vehicles = crit.list();  // run query
    }

另请参阅https://docs.jboss.org/hibernate/orm/3.5/reference/en/html/querycriteria.html

有了上述情况,应该没有 SQL 注入的风险,因为“name”和“n”部分应该 100% 地针对已知的良好列表进行验证。"value" 和 "v" 被正确转义,就像使用 SQL 位置占位符'?'。

E&OE

于 2012-09-24T02:14:32.910 回答
1

另一种选择是通过从名称/值对构建对象来创建示例查询:

Vehicle vehicle = new Vehicle();
int count = context.getCount();
int i;
for (i = 0; (i + 1) < count; i += 2) {
  String name = context.get(String.class, i);
  String value = context.get(String.class, i + 1);
  // This will call the setter for the name, passing the value
  // So if name is 'make' and value is 'ford', it will call vehicle.setMake('ford')
  BeantUtils.setProperty(vehicle, name, value);
}

// This is using a Hibernate example query:
vehicles = session.createCriteria(Vehicle.class).add(Example.create(vehicle)).list();

有关更多信息,请参阅BeanUtils.setProperty示例查询

这假设您只允许每个属性一个值,并且查询参数正确映射到属性名称。可能还需要考虑转换问题,但我认为 setProperty 可以处理常见问题。

于 2012-09-24T02:47:05.653 回答
1

我假设您只需遍历参数 Map 并为每对添加一个 Restriction 。

请注意,如果您不小心,这将使您面临 sql 注入攻击。防止这种情况发生的最简单方法是在添加到标准之前根据已知的车辆属性检查密钥。

于 2012-09-24T01:49:03.710 回答
1

如果它们是查询参数,则应将它们视为查询参数而不是路径参数。您的 URL 应类似于:

www.mywebsite.com/vehicles?make=ford&model=focus&year=2009

你的代码应该是这样的:

public class Vehicles {

@ActivationRequestParameter
private String make;

@ActivationRequestParameter
private String model;

@ActivationRequestParameter
private String year;

@Inject
private Session session;

@OnEvent(EventConstants.ACTIVATE)
void activate() {
    Criteria criteria = session.createCriteria(Vehicle.class);

    if (make != null) criteria.add(Restrictions.eq("make", make));
    if (model != null) criteria.add(Restrictions.eq("model", model));
    if (year != null) criteria.add(Restrictions.eq("year", year));

    vehicles = criteria.list();
}

}

假设您使用 Grid 组件来显示车辆,我强烈建议您使用 HibernateGridDataSource 而不是在“激活”事件处理程序中进行查询。

public class Vehicles {

    @ActivationRequestParameter
    private String make;

    @ActivationRequestParameter
    private String model;

    @ActivationRequestParameter
    private String year;

    @Inject
    private Session session;

    @OnEvent(EventConstants.ACTIVATE)
    void activate() {
    }

    public GridDataSource getVehicles() {
        return new HibernateGridDataSource(session, Vehicles.class) {
            @Override
            protected void applyAdditionalConstraints(Criteria criteria) {
                if (make != null) criteria.add(Restrictions.eq("make", make));
                if (model != null) criteria.add(Restrictions.eq("model", model));
                if (year != null) criteria.add(Restrictions.eq("year", year));
            }
        };
    }
}
于 2012-09-25T21:01:15.687 回答