I try this generic code because i don't want to create a dao class for each entity i have in my database because i have 80 ones specially for those who i will excecute just CRUD query. because in most case i need just to persist or make a find by id.
public interface GenericDao<T, PK extends Serializable> {
T create(T t);
T read(PK id);
T update(T t);
void delete(T t);
}
the impl of the interface
@Component
public class GenericDaoJpaImpl<T, PK extends Serializable>
implements GenericDao<T, PK> {
protected Class<T> entityClass;
@PersistenceContext
protected EntityManager entityManager;
public GenericDaoJpaImpl() {
ParameterizedType genericSuperclass = (ParameterizedType) getClass()
.getGenericSuperclass();
this.entityClass = (Class<T>) 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<T> ts) {
for( T t : ts){
t = this.entityManager.merge(t);
this.entityManager.remove(t);
}
}
}
the exception
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
How to resolve this and what means this ParameterizedType
and why we have to use it in the constructor ?
and when i comment the constructor it works except for public T read(PK id)
i got null pointer exception
public GenericDaoJpaImpl() {
// ParameterizedType genericSuperclass = (ParameterizedType) getClass()
// .getGenericSuperclass();
// this.entityClass = (Class<T>) genericSuperclass
// .getActualTypeArguments()[0];
}
I use it like this :
@Autowired
private GenericDaoJpaImpl<AlerteAcheteur, Integer> acheteurAlerteDao;
i don't want to create a abstract
class and extend it like this :
public class AlerteAcheteurGenericDaoJpaImpl extends GenericDaoJpaImpl<AlerteAcheteur, Integer> ... {
}
@Autowired
private AlerteAcheteurGenericDaoJpaImpl<AlerteAcheteur, Integer> acheteurAlerteDao;
Unfortunately, there is no way to make it work exactly as you want.
Note the exact declaration of GenericDaoJpaImpl
-
GenericDaoJpaImpl<T, PK extends Serializable> implements GenericDao<T, PK>
.
ClassCastException
is thrown because getClass().getGenericSuperclass()
returns an instance of Class<java.lang.Object>
, which is Type
( java.lang.Class
implements java.lang.reflect.Type
), but not ParameterizedType
. In fact, an instance of Class<java.lang.Object>
is returned by getClass().getGenericSuperclass()
for every class whose direct superclass is java.lang.Object
. Thus, a constructor like
public GenericDaoJpaImpl() {
ParameterizedType genericSuperclass = (ParameterizedType) getClass().getGenericSuperclass();
this.entityClass = (Class<T>) genericSuperclass.getActualTypeArguments()[0];
}
works for a class declared like AlerteAcheteurGenericDaoJpaImpl extends GenericDaoJpaImpl<AlerteAcheteur, Integer>
. But this is exactly how you do not want to declare your DAOs.
Snippet 1, if ran from your GenericDaoJpaImpl
, will print T
and PK
(both of them will be the instances of sun.reflect.generics.reflectiveObjects.TypeVariableImpl
).
Snippet 1
Type[] genericInterfaces = getClass().getGenericInterfaces();
ParameterizedType genericInterface = (ParameterizedType) genericInterfaces[0];
System.out.println(genericInterface.getActualTypeArguments()[0]);
System.out.println(genericInterface.getActualTypeArguments()[1]);
Snippet 2
@Bean(name = "alerteVendeurDao")
public GenericDao<AlerteVendeur, Long> alerteVendeurDao() {
return new GenericDaoJpaImpl<AlerteVendeur, Long>();
}
Even if there is something like Snippet 2 in @Configuration
-annotated class, at runtime it is impossible to know what GenericDaoJpaImpl
has been parameterized to because of type erasure . However, if Snippet 1 was executed from something like AlerteAcheuteurDao implements GenericDao<AlerteAcheuteur, Long>
, class somepackage.entity.AlerteAcheteur
and class java.lang.Long
would be printed (because these parameters are unambiguous and known at compile time).
Finally, the component scanning is not even logically applicable to GenericDaoJpaImpl
. Beans of @Component
-annotated classes are "Singleton"-scoped. Besides the fact that only one instance will be created, how would we even know what entity this singleton DAO is supposed to operate on? Nevertheless, the container is still able to instantiate GenericDaoJpaImpl
, because at runtime the type information is already erased ( type erasure! ).
Moreover, in relevant cases it is recommended to annotate DAOs with more specific @Repository
, instead of @Component
.
In your particular case the best bet is to declare an entity class as a constructor parameter. In this way, it is possible to create numerous entity-specific GenericDaoJpaImpl
instances in Spring configuration by passing appropriate constructor argument to each of them.
GenericDaoJpaImpl.java
public class GenericDaoJpaImpl<T, PK extends Serializable>
implements GenericDao<T, PK> {
private final Class<T> entityClass;
@PersistenceContext
protected EntityManager entityManager;
public GenericDaoJpaImpl(Class<T> 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<T> ts) {
for( T t : ts){
t = this.entityManager.merge(t);
this.entityManager.remove(t);
}
}
}
AnnotationContextConfiguration.java
Please note that it is also possible to do the same in XML via constructor-based dependency injection .
@Configuration
@ComponentScan("somepackage.service")// scan for services, but not for DAOs!
public class Config {
@Bean(autowire = Autowire.BY_NAME)
public GenericDaoJpaImpl<AlerteAcheteur, Long> alerteAcheteurDao() {
return new GenericDaoJpaImpl<AlerteAcheteur, Long>(AlerteAcheteur.class);
}
@Bean(autowire = Autowire.BY_NAME)
public GenericDao<AlerteVendeur, Long> alerteVendeurDao() {
return new GenericDaoJpaImpl<AlerteVendeur, Long>(AlerteVendeur.class);
}
// other DAOs
...
}
AlerteServiceImpl.java (how could it look like)
Please note that field names are important, because DAOs are autowired by name. If you do not want to name fields like alerteAcheteurDao
, you can use @Qualifier
with @Autowired
.
@Service
public class AlerteServiceImpl implements AlerteService {
@Autowired
private GenericDao<AlerteAcheteur, Long> alerteAcheteurDao;
@Autowired
private GenericDao<AlerteVendeur, Long> alerteVendeurDao;
...
}
This is quite an elegant solution. You don't have to spam classes like AlerteAcheteurGenericDaoJpaImpl extends GenericDaoJpaImpl<AlerteAcheteur, Integer>
. Upon adding new entities, you just have to add new instances of GenericDaoJpaImpl
to Spring configuration.
I hope this will be helpful.
Your GenericDaoJpaImpl should be abstract. Only concrete descendant type should resolve the generic types T,PK and be defined as Spring beans.
The technical post webpages of this site follow the CC BY-SA 4.0 protocol. If you need to reprint, please indicate the site URL or the original address.Any question please contact:yoyou2525@163.com.