ParameterizedType并创建一个通用的dao

我尝试这个通用代码,因为我不想为我的数据库中的每个实体创建一个dao类,因为我有80个专门用于那些我将只执行CRUD查询的人。 因为在大多数情况下我只需要坚持或通过id进行查找。

public interface GenericDao { T create(T t); T read(PK id); T update(T t); void delete(T t); } 

接口的impl

 @Component public class GenericDaoJpaImpl implements GenericDao { protected Class entityClass; @PersistenceContext protected EntityManager entityManager; public GenericDaoJpaImpl() { ParameterizedType genericSuperclass = (ParameterizedType) getClass() .getGenericSuperclass(); this.entityClass = (Class) genericSuperclass .getActualTypeArguments()[0]; } @Override public T create(T t) { this.entityManager.persist(t); return t; } @Override public T read(PK id) { return this.entityManager.find(entityClass, id); } @Override public T update(T t) { return this.entityManager.merge(t); } @Override public void delete(T t) { t = this.entityManager.merge(t); this.entityManager.remove(t); } @Override public void delete(Set ts) { for( T t : ts){ t = this.entityManager.merge(t); this.entityManager.remove(t); } } } 

例外

 Caused by: org.springframework.beans.BeanInstantiationException: Could not instantiate bean class [dao.GenericDaoJpaImpl]: Constructor threw exception; nested exception is java.lang.ClassCastException: java.lang.Class cannot be cast to java.lang.reflect.ParameterizedType 

如何解决这个问题以及这个ParameterizedType是什么意思以及为什么我们必须在构造函数中使用它?

当我对构造函数进行注释时,除了public T read(PK id)之外,它还null pointer exception

  public GenericDaoJpaImpl() { // ParameterizedType genericSuperclass = (ParameterizedType) getClass() // .getGenericSuperclass(); // this.entityClass = (Class) genericSuperclass // .getActualTypeArguments()[0]; } 

我这样使用它:

 @Autowired private GenericDaoJpaImpl acheteurAlerteDao; 

我不想创建一个abstract类并像这样扩展它:

 public class AlerteAcheteurGenericDaoJpaImpl extends GenericDaoJpaImpl ... { } @Autowired private AlerteAcheteurGenericDaoJpaImpl acheteurAlerteDao; 

不幸的是,没有办法让它完全按照你的意愿工作。

第1部分。为什么它不起作用

注意GenericDaoJpaImpl的确切声明 –
GenericDaoJpaImpl implements GenericDao

ClassCastException是因为getClass().getGenericSuperclass()返回Class的实例,它是Typejava.lang.Class实现java.lang.reflect.Type ),但不是ParameterizedType 。 实际上, getClass().getGenericSuperclass()为其直接超类为java.lang.Object每个类返回Class的实例。 因此,构造函数就像

 public GenericDaoJpaImpl() { ParameterizedType genericSuperclass = (ParameterizedType) getClass().getGenericSuperclass(); this.entityClass = (Class) genericSuperclass.getActualTypeArguments()[0]; } 

适用于像AlerteAcheteurGenericDaoJpaImpl extends GenericDaoJpaImpl声明的类AlerteAcheteurGenericDaoJpaImpl extends GenericDaoJpaImpl 。 但这正是您不想申报DAO的方式。

Snippet 1,如果从GenericDaoJpaImpl ,将打印TPK (它们都将是sun.reflect.generics.reflectiveObjects.TypeVariableImpl的实例)。

片段1

 Type[] genericInterfaces = getClass().getGenericInterfaces(); ParameterizedType genericInterface = (ParameterizedType) genericInterfaces[0]; System.out.println(genericInterface.getActualTypeArguments()[0]); System.out.println(genericInterface.getActualTypeArguments()[1]); 

片段2

 @Bean(name = "alerteVendeurDao") public GenericDao alerteVendeurDao() { return new GenericDaoJpaImpl(); } 

即使在@Configuration -annotated类中有类似Snippet 2的东西,在运行时也无法知道GenericDaoJpaImpl由于类型擦除而被参数化的内容。 但是,如果Snippet 1是由AlerteAcheuteurDao implements GenericDao class somepackage.entity.AlerteAcheteur ,则会打印class somepackage.entity.AlerteAcheteurclass java.lang.Long (因为这些参数是明确的并且在编译时是已知的)。

最后,组件扫描在逻辑上甚至不适用于GenericDaoJpaImpl@Component -annotated类的Bean是“Singleton”-scoped。 除了只创建一个实例的事实之外,我们怎么会知道这个单例DAO应该在哪个实体上运行呢? 尽管如此,容器仍然可以实例化GenericDaoJpaImpl ,因为在运行时类型信息已经被擦除( 类型擦除! )。

此外,在相关情况下,建议使用更具体的@Repository而不是@Component来注释DAO。

第2部分。最好的选择是什么?

在您的特定情况下,最好的办法是将实体类声明为构造函数参数。 通过这种方式,可以通过将适当的构造函数参数传递给每个实例,在Spring配置中创建许多特定于实体的GenericDaoJpaImpl实例。

GenericDaoJpaImpl.java

 public class GenericDaoJpaImpl implements GenericDao { private final Class entityClass; @PersistenceContext protected EntityManager entityManager; public GenericDaoJpaImpl(Class entityClass) { this.entityClass = entityClass; } @Override public T create(T t) { this.entityManager.persist(t); return t; } @Override public T read(PK id) { return this.entityManager.find(entityClass, id); } @Override public T update(T t) { return this.entityManager.merge(t); } @Override public void delete(T t) { t = this.entityManager.merge(t); this.entityManager.remove(t); } @Override public void delete(Set ts) { for( T t : ts){ t = this.entityManager.merge(t); this.entityManager.remove(t); } } } 

AnnotationContextConfiguration.java

请注意,也可以通过基于构造函数的dependency injection在XML中执行相同的操作。

 @Configuration @ComponentScan("somepackage.service")// scan for services, but not for DAOs! public class Config { @Bean(autowire = Autowire.BY_NAME) public GenericDaoJpaImpl alerteAcheteurDao() { return new GenericDaoJpaImpl(AlerteAcheteur.class); } @Bean(autowire = Autowire.BY_NAME) public GenericDao alerteVendeurDao() { return new GenericDaoJpaImpl(AlerteVendeur.class); } // other DAOs ... } 

AlerteServiceImpl.java (看起来怎么样)

请注意,字段名称很重要,因为DAO是按名称自动assembly的。 如果您不想为alerteAcheteurDao命名字段,可以将@Qualifier@Autowired一起使用。

 @Service public class AlerteServiceImpl implements AlerteService { @Autowired private GenericDao alerteAcheteurDao; @Autowired private GenericDao alerteVendeurDao; ... } 

这是一个非常优雅的解决方案。 您不必使用像AlerteAcheteurGenericDaoJpaImpl extends GenericDaoJpaImpl这样的垃圾邮件类AlerteAcheteurGenericDaoJpaImpl extends GenericDaoJpaImpl 。 添加新实体后,您只需将新的GenericDaoJpaImpl实例添加到Spring配置中。

我希望这会有所帮助。

你的GenericDaoJpaImpl应该是抽象的。 只有具体的后代类型才能解析generics类型T,PK并定义为Spring bean。