摘要 class 中的类型安全问题

Issue with type safety within abstract class

我最近开始在 Java 开展一个新项目,该项目将有一个本地数据库。作为设计的一部分,我创建了一个 AbstractEntity class - 这是数据库中一行(或潜在行)的对象表示。

虽然我已经 运行 在这个设计的早期解决了一些问题,但我想确保我不会走上一条糟糕的道路。我遇到问题的一种特殊方法如下:

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;
}

此方法背后的想法是使用一种通用方法从数据库中检索内容,我唯一需要传入的是 SQL 的条件。目前它工作正常,但我有两个问题:

1) 类型安全

我似乎无法在不导致编译器错误的情况下参数化此方法。 ArrayList<AbstractEntity> 不好,而且我似乎也无法使 ArrayList<? extends AbstractEntity> 工作。当我尝试后者时(这对我来说很有意义),以下行给我一个编译器错误:

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

错误是'Type mismatch: cannot convert from ArrayList<capture#1-of ? extends AbstractEntity> to ArrayList<PlayerEntity>'

有没有一种方法可以直接从抽象 class 中引用 superclass?这个方法不是静态的,因为你不能实例化一个抽象的 class (它没有构造函数),要调用这个我必须 always 有一个扩展 class .那为什么我不能引用它的类型?

2) 静态

理想情况下,我希望此方法是静态的。这样我就可以直接调用 PlayerEntity.retrieveEntities() ,而不是创建一个对象来调用它。但是,由于它指的是抽象方法,我不能这样做,所以我坚持使用它。

以上两种抱怨都在我脑海中敲响了警钟。是否有更好的设计方法来避免这些问题,或者更好的是,是否有直接解决我遗漏的这些问题的方法?

第一点:如果你让你的方法是静态的,那么使用多态就没有意义了。多态性在运行时起作用,但是将方法设为静态会强制您在编译时指示动态类型,这是无意义的。

关于方法的 return 类型,您可能需要先将其设置为 ArrayList<? extends AbstractEntity>,否则您只是说您 return 任何对象(即 ArrayList<Object>). 在此之后,您必须创建一个与 return 类型相同类型的局部变量,以免出现编译错误。

现在,您如何填充此集合? 我要给你两个提示:

  1. 您可以使用 Reflection,特别是您可以通过在运行时选择要实例化的 class 来调用 class 构造函数(使用 getClass())。
  2. 您可以利用 Template method pattern 来实现灵活的设计并且没有重复的代码。

总的来说,您遇到的问题已经解决了。所以,如果你只是在寻找一个现成的解决方案,你可以看看像 Hibernate.

这样的 ORM 框架。

我认为你是在重新发明轮子。 ORMs (Object-Relational Mappers) 已经存在很多年并且被证明非常有用。

虽然它们不是防弹的。由于他们要解决的问题是相当困难的(我的意思是object-relational impedance mismatch),因此解决方案通常也有其困难。

为了灵活地完成工作,一些 ORM 牺牲了性能,而另一些则牺牲了使用的简单性等。我的意思是这里没有完美的解决方案。

我想向您介绍我在不同项目中使用过的三个不同的 ORM:

那里有许多比较和基准测试,深入探讨了这个主题。

Hibernate 是使用最广泛的,它健壮而强大,提供了很大的灵活性并且表现良好如果使用得当。不利的是,它有一个陡峭的学习曲线,对于初学者来说有点复杂,并且一般来说,使用 Hibernate 的解决方案最终会永远使用 Hibernate,因为很容易无意中让 Hibernate 潜入您的业务层。

ActiveJDBC 不是很流行,但却是 Java 的最佳 ActiveRecord 解决方案。如果您来自 Ruby,这是您的选择。它的 API 非常流畅和富有表现力,使用它的代码非常易于阅读和维护。它非常简单,而且是一个真正 精简 的框架。

E-Bean 非常强大,它的 API 流畅且富有表现力,并且该产品提供自适应行为以即时优化查询。它使用简单,使用它的代码具有良好的可读性和易于维护。

关于类型安全,我通常采用这种做法:

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;
    }
}

我已经声明了一个抽象存储库 class,需要使用正确的参数类型对其进行扩展:

public class Person extends AbstractEntity {
}

public class PersonRepository extends AbstractRepository<Person> {
}

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

我通常将生成实体的代码与实际实体的代码分开,否则实体最终会承担很多责任并做太多工作。然而,ActiveRecord 方法通过让实体完成所有工作来解决这个问题,它是超越 Java.

世界的一个非常流行的选择。