是否可以在运行时动态实例化 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
)。
所以我环顾四周但仍然找不到任何可以帮助我的东西。所以这是我的代码:
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
@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
)。