I didn't find anywhere whether it's possible to create pageable manually from string, let assume that we have the following service method
public <T> List<T> findAnything(final int page, final int size, final String sort) { // e.g. id,desc&username,asc
final Pageable pageable = new PageRequest(page, size, null);
return null;
}
my question is how can i instantiate an object of
org.springframework.data.domain.Sort
from a given string of format, important note that these parameters are chagned dynamically, so more likely i need a path to the spring parser, in my example im passing null instead the object
id,desc&username,asc
EDIT
A little bit more details I'm looking for a mechanism of how spring converts the 'sort' string(with the rest of default parameters) that's coming to the rest endpoint as a query param to Pageable object
You can do :
private Sort orderBy() {
return new Sort(Sort.Direction.DESC, "ID")
.and(new Sort(Sort.Direction.ASC, "username"));
}
I think this is helpful
Sort class has static nested class Order :
public static class Order{
private final Direction direction;
private final String property;
private final boolean ignoreCase;
private final NullHandling nullHandling;
}
and then you can use :
public static Sort by(List<Order> orders)
where you create your Order from String like simply splitting.
For that purpose I've written something similar to what spring has, i'd be happy if spring exposes SortHandlerMethodArgumentResolver.parseParameterIntoSort for usage outside the package but so far it's not
private Sort parseMultipleSortQueries(final String query) {
final String[] queries = query.split("&");
return parseSortQuery(queries, ",");
}
private Sort parseSortQuery(final String[] query, String delimiter) {
final List<Sort.Order> orders = new ArrayList<>();
for (String q : query) {
if (q == null) {
continue;
}
final String[] parts = q.split(delimiter);
final Sort.Direction direction = parts.length == 0 ? null : Sort.Direction.fromStringOrNull(parts[parts.length - 1]);
for (int i = 0; i < parts.length; i++) {
if (i == parts.length - 1 && direction != null) {
continue;
}
final String property = parts[i];
if (!StringUtils.hasText(property)) {
continue;
}
orders.add(new Sort.Order(direction, property));
}
}
return orders.isEmpty() ? null : new Sort(orders);
}
and here is the test
#Test
public void testParseQuery() {
System.out.println(parseMultipleSortQueries("firstName,asc&lastName,desc")); //firstName: ASC,lastName: DESC
}
Related
I'm implementing server side processing for jQuery datatables. For those of you who are unfamiliar with it, the plugin allows you to sort a column asc/desc, as well as search all columns with a single textbox. Since my list of objects is too large to send to the client, i need to replicate it's sorting functionality thru Java.
This is the object that i'm working with. Each field is a column in the client side table. All fields are either Strings or primitives/wrappers.
public class MyObject{
String id;
String productType;
String productGroup;
double totalSales;
double salesVariance;
int vendorId;
String vendorName;
}
I need to be able to sort by any of the fields, ascending/descending, WITHOUT hard coding a comparator function for each field.
Given a string that represents a field name, how would I implement a generic sorting function?
My current solution is to process the list with Nashorn... :)
Java method that calls Nashorn:
/**
* #param sortBy - field name
* #param sortDirection - asc/desc
*/
public void applyFilteringChanges(List<MyObject> myObjects, String sortBy, String sortDirection) throws Exception{
Invocable invocable = (Invocable) engine;
invocable.invokeFunction("sortObjects", myObjects, sortBy, sortDirection);
}
Nashorn code:
function sortObjects(myObjects, prop, direction) {
var dir = (direction === 'asc') ? 1 : -1;
myObjects.sort(function(a,b){
return compare(a,b,prop) * dir;
})
};
function compare(a,b,prop){
if(a[prop] < b[prop])
return -1;
else if(a[prop] > b[prop])
return 1;
return 0;
}
I also dabbled in Reflection, but it's incomplete at the moment.
public void applyFilteringChanges(List<MyObject> myObjects, String sortBy, String sortDirection) throws Exception{
myObjects.sort((s1,s2)->{
Field field;
try {
field = s1.getClass().getDeclaredField(sortBy);
Class<?> type = field.getType();
if(type.isPrimitive()){
//deal with primitive
}else{
Comparable o1FieldValue = (Comparable) field.get(s1);
Comparable o2FieldValue = (Comparable) field.get(s2);
return o1FieldValue.compareTo(o2FieldValue);
}
} catch (Exception e) {
e.printStackTrace();
}
return 0;
});
}
Both of my approaches feel like hacks, is there a standard way to accomplish this?
You can create a generic Comparator like this :
class MyComparator<T extends MyObject> implements Comparator<T> {
private String field;
public MyComparator(String field) {
this.field = field;
}
#Override
public int compare(T o1, T o2) {
switch (field) {
case "id" :
return o1.id.compareTo(o2.id);
case "productType":
return o1.productType.compareTo(o2.productType);
case "productGroup":
return o1.productGroup.compareTo(o2.productGroup);
//...
}
return 0;
}
}
Use it like this :
public static void main(String[] args)
{
List<MyObject> objects = new ArrayList<>();
Collections.sort(objects, new MyComparator<>("id"));
}
this below class in my database model on Relam object
public class ModelMarketBanners extends RealmObject {
#PrimaryKey
private String id;
private String marketId;
private String imageFileName;
private String title;
}
as far as i know i can get model fields data by class getter such as getId(), but i want to get filed name instead of class getter methods on loop clause, for example using for to show all class fields such as id or marketId, how can i do that?
i want to get all fileds data and if which one isn't empty attach layout with that data, instead of programing multi line to check and attaching that
for example:
for(int i=0; i> model.field_count; i++){
if (model.field.lenght() > 0) Log.v("data is: ", model.field);
}
instead of
SampleModel model = realm.where(SampleModel.class).findfirst();
if(model.getId().lenght() > 0)
Log.v("data is",model.getId());
if(model.getmarketId().lenght() > 0)
Log.v("data is",model.getmarketId());
if(model.getImageFileName().lenght() > 0)
Log.v("data is",model.getImageFileName());
There is a way, using reflection:
for (Field field : ModelMarketBanners.class.getDeclaredFields()) {
for (Method method : ModelMarketBanners.class.getMethods()) {
if ((method.getName().startsWith("get")) && (method.getName().length() == (field.getName().length() + 3))) {
if (method.getName().toLowerCase().endsWith(field.getName().toLowerCase())) {
try {
Object value = method.invoke(model);
if (!TextUtils.isEmpty(String.valueOf(value)) {
Log.v("data is: ", String.valueOf(value));
}
} catch (IllegalAccessException | InvocationTargetException e) {
}
}
}
}
}
I saw the other post, take a look at this using reflection...
public class SomeClass {
private String returnString;
private String id;
private String marketId;
private String imageFileName;
private String title;
// test
public static void main(String[] args) {
List<String> myFields = new ArrayList<>();
Field[] allFields = SomeClass.class.getDeclaredFields();
for (Field field : allFields) {
myFields.add(field.getName());
}
System.out.println(myFields);
}
}
the output will be the fields of the class
[returnString, id, marketId, imageFileName, title]
EDit:
if you need the data in the string variables please take a look at this question/ my answer....
Working with Spring data jpa and specifications, I have a requirement to implement a filter/search feature in spring mvc. The backend receives an object (ReportTemplateBean) which is basically a bean with some fields that represents the filters in the front end.
public class ReportTemplateBean implements Serializable {
private static final long serialVersionUID = -3915391620260021813L;
private Long id;
private String property;
private String city;
private String state;
private String zipCode;
private String propertyStatus;
private String realEstateRep;
//more code
We have the Controller
#RequestMapping(value = "/search", method = RequestMethod.GET)
#ResponseBody
public ReportBean search(#AuthenticationPrincipal ActiveUser activeUser,
#ModelAttribute("templateForm") ReportTemplateBean template,
Pageable pageable) throws GenericException {
LOGGER.info("Pulling report requested");
ReportBean report = reportService.searchProperties(template,
pageable.getPageNumber(), pageable.getPageSize());
return report;
}
The service
#Override
#Transactional(readOnly = true, timeout = 20)
public ReportBean searchProperties(ReportTemplateBean template,
Integer pageNumber, Integer pageSize) throws GenericException,
TransactionTimedOutException {
LOGGER.info("searchProperties({})", template);
try {
// pageNumber = (pageNumber == null ? 0 : pageNumber);
// pageSize = (pageSize == null ? 10 : pageSize);
ReportTemplate t = reportTemplateMapper.beanToEntity(template);
List<PropertyBean> beans = new ArrayList<PropertyBean>();
PropertySpecification spec = new PropertySpecification(t);
Page<Property> properties = propertyRepository.findAll(spec,
new PageRequest(pageNumber, pageSize, Sort.Direction.ASC,
"name"));
And then it builds the query dynamically, but using a long IF chain that I don't like it. This is the Specification.
#SuppressWarnings("unchecked")
#Override
public Predicate toPredicate(Root<Property> root, CriteriaQuery<?> query,
CriteriaBuilder cb) {
Path<String> propertyName = root.get(Property_.name);
Path<String> city = root.get(Property_.city);
Path<String> state = root.get(Property_.state);
Path<String> zipCode = root.get(Property_.zipCode);
final List<Predicate> orPredicates = new ArrayList<Predicate>();
final List<Predicate> andPredicates = new ArrayList<Predicate>();
if (template.getProperty() != null
&& template.getProperty().length() > 0) {
andPredicates.add(cb.equal(propertyName, template.getProperty()));
}
if (template.getCity() != null && template.getCity().length() > 0) {
andPredicates.add(cb.equal(city, template.getCity()));
}
if (template.getState() != null && template.getState().length() > 0) {
andPredicates.add(cb.equal(state, template.getState()));
}
if (template.getZipCode() != null && template.getZipCode().length() > 0) {
andPredicates.add(cb.equal(zipCode, template.getZipCode()));
}
if (template.getRealEstateRep() != null) {
Join<Property, User> pu = null;
if (query.getResultType().getName().equals("java.lang.Long")) {
pu = (Join<Property, User>) root.fetch(Property_.createdBy);
} else {
pu = root.join(Property_.createdBy);
}
Path<Long> userId = pu.get(User_.id);
andPredicates.add(cb.equal(userId, template.getRealEstateRep()));
}
if (template.getProjectType() != null
&& template.getProjectType().length() > 0) {
Join<Property, Project> pp = null;
if (query.getResultType().getName().equals("java.lang.Long")) {
pp = root.join(Property_.projects);
} else {
pp = (Join<Property, Project>) root.fetch(Property_.projects);
}
Path<String> projectType = pp.get(Project_.projectName);
andPredicates.add(cb.equal(projectType, template.getProjectType()));
}
//more IF's
return query.getRestriction();
}
As you can notice the Specification seems ugly and besides that SONAR complains about The Cyclomatic Complexity of this method (which is good).
So question is, How can I refactor the Specification (IF's) to be more OO code?.
Thanks in advance.
UPDATE- I would like to use/implement something like the new feature in Spring Data JPA (Query by Example) It seems that if you pass a bean the ExampleMatcher class will ignore the null value in the bean fields which is almost what I am looking for. Ignore null and empty values.
I write my solution to give you another option, but as i say in the comment i do not used Specification, and i am curious to see if anyone knows another way to do dynamic queries in spring jpa.
You could write your own query with the #Query annotation inside a #Repository interface.
In your case (assuming ReportTemplateBean is your Entity and its primary key is of Long type) it would be like:
#Repository
public interface ReportTemplateRepo extends JpaRepository<ReportTemplateBean, Long>{
#Query("SELECT rb FROM ReportBeanTemplate rb JOIN ExampleTable et WHERE et.idTemplate = rb.id AND (:id is null OR :id = rb.id) AND (:city is null OR :city = rb.city) AND (:state is null OR :state = rb.state)")
public List<ReportTemplateBean> findTemplates(#Param("id") Long id, #Param("city") String city, #Param("state") String state);
}
You can add all the parameters you want, pass it as null when you call the method.
Example of method invocation(in you service class):
#Autowire
ReportTemplateRepo templateRepo;
public void invocation(ReportTemplateBean template){
List<ReportTemplateBean> templateRepo.findTemplates(
template.getId(), template.getCity(), template.getState());
}
This is the only way i found to do this kind of query.
Is it possible to allow multiple #QueryParam keys for a single object/variable in Jersey?
Actual:
#POST
public Something getThings(#QueryParam("customer-number") Integer n) {
...
}
so, if I add ?customer-number=3 after the URL it works.
Expected:
I want to get the behavior above if I add any of the following values:
?customer-number=3
?customerNumber=3
?customerNo=3
Obs:
The QueryParam annotation looks like:
...
public #interface QueryParam {
String value();
}
so, it cannot accept multiple String values (like #Produces).
The approach below allows the user to use multiple keys having the same meaning at the same time (and I want to have an "OR" condition between them):
#POST
public Something getThings(#QueryParam("customer-number") Integer n1,
#QueryParam("customerNumber") Integer n2,
#QueryParam("customerNo") Integer n3) {
...
}
Something like this doesn't work:
#POST
public Something getThings(#QueryParam("customer-number|customerNumber|customerNo") Integer n) {
...
}
How can I do this?
Details:
Jersey 2.22.1
Java 8
To be honest: this is not how webservices are supposed to be designed. You lay down a strict contract that both client and server follow; you define one parameter and that's it.
But of course it would be a perfect world where you have the freedom to dictate what is going to happen. So if you must allow three parameters in, then you'll have to make that the contract. This is one way following approach #2 which I have to provide without being able to test it for goofs:
public Something getThings(#QueryParam("customer-number") Integer n1,
#QueryParam("customerNumber") Integer n2,
#QueryParam("customerNo") Integer n3) throws YourFailureException {
Integer customerNumber = getNonNullValue("Customer number", n1, n2, n3);
// things with stuff
}
private static Integer getNonNullValue(String label, Integer... params) throws YourFailureException {
Integer value = null;
for(Integer choice : params){
if(choice != null){
if(value != null){
// this means there are at least two query parameters passed with a value
throw new YourFailureException("Ambiguous " + label + " parameters");
}
value = choice;
}
}
if(value == null){
throw new YourFailureException("Missing " + label + " parameter");
}
return value;
}
So basically reject any call that does not pass specifically one of the parameters, and let an exception mapper translate the exception you throw into a HTTP response code in the 4xx range of course.
(I made the getNonNullValue() method static is it strikes me as a reusable utility function).
Maybe the simplest and easiest way would be to use a custom #BeanParam:
First define the custom bean merging all the query parameters as:
class MergedIntegerValue {
private final Integer value;
public MergedIntegerValue(
#QueryParam("n1") Integer n1,
#QueryParam("n2") Integer n2,
#QueryParam("n3") Integer n3) {
this.value = n1 != null ? n1
: n2 != null ? n2
: n3 != null ? n3
: null;
// Throw an exception if value == null ?
}
public Integer getValue() {
return value;
}
}
and then use it with #BeanParam in your resource method:
public Something getThings(
#BeanParam MergedIntegerValue n) {
// Use n.getValue() ...
}
Reference: https://jersey.java.net/documentation/latest/user-guide.html#d0e2403
You can create a custom annotation. I won't go in too much about how to do it, you can see this post, or this post. Basically it relies on a different infrastructure than the usual dependency injection with Jersey. You can see this package from the Jersey project. This is where all the injection providers live that handle the #XxxParam injections. If you examine the source code, you will see the the implementations are fairly the same. The two links I provided above follow the same pattern, as well as the code below.
What I did was created a custom annotation
#Target({ElementType.FIELD, ElementType.PARAMETER})
#Retention(RetentionPolicy.RUNTIME)
public #interface VaryingParam {
String value();
#SuppressWarnings("AnnotationAsSuperInterface")
public static class Factory
extends AnnotationLiteral<VaryingParam> implements VaryingParam {
private final String value;
public static VaryingParam create(final String newValue) {
return new Factory(newValue);
}
public Factory(String newValue) {
this.value = newValue;
}
#Override
public String value() {
return this.value;
}
}
}
It may seem odd that I have a factory to create it, but this was required for the implementation of the below code, where I split the value of the String, and end up creating a new annotation instance for each split value.
Here is the ValueFactoryProvider (which, if you've read either of the above articles, you will see that is required for custom method parameter injection). It a large class, only because I put all the required classes into a single class, following the pattern you see in the Jersey project.
public class VaryingParamValueFactoryProvider extends AbstractValueFactoryProvider {
#Inject
public VaryingParamValueFactoryProvider(
final MultivaluedParameterExtractorProvider mpep,
final ServiceLocator locator) {
super(mpep, locator, Parameter.Source.UNKNOWN);
}
#Override
protected Factory<?> createValueFactory(final Parameter parameter) {
VaryingParam annotation = parameter.getAnnotation(VaryingParam.class);
if (annotation == null) {
return null;
}
String value = annotation.value();
if (value == null || value.length() == 0) {
return null;
}
String[] variations = value.split("\\s*\\|\\s*");
return new VaryingParamFactory(variations, parameter);
}
private static Parameter cloneParameter(final Parameter original, final String value) {
Annotation[] annotations = changeVaryingParam(original.getAnnotations(), value);
Parameter clone = Parameter.create(
original.getRawType(),
original.getRawType(),
true,
original.getRawType(),
original.getRawType(),
annotations);
return clone;
}
private static Annotation[] changeVaryingParam(final Annotation[] annos, final String value) {
for (int i = 0; i < annos.length; i++) {
if (annos[i] instanceof VaryingParam) {
annos[i] = VaryingParam.Factory.create(value);
break;
}
}
return annos;
}
private class VaryingParamFactory extends AbstractContainerRequestValueFactory<Object> {
private final String[] variations;
private final Parameter parameter;
private final boolean decode;
private final Class<?> paramType;
private final boolean isList;
private final boolean isSet;
VaryingParamFactory(final String[] variations, final Parameter parameter) {
this.variations = variations;
this.parameter = parameter;
this.decode = !parameter.isEncoded();
this.paramType = parameter.getRawType();
this.isList = paramType == List.class;
this.isSet = paramType == Set.class;
}
#Override
public Object provide() {
MultivaluedParameterExtractor<?> e = null;
try {
Object value = null;
MultivaluedMap<String, String> params
= getContainerRequest().getUriInfo().getQueryParameters(decode);
for (String variant : variations) {
e = get(cloneParameter(parameter, variant));
if (e == null) {
return null;
}
if (isList) {
List list = (List<?>) e.extract(params);
if (value == null) {
value = new ArrayList();
}
((List<?>) value).addAll(list);
} else if (isSet) {
Set set = (Set<?>) e.extract(params);
if (value == null) {
value = new HashSet();
}
((Set<?>) value).addAll(set);
} else {
value = e.extract(params);
if (value != null) {
return value;
}
}
}
return value;
} catch (ExtractorException ex) {
if (e == null) {
throw new ParamException.QueryParamException(ex.getCause(),
parameter.getSourceName(), parameter.getDefaultValue());
} else {
throw new ParamException.QueryParamException(ex.getCause(),
e.getName(), e.getDefaultValueString());
}
}
}
}
private static class Resolver extends ParamInjectionResolver<VaryingParam> {
public Resolver() {
super(VaryingParamValueFactoryProvider.class);
}
}
public static class Binder extends AbstractBinder {
#Override
protected void configure() {
bind(VaryingParamValueFactoryProvider.class)
.to(ValueFactoryProvider.class)
.in(Singleton.class);
bind(VaryingParamValueFactoryProvider.Resolver.class)
.to(new TypeLiteral<InjectionResolver<VaryingParam>>() {
})
.in(Singleton.class);
}
}
}
You will need to register this class' Binder (bottom of class) with Jersey to use it.
What differentiates this class from Jersey QueryParamValueFactoryProvider is that instead of just processing a single String value of the annotation, it splits the value, and tries to extract the values from the query param map. The first value found will be returned. If the parameter is a List or Set, it just continues to keep looking up all the options, and adding them to the list.
For the most part this keeps all the functionality you would expect from an #XxxParam annotation. The only thing that was difficult to implement (so I left out supporting this use case), is multiple parameters, e.g.
#GET
#Path("multiple")
public String getMultipleVariants(#VaryingParam("param-1|param-2|param-3") String value1,
#VaryingParam("param-1|param-2|param-3") String value2) {
return value1 + ":" + value2;
}
I actually don't think it should be that hard to implement, if you really need it, it's just a matter of creating a new MultivaluedMap, removing a value if it is found. This would be implemented in the provide() method of the VaryingParamFactory above. If you need this use case, you could just use a List or Set instead.
See this GitHub Gist (it's rather long) for a complete test case, using Jersey Test Framework. You can see all the use cases I tested in the QueryTestResource, and where I register the Binder with the ResourceConfig in the test configure() method.
I have the next couple of beans:
Address {
String name;
String number;
String zipcode;
String town;
}
MyEntity {
Address address;
String value1;
String value2;
}
I'm trying to do the next Hibernate query:
private final List<String> propertiesDistinct = Arrays.asList("address.name");
private final List<String> properties = Arrays.asList("address.number",
"address.zipcode", "address.town")
ProjectionList projectionList = Projections.projectionList();
if (propertiesDistinct != null) {
ProjectionList projectionListDistinct = Projections.projectionList();
for (String propertyDistinct : propertiesDistinct)
projectionListDistinct.add(Projections.property(propertyDistinct).as(propertyDistinct));
projectionList.add(Projections.distinct(projectionListAgrupar));
}
if (properties != null)
for (String property : properties)
projectionList.add(Projections.property(property).as(property));
criterio.setProjection(projectionList);
// MORE FILTERS ON MyEntity FIELDS
//... criterio.add(Restrinctions...);
// I want to recover the results on my bean MyEntity so I don't have to create a new one
criterio.setResultTransformer(Transformers.aliasToBean(MyEntity.class));
Problem:
Caused by: org.hibernate.PropertyNotFoundException: Could not find setter for address.name on class com.entities.MyEntity
I understand that Hibernate is looking for something like:
public String getAddressName() {} // This should be in MyEntity
Instead of:
public String getName() {} // In my Address bean
Ideas about how can I fix this without creating a new bean?
Thanks!
I wrote a ResultTransformer that can fix your problem. It's name is AliasToBeanNestedResultTransformer, check it out on github.
Code provided in Github works fine but there is change in import for new versions of hibernate. Its as follow.
org.hibernate.property.PropertyAccessor replaced byorg.hibernate.property.access.spi.PropertyAccess
and
org.hibernate.property.PropertyAccessorFactory replaced by org.hibernate.property.access.internal.PropertyAccessStrategyBasicImpl
So you'll have change the code from
PropertyAccessor accessor = PropertyAccessorFactory.getPropertyAccessor("property");
accessor.getSetter(resultClass, (String)subclassToAlias.get(subclass).get(2)).set(root, subObject, null);
to
PropertyAccess propertyAccess = PropertyAccessStrategyBasicImpl.INSTANCE.buildPropertyAccess(resultClass, (String)subclassToAlias.get(subclass).get(2));
propertyAccess.getSetter().set(root, subObject, null);
AliasToBeanNestedResultTransformer does not handle Nested Multi Level DTOs, so I rewrote one that handles n-level dtos.
Hope this helps.
public class AliasToBeanNestedMultiLevelResultTransformer extends AliasedTupleSubsetResultTransformer {
private static final long serialVersionUID = -8047276133980128266L;
public boolean isTransformedValueATupleElement(String[] aliases, int tupleLength) {
return false;
}
private boolean initialized;
private Class<?> resultClass;
private Map<String,Class<?>> clazzMap = new HashMap<>();
private Map<String,Setter> settersMap = new HashMap<>();
public AliasToBeanNestedMultiLevelResultTransformer(Class<?> resultClass) {
this.resultClass = resultClass;
}
public Object transformTuple(Object[] tuples, String[] aliases) {
Map<String,Object> nestedObjectsMap = new HashMap<>();
Object result;
try {
result = resultClass.newInstance();
if (!initialized){
initialized = true;
initialize(aliases);
}
for (int a=0;a<aliases.length;a++){
String alias = aliases[a];
Object tuple = tuples[a];
Object baseObject = result;
int index = alias.lastIndexOf(".");
if(index>0){
String basePath = alias.substring(0, index);
baseObject = nestedObjectsMap.get(basePath);
if (baseObject == null){
baseObject = clazzMap.get(basePath).newInstance();
nestedObjectsMap.put(basePath, baseObject);
}
}
settersMap.get(alias).set(baseObject, tuple,null);
}
for (Entry<String,Object> entry:nestedObjectsMap.entrySet()){
Setter setter = settersMap.get(entry.getKey());
if (entry.getKey().contains(".")){
int index = entry.getKey().lastIndexOf(".");
String basePath = entry.getKey().substring(0, index);
Object obj = nestedObjectsMap.get(basePath);
setter.set(obj, entry.getValue(), null);
}
else{
setter.set(result, entry.getValue(), null);
}
}
}catch ( InstantiationException | IllegalAccessException e) {
throw new HibernateException( "Could not instantiate resultclass: " + resultClass.getName() );
}
return result;
}
private void initialize(String[] aliases) {
PropertyAccessor propertyAccessor = new ChainedPropertyAccessor(
new PropertyAccessor[] {
PropertyAccessorFactory.getPropertyAccessor( resultClass, null ),
PropertyAccessorFactory.getPropertyAccessor( "field" )
}
);
for (int a=0;a<aliases.length;a++){
String alias = aliases[a];
Class<?> baseClass = resultClass;
if (alias.contains(".")){
String[] split = alias.split("\\.");
StringBuffer res = new StringBuffer();
for (int i=0;i<split.length;i++){
if (res.length()>0) res.append(".");
String item = split[i];
res.append(item);
String resString = res.toString();
if (i==split.length-1){
clazzMap.put(resString,baseClass);
settersMap.put(resString, propertyAccessor.getSetter(baseClass, item));
break;
}
Class<?> clazz = clazzMap.get(resString);
if (clazz==null){
clazz = propertyAccessor.getGetter(baseClass,item).getReturnType();
settersMap.put(resString, propertyAccessor.getSetter(baseClass, item));
clazzMap.put(resString,clazz);
}
baseClass = clazz;
}
}
else{
clazzMap.put(alias, resultClass);
settersMap.put(alias, propertyAccessor.getSetter(resultClass, alias));
}
}
}
}
My solution is very basic. It's not as clean as a proper result transformer but it's useful when you just need to do a quick projection for a few properties.
If you get Could not find setter for address.name on class com.entities.MyEntity
It doesn't mean Hibernate is looking for public String getAddressName() {}. Instead it looks for a setter with the impossible "setAddress.name" name.
Instead of .add(Projections.property("address.name"),"address.name")) type a proper setter name as second argument to the .add() method as follows .add(Projections.property("address.name"),"addressName"))
Then, just add a setter on your "MyEntity" root object: "setAddressName".
public void setAddressName(String addressName) {
this.address= (this.address==null) ? new Address() : address;
this.address.setName(addressName);
}
The drawback is that it "dirties" your object with extra methods.
Also posted here.
Try creating an alias like criterio.createAlias("address", "add"); and then edit your properties to be like Arrays.asList("add.number","add.zipcode", "add.town").
Hope this helps.