简体   繁体   中英

ParameterizedType and creating a generic dao

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.

Part 1. Why it does not work

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 .

Part 2. What could be the best bet?

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.

 
粤ICP备18138465号  © 2020-2024 STACKOOM.COM