4

似乎我在 Java 泛型方面遗漏了一些东西,因为我认为有些东西很简单,但在我看来这是无法完成的。也许你可以帮助...

这是场景:我正在使用简单的 CRUD 操作编写通用抽象 DAO,因此我的应用程序的每个特定 DAO 都可以免费使用它:

public abstract DefaultDAO<T,V> {
  private EntityManager manager;

  public BaseDAO(EntityManager em) {
    this.manager = em;
  }

  public void create(T entity) {
    manager.persist(entity);
  }

  // You know the others...

  public T read(V pk) {
    // Now, here is the problem. 
    // EntityManager signature is: public <T> T find(Class<T> entityClass, Object primaryKey);
    // So I must provide the type of the object this method will be returning and 
    // the primary key.
    // resulting object will be T typed and pk type is V type (although is not needed to type it since the method expects an Object)
    // So... I expected to be allowed to do something like this
    return manager.find(T, pk); // But it's not allowed to use T here. T is not an instance
  }   
}

现在我会去实现一个特定的 DAO:

public PersonDAO extends DefaultDAO<PersonEntity, Long> {
  public PersonDAO(EntityManager em) {
    super(em);
  }
  // CRUD methods are inherited
}

我的 DAO 的客户端代码是:

EntityManager manager = ...
PersonDAO dao = new PersonDAO(manager);
Long pk = .....
PersonEntity person = dao.find(pk); // DAO would return a PersonEntity

当客户端执行代码时,BaseDAO 知道它必须返回的实体类型以及该实体的主键类型,因为我将它设置在特定的 dao 上,但我不知道如何正确编写 read() 方法。

希望你能帮忙。非常感谢!

4

5 回答 5

14

您正在尝试像使用普通表达式一样使用类型参数。你不能这样做。在其他语言中,您可能能够在执行时获取类型参数的类,但由于类型擦除,您不能在 Java 中。

在这种情况下,您需要传入Class<T>at 执行时间 - 例如给构造函数:

public abstract class DefaultDAO<T, V> {

    private EntityManager manager;
    private final Class<T> clazz;

    public DefaultDAO(EntityManager em, Class<T> clazz) {
        this.manager = em;
        this.clazz = clazz;
    }

    public T read(V pk) {
        return manager.find(clazz, pk);
    }
}
于 2009-11-18T15:53:03.660 回答
3

Java 在运行时不再具有通用类信息(称为类型擦除)。我所做的是给我的抽象 Daos 泛型类的实例。

public abstract DefaultDAO<T,V> {

  protected Class<T> genericClass;
  private EntityManager manager;

  protected BaseDAO(EntityManager em, Class<T> implclass) {
    this.manager = em;
  }

  public void create(T entity) {
    manager.persist(entity);
  }

  // You know the others...

  public T read(V pk) {
    return manager.find(this.getGenericClass(), pk); // But it's not allowed to use T here. T is not an instance
  }

  public Class<T> getGenericClass()
  {
    return genericClass;
  }
}

public class Blubb
{
    private String id;

    // Getters and Stuff...
}

public class BlubbDao extends DefaultDAO<Blubb, String>
{
    public BlubbDao(EntityManager em)
    {
        super(em, Blubb.class);
    }
}

我不能保证它会开箱即用,但我希望你明白这一点。

于 2009-11-18T15:53:35.267 回答
3

有一种使用反射的方法,只要您的类在泛型方面遵循一致的类层次结构(即,基类和具体类之间的继承层次结构中的任何中间类以相同的顺序使用相同的泛型参数)。

我们在抽象类中使用类似的东西,定义为 HibernateDAO,其中 T 是实体类型,K 是 PK:

private Class getBeanClass() {
    Type daoType = getClass().getGenericSuperclass();
    Type[] params = ((ParameterizedType) daoType).getActualTypeArguments();
    return (Class) params[0];
}

这有点味道,但权衡了从构造函数中的具体实现传递 .class 的恶心,而不是坚持以这种方式保持类型层次结构一致的恶心。

于 2009-11-18T23:22:44.650 回答
3

我认为你可以这样做:

 public <T> T find(Class<T> clazz, Object id) {
        return entityManager.find(clazz, id);
    }
于 2014-05-23T18:28:45.820 回答
2

这篇文章做了你需要它做的事情。http://blog.xebia.com/2009/03/09/jpa-implementation-patterns-data-access-objects/

于 2009-11-18T16:23:49.403 回答