Spring/roo 自定义查找器

发布于 2024-12-11 04:47:39 字数 3876 浏览 0 评论 0原文

我想创建一个自定义查找器(作为一名 Web 开发老手,但在 Java、Spring 和 Roo 方面完全是新手)。

下面的第一个方法来自我的“资产”视图控制器类。这就是您第一次访问资产管理页面时所发生的情况——它会返回一个视图和一批当前活动的资产。它工作正常:

@RequestMapping("/assets")
public ModelAndView listAssets() {
    ModelAndView mav = new ModelAndView();
    mav.setViewName("admin/asset");
    Collection<com.myapp.model.Asset> assets = com.myapp.model.Asset.findAllAssets() ;
    mav.addObject("assets", assets);    
    return mav;
}

所以现在我想要对该 URL 发出“POST”请求,接受资产搜索表单的提交,其中包含 Asset 对象的几个关键属性的字段。

(现在我正在我的视图控制器中编写该查找器,我知道最终我想将其推送到模型类。现在我只是为了方便起见在这里工作。另外,我知道我不不必给出对象的完全限定名称,我已经将我的控制器新命名为与它正在交谈的模型相同的名称,所以在我解决这个问题之前,我正在解决它。)

我借了一些来自一些 Roo 发现者的代码 I生成,并最终得到:

@RequestMapping(value = "/assets", method = RequestMethod.POST)
public ModelAndView searchAssets(
            @RequestParam("category") String category,
            @RequestParam("year") String year,
            @RequestParam("manufacturer") String manufacturer,
            @RequestParam("drive_type") String driveType,
            @RequestParam("subcategory") String subcategory,
            @RequestParam("serial") String serial,
            @RequestParam("listing_type") String listingType,
            @RequestParam("hours") String hours,
            @RequestParam("model") String model,
            @RequestParam("mileage") String mileage  ) {
    ModelAndView mav = new ModelAndView();
    mav.setViewName("admin/asset");


    EntityManager em = com.myapp.model.Asset.entityManager();
    TypedQuery<Asset> q = em.createQuery("SELECT o FROM Asset AS o ", Asset.class);
    Collection<Asset> assets =  q.getResultList();
    mav.addObject("assets", assets);

    return mav;
}

所以问题是:

1)有没有办法获取我的参数集合,而不是将它们烘焙到方法签名中?因为我有点讨厌那样。

2)有没有办法迭代我的参数并以这种方式生成查询字符串的 WHERE 子句?我不想对这里给我的领域了解太多——而且我需要能够处理我基本上不会拥有所有这些领域。因此,如果有意义的话,我宁愿以迭代方式而不是声明方式构建查询。

您将如何在这里构建 select 语句?

编辑(解决方案):

@madth3 在这里为我指明了正确的方向。这就是我最终得到的结果,我对此感到非常自豪:

public static TypedQuery<Asset> findAssetsByWebRequest( WebRequest request) {

    ArrayList<String> wheres = new ArrayList<String>();

    Iterator<String> i = request.getParameterNames();

    while (i.hasNext()) {
        String fieldname = i.next();
        String value = request.getParameter(fieldname);

        if (value.length() == 0) {
            continue;
        }

        if (fieldname.equals("manufacturer") || fieldname.equals("model") ) {

            value = value.replace('*', '%');
            if (value.charAt(0) != '%') {
                value = "%" + value;
            }
            if (value.charAt(value.length() - 1) != '%') {
                value = value + "%";
            }
            wheres.add(" o." + fieldname + " LIKE '" + value + "'");
        }
        else if (fieldname.contains("min")) {
            fieldname = fieldname.replace("min", "").toLowerCase();
            wheres.add(" o." + fieldname + " >= '" + value + "' ");
        }
        else if (fieldname.contains("max")) {
            fieldname = fieldname.replace("max", "").toLowerCase();
            wheres.add(" o." + fieldname + " <= '" + value + "' ");

        }
        else {
            wheres.add(" o." + fieldname + " = '" + value + "' ");
        }
    }


    String query = "SELECT o FROM Asset AS o ";
    if (wheres.size() > 0) {
        query += " WHERE ";
        for (String clause: wheres) {
            query += clause + " AND "; 
        }
        query += " 1 = 1 ";
    }


    EntityManager em = Asset.entityManager();
    TypedQuery<Asset> q = em.createQuery(query, Asset.class);

    return q;
}

