I need to execute a procedure on my sql server database that will return me some fields and I wish to transform this fields directly in a List of my DTO Object that will be returned, but i'm new on spring boot and can't get it to work. I tried to do a Converter class but didnt understand much of how it works e probally did it wrong, here is my code on a way i wish it work:
public interface IMyDtoRepository extends JpaRepository<SomeEntity, Long> {
#Query(value = "EXECUTE MyProcedure :param1, :param2, :param3, :param4, :param5)")
public List<MyDtoObject> execMyProcedure(#Param(value = "param1") Integer param1,
#Param(value = "param2") String param2,
#Param(value = "param3") String param3,
#Param(value = "param4") String param4,
#Param(value = "param5") Integer param5);
}
The DtoObject
public class MyDtoObject{
// My Declared Fields...
public MyDtoObject() {
}
public MyDtoObject(/* My Fields */) {
// Setting fields
}
public MyDtoObject(Object[] objects) {
// Setting fields
}
// Getters n Setters...
I omitted the information that i didn't think it was necessary but i can give more explanation if need it
to map the result on your DtoObject with spring-data-jpa your can use : #SqlResultSetMapping
javadoc here
I have a similar method that I use in my DAL. It uses reflection and generics to convert a datatable to whatever type you pass in. Just pass in the datatable you get as a result of your procedure and you're good to go.
public List<T> ConvertDataToTypeList<T>(System.Data.DataTable DataTable) where T : class, new()
{
try
{
System.Type t_Object_Type = typeof(T);
ICollection<PropertyInfo> p_Properties;
lock (Properties_Dictionary)
{
if (!Properties_Dictionary.TryGetValue(t_Object_Type, out p_Properties))
{
p_Properties = t_Object_Type.GetProperties().Where(property => property.CanWrite).ToList();
Properties_Dictionary.Add(t_Object_Type, p_Properties);
}
}
System.Collections.Generic.List<T> l_List = new List<T>(DataTable.Rows.Count);
foreach (var v_Row in DataTable.AsEnumerable())
{
T o_Object = new T();
foreach (var prop in p_Properties)
{
var propType = Nullable.GetUnderlyingType(prop.PropertyType) ?? prop.PropertyType;
var safeValue = v_Row[prop.Name] == null ? null : Convert.ChangeType(v_Row[prop.Name], propType);
prop.SetValue(o_Object, safeValue, null);
}
l_List.Add(o_Object);
}
return l_List;
}
catch
{
return new List<T>();
}
}
Related
Edit: I was trying to simplify my problem at hand a little, but turns out, it created more confusion instead. Here's the real deal:
I am working with AWS's Java SDK for DynamoDB. Using the DynamoDBMapper class, I am trying to query DynamoDB to retrieve items from a particular table. I have several objects that map to my DynamoDB tables, and I was hoping to have a generic method that could accept the mapped objects, query the table, and return the item result.
Psudo-code:
#DynamoDBTable(tableName="testTable")
public class DBObject {
private String hashKey;
private String attribute1;
#DynamoDBHashKey(attributeName="hashKey")
public String getHashKey() { return this.hashKey; }
public void setHashKey(String hashKey)
#DynamoDBAttribute(attributeName="attribute1")
public String getAttribute1() { return this.attribute1; }
public void setAttribute1(String attribute1) { this.attribute1 = attribute1; }
}
public class DatabaseRetrieval {
public DatabaseRetrieval()
{
DBObject dbObject = new DBObject();
dbObject.setHashKey = "12345";
DBRetrievalAgent agent = new DBRetrievalAgent;
dbObject = agent.retrieveDBObject(dbObject.getClass(), dbObject);
}
}
public class DBRetrievalAgent {
public Object retrieveDBObject(Class<?> classType, Object dbObject)
{
DynamoDBQueryExpression<classType> temp = new DynamoDBQueryExpression<classType>().withHashKeyValues(dbObject);
return this.dynamoDBMapper.query(classType, temp);
}
}
Use a type witness within your method:
public <T> String getResult(Class<T> type) {
List<T> newList = new ArrayList<>();
//other code
}
Try this
ArrayList<T> newList = new ArrayList<>();
You can specify the type as T for your getResult() to make it generic (i.e., accepts any class) as shown below:
public <T> String getResult(T t) {
String result = "";
List<T> newList = new ArrayList<>();
// perform actions
return result;
}
I am using spring and hibernate. I have a class (DTO) with a lot of string member variables. I'm trying to implement search for this class. The user should be able to search by each field. I'm using jackson json mapper to serialize and deserialize objects. Is there anyway to identify the fieldName by using JsonProperty value?
Let this be an example: my DTO
public class SampleDTO{
private String field1;
private String field2;
private String field3;
private String field4;
#JsonProperty("FIELD_1")
public String getField1(){
return field1;
}
#JsonProperty("FIELD_2")
public String getField2(){
return field2;
}
#JsonProperty("FIELD_3")
public String getField3(){
return field3;
}
#JsonProperty("FIELD_4")
public String getField4(){
return field4;
}
}
Let this be my search function
public Set<T> search(String fieldName, String searchKeyword) {
String originalFieldName = someMagicFunction(fieldName);
//if fieldName= "FIELD_1", someMagicFunction should return "field1"
Criteria criteria = session.createCriteria(T.class);
criteria.add(Restrictions.eq(originalFieldName, searchKeyword));
return new HashSet<T>(criteria.list());
}
Any implementation is fine. I'm looking for a good approach to handle cases like this. It feels like finding fields manually involves "too much typing".
You basically want to use reflection. There are two possibilities here when it comes to field lookup:
Value of #JsonProperty annotation
Real name of the field
In the first case you may want to use some additional library to ease the pain when using reflection + annotation, but the crude code would look more less like this:
SampleDTO dto = new SampleDTO();
// setup some values here
Field[] fields = r.getClass().getFields();
for(Field f : fields) {
JsonProperty jsonProperty = f.getDeclaredAnnotation(JsonProperty.class);
if (jsonProperty != null && jsonProperty.value().equals("FIELD_1")) {
return (String) f.get(dto);
}
// throw exception since passed field name is illegal
}
In the second one it would be so much easier:
SampleDTO dto = new SampleDTO();
// setup some values here
String field1Value = (String) r.getClass().getField("field1").get(dto);
In case if anyone is interested, this is how I solved the problem. I added this code to DAO's constructor.
try {
BeanInfo beanInfo = Introspector.getBeanInfo(T.class);
Method[] methods = T.class.getMethods();
PropertyDescriptor[] propertyDescriptors = beanInfo.getPropertyDescriptors();
for(PropertyDescriptor propertyDescriptor: propertyDescriptors) {
//I'm looking for string fields only
if (propertyDescriptor.getPropertyType().equals( String.class)) {
//My annotations are on methods
for(Method method: methods) {
if(propertyDescriptor.getReadMethod().equals(method)) {
JsonProperty jsonProperty = method.getAnnotation(JsonProperty.class);
if (jsonProperty != null) {
//jsonFieldMapping is a Map<String,String>
//will be saving the mapping in the format {"FIELD_1":"field1", "FIELD_2":"field2"}
jsonFieldMapping.put(jsonProperty.value(), propertyDescriptor.getDisplayName());
} else {
logger.debug("jsonProperty is null");
}
}
}
}
}
// just printing out the values identified from class
for(String key: jsonFieldMapping.keySet()) {
logger.debug("key: " + key + "value: " + jsonFieldMapping.get(key));
}
} catch (IntrospectionException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
So, my magic method will be
public String getField(String jsonFieldName){
if (jsonFieldMapping.containsKey(jsonFieldName)) {
return jsonFieldMapping.get(jsonFieldName);
} else {
throw new IllegalArgumentException("searching field not found");
}
}
I haven't tested this code completely. Looks like the values in the logs are correct.
I would like to pass parameter name as a parameter to other method, f.e:
I have class:
public class Foo() {
public Bar bar;
public Bar anotherBar;
public Bar yetAnotherBar;
public void doSomethingWithBar() {
common.doingSomething(
getMostImportantBarParameterName()
);
}
}
And in this class I would to have method:
public String getMostImportantBarParameterName() {
return Foo.bar;
}
but instead of returning value of bar, I would like to get a name of parameter bar, so it should just return "bar".
For now I have to do this that way:
public String getMostImportantBarParameterName() {
return "bar";
}
Why I wanna achieve something like that?
I am trying as much I can to avoid using strings in my code, cause in refactorization process I will bypass (skip) it accidentally.
But if I will have "hard coded" parameters that way, when I will later rename this parameter it will be automatically replaced in all instances by Eclipse IDE (Using LALT+LSHIFT+R)
Also my method: common.doingSomething() use parameter in runtime, So I won't get compilation error, which it makes hard to maintain this method.
I don't write unit test, cause I can't yet.
Please give me some help on this. Thanks
----------------- EDIT ------------------------
Real life usage.
I would like to have method to access database records in generic way.
Common database operation in my application is:
Getting records from TableName where Parameter = SomeValue
So I would like to have generic method for that in generic entity listed below:
#MappedSuperclass
public abstract class GenericModel<T extends GenericModel> {
#Transient protected Class<T> entityClass;
private List<T> getByParameterAndValue(String parameter, String value) {
List<T> entities = new ArrayList<T>();
String sqlString = "SELECT e FROM " + entityClass.getSimpleName() + " e WHERE e."+ parameter + " = :value";
TypedQuery<T> query = JPA.em().createQuery(sqlString, entityClass).setParameter("value", value);
try {
entities = query.getResultList();
} catch (NoResultException e1) {
entities = null;
} catch (Exception e) {
Index.toLog("error","Unsupported error in Generic model class in " + entityClass);
}
return entities;
}
which is extended by real entities f.e.:
public class User extends GenericModel<User> {
public String name;
public String email;
public String date;
public String department;
public List<User> getUsersByDepartments(String dep) {
return getByParameterAndValue("department", dep);
}
}
The problem is that in JPA TypedQuery:
TypedQuery<User> query = em.createQuery("SELECT u FROM User u WHERE u.department = :department", User.class);
return query.setParameter("department", department).getSingleResult();
First of all, I think you should reconsider your approach. Using field names like this (either by reflection or hard coded Strings) is not very robust. In general, reflection should be avoided if possible.
What are you trying to achieve? What will common.doingSomething be doing with the field name?
It might be better to model the importance explicitly with an accessor:
class Foo {
private Bar bar;
private Bar anotherBar;
private Bar yetAnotherBar;
public Bar getMostImportantBar() {
return bar;
}
}
To answer your question about generics. You can either select the field by its index or by its name. Both are not robust, for when you change the field name, the String used to get it via reflection will not change with it, and if you change the order of the fields, the index will be wrong.
Here's how to do it:
Class foo = Foo.class;
Field[] fields = foo.getFields();
// get by index
Field firstField = fields[0];
String firstFieldName = firstField.getName();
// get by name
Field barField = foo.getField("bar");
String barFieldName = barField.getName();
EDIT (after reading updated question):
In any Object Relational Mapping solution there is a boundary where the object-oriented realm ends and the relational realm begins. With your solution you are pulling that boundary a bit further into your code, in order to gain ease of use for your specific model classes and queries. The consequence of that is that you get more 'boiler plate' style code as part of your application (the GenericModel class) and that the boundary becomes more visible (the reference to a field by index or name using reflection). This type of code is generally harder to understand, test and maintain. On the other hand, once you get it right it doesn't change that often (if your assumption about the query type you usually need turns out to be valid).
So I think this is not a ridiculous use case for reflection, even though I myself would probably still stick to JPA and accept the similarity of the queries. With a good JPA framework, expressing these queries does not incur a lot of code.
About the hard-coded field names vs indexes, I advise you to go with the field names because they are easier to understand and debug for your successors. I would make sure the field name is expressed in the model class where the field resides, to make it as clear as possible that the two belong together, similar to the example you gave:
public class User extends GenericModel<User> {
public static final String FIELD_NAME = "name";
public static final String FIELD_EMAIL = "email";
public static final String FIELD_DATE = "date";
public static final String FIELD_DEPARTMENT = "department";
private String name;
private String email;
private String date;
private String department;
// the byXXX naming scheme is a quite common shorthand for lookups
public List<User> byDepartment(String department) {
return getByParameterAndValue(FIELD_DEPARTMENT, department);
}
BTW I think getByParameterAndValue cannot be private (must be at least default). Also I don't think you should initialize List<T> entities = new ArrayList<T>() at the start. You can do that in the catch(Exception e) to avoid unnecessary initialization if the query succeeds or returns no results. An your fields should be private (shown above).
Of course, this approach still results in one lookup method for each field. A different solution is to create a service for this and leave the model objects aenemic (without behavior):
public class DaoService {
public <T extends GenericModel> List<T> get(Class<T> entityClass, String fieldName, String value) {
List<entityClass> entities;
String sqlString = "SELECT e FROM " + entityClass.getSimpleName() + " e WHERE e."+ fieldName+ " = :value";
TypedQuery<T> query = JPA.em().createQuery(sqlString, entityClass).setParameter("value", value);
try {
entities = query.getResultList();
} catch (NoResultException e) {
entities = null;
} catch (Exception e) {
entities = new ArrayList<T>()
}
return entities;
}
}
Usage:
List<User> = daoService.get(User.class, User.FIELD_DEPARTMENT, value);
Here's another (slightly wild) idea I just had. Each model class is also a query template:
public abstract class ModelQuery<T extends ModelQuery> {
// TODO set from constructor
private Class<T> entityClass;
private Field[] allFields = entityClass.getFields();
private List<T> getByTemplate() {
List<Field> queryFields = new ArrayList<Field>();
String sql = selectFieldsAndCreateSql(queryFields);
TypedQuery<T> query = setQueryParameters(queryFields, sql);
return executeQuery(query);
}
private String selectFieldsAndCreateSql(List<Field> queryFields) throws IllegalAccessException {
StringBuilder sql = new StringBuilder();
sql.append("SELECT e FROM ")
.append(entityClass.getSimpleName())
.append("e WHERE ");
for (Field field : allFields) {
if (field.get(this) != null) {
sql.append("e.")
.append(field.getName())
.append(" = :")
.append(field.getName());
// keep track of the fields used in the query
queryFields.add(field);
}
}
return sql.toString();
}
private TypedQuery<T> setQueryParameters(List<Field> queryFields, String sql) throws IllegalAccessException {
TypedQuery<T> query = JPA.em().createQuery(sql, entityClass);
for (Field field : queryFields) {
query.setParameter(field.getName(), field.get(this));
}
return query;
}
private List<T> executeQuery(TypedQuery<T> query) {
List<T> entities;
try {
entities = query.getResultList();
} catch (NoResultException e1) {
entities = null;
} catch (Exception e) {
entities = new ArrayList<T>();
}
return entities;
}
}
Usage:
User userQuery = new User();
userQuery.setDepartment("finance");
List<User> results = userQuery.getByTemplate();
I guess there are more ways to skin this cat. Good luck with finding your optimal solution!
To get private field names
use foo.getDeclaredFields(); instead of foo.getFields();
Here are also you have some minor issue
fields[0] means, the first declared field, in which 0 is again hard coded
If you change the order of declaration then again it could be a trouble for you, which will never get refracted
I would recommend using
1.) The Class.forName() SPI logic where you can inject the expected business logic on the fly.
2.) The Spring DI with interfaces and implementations using auto wiring
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.
What I am trying to achieve is to set a result transformer on a query defined in the following way:
String hqlQueryString = "select o.id as id, o.name as objectName from MyObject";
Class resultClass = MyObject.class;
Query query = session.createQuery(hqlQueryString).setResultTransformer(
new new AliasToBeanResultTransformer(resultClass));
List result = query.list();
MyObject looks like this:
public class MyObject {
private int id;
private String objectName;
public int getId() {
return id;
}
public void setId(int value) {
this.id = value;
}
public String getObjectName() {
return objectName;
}
public void setobjectName(String value) {
this.objectName = value;
}
}
The problem is, that although I have specified id and objectName to be my aliases, the actual query being executed uses different aliases. This causes my AliasToBeanResultTransformer to fail to construct MyObject because the aliases do not match property names.
Is it possible to obtain the aliases of the query generated by hibernate programmatically (I can set them to the alias to bean result tranformer)? I tried using query.getReturnAliases() but it returns the aliases that I have defined in my HQL, not the ones that Hibernate actually uses.
Can I explicitly specify the aliases in a createQuery statement? Currently I am trying not to use criterion for this to work, so I'd appreciate an approach that uses query objects, if such exists.
Update
Although the issue described above is invalid for standard HQL queries (see comments), it is valid when executing a native query. To be specific - native queries seemed to treat all aliases as lowecase strings (despite specific capitalization that might have been introduced in the query). This causes the AliasToBeanResultTransformer to fail when setting the properties, in cases where capitalization matters.
Actually don't need to implement another AliasToBeanResultTransformer , you can use addScalar(String columnAlias, Type type) to explicitly alias the columns of the native SQL:
String nativeSQL = "select o.id as id, o.name as objectName from MyObject";
List<MyObject> resultList = session.createSQLQuery(nativeSQL)
.addScalar("id" ,StandardBasicTypes.INTEGER)
.addScalar("objectName",StandardBasicTypes.STRING)
.setResultTransformer(new AliasToBeanResultTransformer(MyObject.class))
.list();
The transformer will then look for a MyObject class and expect it having the setters setId() and setObjectName() in order to populate the returned values to the MyObject instance
As for native queries, there was no simple solution involved. I had to look into the implementation of the AliasToBeanResultTransformer class and put a fix in there. I resolved the problem by creating a copy of the AliasToBeanResultTransformer class and modified the private initialize method of that class in the following way:
public class CaseInsensitiveAliasToBeanResultTransformer {
private void initialize(String[] aliases) {
this.aliases = new String[ aliases.length ];
setters = new Setter[aliases.length];
for ( int i = 0; i < aliases.length; i++ ) {
String alias = aliases[i];
if (alias != null) {
this.aliases[i] = alias;
setters[i] = CaseInsensitiveSetter.getSetter(resultClass, alias);
}
}
isInitialized = true;
}
}
This code differs mainly in the line CaseInsensitiveSetter.getSetter(resultClass, alias), where I have introduced a CaseInsensitiveSetter class I will describe below. This class implements the Setter interface and allows retrieving the setter method of a class using case-insensitive matching - so this will allow me to bind the lower-cased query aliases to the proper members of my result class. Here is the code of the custom setter (only the important lines are shown for brevity):
public class CaseInsensitiveSetter {
public static Setter getSetter(Class<?> theClass, String propertyName) {
Setter setter;
if (theClass == Object.class || theClass == null) {
setter = null;
} else {
setter = doGetSetter(theClass, propertyName);
if (setter != null) {
if (!ReflectHelper.isPublic(theClass, setter.getMethod())) {
setter.getMethod().setAccessible(true);
}
} else {
setter = doGetSetter(theClass.getSuperclass(), propertyName);
if (setter == null) {
Class<?>[] interfaces = theClass.getInterfaces();
for (int i = 0; setter == null && i < interfaces.length; i++) {
setter = doGetSetter( interfaces[i], propertyName);
}
}
}
if (setter == null) {
throw new PropertyNotFoundException(
"Could not find a setter for property " +
propertyName + " in class " + theClass.getName());
}
}
return setter;
}
// The actual work is done here
private static Setter doGetSetter(Class<?> resultClass, String propertyName) {
Method[] methods = resultClass.getDeclaredMethods();
for (int i = 0; i < methods.length; i++) {
// only carry on if the method has 1 parameter
if ( methods[i].getParameterTypes().length == 1 ) {
String methodName = methods[i].getName();
if (methodName.startsWith("set")) {
String testStdMethod = methodName.substring(3);
if (testStdMethod.equalsIgnoreCase(propertyName)) {
Setter result = new CustomSetter(
resultClass, methods[i], propertyName);
return result;
}
}
}
}
return null;
}
}
The source of this is based on the BaseSetter class that comes with Hibernate, but is changed to support case-insensitive matching. Still, this one, and the original class that Hibernate uses, lacks performance because of the heavy usage of reflection.
Also, keep in mind that if the result class contains different properties with names that would be equal in case-insensitive comparison, then only one of them will be picked by the current code and it might not work as expected.