I have a datasource defined on a browse screen with a query that contains a UNION construct from the same entity as following:
<dsContext>
<groupDatasource id="employeesGroupsDs"
class="com.company.vp.entity.EmployeesGroup"
view="employeesGroup-view-setup">
<query>
<![CDATA[select e from vp$EmployeesGroup e]]>
</query>
</groupDatasource>
<groupDatasource id="workSchedulesDs"
class="com.company.vp.entity.WorkSchedule"
view="workSchedule-view_employeesGroup">
<query>
<![CDATA[ select e from vp$WorkSchedule e where e.employeesGroup.id = :ds$employeesGroupsDs and (current_date between e.startDate and coalesce(e.endDate, current_date))
union
select e from vp$WorkSchedule e where e.country.id = :ds$employeesGroupsDs.country.id and (current_date between e.startDate and coalesce(e.endDate, current_date))]]>
</query>
</groupDatasource>
</dsContext>
while opening the screen and upon selecting a parent record it shows the following error:
IllegalStateException: Query argument ds_employeesGroupsDs_country_id not found in the list of parameters provided during query execution.
when i tried every single select in the union separately it works, that makes me think the problem is with the UNION, but eclipse link should be supporting unions, right?
After A lot of debugging for the reason, it seems that only one reference to the related DS can be used -even multiple times-, so for example i can use the following and it will work fine but of course it will retrieve wrong records:
select e from vp$WorkSchedule e where e.employeesGroup.id = :ds$employeesGroupsDs.id and (current_date between e.startDate and coalesce(e.endDate, current_date))
union
select e2 from vp$WorkSchedule e2 where e2.country.id = :ds$employeesGroupsDs.id and (current_date between e2.startDate and coalesce(e2.endDate, current_date))
so i can either refer to :ds$employeesGroupsDs.id and use the same reference as many times in my query as i want or i can use :ds$employeesGroupsDs.country.id and use the same reference as many times as i want, but if used both as i want to it results in only the first one in the bind list.
i think this is a bug. i appreciate if any one can help me with an alternative, my goal is to select a single record based on the unions so in my original query if the first select in the union retrieved a record then it will be showed otherwise i will show the second record retrieved from the second select of the union.
create a service with a function that returns a list of the type you want to display, the service will be responsible to get the information you want from the database for example:
@Service(WorkScheduleService.NAME)
public class WorkScheduleServiceBean implements WorkScheduleService {
@Inject
Persistence persistence;
@Inject
VPDateUtilsService vpDateUtilsService;
//returns specific work schedules linked to employees group or empty list if none found
@Transactional(readOnly = true)
@Override
public List<WorkSchedule> getEmployeesGroupWorkSchedules(EmployeesGroup employeesGroup){
EntityManager entityManager = persistence.getEntityManager();
TypedQuery<WorkSchedule> query = entityManager.createQuery("select wrks from vp$WorkSchedule wrks where wrks.employeesGroup.id = :employeesGroupId", WorkSchedule.class);
query.setViewName("workSchedule-view_employeesGroup");
query.setParameter("employeesGroupId", employeesGroup.getId());
return query.getResultList();
}
@Transactional(readOnly = true)
@Override
public List<WorkSchedule> getEmployeeWorkSchedules(Employee employee) {
EntityManager entityManager = persistence.getEntityManager();
TypedQuery<WorkSchedule> query = entityManager.createQuery("select wrks from vp$WorkSchedule wrks where wrks.employee.id = :employeeId", WorkSchedule.class);
query.setViewName("workSchedule-view_employee");
query.setParameter("employeeId", employee.getId());
return query.getResultList();
}
//returns sector work schedules linked to employees group sector or empty list if none found
@Transactional(readOnly = true)
@Override
public List<WorkSchedule> getSectorWorkSchedules(EmployeesGroup employeesGroup){
EntityManager entityManager = persistence.getEntityManager();
TypedQuery<WorkSchedule> query = entityManager.createQuery("select wrks from vp$WorkSchedule wrks where wrks.employeesGroup is null and wrks.sector.id = :employeesGroupSectorId", WorkSchedule.class);
query.setViewName("workSchedule-view_employeesGroup");
query.setParameter("employeesGroupSectorId", employeesGroup.getSector().getId());
return query.getResultList();
}
@Transactional(readOnly = true)
@Override
public List<WorkSchedule> getAllWorkSchedules(EmployeesGroup employeesGroup) {
List<WorkSchedule> list = new ArrayList<>();
list.addAll(getEmployeesGroupWorkSchedules(employeesGroup));
list.addAll(getSectorWorkSchedules(employeesGroup));
return list;
}
@Transactional(readOnly = true)
@Override
public List<WorkSchedule> getAllWorkSchedules(Employee employee) {
List<WorkSchedule> list = new ArrayList<>();
list.addAll(getEmployeesGroupWorkSchedules(employee.getEmployeesGroup()));
list.addAll(getSectorWorkSchedules(employee.getEmployeesGroup()));
list.addAll(getEmployeeWorkSchedules(employee));
return list;
}
}
have a look at getAllWorkSchedules function where the List.addAll is the alternative of the SQL union.
create a custom data source in the same package as your screen (or a difference package depends on how you want to organize your code), for example:
finally you need to pass a parameter from the browse screen to your datasource refresh method, for example:
public class EmployeesGroupBrowse extends AbstractLookup {
@Inject
private GroupDatasource<EmployeesGroup, UUID> employeesGroupsDs; //this is the master data source that will refresh the custom datasource when it's selected value changes
@Inject
private WorkScheduleDatasource workSchedulesDs;
@Override
public void init(Map<String, Object> params) {
employeesGroupsDs.addItemChangeListener(event -> {
workSchedulesDs.refresh(ParamsMap.of("employeesGroup", employeesGroupsDs.getItem()));
});
}
}