Nhibernate generating OUTER JOIN for a fetch - linq

Despite setting up mapping to be Not.Nullable() and Not.LazyLoad()
For some reason NH is joining a table twice, once with a INNER JOIN to appease the WHERE, and secondly on a OUTER JOIN to select the data.
Surely, as we've already JOINED the data, it would make sense to just use the joined table...
SELECT
...Tables..
from Tasks taskentity0_,
outer Cases caseentity1_,
outer Grades gradeentit2_,
Cases caseentity5_
WHERE
....
My LINQ query for this is:
IQueryable<TaskEntity> tasks = TaskRepo.Find(
t => t.DueDate <= DateTime.Now
&& (t.TaskInitials == userInitials || (t.TaskInitials == "" || t.TaskInitials == null))
&& t.Team.GST.Any
(x => x.Initials == userInitials
&& x.WorkType.WorkTypeCode == t.WorkType.WorkTypeCode
&& x.Team.TeamCode == t.Team.TeamCode
)
&& (t.Case.CaseOnHold <= DateTime.Now || t.Case.CaseOnHold == null || (t.SingleTask == "M" || t.SingleTask == "m"))
&& (t.Case.CaseMatter.StartsWith("0") || t.Case.CaseMatter.StartsWith("9"))
).Fetch(t => t.Case,FetchProvider)
My Reference Mapping:
References(x => x.Case).Column("ta_c_ref").Not.Nullable();
Thoughts?
We are using the repository pattern, and have reimplemented the Fetch extension method to work this way (Hence passing the FetchProvider in).
Also, QueryOver<T> is not an option here as we require IQueryables..
I am using NH 3.1.
For the masses:
We no longer use the Fetch or LINQ, we moved to HQL...
/// <summary>
/// Interfaces for Fetch() statements
/// </summary>
public interface IFetchingProvider
{
IFetchRequest<TOriginating, TRelated> Fetch<TOriginating, TRelated>(IQueryable<TOriginating> query, Expression<Func<TOriginating, TRelated>> relatedObjectSelector);
IFetchRequest<TOriginating, TRelated> FetchMany<TOriginating, TRelated>(IQueryable<TOriginating> query, Expression<Func<TOriginating, IEnumerable<TRelated>>> relatedObjectSelector);
IFetchRequest<TQueried, TRelated> ThenFetch<TQueried, TFetch, TRelated>(IFetchRequest<TQueried, TFetch> query, Expression<Func<TFetch, TRelated>> relatedObjectSelector);
IFetchRequest<TQueried, TRelated> ThenFetchMany<TQueried, TFetch, TRelated>(IFetchRequest<TQueried, TFetch> query, Expression<Func<TFetch, IEnumerable<TRelated>>> relatedObjectSelector);
}
public class NhFetchingProvider : IFetchingProvider
{
public IFetchRequest<TOriginating, TRelated> Fetch<TOriginating, TRelated>(IQueryable<TOriginating> query, Expression<Func<TOriginating, TRelated>> relatedObjectSelector)
{
var fetch = EagerFetchingExtensionMethods.Fetch(query, relatedObjectSelector);
return new FetchRequest<TOriginating, TRelated>(fetch);
}
public IFetchRequest<TOriginating, TRelated> FetchMany<TOriginating, TRelated>(IQueryable<TOriginating> query, Expression<Func<TOriginating, IEnumerable<TRelated>>> relatedObjectSelector)
{
var fecth = EagerFetchingExtensionMethods.FetchMany(query, relatedObjectSelector);
return new FetchRequest<TOriginating, TRelated>(fecth);
}
public IFetchRequest<TQueried, TRelated> ThenFetch<TQueried, TFetch, TRelated>(IFetchRequest<TQueried, TFetch> query, Expression<Func<TFetch, TRelated>> relatedObjectSelector)
{
var impl = query as FetchRequest<TQueried, TFetch>;
var fetch = EagerFetchingExtensionMethods.ThenFetch(impl.NhFetchRequest, relatedObjectSelector);
return new FetchRequest<TQueried, TRelated>(fetch);
}
public IFetchRequest<TQueried, TRelated> ThenFetchMany<TQueried, TFetch, TRelated>(IFetchRequest<TQueried, TFetch> query, Expression<Func<TFetch, IEnumerable<TRelated>>> relatedObjectSelector)
{
var impl = query as FetchRequest<TQueried, TFetch>;
var fetch = EagerFetchingExtensionMethods.ThenFetchMany(impl.NhFetchRequest, relatedObjectSelector);
return new FetchRequest<TQueried, TRelated>(fetch);
}
}
public static IFetchRequest<TOriginating, TRelated> Fetch<TOriginating, TRelated>(this IQueryable<TOriginating> query, Expression<Func<TOriginating, TRelated>> relatedObjectSelector, Func<IFetchingProvider> FetchingProvider)
{
return FetchingProvider().Fetch(query, relatedObjectSelector);
}

