是否可以在运行时动态实例化 DAO class?

Is it possible to instantiate a DAO class dynamically at runtime?

所以我环顾四周但仍然找不到任何可以帮助我的东西。所以这是我的代码:

public ExtendedBaseDAO getCorrespondingDAO(String tableName) throws DAOException {
        log.info("BaseService.getCorrespondingDAO(...): Getting DAO correspondant to table with name " + tableName
                + "...");
        try {
            Optional<EntityType<?>> entityFound = entityManager.getMetamodel().getEntities().stream()
                    .filter(entity -> ((Table) entity.getJavaType().getAnnotation(Table.class)).name().equalsIgnoreCase(tableName)).findFirst();
            log.info("Found entity with name " + entityFound.get().getJavaType().getSimpleName() + " mapped to " + tableName);
            Reflections reflections = new Reflections("eu.unicredit.fit.fit_core.dao");
            Optional<Class<? extends ExtendedBaseDAO>> daoClassFound = reflections.getSubTypesOf(ExtendedBaseDAO.class)
                    .stream().filter(daoClass -> daoClass.getSimpleName().replaceAll("DAO", "")
                            .equals(entityFound.get().getJavaType().getSimpleName()))
                    .findFirst();
            
            log.info("The correspondant DAO found is " + daoClassFound.get().getSimpleName() + ". Instantiating it...");
            return daoClassFound.get().getConstructor().newInstance();
        } catch (Exception e) {
            throw new DAOException("It was not possible to find the DAO associated with the table " + tableName
                    + "! Error: " + e.getLocalizedMessage());
        }
    }

如您所见,我正在 return 使用 'tablename' 找到相应 DAO 的实例。我需要这个方法,因为我会知道在运行时通过一些参数询问哪个 table 。唯一的问题是,当我调用 'findById' 方法时,它只会给我一个空指针异常,因为那个 dao 的 EntityManager 是空的。

现在...EntityManager 工作正常。这是 class 调用该方法

public class WizardFieldConfigService extends BaseService {

    @Inject
    private WizardFieldConfigDAO wizardFieldConfigDAO;

    /**
     * Retrieves the field data from the specific table requested for. To specify
     * the table use the fieldDataRequest.
     * 
     * @param fieldDataRequest The DTO to be used as input
     * @return a {@link FieldDataResponseDTO} object with a map containing the
     *         requested values
     */
    @SuppressWarnings({ "unchecked", "rawtypes" })
    public FieldDataResponseDTO getFieldData(FieldDataRequestDTO fieldDataRequest) {
        log.info("WizardFieldConfigService.getFieldData(...): Retrieving field data for field with id "
                + fieldDataRequest.getFieldID() + "...");
        
        WizardFieldConfig fieldBaseData = wizardFieldConfigDAO.findByID((long)fieldDataRequest.getFieldID());
        log.info("Found field data: " + fieldBaseData.toString());
        
        List<BaseEntity> response = getCorrespondingDAO(fieldBaseData.getDomainName())
                .findWithConditions(fieldDataRequest.getConditions());
        
        return new FieldDataResponseDTO().setPlaceHolder(fieldBaseData.getPlaceholder())
                .setLabel(fieldBaseData.getFieldName()).setRegex(fieldBaseData.getRegex())
                .setValueList((Map<? extends Serializable, String>) response.stream()
                        .collect(Collectors.toMap(BaseEntity::getId, BaseEntity::getDescription)));
    }

}

所以这里第一个 'findById' 与注入的 DAO 相关的工作正常,而另一个 DAO 无论如何都会 return 由于实体管理器被调用的任何方法的空指针无效的。我想这是因为它不是注入的 bean,有没有办法解决这个问题并修复实体管理器为空?

编辑:我忘了说我是在没有 Spring 的情况下使用普通 CDI 的。无论如何,如评论中所述,共享 DAO classes 结构可能很有用:

这是 ExtendedDAO,它扩展了包含一些默认查询方法的 BaseDAO:

@Slf4j public 抽象 class ExtendedBaseDAO extends BaseDao{

@PersistenceContext(unitName = "fit-core-em")
private EntityManager em;

protected ExtendedBaseDAO(Class<T> type) {
    super(type);
}

public List<T> findWithConditions(List<ConditionDTO> conditions) {
    //...
}


@Override
protected EntityManager getEntityManager() {
    return this.em;
}

}

任何 DAO class 都会扩展这个,因此可以访问 EntityManager。这实际上对于服务方法中的 Injected DAO 非常有效

由于您控制着 DAO 类,我认为将字符串动态转换为 bean 的最简单解决方案是使用具有绑定成员的 CDI 限定符(参见 CDI 2.0 规范第 5.2.6 节) .

所以你已经拥有:

public abstract class ExtendedBaseDAO<T extends BaseEntity, ID extends Serializable> extends BaseDao<T, ID>{ {
    ...
}

@ApplicationScoped
public class WizardFieldConfigDAO extends ExtendedBaseDAO<...> {
    ...
}

@ApplicationScoped
public class OtherDAO extends ExtendedBaseDAO<...> {
    ...
}

使用绑定成员定义限定符注释:

@Qualifier
@Retention(RUNTIME)
@Target({METHOD, FIELD, PARAMETER, TYPE})
public @interface MyDao {
    String tableName();
}

将其添加到您的 bean 中:

@MyDao(tableName="other")
@ApplicationScoped
public class WizardFieldConfigDAO extends ExtendedBaseDAO<...> {...}

@MyDao(tableName="other")
@ApplicationScoped
public class OtherDAO extends ExtendedBaseDAO<...> {...}

创建实用注释文字:

import javax.enterprise.util.AnnotationLiteral;

public class MyDaoQualifier extends AnnotationLiteral<MyDao> implements MyDao {
    private String tableName;

    public MyDaoQualifier(String tableName) {
        this.tableName = tableName;
    }

    @Override
    public String tableName() {
        return tableName;
    }
}

喜欢它:

@Inject @Any
private Instance<ExtendedBaseDAO<?,?>> instance;

public ExtendedBaseDAO getCorrespondingDAO(String tableName) throws DAOException {
    try {
        return instance.select(new MyDaoQualifier(tableName)).get();
    } catch (ResolutionException re) {
        throw new DAOException(re);
    }
}

Here 是一篇很好的文章,描述了这一点。

当心 动态创建 @Dependent 作用域的 beans,参见 this!!! TL;DR:最好为您的 DAO 定义一个明确的正常范围(例如 @ApplicationScoped)。