1

I've recently started working on a new project in Java which will have a local database. As part of the design, I have created an AbstractEntity class - this is intended as an object representation of a row (or potential row) on the database.

I've run into a few issues early on in this design though and want to make sure I'm not going down a bad path. One particular method I'm having trouble with is the following:

public ArrayList retrieveEntities(String sql)
{
    ArrayList ret = new ArrayList();

    String query = "SELECT " + getColumnsForSelectStatement() + " FROM " + getTableName() + " WHERE " + sql;

    try (Connection conn = DatabaseUtil.createDatabaseConnection();
      Statement s = conn.createStatement();
      ResultSet rs = s.executeQuery(query))
    {
        while (rs.next())
        {
            AbstractEntity entity = factoryFromResultSet(rs);
            ret.add(entity);
        }
    }
    catch (SQLException sqle)
    {
        Debug.logSqlException(query, sqle);
    }

    return ret;
}

The idea behind this method is to have a generic way to retrieve things from the database, where the only thing I have to pass in are the conditions for the SQL. As it stands it works correctly, but I have two problems with it:

1) Type Safety

I can't seem to parameterise this method without causing compiler errors. ArrayList<AbstractEntity> is no good, and I can't seem to get ArrayList<? extends AbstractEntity> to work either. When I try the latter (which makes sense to me), the following line gives me a compiler error:

ArrayList<PlayerEntity> list = new PlayerEntity().retrieveEntities("1 = 1");

The error is 'Type mismatch: cannot convert from ArrayList<capture#1-of ? extends AbstractEntity> to ArrayList<PlayerEntity>'

Is there a way I can directly reference the superclass from an abstract class? This method isn't static, and since you cannot instantiate an abstract class (it has no constructor), to call this I must always have an extending class. So why can't I reference it's type?

2) Staticness

Ideally, I'd like for this method to be static. That way I can call PlayerEntity.retrieveEntities() directly, rather than making an object just to call it. However, since it refers to abstract methods I can't do this, so I'm stuck with it.

Both of the above gripes are ringing alarm bells in my head. Is there a better way to design this that avoids these problems, or better yet are there direct solutions to either of these problems that I'm missing?

Manu
  • 4,019
  • 8
  • 50
  • 94
Alyssa
  • 835
  • 1
  • 7
  • 23
  • 1
    Side note: you're reinventing the wheel (http://en.wikipedia.org/wiki/Object-relational_mapping) – m0skit0 Mar 09 '15 at 23:21
  • 1
    See http://stackoverflow.com/questions/2745265/is-listdog-a-subclass-of-listanimal-why-arent-javas-generics-implicitly-p for why you can't convert the classes. – Adrian Leonhard Mar 09 '15 at 23:34

2 Answers2

0

First point: if you make your method static, then it makes no sense using polymorphism. Polymorphism works at runtime, but making the method static forces you to indicate the dynamic type at compile time, which is a non-sense.

Concerning the return type of the method, you may want first to set it to ArrayList<? extends AbstractEntity>, otherwise you are just saying that you return any Object (i.e. ArrayList<Object>). After this, you have to create a local variable of the same type as the return type, so to not have a compile error.

Now, how do you populate this collection? I am going to give you just two hints:

  1. You can use Reflection, in particular you can invoke a class constructor by selecting at runtime the class you want to instantiate (use getClass()).
  2. You can leverage the Template method pattern to end up having a flexible design and no duplicate code.

In general, however, the problem you are facing has already been solved. So, if you are just looking for a ready-to-use solution, you may take a look at a ORM framework like Hibernate.

Manu
  • 4,019
  • 8
  • 50
  • 94
0

I think you are reinventing the wheel. ORMs (Object-Relational Mappers) have been around for many years and have proven to be very useful.

They're no bullet-proof, though. As the problem they intend to solve is quite difficult (I mean object-relational impedance mismatch), solutions usually have its difficulties as well.

In order to do their work in a flexible manner, some ORMs compromise performance, while others compromise simplicity of usage, etc. What I mean is that there are no perfect solutions here.

I'd like to point you to three different ORMs I've worked with in different projects:

THere are many comparisons and benchmarks out there, that cover this topic in depth.

Hibernate is the most widely-used, it's robust and powerful, offers a lot of flexibility and performs well if used well. On the cons, it has a steep learning curve, it's a little bit complex for beginners and, in general, solutions that use Hibernate end up using Hibernate forever, since it's very easy to inadvertently let Hibernate sneak into your business layer.

ActiveJDBC is not very popular, but is the best ActiveRecord solution for Java. If you come from Ruby, this is your choice. Its API is very fluent and expressive and code that uses it is very easy to read and maintain. It's very simple and a really thin framework.

E-Bean is quite powerful, its API is fluent and expressive and the product provides adaptive behavior to optimize queries on-the-fly. It's simple to use and code that uses it has good readability and is easy to maintain.

Regarding type safety, I usually follow this approach:

public class AbstractRepository<T extends AbstractEntity> {

    protected final Class<T> entityClazz;

    protected AbstractRepository() {
        Type type = this.getClass().getGenericSuperclass();
        ParameterizedType paramType = (ParameterizedType) type;
        this.entityClazz = (Class<T>) paramType.getActualTypeArguments[0];
        // TODO exception handling
    }

    public List<T> list(String sql) { // retrieveEntities => very long name
        List<T> ret = new ArrayList<>();

        String query = "SELECT " + getColumnsForSelectStatement() + " FROM " + getTableName() + " WHERE " + sql;

        try (Connection conn = DatabaseUtil.createDatabaseConnection();
            Statement s = conn.createStatement();
            ResultSet rs = s.executeQuery(query)) {
            while (rs.next()) {
                T entity = factoryFromResultSet(rs);
                ret.add(entity);
            }
        } catch (SQLException sqle) {
            Debug.logSqlException(query, sqle);
        }

        return ret;
    }

    protected T factoryFromResultSet(ResultSet rs) {
        // Create new entity instance by reflection
        T entity = clazz.getConstructor().newInstance();

        // TODO exception handling 
        // Fill entity with result set data

        return entity;
    }
}

I've declared an abstract repository class, which needs to be extended with the right parameter types:

public class Person extends AbstractEntity {
}

public class PersonRepository extends AbstractRepository<Person> {
}

PersonRepository repo = new PersonRepository();
List<Person> people = repo.list("some SQL");

I usually separate the code that generates the entities from the actual entities' code, otherwise entities end up having many responsibilities and doing too much work. However, the ActiveRecord approach addresses this problem by letting the entities do all the work, and it's a very popular choice beyond the world of Java.

fps
  • 33,623
  • 8
  • 55
  • 110