I've added support for it!!
https://www.nuget.org/packages/NHibernate.Linq.InnerJoinFetch
Enjoy!!!

Using inner joins with linq in NHibernate isn't supported yet. more info can be found here: https://nhibernate.jira.com/browse/NH-2790

Related

Spring data jpa "IN" clause using specification

i am trying to get the records from the database using spring data jpa Speicification API.
here i need to put a condition for "In" clause for status column, for that i am code like below.
public static Specification<UserEntity> userSpecificationsforOperator(String orgName, String groupID,
List<String> status, Date startDate, Date endDate) {
return (root, query, builder) -> {
List<Predicate> predicates = new ArrayList<>();
if (orgName != null) {
Join<UserEntity, OrganizationEntity> organization = root.join("organization");
predicates.add(builder.equal(organization.get("name"), orgName));
}
/*
* if (groupID != null) {
* predicates.add(builder.equal(root.get("refApprovalGroupId"), groupID)); }
*/
if (status != null && status.size()>0) {
predicates.add(root.get("status").in(status));
}
if (startDate != null) {
predicates.add(builder.greaterThanOrEqualTo(root.get("createdDate"), startDate.toInstant()));
}
if (endDate != null) {
predicates.add(builder.lessThanOrEqualTo(root.get("createdDate"), endDate.toInstant()));
}
Predicate[] p = predicates.toArray(new Predicate[predicates.size()]);
return p.length == 0 ? null : p.length == 1 ? p[0] : builder.and(p);
};
}
the above code generating the query in cosole like below
SELECT userentity0_.id AS id1_68_,
userentity0_.created_by AS created_2_68_,
userentity0_.created_date AS created_3_68_,
userentity0_.last_modified_by AS last_mod4_68_,
userentity0_.last_modified_date AS last_mod5_68_,
userentity0_.group_id AS group_id6_68_,
userentity0_.group_name AS group_na7_68_,
userentity0_.is_enrollment_updated AS is_enrol8_68_,
userentity0_.is_federated AS is_feder9_68_,
userentity0_.name AS name10_68_,
userentity0_.organization_id AS organiz17_68_,
userentity0_.ref_approval_group_id AS ref_app11_68_,
userentity0_.reference_name AS referen12_68_,
userentity0_.status AS status13_68_,
userentity0_.uims_id AS uims_id14_68_,
userentity0_.user_status AS user_st15_68_,
userentity0_.version AS version16_68_
FROM user userentity0_
INNER JOIN organization organizati1_
ON userentity0_.organization_id = organizati1_.id
WHERE organizati1_.name ='utopia'
AND ( userentity0_.status =(?,?)
when i take the query into db tool and passing the values i am getting the data.
but while running from the application i am not getting the data.
here i understood that i am able to generate the query properly but my values are not passing correctly.
so could you please suggest how i can get my code return the data.
Maybe the implementation of it causes the issue...
Here, use this and let me know if it works
if (status != null && status.size()>0) {
predicates.add(builder.in(root.get("status")).value(status));
}

duplicate result of criteria Api in join query using JpaSpecification

i am new to criteria api, so i have two models: GithubRepository and Issue where GithubReposity has many issue and every issue has one GithubRepository.
I have created many filters but the filter that did not work is the filter for GithubRepository where i want to check if a githubRepository has issues or Not.
after executing and applying has issues unfortunately something wrong happened and i see many duplicates.
here what i did in RepoSpecification:
public Specification<GithubRepository> getReposByIdIn(List<Long> iDs, String repoName) {
return (root, query, criteriaBuilder) -> {
List<Predicate> predicates = new ArrayList<>();
if (repoName != null && !repoName.isEmpty()) {
predicates.add(criteriaBuilder.like(criteriaBuilder.lower(root.get("repositoryName")),
"%" + repoName.toLowerCase() + "%"));
}
if (iDs != null && !iDs.isEmpty()) {
predicates.add( criteriaBuilder.in(root.get("id")).value(iDs) );
}
query.orderBy(criteriaBuilder.asc(root.get("repositoryName")));
return criteriaBuilder.and(predicates.toArray(new Predicate[0]));
};
}
public Specification<GithubRepository> getRepos(Boolean hasIssues,Boolean IsPrivate , String repoName) {
return (root, query, criteriaBuilder) -> {
List<Predicate> predicates = new ArrayList<>();
if (repoName != null && !repoName.isEmpty()) {
predicates.add(criteriaBuilder.like(criteriaBuilder.lower(root.get("repositoryName")),
"%" + repoName.toLowerCase() + "%"));
}
if ( hasIssues!= null && hasIssues.equals(true)) {
// JOINS INSTANCE
Join<GithubRepository, Issue> repoIssueJoin = root.join("issues");
//Predicate predicate = criteriaBuilder.equal(repoIssueJoin.get("githubRepository").get("repositoryName"),
repoName);
//predicates.add(criteriaBuilder.isNotNull(repoIssueJoin));
predicates.add(criteriaBuilder.equal(repoIssueJoin.get("githubRepository"),root));
}
if (IsPrivate != null && IsPrivate.equals(true)) {
predicates.add(criteriaBuilder.equal(root.get("isPrivate"), true));
}
query.orderBy(criteriaBuilder.asc(root.get("repositoryName")));
return criteriaBuilder.and(predicates.toArray(new Predicate[0]));
};
}
}
my problem is in here exactly:
if ( hasIssues!= null && hasIssues.equals(true)) {
// JOINS INSTANCE
Join<GithubRepository, Issue> repoIssueJoin = root.join("issues");
//Predicate predicate = criteriaBuilder.equal(repoIssueJoin.get("githubRepository").get("repositoryName"),
repoName);
//predicates.add(criteriaBuilder.isNotNull(repoIssueJoin));
predicates.add(criteriaBuilder.equal(repoIssueJoin.get("githubRepository"),root));
}
i do not know what is wrong exactly and how to fix the problem but i am stacked with it because i am planning to add a new filter to filter greatOrEqual by number of issues