它显然需要参数化以避免 SQL 注入攻击,但很酷的是它(几乎)不需要知道有关所提供的数据的任何信息,即它只会根据给定的字段组装一个查询。它确实需要知道它正在对哪些字段执行什么操作 - 哪些字段是“like”与“equals”,如何处理定义范围的“min”和“max”字段等。但这还不错。

I want to create a custom finder (being a web development veteran but a complete first-timer at Java, Spring, AND Roo).

The first method below is from my "Asset" view controller class. It's what happens on your first hit to the asset administration page--it comes back with a view and a batch of the currently active assets. It works fine:

@RequestMapping("/assets")
public ModelAndView listAssets() {
    ModelAndView mav = new ModelAndView();
    mav.setViewName("admin/asset");
    Collection<com.myapp.model.Asset> assets = com.myapp.model.Asset.findAllAssets() ;
    mav.addObject("assets", assets);    
    return mav;
}

So now I want to have a "POST" request to that URL accept the submission of the asset search form, which contains fields for several of the key properties of the Asset object.

(Right now I'm coding that finder in my view controller, and I know ultimately I'll want to push that down to the model class. For now I'm just working it here for convenience sake. Also, I know I don't have to give the fully qualified name of the object, I've newbishly named my controller the same name as the model it's talking to, so until I sort that out, I'm working around it.)

I've borrowed some code from some Roo finders I generated, and ended up with:

@RequestMapping(value = "/assets", method = RequestMethod.POST)
public ModelAndView searchAssets(
            @RequestParam("category") String category,
            @RequestParam("year") String year,
            @RequestParam("manufacturer") String manufacturer,
            @RequestParam("drive_type") String driveType,
            @RequestParam("subcategory") String subcategory,
            @RequestParam("serial") String serial,
            @RequestParam("listing_type") String listingType,
            @RequestParam("hours") String hours,
            @RequestParam("model") String model,
            @RequestParam("mileage") String mileage  ) {
    ModelAndView mav = new ModelAndView();
    mav.setViewName("admin/asset");


    EntityManager em = com.myapp.model.Asset.entityManager();
    TypedQuery<Asset> q = em.createQuery("SELECT o FROM Asset AS o ", Asset.class);
    Collection<Asset> assets =  q.getResultList();
    mav.addObject("assets", assets);

    return mav;
}

So the questions are:

1) Is there a way to get a collection of my parameters, rather than baking them into the method signature? Because I sort of hate that.

2) Is there a way to iterate over my parameters and generate the WHERE clause of my query string that way? I'd like to not have to know much about the fields I'm being given here--and I need to be able to handle that I mostly won't have all of them. So I'd prefer to just build my query iteratively rather than declaratively, if that makes sense.

How would you go about building the select statement here?

EDIT (Solution):

@madth3 pointed me in the right direction here. Here's what I ended up with, which I'm pretty proud of:

public static TypedQuery<Asset> findAssetsByWebRequest( WebRequest request) {

    ArrayList<String> wheres = new ArrayList<String>();

    Iterator<String> i = request.getParameterNames();

    while (i.hasNext()) {
        String fieldname = i.next();
        String value = request.getParameter(fieldname);

        if (value.length() == 0) {
            continue;
        }

        if (fieldname.equals("manufacturer") || fieldname.equals("model") ) {

            value = value.replace('*', '%');
            if (value.charAt(0) != '%') {
                value = "%" + value;
            }
            if (value.charAt(value.length() - 1) != '%') {
                value = value + "%";
            }
            wheres.add(" o." + fieldname + " LIKE '" + value + "'");
        }
        else if (fieldname.contains("min")) {
            fieldname = fieldname.replace("min", "").toLowerCase();
            wheres.add(" o." + fieldname + " >= '" + value + "' ");
        }
        else if (fieldname.contains("max")) {
            fieldname = fieldname.replace("max", "").toLowerCase();
            wheres.add(" o." + fieldname + " <= '" + value + "' ");

        }
        else {
            wheres.add(" o." + fieldname + " = '" + value + "' ");
        }
    }


    String query = "SELECT o FROM Asset AS o ";
    if (wheres.size() > 0) {
        query += " WHERE ";
        for (String clause: wheres) {
            query += clause + " AND "; 
        }
        query += " 1 = 1 ";
    }


    EntityManager em = Asset.entityManager();
    TypedQuery<Asset> q = em.createQuery(query, Asset.class);

    return q;
}

