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.
Related
I have used Lombok in my code to automatically generate getter and setter code. I want to add other personal annotations and use it.
For example, I want to add an #Exist method which verifies the existence of a key in a list:
#Getter #Setter
public class User {
private String name;
private List<Integer> keys;
public boolean existKeys(Integer key) {
boolean exist = keys.contains(key);
return exist;
}
}
After creating the annotation, I would do something like:
#Getter #Setter
public class User {
private String name;
#Exist
private List<Integer> keys;
}
General Considerations
If you are already using Lombok, you can add custom Lombok transformation annotation and handler.
Define Exists annotation with #Target(FIELD) and #Retention(SOURCE)
Create a handler
#ProviderFor(JavacAnnotationHandler.class)
public class HandleExists extends JavacAnnotationHandler<Exists>{ ...`
to process your annotation. Handler class package must start with the lombok. prefix. If you need to support Eclipse, etc. in addition to javac, you'll need to write more handlers extending appropriate framework classes.
In the handler override/implement the handle() method to generate the required code through AST manipulation.
You can take as a sample the #Getter implementation:
Annotation:
Getter.java
Handler:
HandleGetter.java
You can also look into sources of other annotations and handlers to see how to generate particular code.
You'll need to add dependencies on lombok, JDK tools.jar.
Some resources:
The lombok-pg project with a source for a bunch of custom lombok annotations, in particular FluentSetter.java, HandleFluentSetter.java / FluentSetterHandler.java
An overview of a custom transformation
Simple annotation example with explanations.
Note, there are some points to consider here
This is a bunch of non-trivial code to write and maintain. If you plan to use annotation 5-6 times it is just not worth it.
You may need to change your annotation processor implementation with lombok upgrades.
The hole in compiler that lombok relies on also may be closed (then the whole Lombok project will change dramatically or cease to exist; in this case you'll have a more serious problem anyway if you use Lombok extensively, even if just for #Getter).
A more complex alternative without Lombok is to use standard annotation processing for code generation but, AFAIK, you can't change original classes and must generate/use classes that extend them (unless you'll exploit the same back-door as Lombok or resort to a code manipulation like CGLib or ASM).
Lombok Example
Below is some working code to create custom Lombok annotation that I've called #Contains.
It is javac implementation only, no Eclipse, etc. I guess it will be not hard to create a similar handler for Eclipse or other IDE.
It will generate fieldNameContains() member method which is delegated to the fieldName.contains().
Note, the code is just quick and dirty (but working) sample. For production grade annotation, you will need to handle many boundary conditions, check correct types, handle Lombok configuration and so on, as it can be observed in lombok or lombok-pg library sources.
Sample usage
SomeEnity.java
#Getter
#Setter
public class SomeEntity {
#NonNull
#Contains
private Collection<String> fieldOne = new ArrayList<>();
#NonNull
#Contains
private Collection<String> fieldTwo = new ArrayList<>();
}
SomeEntityTest.java
public class SomeEntityTest {
#Test
public void test() {
SomeEntity entity = new SomeEntity();
Collection<String> test1 = Arrays.asList(new String[] { "1", "2" });
entity.setFieldOne(test1);
assertSame(test1, entity.getFieldOne());
Collection<String> test2 = new HashSet<String>(Arrays.asList(new String[] { "3", "4" }));
entity.setFieldTwo(test2);
assertSame(test2, entity.getFieldTwo());
assertTrue(entity.fieldOneContains("1"));
assertTrue(entity.fieldOneContains("2"));
assertFalse(entity.fieldOneContains("3"));
assertFalse(entity.fieldOneContains("4"));
assertFalse(entity.fieldTwoContains("1"));
assertFalse(entity.fieldTwoContains("2"));
assertTrue(entity.fieldTwoContains("3"));
assertTrue(entity.fieldTwoContains("4"));
try {
entity.setFieldOne(null);
fail("exception expected");
} catch (Exception ex) {
}
try {
entity.setFieldTwo(null);
fail("exception expected");
} catch (Exception ex) {
}
}
}
Annotation Implementaiton
Contains.java
#Target({ElementType.FIELD})
#Retention(RetentionPolicy.SOURCE)
public #interface Contains {
Class<?>[] types() default {};
Class<?>[] excludes() default {};
}
HandleContains.java
#ProviderFor(JavacAnnotationHandler.class)
#HandlerPriority(65536)
#ResolutionResetNeeded
public class HandleContains extends JavacAnnotationHandler<Contains> {
#Override
public void handle(AnnotationValues<Contains> annotation, JCAnnotation ast, JavacNode annotationNode) {
try {
JavacNode node = annotationNode.up();
if (node.getKind() != Kind.FIELD) {
annotationNode.addError("#Contains is allowed only on fields");
return;
}
Name delegateName = annotationNode.toName(node.getName());
JavacResolution reso = new JavacResolution(annotationNode.getContext());
JCTree member = node.get();
if (member.type == null) {
reso.resolveClassMember(node);
}
Type delegateType = member.type;
if (delegateType instanceof ClassType) {
ClassType ct = (ClassType) delegateType;
//TODO validate that this field is a collection type
// if(!Collection)
// annotationNode.addError("#Contains can only be used on collections");
final String methodName = "contains";
MethodSig methodSig = getMethodBinding(methodName, ct, annotationNode.getTypesUtil());
if (methodSig == null) throw new Exception("no method " + methodName + " in " + ct.tsym.name);
JCMethodDecl methodDecl = createDelegateMethod(methodSig, annotationNode, delegateName);
injectMethod(node.up(), methodDecl);
} else {
annotationNode.addError("#Contains can only use concrete class types");
return;
}
} catch (Exception ex) {
//ex.printStackTrace();
annotationNode.addError("#Contains unexpected error: " + ex.getMessage());
}
}
public JCMethodDecl createDelegateMethod(MethodSig sig, JavacNode annotation, Name delegateName) throws TypeNotConvertibleException {
JavacTreeMaker maker = annotation.getTreeMaker();
com.sun.tools.javac.util.List<JCAnnotation> annotations;
if (sig.isDeprecated) {
annotations = com.sun.tools.javac.util.List.of(maker.Annotation(genJavaLangTypeRef(annotation, "Deprecated"), com.sun.tools.javac.util.List.<JCExpression>nil()));
} else {
annotations = com.sun.tools.javac.util.List.nil();
}
JCModifiers mods = maker.Modifiers(PUBLIC, annotations);
JCExpression returnType = JavacResolution.typeToJCTree((Type) sig.type.getReturnType(), annotation.getAst(), true);
boolean useReturn = sig.type.getReturnType().getKind() != TypeKind.VOID;
ListBuffer<JCVariableDecl> params = sig.type.getParameterTypes().isEmpty() ? null : new ListBuffer<JCVariableDecl>();
ListBuffer<JCExpression> args = sig.type.getParameterTypes().isEmpty() ? null : new ListBuffer<JCExpression>();
ListBuffer<JCExpression> thrown = sig.type.getThrownTypes().isEmpty() ? null : new ListBuffer<JCExpression>();
ListBuffer<JCTypeParameter> typeParams = sig.type.getTypeVariables().isEmpty() ? null : new ListBuffer<JCTypeParameter>();
ListBuffer<JCExpression> typeArgs = sig.type.getTypeVariables().isEmpty() ? null : new ListBuffer<JCExpression>();
Types types = Types.instance(annotation.getContext());
for (TypeMirror param : sig.type.getTypeVariables()) {
Name name = ((TypeVar) param).tsym.name;
ListBuffer<JCExpression> bounds = new ListBuffer<JCExpression>();
for (Type type : types.getBounds((TypeVar) param)) {
bounds.append(JavacResolution.typeToJCTree(type, annotation.getAst(), true));
}
typeParams.append(maker.TypeParameter(name, bounds.toList()));
typeArgs.append(maker.Ident(name));
}
for (TypeMirror ex : sig.type.getThrownTypes()) {
thrown.append(JavacResolution.typeToJCTree((Type) ex, annotation.getAst(), true));
}
int idx = 0;
String[] paramNames = sig.getParameterNames();
boolean varargs = sig.elem.isVarArgs();
for (TypeMirror param : sig.type.getParameterTypes()) {
long flags = JavacHandlerUtil.addFinalIfNeeded(Flags.PARAMETER, annotation.getContext());
JCModifiers paramMods = maker.Modifiers(flags);
Name name = annotation.toName(paramNames[idx++]);
if (varargs && idx == paramNames.length) {
paramMods.flags |= VARARGS;
}
params.append(maker.VarDef(paramMods, name, JavacResolution.typeToJCTree((Type) param, annotation.getAst(), true), null));
args.append(maker.Ident(name));
}
JCExpression accessor = maker.Select(maker.Ident(annotation.toName("this")), delegateName);
JCExpression delegateCall = maker.Apply(toList(typeArgs), maker.Select(accessor, sig.name), toList(args));
JCStatement body = useReturn ? maker.Return(delegateCall) : maker.Exec(delegateCall);
JCBlock bodyBlock = maker.Block(0, com.sun.tools.javac.util.List.of(body));
StringBuilder generatedMethodName = new StringBuilder(delegateName);
generatedMethodName.append(sig.name.toString());
generatedMethodName.setCharAt(delegateName.length(), Character.toUpperCase(generatedMethodName.charAt(delegateName.length())));
return recursiveSetGeneratedBy(maker.MethodDef(mods, annotation.toName(generatedMethodName.toString()), returnType, toList(typeParams), toList(params), toList(thrown), bodyBlock, null), annotation.get(), annotation.getContext());
}
public static <T> com.sun.tools.javac.util.List<T> toList(ListBuffer<T> collection) {
return collection == null ? com.sun.tools.javac.util.List.<T>nil() : collection.toList();
}
public static class MethodSig {
final Name name;
final ExecutableType type;
final boolean isDeprecated;
final ExecutableElement elem;
MethodSig(Name name, ExecutableType type, boolean isDeprecated, ExecutableElement elem) {
this.name = name;
this.type = type;
this.isDeprecated = isDeprecated;
this.elem = elem;
}
String[] getParameterNames() {
List<? extends VariableElement> paramList = elem.getParameters();
String[] paramNames = new String[paramList.size()];
for (int i = 0; i < paramNames.length; i++) {
paramNames[i] = paramList.get(i).getSimpleName().toString();
}
return paramNames;
}
#Override public String toString() {
return (isDeprecated ? "#Deprecated " : "") + name + " " + type;
}
}
public MethodSig getMethodBinding(String name, ClassType ct, JavacTypes types) {
MethodSig result = null;
TypeSymbol tsym = ct.asElement();
if (tsym == null) throw new IllegalArgumentException("no class");
for (Symbol member : tsym.getEnclosedElements()) {
if (member.getKind() != ElementKind.METHOD || !name.equals(member.name.toString())) {
continue;
}
if (member.isStatic()) continue;
if (member.isConstructor()) continue;
ExecutableElement exElem = (ExecutableElement) member;
if (!exElem.getModifiers().contains(Modifier.PUBLIC)) continue;
ExecutableType methodType = (ExecutableType) types.asMemberOf(ct, member);
boolean isDeprecated = (member.flags() & DEPRECATED) != 0;
result = new MethodSig(member.name, methodType, isDeprecated, exElem);
}
if (result == null) {
if (ct.supertype_field instanceof ClassType) {
result = getMethodBinding(name, (ClassType) ct.supertype_field, types);
}
if (result == null) {
if (ct.interfaces_field != null) {
for (Type iface : ct.interfaces_field) {
if (iface instanceof ClassType) {
result = getMethodBinding(name, (ClassType) iface, types);
if (result != null) {
break;
}
}
}
}
}
}
return result;
}
}
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>();
}
}
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 need to compare dozens of fields in two objects (instances of the same class), and do some logging and updating in case there are differences. Meta code could look something like this:
if (a.getfield1 != b.getfield1)
log(a.getfield1 is different than b.getfield1)
b.field1 = a.field1
if (a.getfield2!= b.getfield2)
log(a.getfield2 is different than b.getfield2)
b.field2 = a.field2
...
if (a.getfieldn!= b.getfieldn)
log(a.getfieldn is different than b.getfieldn)
b.fieldn = a.fieldn
The code with all the comparisons is very terse, and I would like to somehow make it more compact. It would be nice if I could have a method which would take as a parameter method calls to setter and getter, and call this for all fields, but unfortunately this is not possible with java.
I have come up with three options, each which their own drawbacks.
1. Use reflection API to find out getters and setters
Ugly and could cause run time errors in case names of fields change
2. Change fields to public and manipulate them directly without using getters and setters
Ugly as well and would expose implementation of the class to external world
3. Have the containing class (entity) do the comparison, update changed fields and return log message
Entity should not take part in business logic
All fields are String type, and I can modify code of the class owning the fields if required.
EDIT: There are some fields in the class which must not be compared.
Use Annotations.
If you mark the fields that you need to compare (no matter if they are private, you still don't lose the encapsulation, and then get those fields and compare them. It could be as follows:
In the Class that need to be compared:
#ComparableField
private String field1;
#ComparableField
private String field2;
private String field_nocomparable;
And in the external class:
public <T> void compare(T t, T t2) throws IllegalArgumentException,
IllegalAccessException {
Field[] fields = t.getClass().getDeclaredFields();
if (fields != null) {
for (Field field : fields) {
if (field.isAnnotationPresent(ComparableField.class)) {
field.setAccessible(true);
if ( (field.get(t)).equals(field.get(t2)) )
System.out.println("equals");
field.setAccessible(false);
}
}
}
}
The code is not tested, but let me know if helps.
The JavaBeans API is intended to help with introspection. It has been around in one form or another since Java version 1.2 and has been pretty usable since version 1.4.
Demo code that compares a list of properties in two beans:
public static void compareBeans(PrintStream log,
Object bean1, Object bean2, String... propertyNames)
throws IntrospectionException,
IllegalAccessException, InvocationTargetException {
Set<String> names = new HashSet<String>(Arrays
.asList(propertyNames));
BeanInfo beanInfo = Introspector.getBeanInfo(bean1
.getClass());
for (PropertyDescriptor prop : beanInfo
.getPropertyDescriptors()) {
if (names.remove(prop.getName())) {
Method getter = prop.getReadMethod();
Object value1 = getter.invoke(bean1);
Object value2 = getter.invoke(bean2);
if (value1 == value2
|| (value1 != null && value1.equals(value2))) {
continue;
}
log.format("%s: %s is different than %s%n", prop
.getName(), "" + value1, "" + value2);
Method setter = prop.getWriteMethod();
setter.invoke(bean2, value2);
}
}
if (names.size() > 0) {
throw new IllegalArgumentException("" + names);
}
}
Sample invocation:
compareBeans(System.out, bean1, bean2, "foo", "bar");
If you go the annotations route, consider dumping reflection and generating the comparison code with a compile-time annotation processor or some other code generator.
I would go for option 1, but I would use getClass().getDeclaredFields() to access the fields instead of using the names.
public void compareAndUpdate(MyClass other) throws IllegalAccessException {
for (Field field : getClass().getDeclaredFields()) {
if (field.getType() == String.class) {
Object thisValue = field.get(this);
Object otherValue = field.get(other);
// if necessary check for null
if (!thisValue.equals(otherValue)) {
log(field.getName() + ": " + thisValue + " <> " + otherValue);
field.set(other, thisValue);
}
}
}
}
There are some restrictions here (if I'm right):
The compare method has to be implemented in the same class (in my opinion it should - regardless of its implementation) not in an external one.
Just the fields from this class are used, not the one's from a superclass.
Handling of IllegalAccessException necessary (I just throw it in the example above).
This is probably not too nice either, but it's far less evil (IMHO) than either of the two alternatives you've proposed.
How about providing a single getter/setter pair that takes a numeric index field and then have getter/setter dereference the index field to the relevant member variable?
i.e.:
public class MyClass {
public void setMember(int index, String value) {
switch (index) {
...
}
}
public String getMember(int index) {
...
}
static public String getMemberName(int index) {
...
}
}
And then in your external class:
public void compareAndUpdate(MyClass a, MyClass b) {
for (int i = 0; i < a.getMemberCount(); ++i) {
String sa = a.getMember();
String sb = b.getMember();
if (!sa.equals(sb)) {
Log.v("compare", a.getMemberName(i));
b.setMember(i, sa);
}
}
}
This at least allows you to keep all of the important logic in the class that's being examined.
While option 1 may be ugly, it will get the job done. Option 2 is even uglier, and opens your code to vulnerabilities you can't imagine. Even if you eventually rule out option 1, I pray you keep your existing code and not go for option 2.
Having said this, you can use reflection to get a list of the field names of the class, if you don't want to pass this as a static list to the method. Assuming you want to compare all fields, you can then dynamically create the comparisons, in a loop.
If this isn't the case, and the strings you compare are only some of the fields, you can examine the fields further and isolate only those that are of type String, and then proceed to compare.
Hope this helps,
Yuval =8-)
since
All fields are String type, and I can modify code of the class owning the fields if required.
you could try this class:
public class BigEntity {
private final Map<String, String> data;
public LongEntity() {
data = new HashMap<String, String>();
}
public String getFIELD1() {
return data.get(FIELD1);
}
public String getFIELD2() {
return data.get(FIELD2);
}
/* blah blah */
public void cloneAndLogDiffs(BigEntity other) {
for (String field : fields) {
String a = this.get(field);
String b = other.get(field);
if (!a.equals(b)) {
System.out.println("diff " + field);
other.set(field, this.get(field));
}
}
}
private String get(String field) {
String value = data.get(field);
if (value == null) {
value = "";
}
return value;
}
private void set(String field, String value) {
data.put(field, value);
}
#Override
public String toString() {
return data.toString();
}
magic code:
private static final String FIELD1 = "field1";
private static final String FIELD2 = "field2";
private static final String FIELD3 = "field3";
private static final String FIELD4 = "field4";
private static final String FIELDN = "fieldN";
private static final List<String> fields;
static {
fields = new LinkedList<String>();
for (Field field : LongEntity.class.getDeclaredFields()) {
if (field.getType() != String.class) {
continue;
}
if (!Modifier.isStatic(field.getModifiers())) {
continue;
}
fields.add(field.getName().toLowerCase());
}
}
this class has several advantages:
reflects once, at class loading
it is very simply adding new fields, just add new static field (a better solution here
is using Annotations: in the case you care using reflection works also java 1.4)
you could refactor this class in an abstract class, all derived class just get both
data and cloneAndLogDiffs()
the external interface is typesafe (you could also easily impose immutability)
no setAccessible calls: this method is problematic sometimes
A broad thought:
Create a new class whose object takes the following parameters: the first class to compare, the second class to compare, and a lists of getter & setter method names for the objects, where only methods of interest are included.
You can query with reflection the object's class, and from that its available methods. Assuming each getter method in the parameter list is included in the available methods for the class, you should be able to call the method to get the value for comparison.
Roughly sketched out something like (apologies if it isn't super-perfect... not my primary language):
public class MyComparator
{
//NOTE: Class a is the one that will get the value if different
//NOTE: getters and setters arrays must correspond exactly in this example
public static void CompareMyStuff(Object a, Object b, String[] getters, String[] setters)
{
Class a_class = a.getClass();
Class b_class = b.getClass();
//the GetNamesFrom... static methods are defined elsewhere in this class
String[] a_method_names = GetNamesFromMethods(a_class.getMethods());
String[] b_method_names = GetNamesFromMethods(b_class.getMethods());
String[] a_field_names = GetNamesFromFields(a_class.getFields());
//for relative brevity...
Class[] empty_class_arr = new Class[] {};
Object[] empty_obj_arr = new Object[] {};
for (int i = 0; i < getters.length; i++)
{
String getter_name = getter[i];
String setter_name = setter[i];
//NOTE: the ArrayContainsString static method defined elsewhere...
//ensure all matches up well...
if (ArrayContainsString(a_method_names, getter_name) &&
ArrayContainsString(b_method_names, getter_name) &&
ArrayContainsString(a_field_names, setter_name)
{
//get the values from the getter methods
String val_a = a_class.getMethod(getter_name, empty_class_arr).invoke(a, empty_obj_arr);
String val_b = b_class.getMethod(getter_name, empty_class_arr).invoke(b, empty_obj_arr);
if (val_a != val_b)
{
//LOG HERE
//set the value
a_class.getField(setter_name).set(a, val_b);
}
}
else
{
//do something here - bad names for getters and/or setters
}
}
}
}
You say you presently have getters and setters for all these fields? Okay, then change the underlying data from a bunch of individual fields to an array. Change all the getters and setters to access the array. I'd create constant tags for the indexes rather than using numbers for long-term maintainability. Also create a parallel array of flags indicating which fields should be processed. Then create a generic getter/setter pair that use an index, as well as a getter for the compare flag. Something like this:
public class SomeClass
{
final static int NUM_VALUES=3;
final static int FOO=0, BAR=1, PLUGH=2;
String[] values=new String[NUM_VALUES];
static boolean[] wantCompared={true, false, true};
public String getFoo()
{
return values[FOO];
}
public void setFoo(String foo)
{
values[FOO]=foo;
}
... etc ...
public int getValueCount()
{
return NUM_VALUES;
}
public String getValue(int x)
{
return values[x];
}
public void setValue(int x, String value)
{
values[x]=value;
}
public boolean getWantCompared(int x)
{
return wantCompared[x];
}
}
public class CompareClass
{
public void compare(SomeClass sc1, SomeClass sc2)
{
int z=sc1.getValueCount();
for (int x=0;x<z;++x)
{
if (!sc1.getWantCompared[x])
continue;
String sc1Value=sc1.getValue(x);
String sc2Value=sc2.getValue(x);
if (!sc1Value.equals(sc2Value)
{
writeLog(x, sc1Value, sc2Value);
sc2.setValue(x, sc1Value);
}
}
}
}
I just wrote this off the top of my head, I haven't tested it, so their may be bugs in the code, but I think the concept should work.
As you already have getters and setters, any other code using this class should continue to work unchanged. If there is no other code using this class, then throw away the existing getters and setters and just do everything with the array.
I would also propose a similar solution to the one by Alnitak.
If the fields need to be iterated when comparing, why not dispense with the separate fields, and put the data into an array, a HashMap or something similar that is appropriate.
Then you can access them programmatically, compare them etc. If different fields need to be treated & compared in different ways, you could create approriate helper classes for the values, which implement an interface.
Then you could just do
valueMap.get("myobject").compareAndChange(valueMap.get("myotherobject")
or something along those lines...