Querydsl - filter on Left join with subquery

I have one of the complex query dynamically generated through Querydsl predicate and JPQL. I am also using the Q classes.
I am able to generate the following query by passing a predicate to the JPA repository.
select company0_.id as id1_18_,
company0_.name as name2_18_
from company company0_
left outer join companyAddress companyadd1_ on company0_.id=companyadd1_.company_id
where company0_.id in
(select companyadd2_.company_id
from companyAddress companyadd2_
where companyadd2_.address_type='1')
order by companyadd1_.addressline1;
but I want the query mentioned below
select company0_.id as id1_18_,
company0_.name as name2_18_
from company company0_
left outer join companyAddress companyadd1_ on company0_.id=companyadd1_.company_id
and companyadd1_.status = 'Active' -- New added(Failed to implement this)
where company0_.id in
(select companyadd2_.company_id
from companyAddress companyadd2_
where companyadd2_.address_type='1'
and and companyadd2_.status = 'Active') -- New Added(I am able to achieve this)
order by companyadd1_.addressline1;
We are using following kind of code, I can not possibly to share exact code due to security concern but you can help me by providing basic structure or code to achieve this.
final JPQLQuery<QCompanyAlias> subQuery = new JPAQuery<>();
BooleanExpression exp = null;
QueryBase<?> q = (QueryBase<?>) subQuery.from(qCompanyAddress);
if (requestMap.containsKey(CompanyQueryConstants.ADDRESS_TYPE)) {
BooleanExpression addrExp = null;
for (String addressType : addressTypes) {
if (addrExp == null) {
addrExp = qCompanyAddress.addressType.addressTypeCode.eq(addressType);
} else {
addrExp = addrExp.or(qCompanyAddress.addressType.addressTypeCode.eq(addressType));
}
}
exp = addrExp;
}
To add join on two conditions use
new JPAQuery(em)
.from(qCompany)
.leftJoin(qCompany, qCompanyAddress.company)
.on(
qCompany.id.eq(qCompanyAddress.company.id)
.and(qCompanyAddress.status.eq(status))
);
For subquery try to use this
final JPQLQuery<QCompanyAlias> subQuery = new JPAQuery<>();
BooleanExpression exp = null;
QueryBase<?> q = (QueryBase<?>) subQuery.from(qCompanyAddress);
if (requestMap.containsKey(CompanyQueryConstants.ADDRESS_TYPE)) {
BooleanExpression addrExp = null;
for (String addressType : addressTypes) {
if (addrExp == null) {
addrExp = qCompanyAddress.addressType.addressTypeCode.eq(addressType);
} else {
addrExp = addrExp.or(qCompanyAddress.addressType.addressTypeCode.eq(addressType));
}
}
exp = addrExp;
}
BooleanExpression statusExp = qCompanyAddress.status.eq(status);
if(exp == null) {
exp = statusExp;
} else {
exp = statusExp.and(exp);
}
But in your case I can't understand the reason for filtering by status twice. Subquery filtering should be enought. I suspect that you can achieve the same result without subquery. It depends on your entities.

