0

I am trying to create a generic row mapper for JDBC.

My initial attempt looks like:

public class GenericRowMapper<T> implements RowMapper<T> {

    private final Class<?> clazz;

    public GenericRowMapper(T instance) {
        this.clazz = instance.getClass().getComponentType();
    }

    @Override
    public T mapRow(ResultSet rs, int rowNum) throws SQLException {
        Field[] fields = clazz.getDeclaredFields();
        for(Field f : fields){
            System.out.println("field name : " + f.getName() + " , type : " + f.getType());
        }

        try {
            return (T) clazz.getConstructor().newInstance();
        } catch (NoSuchMethodException | InvocationTargetException | InstantiationException | IllegalAccessException e) {
            e.printStackTrace();
            return null;
        }
    }
}

But in the constructor, the getComponentType() is returning null, so clazz = null; and I am not being able to make any progress.

What am I doing wrong?

Mark Rotteveel
  • 100,966
  • 191
  • 140
  • 197
Alex Vergara
  • 1,766
  • 1
  • 10
  • 29
  • 1
    Now if only Spring would ship with something already... [`BeanPropertyRowMapper`](https://docs.spring.io/spring-framework/docs/current/javadoc-api/org/springframework/jdbc/core/BeanPropertyRowMapper.html). – M. Deinum Jun 20 '22 at 14:44

1 Answers1

2

What I am doing wrong?

A mistake many newbie programmers make (and some experts, too!).

You have failed to read documentation.

Had you done so, you may have noticed that the docs for getComponentType() tell you that it returns the component type of an array.

Given, say, Class<?> c = String[].class, I can call c.getComponentType() and I get String.class back. Or new String[10].getClass().getComponentType() would get me the same thing.

You're clearly invoking .getComponentType() on a class that is not representing an array type, thus, you get null back.

Doing it right

Your code is rather confused. T is a type variable, thus, taking a parameter of T instance means you must provide an actual instance of it, i.e. if you have a RowMapper that maps a row into a string, you'd have to provide a string, which doesn't make sense. Presumably what you actually want as arg is Class<T> instead, in which case - voila, there is your class. No need to invoke .getClass() or .getComponentType() on it in the first place. If truly you intended for this method to require that you pass an actual instance first, .getClass() would be the method you presumably want, except I don't think that's going to work: .getClass() always gives you the actual type, i.e. calling .getClass() on a variable of type List cannot ever return List.class - it would return perhaps ArrayList.class or some anonymous inner class that implements List.

The rest of your code is similarly confused

.getDeclaredFields() only returns the fields in the class definition itself, not any parent classes (whose fields are just as much part of any instance of the class as any other) - again, read docs to figure this out.

'print a stack trace, return null' is very silly exception handling. The right 'I do not (yet) care about it' exception handler is throw new RuntimeException("unhandled", e); - shorter, better. There is no reason not to. Fix your IDE's templates if you must.

Not ready for modern java

Many, many modern takes on java (notably including the record feature) has an immutable class where you therefore cannot just 'set' a bunch of fields, and there won't be a no-argument constructor (which .getConstructor() gets you, thus, .getConstructor() would fail). Instead, the constructor itself takes all args; therefore, you're not interested in the fields, you're interested in the parameters of the constructor. Except there could be more than one, and classes do not (neccessarily) contain the names of parameters.

rzwitserloot
  • 85,357
  • 5
  • 51
  • 72
  • Thanks for your comprehensive response. But, you are making a little bit assumptions. That code snippets, isn't production ready. It's a quick snippet to figure out how can I retrieve the class, so it w'd be fine to not pay to much attention to this. What you're completly right is in the fact of passing a `Class` arg to the constructor do the trick. Nice point on this. I am aware of the `getDeclaredFields()` docs. But going further, I am making that RowMapper for anemic models, so they will always work with the fields of the type. – Alex Vergara Jun 20 '22 at 14:13
  • Idea: Get the type via constructor with a `Class>` paramenter. -> Getting the declared fields with it's types. -> Make a simple relation, if one row is an string and other an int, call the setter methods of the type to be able to call the correct `rs` getType – Alex Vergara Jun 20 '22 at 14:16
  • I explained why you were getting the `null` - I explained the many, many, many problems with the general setup. The rest is up to you. Is there anything further that isn't clear (and is at least somewhat related to the question?) – rzwitserloot Jun 20 '22 at 14:25
  • Don't misunderstood me. You did a fine job. `null` it's solved. I am trying to set up a nice return value for the noexcept branch, but the best I can do is `return (T) clazz.getDeclaredConstructor().newInstance();` Where I got this warning: Unchecked cast: 'capture>' to 'T' . How can I get rid out of that warning and implement a better solution? – Alex Vergara Jun 20 '22 at 14:36
  • 1
    The `clazz` variable needs to be of type `Class extends T>`. – rzwitserloot Jun 20 '22 at 14:56