It obviously needs to be parameterized to avoid SQL injection attack, but it's cool that it (nearly) doesn't have to know anything about the data being given it, that it'll just assemble a query from the fields it was given. It does need to know which fields it's doing what with--which fields are "like" versus "equals", how to handle "min" and "max" fields that define a range, etc. But that's not so bad.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(3

﹉夏雨初晴づ 2024-12-18 04:47:39

嗯,还是走老路吧……
Java 中的 Servlet 标准定义了对象 Request,其中有带参数的 Map(哈希、字典),因此您可以像这样访问它们

public ModelAndView searchAssets(HttpRequest request) {
    StringBuilder builder = new StringBuilder();
    for (String param : request.getParameterNames()) {
        value = request.getParameter(param);
        builder.append(param);
        builder.append("="); // or LIKE
        builder.append("\'" + value "\'");
        builder.append(" AND ");
    }
    // deleting the last " AND "
    builder.delete(builder.length-5, builder.length);
    // In the entity create a method that executes a query with the string
    // If the parameter names are column names then you'd have to use a nativeQuery
    // You'd have to look it up in JPA
   List<Asset> list = Asset.search(builder.toString());
   // put the list in the ModelAndView
}

Well, going the old fashioned way...
The Servlets standard in Java defines object Request where there is Map (hash, dictionary) with the parameters, so you could access them something like

public ModelAndView searchAssets(HttpRequest request) {
    StringBuilder builder = new StringBuilder();
    for (String param : request.getParameterNames()) {
        value = request.getParameter(param);
        builder.append(param);
        builder.append("="); // or LIKE
        builder.append("\'" + value "\'");
        builder.append(" AND ");
    }
    // deleting the last " AND "
    builder.delete(builder.length-5, builder.length);
    // In the entity create a method that executes a query with the string
    // If the parameter names are column names then you'd have to use a nativeQuery
    // You'd have to look it up in JPA
   List<Asset> list = Asset.search(builder.toString());
   // put the list in the ModelAndView
}
游魂 2024-12-18 04:47:39

我发表了评论,但我更喜欢在答案中解释我的方法:

我认为最好创建一个自定义查找器,如下 Raloh 所说(使用 Roo 生成的代码),使用 POJO 作为参数而不是 WebRequest,以免与 MVC 具有依赖关系。

尝试创建一个 POJO(例如 AssetFilter),其中包含要在查找器中使用的属性。

public static TypedQuery<Asset> findAssetsByFilter(AssetFilter filter) {
    If (filter.getManufacturer()!=null) where.append(" AND ...")
}

控制器将使用请求创建 POJO 过滤器,您可以在任何地方使用此查找器,甚至可以为其创建集成测试。

I made a comment, but I prefer to explain my approach in an answer:

I think it's better to create a custom finder as Raloh said below (using the code generated by Roo) using a POJO as parameter instead of WebRequest, in order to not have dependencies with the MVC.

Try to create a POJO, AssetFilter, for instance, containing the attributes you want to use in the finder.

public static TypedQuery<Asset> findAssetsByFilter(AssetFilter filter) {
    If (filter.getManufacturer()!=null) where.append(" AND ...")
}

The Controller will create the POJO filter with the Request, and you can use this finder everywhere, you can even create an integration test for it.

︶ ̄淡然 2024-12-18 04:47:39

Spring roo 可以为您生成查找器:

输入:

finder list --class ~.domain.MyClass   
finder add --finderName findMyClassByWhatEver

finder list 将列出具有一个属性的所有查找器,您可以使用可选参数设置属性的数量

Spring roo can generate the finders for you:

type:

finder list --class ~.domain.MyClass   
finder add --finderName findMyClassByWhatEver

finder list will list all finder with one attribute, you can set the number of attributes with an optional parameter

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文