How to select data in spring boot using jpa based on where condition of jsonb column of postgres(without native query)?

I have Postgres database with jsonb field. I have used Predicate list for where condition with criteria builder in jpa. Now, I want to fetch JSON data stored in a database based on multiple where cause along with JSON. How it could be possible to do?
Database table
private List<Predicate> whereClause(CriteriaBuilder criteriaBuilder, Root<KafkaLog> kafkaLog,
KafkaLogSearchDto search) {
List<Predicate> predicates = new ArrayList<>();
if (search.getTopic() != null && !search.getTopic().isEmpty()) {
Expression<String> literal =
criteriaBuilder.literal("%" + search.getTopic().toUpperCase() + "%");
predicates.add(criteriaBuilder.like(criteriaBuilder.upper(kafkaLog.get("topic")), literal));
}
if (search.getOffset() != null) {
predicates.add(criteriaBuilder.equal(kafkaLog.get("offset"), search.getOffset()));
}
if (search.getResourceId() != null) {
predicates.add(criteriaBuilder.equal(kafkaLog.get("invoiceId"), search.getResourceId()));
}
if (search.getPartition() != null) {
predicates.add(criteriaBuilder.equal(kafkaLog.get("partition"), search.getPartition()));
}
if (search.getStatus() != null) {
predicates.add(criteriaBuilder.equal(kafkaLog.get("status"), search.getStatus()));
}
if (search.getCreatedAtFrom() != null && search.getCreatedAtTo() != null) {
predicates.add(criteriaBuilder.and(
criteriaBuilder.greaterThanOrEqualTo(kafkaLog.get("createdAt").as(Date.class),
search.getCreatedAtFrom()),
criteriaBuilder.lessThanOrEqualTo(kafkaLog.get("createdAt").as(Date.class),
search.getCreatedAtTo())));
}
if (search.getPayload() != null && !search.getPayload().isEmpty()) {
Expression<String> literal =
criteriaBuilder.literal("%" + search.getPayload().toUpperCase() + "%");
}
return predicates;
}
private CriteriaQuery<KafkaLog> getKafkaBySearchCriteria(KafkaLogSearchDto search, String orderBy,
int sortingDirection) {
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<KafkaLog> criteriaQuery = criteriaBuilder.createQuery(KafkaLog.class);
Root<KafkaLog> kafkaLog = criteriaQuery.from(KafkaLog.class);
List<Predicate> predicates = whereClause(criteriaBuilder, kafkaLog, search);
criteriaQuery.where(predicates.toArray(new Predicate[] {}));
if (orderBy == null || orderBy.isEmpty()) {
orderBy = "topic";
}
Expression<?> orderColumn = kafkaLog.get(orderBy);
if (orderColumn != null) {
if (sortingDirection == 0) {
criteriaQuery.orderBy(criteriaBuilder.asc(orderColumn));
} else if (sortingDirection == 1) {
criteriaQuery.orderBy(criteriaBuilder.desc(orderColumn));
}
}
return criteriaQuery;
}
apparently, custom data type support down to the JDBC/database level is not part of JPA itself. For just reading the JSON you might be able to do it with an entity mapping to String (if the JPA implementation does allow that).
However, you have some alternatives:
If you need to stick with JPA, you can implement a converter to handle the JSON for you (either generic as in this article or more specific - as you like): Using JPA with PostgreSQL JSON
If you can, ditch JPA and do it with JDBC/SQL directly, which gives you the full potential of PostgreSQL: how to store PostgreSQL jsonb using SpringBoot + JPA?
When reading the JSONB field, you can let the database do the conversion to String (as there is no direct JSON support in JDBC, yet) using explicit type conversion, e.g.:
SELECT payload::text FROM my_table WHERE ...
...or do some fancy filtering on the JSON field itself or return only a portion of the JSON data, aso.
This is example predicate to search jsonb with like clause :
predicate.getExpressions().add(cb.and(cb.like(cb.function("jsonb_extract_path_text", String.class, root.get("tags"), cb.literal(this.key)), "%"+ this.value + "%" )));

