How to create a single add method for different entities?

Viewed 154

How can I create a single add method that will insert different entities in the jdbc? I want to use annotations and reflection for that.
I have created 2 annotations:

(...)
public @interface Column {
    String name();
    boolean isPrimaryKey() default false;
}

and

(...)
public @interface Table {
    String name();
}

Suppose we have 2 entities/models/etc. : Client and Waiter. For both we should make 2 add methods, each with own INSERT.
If we have like 4 entities, we should have 4 add methods for each entity. Instead of having 4 add methods, how can I make just 1 add method? and by using the annotations and the reflection.

Eg:

@Table(name = "Table_Client")
public class Client{
    @Column(name = "ID", isPrimaryKey = true)
    private long id;

    @Column(name = "FULL_NAME")
    private String name;
}

@Table(name = "Table_Waiter")
public class Waiter {

    @Column(name = "FULL_NAME", isPrimaryKey = true)
    private String name;

    @Column(name = "AGE")
    private int age;
}

case: db.add(Client c1) => add to the database in the table Table_Client the client c1
      db.add(Waiter w1) => add to the database in the table Table_Waiter the waiter w1
      and so on...

My idea is to take the class of the given object and scan it for the TYPE annotation to get the table's name. Then, take all the field's annotations and make a dynamic INSERT INTO VALUES query, but the problem is that I can't actually do that, because I can't pass the object's arguments.
Another question: if this can be done, update and delete methods can follow the same path?

1 Answers

I cannot refrain from mentioning how many holes you may find in the road ahead. But judging from the comments, that's the path you want to explore.

First of all, regarding your existing code, you need to apply a retention meta-annotation to your annotations, Column and Table. For example:

@Retention(RetentionPolicy.RUNTIME)
public @interface Column {
    String name();
    boolean isPrimaryKey() default false;
}

This meta-annotation will ensure that you can read your annotations at runtime through reflection.

Then, you need to inspect the class searching for these annotations at both class and field levels.

The Class class will have everything you need. You should know you can get it from any object by calling the getClass method. It contains a couple of important methods for what you are trying to achieve:

  • getAnnotation(Class c) will return the annotation if it exists, or null otherwise.
  • getDeclaredFields will return all declared class fields, even private ones.

At a field level, the Field class provides the following methods:

  • getAnnotation(Class c), same as above, will return the annotation if it exists, or null otherwise.
  • getType will return the class associated with the field

Now consider the following piece of code:

public static void inspectClass(Class<?> cls) {
    Table t = cls.getAnnotation(Table.class);

    if (t != null) {
        System.out.print(t.name() + " --> ");
        for (Field f: cls.getDeclaredFields()) {
            Column c = f.getAnnotation(Column.class);
            if (c != null) {
                System.out.print(c.name()
                        + " "
                        + f.getType().getSimpleName()
                        + (c.isPrimaryKey() ? " PK" : "") + ", ");
            }
        }
    }
}

Applying this to your Client class, for instance, would return something like:

Table_Client --> ID long PK, FULL_NAME String,

Of course, this needs some work, but the idea is there.

EDIT:

To access values of an instance through reflection at runtime, for creating a dynamic INSERT statement, that could be done by calling get method on the Field class. When dealing with private fields though, it's necessary to tweak the privacy mode first:

f.setAccessible(true);
Object value = f.get(myInstance);
Related