我最近开始在Java上开发一个新项目,该项目将拥有一个本地数据库。作为设计的一部分,我创建了一个AbstractEntity类 - 它用作数据库中行(或潜在行)的对象表示。
我在这个设计的早期遇到了一些问题,并且想确保我不会走坏路。我遇到问题的一个特殊方法如下:
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");
错误是'类型不匹配:无法从ArrayList <capture#1-of ? extends AbstractEntity>
转换为ArrayList<PlayerEntity>
'
有没有办法可以直接从抽象类中引用超类?这个方法不是静态的,因为你不能实例化一个抽象类(它没有构造函数),要调用它,我必须总是有一个扩展类。那么为什么我不能引用它的类型呢?
2)静态
理想情况下,我希望这种方法是静态的。这样我就可以直接调用PlayerEntity.retrieveEntities(),而不是仅仅调用它来调用它。但是,由于它涉及抽象方法,我不能这样做,所以我坚持不懈。
上面的两个抱怨都在我的脑袋里响起警钟。有没有更好的方法来设计这个以避免这些问题,或者更好的是,我是否缺少这些问题的直接解决方案?
答案 0 :(得分:0)
第一点:如果你的方法是静态的,那么使用多态就没有意义了。多态性在运行时工作,但是使方法静态强制您在编译时指示动态类型,这是无意义的。
关于方法的返回类型,您可能需要先将其设置为ArrayList<? extends AbstractEntity>
,否则您只是说要返回任何对象(即ArrayList<Object>
)。
在此之后,您必须创建与返回类型相同类型的局部变量,以便不会出现编译错误。
现在,您如何填充此系列? 我将给你两个提示:
getClass()
)。但是,一般而言,您遇到的问题已经解决了。因此,如果您只是在寻找一个现成的解决方案,您可以查看一个像Hibernate这样的ORM框架。
答案 1 :(得分:0)
我认为你正在重新发明轮子。 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;
}
}
我已经声明了一个抽象的存储库类,需要使用正确的参数类型进行扩展:
public class Person extends AbstractEntity {
}
public class PersonRepository extends AbstractRepository<Person> {
}
PersonRepository repo = new PersonRepository();
List<Person> people = repo.list("some SQL");
我通常将生成实体的代码与实际实体分开。代码,否则实体最终会承担很多责任并做太多工作。但是,ActiveRecord方法通过让实体完成所有工作来解决这个问题,并且它是Java之外的一个非常受欢迎的选择。