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
。
ClassCastException
是因为getClass().getGenericSuperclass()
返回Class
的实例,它是Type
( java.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
,将打印T
和PK
(它们都将是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.AlerteAcheteur
和class 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。