automatically expand the result of an odata function

I defined an odata function to mimic $search which is not supported yet in the recent core release. I want to return the core entity plus an expanded entity which would translate into a js object on each Person in the returned json values array.
I tried odata/People/MyNS.Find(text='john', orderby='CreatedOn')?$expand=CurrentWork where CurrentWork is on People, but that did not work.
Thoughts on how to do this?
// my controller code for the function
[HttpGet]
public ActionResult<ICollection<People>> Find([FromODataUri] string text,
[FromODataUri] string orderBy)
{
if (text == null || text.Length == 0)
return Get().ToList();
if (orderBy == null || orderBy.Length == 0)
orderBy = "CreatedOn";
return _db.People
.Where(p => p.FirstName.Contains(text)
|| p.LastName.Contains(text)
|| p.Nickname.Contains(text))
.OrderBy(orderBy)
.Take(5000)
.ToList();
}
Regular expansion of CurrentWork in a non-function works fine e.g. odata/People?$expand=CurrentWork.
By looking at the Linq query, it's fetching only People data and not any of it's child collections. You should use Include to fetch data for child collections along with parent entity like below. Read more on loading related entities here.
// my controller code for the function
[HttpGet]
public ActionResult<ICollection<People>> Find([FromODataUri] string text,
[FromODataUri] string orderBy)
{
if (text == null || text.Length == 0)
return Get().ToList();
if (orderBy == null || orderBy.Length == 0)
orderBy = "CreatedOn";
return _db.People
.Where(p => p.FirstName.Contains(text)
|| p.LastName.Contains(text)
|| p.Nickname.Contains(text))
.Include(p => p.CurrentWork) // I have added this line
.OrderBy(orderBy)
.Take(5000)
.ToList();
}
Note: You still need to use $expand=CurrentWork as query string. Without this query string, server will remove child collections before sending response to client.
Here's what I came up with in the end. I noticed that the included entities were pulling in alot of data from the database so I reduced down the pull quite a bit by being specific. Include just pulled everything and I could not reduce the Include down directly so I had to use a Select.
[HttpGet]
public IOrderedQueryable Find2([FromODataUri] string text,
[FromODataUri] string orderBy)
{
if (orderBy == null || orderBy.Length == 0)
orderBy = "CreatedOn DESC";
if (text == null || text.Length == 0)
return Get().OrderBy(orderBy);
var r = LikeToRegular(text);
return _db.People
.AsNoTracking() // can't use if using lazy loading
.Select(p => new
{
p.FirstName,
p.LastName,
p.Nickname,
p.CreatedOn,
p.CurrentWork.Title,
p.CurrentWork.Company.CompanyName
})
// Forces local computation, so pulls entire people dataset :-(
.Where(x => Regex.IsMatch(x.LastName ?? "", r)
|| Regex.IsMatch(x.FirstName ?? "", r, RegexOptions.IgnoreCase)
|| Regex.IsMatch(x.Nickname ?? "", r, RegexOptions.IgnoreCase)
|| Regex.IsMatch($"{x.FirstName} {x.LastName}", r,
RegexOptions.IgnoreCase))
.OrderBy(orderBy);
}
// Allow some wildcards in the search...
public static String LikeToRegular(String value)
{
return "^" + Regex.Escape(value)
.Replace("_", ".")
.Replace("%", ".*") + "$";
}

Resources