简体   繁体   English

ParameterizedType并创建一个通用的dao

[英]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. 我尝试这个通用代码,因为我不想为我的数据库中的每个实体创建一个dao类,因为我有80个专门用于那些我将只执行CRUD查询的人。 because in most case i need just to persist or make a find by id. 因为在大多数情况下我只需坚持或通过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 接口的impl

@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 ? 如何解决这个问题以及这个ParameterizedType是什么意思以及为什么我们必须在构造函数中使用它?

and when i comment the constructor it works except for public T read(PK id) i got null pointer exception 当我对构造函数进行注释时,除了public T read(PK id)之外,它还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 : 我不想创建一个abstract类并像这样扩展它:

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 第1部分。为什么它不起作用

Note the exact declaration of GenericDaoJpaImpl - 注意GenericDaoJpaImpl的确切声明 -
GenericDaoJpaImpl<T, PK extends Serializable> implements GenericDao<T, PK> . 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 . ClassCastException是因为getClass().getGenericSuperclass()返回Class<java.lang.Object>的实例,它是Typejava.lang.Class实现java.lang.reflect.Type ),但不是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 . 实际上, getClass().getGenericSuperclass()为其直接超类为java.lang.Object每个类返回Class<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> . 适用于声明为AlerteAcheteurGenericDaoJpaImpl extends GenericDaoJpaImpl<AlerteAcheteur, Integer>的类, AlerteAcheteurGenericDaoJpaImpl extends GenericDaoJpaImpl<AlerteAcheteur, Integer> But this is exactly how you do not want to declare your DAOs. 但这正是您不想申报DAO的方式。

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,如果从GenericDaoJpaImpl ,将打印TPK (它们都将是sun.reflect.generics.reflectiveObjects.TypeVariableImpl的实例)。

Snippet 1 片段1

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

Snippet 2 片段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 . 即使在@Configuration -annotated类中有类似Snippet 2的东西,在运行时也不可能知道GenericDaoJpaImpl由于类型擦除而被参数化。 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). 但是,如果Snippet 1是从AlerteAcheuteurDao implements GenericDao<AlerteAcheuteur, Long> class somepackage.entity.AlerteAcheteur东西AlerteAcheuteurDao implements GenericDao<AlerteAcheuteur, Long>则会打印class somepackage.entity.AlerteAcheteurclass java.lang.Long (因为这些参数是明确的并且在编译时是已知的)。

Finally, the component scanning is not even logically applicable to GenericDaoJpaImpl . 最后,组件扫描在逻辑上甚至不适用于GenericDaoJpaImpl Beans of @Component -annotated classes are "Singleton"-scoped. @Component -annotated类的Bean是“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? 除了只创建一个实例的事实之外,我们怎么会知道这个单例DAO应该在哪个实体上运行呢? Nevertheless, the container is still able to instantiate GenericDaoJpaImpl , because at runtime the type information is already erased ( type erasure! ). 尽管如此,容器仍然可以实例化GenericDaoJpaImpl ,因为在运行时类型信息已经被擦除( 类型擦除! )。

Moreover, in relevant cases it is recommended to annotate DAOs with more specific @Repository , instead of @Component . 此外,在相关情况下,建议使用更具体的@Repository而不是@Component来注释DAO。

Part 2. What could be the best bet? 第2部分。最好的选择是什么?

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. 通过这种方式,可以通过将适当的构造函数参数传递给每个实例,在Spring配置中创建许多特定于实体的GenericDaoJpaImpl实例。

GenericDaoJpaImpl.java 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 AnnotationContextConfiguration.java

Please note that it is also possible to do the same in XML via constructor-based dependency injection . 请注意,也可以通过基于构造函数的依赖注入在XML中执行相同的操作。

@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) AlerteServiceImpl.java (看起来怎么样)

Please note that field names are important, because DAOs are autowired by name. 请注意,字段名称很重要,因为DAO按名称自动装配。 If you do not want to name fields like alerteAcheteurDao , you can use @Qualifier with @Autowired . 如果您不想为alerteAcheteurDao命名字段,可以将@Qualifier@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> . 您不需要像AlerteAcheteurGenericDaoJpaImpl extends GenericDaoJpaImpl<AlerteAcheteur, Integer>这样的垃圾邮件类AlerteAcheteurGenericDaoJpaImpl extends GenericDaoJpaImpl<AlerteAcheteur, Integer> Upon adding new entities, you just have to add new instances of GenericDaoJpaImpl to Spring configuration. 添加新实体后,您只需要将新的GenericDaoJpaImpl实例添加到Spring配置中。

I hope this will be helpful. 我希望这会有所帮助。

Your GenericDaoJpaImpl should be abstract. 你的GenericDaoJpaImpl应该是抽象的。 Only concrete descendant type should resolve the generic types T,PK and be defined as Spring beans. 只有具体的后代类型才能解析泛型类型T,PK并定义为Spring bean。

声明:本站的技术帖子网页,遵循CC BY-SA 4.0协议,如果您需要转载,请注明本站网址或者原文地址。任何问题请咨询:yoyou2525@163.com.

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