I have a database with several different, but related tables:
class AccountInfo {
string id;
string name;
string email;
}
class ExtraInfo {
string id;
string proxy;
}
class UserInfo {
AccountInfo account;
ExtraInfo extra;
}
public class MyDbContext : DbContext {
public DbSet<AccountInfo> AccountInfo { get; set; }
public DbSet<ExtraInfo> ExtraInfo { get; set; }
}
public class ExtraInfoController : ODataController
{
private readonly DS2DbContext _context;
private readonly ILogger<UserInfoController> _logger;
public ExtraInfoController(DS2DbContext context) {
_context = context;
}
[EnableQuery(PageSize = 15)]
public IQueryable<UserInfo> Get() {
IQueryable<UserInfo> query =
from a in _context.AccountInfo
from x in _context.ExtraInfo
where (a.id == x.id)
select new UserInfo() { account = a, extra = x };
return query;
}
The query in ExtraInfoController.Get() works, but the result cannot be transferred back to the calling code, because the DbSet is declared for type ExtraInfo and I guess because of the way Blazor mangles all the pseudo code passed to it into working code, so it expects the return type to be IQueryable and not IQeryable.
I am new to EF core, so I don't know how to create a controller that is not directly related to a db table that would do the desired join and return an IQueryable without there being a UserInfo table in the db.
Taking a reference from link: Postgres to fetch the list having comma separated values, I want to write a query which somhow brings Employee email Table fields as a list for a particular Empployee. This is needed for Spring Batch to Simply match it from the Resultset and create a POJO/Model class like List emails for Employee class?
Can this be possible ?
select c.*, ce.*, string_agg(ce.email, ',') as emails
from root.employee c
full outer join root.employee_email ce
on c.employee_id = ce.employee_id
group by
c.employee_id, ce.employee_email_id
order by
c.employee_id
limit 1000
offset 0;
Your problem is a common one in the batch processing realm and with Spring Batch it is called "Driving Query Based ItemReaders", you can find more about that in here.
Basically you retrieve the Contacts in your reader, and in your processor you add the list of Emails to them.
#Bean(destroyMethod = "")
public JdbcCursorItemReader<Employee> employeeReader(DataSource dataSource) {
JdbcCursorItemReader<Employee> ItemReader = new JdbcCursorItemReader<>();
ItemReader.setDataSource(dataSource);
ItemReader.setSql("SELECT * FROM employee.employee C ");
ItemReader.setRowMapper(new EmployeeRowMapper());
return ItemReader;
}
#Bean
public ItemProcessor<Employee, Employee> settlementHeaderProcessor(JdbcTemplate jdbcTemplate){
return item -> {
root.EMPLOYEE_EMAIL CE WHERE ce.employee_id = ? ",
new Object[]{item.getId()},
new RowMapper<String>() {
#Override
public String mapRow(ResultSet resultSet, int i) throws SQLException {
return resultSet.getString("EMAIL");
}
});
item.setEmails(emails);
return item;
};
}
PS : this could have some performance issues if you have lots of contacts, because for each contact Item you will hit the database to retrieve Emails.
There is another optimized way, by creating a custom reader that will return a List of Contacts (For example 1000 by 1000), and a processor that will enrich them with their emails. This way you will hit the database again for each 1000 Contact Item.
In your reader your retrieve a list of unique Employees page per page (Say your page is 1000 long).
And in your processor for the 1000 employees you retrieve all their emails in one query.
Then for each employee you set the emails retrieved in the last query.
An example might like the following:
public interface EmployeeRepository extends PagingAndSortingRepository<Employee, Integer> {
}
#Getter
class EmployeeVO {
private Long employeeId;
private String email;
EmployeeVO(Long employeeId, String email) {
this.employeeId= employeeId;
this.email = email;
}
}
public class EmployeeListReader implements ItemReader<List<Employee>> {
private final static int PAGE_SIZE = 1000;
private final EmployeeRepository employeeRepository;
private int page = 0;
public EmployeeListReader(EmployeeRepository employeeRepository) {
this.employeeRepository = employeeRepository;
}
public List<Employee> read() throws Exception {
Page<Employee> employees = employeeRepository.findAll(PageRequest.of(page, PAGE_SIZE));
page++;
return employees.getContent();
}
}
#Bean
EmployeeListReader reader(){
return new EmployeeListReader(this.employeeRepository);
}
#Bean
public ItemProcessor<List<Employee>, List<Employee>> settlementHeaderProcessor(NamedParameterJdbcTemplate namedParameterJdbcTemplate) {
return item -> {
List<Long> employeesIds = item.stream().map(Employee::getId).collect(Collectors.toList());
SqlParameterSource parameters = new MapSqlParameterSource("ids", employeesIds);
List<ContactVO> emails = namedParameterJdbcTemplate
.query("SELECT CE.employeeId, CE.EMAIL FROM employee_EMAIL CE WHERE ce.contact_id IN (:ids) ",
parameters,
new RowMapper<ContactVO>() {
#Override
public ContactVO mapRow(ResultSet resultSet, int i) throws SQLException {
return new ContactVO(
resultSet.getLong("EMPLOYEE_ID"),
resultSet.getString("EMAIL"));
}
});
Map<Long, List<ContactVO>> emailsByContactId = emails.stream().collect(Collectors.groupingBy(ContactVO::getContactId));
List<Employee> newEmployeesWithEmails = Collections.unmodifiableList(item);
newEmployeesWithEmails.forEach(employee -> {
employee.setEmails(emailsByContactId.get(employee.getId()).stream().map(ContactVO::getEmail).collect(Collectors.toList()));
});
return newEmployeesWithEmails;
};
}
Hope this helps
How to write OneToMany relation in jpa entity class using javafx listproperty
I tried this:
private final ListProperty<Bill> bills = new SimpleListProperty<>();
#OneToMany(targetEntity = Bill.class, mappedBy = "invoice")
public ObservableList getBills() {
return bills.get();
}
public void setBills(ObservableList value) {
bills.set(value);
}
public ListProperty billsProperty() {
return bills;
}
getting error : " oneTomany attribute type should be [java.util.colletion, java.util.List]
Simply use List rather than ObservableList from the getter and setter functions.
private final ListProperty<Bill> bills = new SimpleListProperty<>();
#OneToMany(targetEntity = Bill.class, mappedBy = "invoice")
public List getBills() {
return bills.get();
}
public void setBills(List value) {
bills.set(FXCollections.observableArrayList(value));
}
public ListProperty billsProperty() {
return bills;
}
I have a provider:user=1:N association modeled with entities, Hibernate/JPA.
Then I want to query a provider/user pair via restrictions on attributes of the dependent entity, like certain values for the attributes of the user, let’s say its id, date of birth, etc.
The logged sql has a proper join and all the attributes of the two entities in the select. I tried it manually, it returns the expected single row.
Thus, on entity level, I expect a single provider entity to be returned with the user list containing the queried user.
Indeed, the corresponding provider entity is returned, but when I then want to access the user via the provider’s user list, it hits the DB a second time and reads all users of the provider totally neglecting my restrictions of the query.
The observed behavior is the same for queries formulated with HQL, Hibernate Criteria (also with #Filter), JPA CriteriaBuilder.
What am I missing here?
Do those restrictions only affect the selection of the root entities (which is provider in my case)?
The problem is sketched on
https://docs.jboss.org/hibernate/orm/3.3/reference/en/html/querycriteria.html
Under 5.4 Associations it says:
The kittens collections held by the Cat instances returned by the previous two queries are not pre-filtered by the criteria. If you want to retrieve just the kittens that match the criteria, you must use a ResultTransformer.
Is this thus the intended behavior for this kind of API?
Or is there a convenient possibility to access just the restricted sub set of the dependent entities?
Regards,
Wolfgang
Here comes some source code to precise my verbal description above.
Provider and User table.
drop table EX_USER;
drop table EX_PROVIDER;
create table EX_PROVIDER
( id number(*,0) not null
,name varchar2(255) not null
,primary key (id)
);
insert into EX_PROVIDER (id,name) values (0 ,'Provider_A');
insert into EX_PROVIDER (id,name) values (1 ,'Provider_B');
commit;
create table EX_USER
( id number(*,0) not null
, ex_provider_id number(*,0) not null
,name varchar2(255)
,location varchar2(255)
,primary key (id)
,constraint ex_user_provider_fk foreign key(ex_provider_id) references EX_PROVIDER(id)
);
insert into EX_USER (id,ex_provider_id,name,location) values (0,0,'User_1','Munich');
insert into EX_USER (id,ex_provider_id,name,location) values (1,0,'User_2','Berlin');
insert into EX_USER (id,ex_provider_id,name,location) values (2,1,'User_3','Munich');
commit;
Entities generated with Eclipse "JPA Tools".
#Entity
#Table(name="EX_PROVIDER")
#NamedQuery(name="ExProvider.findAll", query="SELECT e FROM ExProvider e")
public class ExProvider implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#SequenceGenerator(name="EX_PROVIDER_ID_GENERATOR", sequenceName="KONST_SD_SEQ")
#GeneratedValue(strategy=GenerationType.SEQUENCE, generator="EX_PROVIDER_ID_GENERATOR")
private long id;
private String name;
//bi-directional many-to-one association to ExUser
#OneToMany(mappedBy="exProvider",fetch=FetchType.LAZY)
private Set<ExUser> exUsers=new HashSet<ExUser>();
public ExProvider() {
}
public long getId() {
return this.id;
}
public void setId(long id) {
this.id = id;
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public Set<ExUser> getExUsers() {
return this.exUsers;
}
public void setExUsers(Set<ExUser> exUsers) {
this.exUsers = exUsers;
}
public ExUser addExUser(ExUser exUser) {
getExUsers().add(exUser);
exUser.setExProvider(this);
return exUser;
}
public ExUser removeExUser(ExUser exUser) {
getExUsers().remove(exUser);
exUser.setExProvider(null);
return exUser;
}
}
#Entity
#Table(name="EX_USER")
#NamedQuery(name="ExUser.findAll", query="SELECT e FROM ExUser e")
public class ExUser implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#SequenceGenerator(name="EX_USER_ID_GENERATOR", sequenceName="KONST_SD_SEQ")
#GeneratedValue(strategy=GenerationType.SEQUENCE, generator="EX_USER_ID_GENERATOR")
private long id;
private String location;
private String name;
//bi-directional many-to-one association to ExProvider
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name="EX_PROVIDER_ID")
private ExProvider exProvider;
public ExUser() {
}
public long getId() {
return this.id;
}
public void setId(long id) {
this.id = id;
}
public String getLocation() {
return this.location;
}
public void setLocation(String location) {
this.location = location;
}
public String getName() {
return this.name;
}
public void setName(String name) {
this.name = name;
}
public ExProvider getExProvider() {
return this.exProvider;
}
public void setExProvider(ExProvider exProvider) {
this.exProvider = exProvider;
}
}
My intention with the below code was to retrieve Provider 0 (Provider_A) containing User 1 (Berlin) in its user list.
#SuppressWarnings("unchecked")
private void demo() {
EntityManager em = null;
try {
em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
try {
tx.begin();
Session session = (Session) em.getDelegate();
Criteria crit = session.createCriteria(ExProvider.class, "provider")
.createCriteria("exUsers", "user")
.add(Restrictions.eq("user.location","Berlin"))
;
List<ExProvider> providerList=(List<ExProvider>)crit.list();
logExProviderList(providerList);
tx.commit();
} finally {
if (tx!=null && tx.isActive()) tx.rollback();
}
} finally {
if (em!=null) em.close();
}
}
private void logExProviderList(List<ExProvider> providerList) {
for (ExProvider provider: providerList) {
logger.info("Criteria: provider=["+provider.getId()+"]");
for (ExUser user : provider.getExUsers()) {
logger.info("Criteria: user=["+user.getId()+"] name=["+user.getName()+"] location=["+user.getLocation()+"]");
}
}
}
The 1st SQL is what I expected. It is executed when crit.list() is called. On SQL level it returns the expected single row for Provider 0, User 1.
SELECT this_.id AS id43_1_,
this_.name AS name43_1_,
user1_.id AS id44_0_,
user1_.EX_PROVIDER_ID AS EX4_44_0_,
user1_.location AS location44_0_,
user1_.name AS name44_0_
FROM EX_PROVIDER this_
INNER JOIN EX_USER user1_
ON this_.id=user1_.EX_PROVIDER_ID
WHERE user1_.location=?;
However, this is not mapped to entity level as I expected. There, the restriction seems to affect the selection of the Provider only. When the user list is accessed, all users of the provider are read from DB neglecting the 'Berlin' restriction.
This was the same whether I used HQL, Hibernate Criteria (also with #Filter), JPA CriteriaBuilder.
SELECT exusers0_.EX_PROVIDER_ID AS EX4_43_1_,
exusers0_.id AS id1_,
exusers0_.id AS id44_0_,
exusers0_.EX_PROVIDER_ID AS EX4_44_0_,
exusers0_.location AS location44_0_,
exusers0_.name AS name44_0_
FROM EX_USER exusers0_
WHERE exusers0_.EX_PROVIDER_ID=?;
The log.
Criteria: provider=[0]
Criteria: user=[1] name=[User_2] location=[Berlin]
Criteria: user=[0] name=[User_1] location=[Munich]
I may achieve the expected result set using setResultTransformer(), but in this case the properly selected result is returned as some rows of entities.
#SuppressWarnings("unchecked")
private void demo() {
EntityManager em = null;
try {
em = emf.createEntityManager();
EntityTransaction tx = em.getTransaction();
try {
tx.begin();
Session session = (Session) em.getDelegate();
Criteria crit = session.createCriteria(ExProvider.class, "provider")
.createCriteria("exUsers", "user")
.add(Restrictions.eq("user.location","Berlin"))
.setResultTransformer(Criteria.ALIAS_TO_ENTITY_MAP);
;
List<ExProvider> providerList=(List<ExProvider>)crit.list();
logExProviderMapList(providerList);
tx.commit();
} finally {
if (tx!=null && tx.isActive()) tx.rollback();
}
} finally {
if (em!=null) em.close();
}
}
private void logExProviderMapList(List providerList) {
Iterator iter = providerList.iterator();
while ( iter.hasNext() ) {
Map map = (Map) iter.next();
ExProvider provider = (ExProvider) map.get("provider");
if(provider!=null) {
logger.info("Criteria: provider=["+provider.getId()+"]");
}
ExUser user = (ExUser) map.get("user");
if(user!=null) {
logger.info("Criteria: user=["+user.getId()+"] name=["+user.getName()+"] location=["+user.getLocation()+"]");
}
}
}
The SQL is the same as the 1st SQL above.
SELECT this_.id AS id43_1_,
this_.name AS name43_1_,
user1_.id AS id44_0_,
user1_.EX_PROVIDER_ID AS EX4_44_0_,
user1_.location AS location44_0_,
user1_.name AS name44_0_
FROM EX_PROVIDER this_
INNER JOIN EX_USER user1_
ON this_.id=user1_.EX_PROVIDER_ID
WHERE user1_.location=?;
The log.
Criteria: provider=[0]
Criteria: user=[1] name=[User_2] location=[Berlin]
My question was and is, whether it is possible to get this result as proper entity tree with Provider_A containing exactly the User from Berlin in its list.
Thanks again,
Wolfgang B
I have a model called 'UserRoleHolder' like below.
#Entity
public class UserRoleHolder extends Model implements RoleHolder {
private static final long serialVersionUID = 1L;
#EmbeddedId
public UserRoleHolderPK userRoleHolderPK;
public List<UserPermission> permissions;
public List<UserRole> roles;
....
I made a composite PK called UserRoleHolderPK and it contains two foreign keys like below.
#Embeddable
public class UserRoleHolderPK {
#Basic
public Long userId;
#Basic
public Long projectId;
public UserRoleHolderPK(Long userId, Long projectId) {
this.userId = userId;
this.projectId = projectId;
}
public boolean equals(Object object) {
if (object instanceof UserRoleHolderPK) {
UserRoleHolderPK userRoleHolderPK = (UserRoleHolderPK) object;
return userId == userRoleHolderPK.userId && projectId == userRoleHolderPK.projectId;
} else {
return false;
}
}
public int hashCode() {
return (int) (userId + projectId);
}
}
userId and projectId are from other Models. (User.java and Project.java)
Then, in 'UserRoleHolder' class, I made a method called 'findRolesById' like below.
public static List<? extends Role> findRolesById(Long userId, Long projectId) {
return find
.where()
.eq("userRoleHolderPK", new UserRoleHolderPK(userId, projectId))
.findUnique().roles;
}
However, when I tried to run a test code like below, I encountered serious errors.
#Test
public void findRolesById() {
// Given
// When
#SuppressWarnings("unchecked")
List<UserRole> list = (List<UserRole>) UserRoleHolder.findRolesById(1l, 1l);
// Then
assertThat(list.get(0).name).isEqualTo("manager");
}
Errors are like,
'Syntax error in SQL statement "SELECT T0.USER_ID C0, T0.PROJECT_ID C1 FROM USER_ROLE_HOLDER T0 WHERE T0.NULL[*] = ? "; expected "identifier"; SQL statement: select t0.user_id c0, t0.project_id c1 from user_role_holder t0 where t0.null = ? [42001-158]
Bind values:[null]
Query was:
select t0.user_id c0, t0.project_id c1 from user_role_holder t0 where t0.null = ?
I think I missed some serious and basic stuff when I used JPA. Please, let me know what is the problem.
I think your problem is that you are trying to compare the Embeddedid object and not its fields, I don't think that the program will be smart enough as to know how to convert an user object comparison (the equals) to sql, so you might want to try something like this:
public static List<? extends Role> findRolesById(Long userId, Long projectId) {
return find
.where()
.eq("userRoleHolderPK.userId", userId)
.eq("userRoleHolderPK.projectId", projectId)
.findUnique().roles;
}