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.
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
}
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.
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
来源:https://stackoverflow.com/questions/7838420/spring-roo-custom-finder