Cannot index enum type with Hibernate Search MassIndexer - hibernate-search

#Entity
#Table(name = "asset")
#Indexed(index = "asset")
#Analyzer(impl = IKAnalyzer.class)
public class Asset {
#Column(name = "asset_name", length = 128, nullable = false)
#Field(name = "asset_name", index = Index.TOKENIZED, store = Store.YES)
private String assetName;
#Column(name = "type", length = 32, nullable = false)
#Enumerated(value = EnumType.STRING)
#Field(name = "type", index = Index.TOKENIZED, store = Store.YES)
private AssetTypeEnum type;
}
This is my configuration of hibernate search. The AssetTypeEnum is Enum type. I want to index the existed data in database. This is the code
Session session = sessionFactory.openSession();
FullTextSession fullTextSession = Search.getFullTextSession(session);
Transaction tx = fullTextSession.beginTransaction();
fullTextSession.createIndexer(Asset.class)
.batchSizeToLoadObjects(25)
.cacheMode(CacheMode.IGNORE)
.threadsToLoadObjects(5)
.threadsForSubsequentFetching(20)
.startAndWait();
tx.commit();
session.close();
It has no problem when I create a new asset, the asset can index auto. But I can not index with the enum type for the existed data in db. and if I remove the enum type it can work.
Does anyone meet this problem? thank you.

Related

How to query a many to many entity list via Specification API in Spring Data JPA

I have 2 entities with Many-To-Many relationships
public class Enterprise{
#Id
#Column(name = "id", nullable = false, length = 50)
#GeneratedValue(generator = "jpa-uuid")
private String id;
fields...
#ManyToMany
#JoinTable(name = "enterprise_to_tag",
joinColumns = #JoinColumn(name = "enterprise"),
inverseJoinColumns = #JoinColumn(name = "tag"))
private Set<EnterpriseTag> tags;
}
and
public class EnterpriseTag{
#Id
#Column(name = "id", nullable = false, length = 50)
#GeneratedValue(generator = "jpa-uuid")
private String id;
fields...
#ManyToMany(mappedBy = "tags")
private Set<Enterprise> enterprises;
}
I want to query enterprise list by some tags' ID then pack them to Page
private Page<Enterprise> searchEnterprise(int number, int size, String keyword, String tags, String county)
throws BusinessException {
validPageNumberAndPageSize(number, size);
Pageable pageable = PageRequest.of(number, size);
Specification<Enterprise> specification = (Specification<Enterprise>) (root, criteriaQuery, criteriaBuilder) -> {
criteriaQuery.distinct(true);
List<Predicate> predicates = new ArrayList<>();
if (StringUtils.isNoneBlank(keyword)) {
Predicate predicateName = criteriaBuilder.like(root.get("name"), "%" + keyword + "%");
Predicate predicateSerialNumber = criteriaBuilder.like(root.get("serialNumber"), "%" + keyword + "%");
predicates.add(criteriaBuilder.and(criteriaBuilder.or(predicateName, predicateSerialNumber, predicateOrganizationCode)));
}
return criteriaQuery.where(predicates.toArray(new Predicate[0])).getRestriction();
};
//filter by tags here
if (StringUtils.isNoneBlank(tags)) {
List<String> tagIds = Arrays.asList(StringUtils.split(tags, ','));
List<Enterprise> enterprises = enterpriseRepository.findAll(specification).stream().filter(enterprise ->
enterprise.getTags().stream().map(EnterpriseTag::getId).collect(Collectors.toList()).containsAll(tagIds))
.collect(Collectors.toList());
return new PageImpl<>(enterprises, pageable, enterprises.size());
} else {
return enterpriseRepository.findAll(specification, pageable);
}
}
I don't know how to write this query. I have to handle it base on a database query result. But it's risky. If too much data is queried from the database, it will take up a lot of memory. Please help me to write this query by Specification API. Thanks.

hibernate envers throw entity not found exception, when audit table doesn't have record with ID

Account Entity
#Getter
#Accessors(chain = true, fluent = true)
#NoArgsConstructor
#AllArgsConstructor(staticName = "of")
#ToString(of = {"customerName"}, callSuper = true)
#Entity
#Table(name = "ACCOUNTS",
uniqueConstraints = #UniqueConstraint(name = UNQ_CUSTOMER_NAME, columnNames = { "CUSTOMER_NAME" }),
indexes = {
#Index(name = IDX_CUSTOMER_ENTITY, columnList = "CUSTOMER_ENTITY")
}
)
public class Account extends BaseAutoAssignableVersionableEntity<String, Long> implements Diffable<Account> {
public static final String CUSTOMER_ENTITY = "Customer Entity";
public static final String CUSTOMER_REPORTING_MANAGER = "Customer Reporting Manager";
public static final String CUSTOMER_NAME = "Customer Name";
public static final String ACCOUNT_MANAGER = "Account Manager";
public static final String CITY = "City";
public static final String CUSTOMER_TIME_TRACKING = "Customer Time Tracking";
#NotNull
#Column(name = "CUSTOMER_NAME", nullable = false, length = 150)
#Audited(withModifiedFlag = true)
private String customerName; // Unique
#NotNull
#Size(min = 1, max = 4)
#Column(name = "CUSTOMER_GROUP", nullable = false, length = 4)
#Audited(withModifiedFlag = true)
private String customerGroup;
// #NotNull
#Column(name = "CUSTOMER_ENTITY", nullable = true, length = 150)
#Audited(withModifiedFlag = true)
private String customerEntity; // customer entity is optional and can be same for multiple accounts
#NotNull
#Column(name = "CUSTOMER_REPORTING_MANAGER", nullable = false, length = 150)
#Audited(withModifiedFlag = true)
private String customerReportingManager;
#Column(name = "CUSTOMER_TIME_TRACKING", length = 1)
#Audited(withModifiedFlag = true)
private boolean customerTimeTracking = false;
#NotNull
#OneToOne(fetch = FetchType.EAGER, optional = false)
#JoinColumn(name = "ACCOUNT_MANAGER_CODE", unique = false, nullable = false, foreignKey = #ForeignKey(name = FK_ACCOUNTS_MGR_RESOURCE_CODE))
#Audited(withModifiedFlag = true, targetAuditMode = RelationTargetAuditMode.NOT_AUDITED, modifiedColumnName = "ACCOUNT_MANAGER_CODE_MOD")
private Resource accountManager;
Resource Entity
#Getter
#Accessors(chain = true, fluent = true)
#NoArgsConstructor
#ToString(of = { "name" }, callSuper = true)
#Entity
// #formatter:off
#Table(name = "RESOURCES",
uniqueConstraints = {
#UniqueConstraint(name = UNQ_RESOURCES_LOGIN_ID, columnNames = {"LOGIN_ID"}),
#UniqueConstraint(name = UNQ_RESOURCES_EMAIL_ID, columnNames = {"EMAIL_ID"}),
#UniqueConstraint(name = UNQ_RESOURCES_PAYROLL_ID, columnNames = {"PAYROLL_ID"})
},
indexes = {
#Index(name = IDX_RESOURCES_NAME, columnList = "NAME")
}
)
// #formatter:on
public class Resource extends BaseAutoAssignableVersionableEntity<String, Long> implements Diffable<Resource> {
public static final String EMAIL_ID = "Email ID";
public static final String GRAYT_HR_ID = "Grayt HR ID";
public static final String NAME = "Name";
public static final String GENEDER = "Gender";
public static final String DESIGNATION = "Designation";
public static final String TYPE_OF_RESOURCE = "Resource Type";
public static final String STATUS_OF_RESOURCE = "Resource Status";
public static final String BASE_LOCATION = "Base Location";
public static final String DEPUTED_LOCATION = "Deputed Location";
public static final String PRIMARY_SKILLS = "Primary Skills";
public static final String SECONDARY_SKILLS = "Secondary Skills";
public static final String EXPECTED_JOINING_DATE = "Expected Joining Date";
public static final String ACTUAL_JOINING_DATE = "Actual Joining Date";
public static final String EXIST_DATE = "Exist Date";
// #Pattern(regexp = REGEX_LOGIN_ID, message = MESSAGE_LOGIN_ID_INVALID)
// #NotNull(message = MESSAGE_LOGIN_ID_MANDATORY)
#Column(name = "LOGIN_ID", nullable = false, length = 100)
private String loginId;
#Email(message = MESSAGE_EMAIL_INVALID)
#Column(name = "EMAIL_ID", nullable = true, length = 255)
#Audited(withModifiedFlag = true)
private String emailId;
#Pattern(regexp = REGEX_PAYROLL_ID, message = MESSAGE_PAYROLL_ID_INVALID)
#Column(name = "PAYROLL_ID", nullable = true, length = 100)
#Audited(withModifiedFlag = true, modifiedColumnName = "PAYROLL_ID_MOD")
private String payrollId;
#NotNull
#Column(name = "NAME", nullable = false, length = 255)
#Audited(withModifiedFlag = true)
private String name;
#NotNull
#Enumerated(EnumType.STRING)
#Column(name = "GENDER", nullable = false, length = 10)
#Audited(withModifiedFlag = true)
private Gender gender;
#NotNull
#OneToOne(fetch = FetchType.EAGER, optional = false)
#JoinColumn(name = "DESIGNATION_ID", unique = false, nullable = false, foreignKey = #ForeignKey(name = FK_RESOURCES_DESIGNATION_ID))
#Audited(withModifiedFlag = true, targetAuditMode = RelationTargetAuditMode.NOT_AUDITED, modifiedColumnName = "DESIGNATION_ID_MOD")
private Designation designation;
#NotNull
#OneToOne(fetch = FetchType.EAGER, optional = false)
#JoinColumn(name = "EMP_TYPE_CODE", unique = false, nullable = false, foreignKey = #ForeignKey(name = FK_RESOURCES_EMP_TYPE_CODE))
#Audited(withModifiedFlag = true, targetAuditMode = RelationTargetAuditMode.NOT_AUDITED, modifiedColumnName = "EMP_TYPE_CODE_MOD")
private EmploymentType employmentType;
public boolean isFTE() {
return (this.employmentType.equals(EmploymentType.fullTimeEmployee()));
}
#Audited(withModifiedFlag = true, modifiedColumnName = "EMP_STATUS_ID_MOD")
#OneToOne(optional = false, cascade = CascadeType.PERSIST, orphanRemoval = true)
#JoinColumn(name = "emp_status_id", unique = false, foreignKey = #ForeignKey(name = FK_RESOURCES_EMP_STATUS_ID))
private EmploymentStatus employmentStatus;
When i'm creating the account, used the existing resource as account manager and created successfully. Then fetch the history successfully. But unfortunately deleted the resource record in resource_aud table, then fetch the history. It's throwing below error
javax.persistence.EntityNotFoundException: Unable to find com.gspann.itrack.domain.model.staff.Resource with id IN10000
at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl$JpaEntityNotFoundDelegate.handleEntityNotFound(EntityManagerFactoryBuilderImpl.java:159)
at org.hibernate.proxy.AbstractLazyInitializer.checkTargetState(AbstractLazyInitializer.java:244)
at org.hibernate.proxy.AbstractLazyInitializer.initialize(AbstractLazyInitializer.java:166)
at org.hibernate.proxy.AbstractLazyInitializer.getImplementation(AbstractLazyInitializer.java:268)
at org.hibernate.proxy.pojo.javassist.JavassistLazyInitializer.invoke(JavassistLazyInitializer.java:73)
at com.gspann.itrack.domain.model.staff.Resource_$$_jvst4e_12.name(Resource_$$_jvst4e_12.java)
at com.gspann.itrack.domain.model.business.Account.diff(Account.java:328)
at com.gspann.itrack.domain.model.business.Account.diff(Account.java:1)
at com.gspann.itrack.domain.model.common.audit.util.AuditQueryUtils.getAllRevisionById(AuditQueryUtils.java:66)
at com.gspann.itrack.domain.service.impl.AccountManagementServiceImpl.getAllRevisionByAccountCode(AccountManagementServiceImpl.java:268)
below is the query generated by hibernate envers
/* select
e__,
r
from
com.gspann.itrack.domain.model.business.Account_AUD e__,
com.gspann.itrack.audit.domain.CustomRevisionEntity r
where
e__.REVTYPE <> :_p0
and e__.originalId.code = :_p1
and e__.originalId.REV.id = r.id
order by
e__.originalId.REV.id asc */ select
account_au0_.code as code1_1_0_,
account_au0_.rev as rev2_1_0_,
customrevi1_.id as id1_49_1_,
account_au0_.revtype as revtype3_1_0_,
account_au0_.customer_entity as customer4_1_0_,
account_au0_.customer_entity_mod as customer5_1_0_,
account_au0_.customer_group as customer6_1_0_,
account_au0_.customer_group_mod as customer7_1_0_,
account_au0_.customer_name as customer8_1_0_,
account_au0_.customer_name_mod as customer9_1_0_,
account_au0_.customer_reporting_manager as custome10_1_0_,
account_au0_.customer_reporting_manager_mod as custome11_1_0_,
account_au0_.customer_time_tracking as custome12_1_0_,
account_au0_.customer_time_tracking_mod as custome13_1_0_,
account_au0_.account_manager_code as account14_1_0_,
account_au0_.account_manager_code_mod as account15_1_0_,
account_au0_.country_code as country16_1_0_,
account_au0_.country_code_mod as country17_1_0_,
customrevi1_.last_modified_on as last_mod2_49_1_,
customrevi1_.modified_by as modified3_49_1_,
customrevi1_.timestamp as timestam4_49_1_
from
accounts_aud account_au0_ cross
join
revinfo customrevi1_
where
account_au0_.revtype<>?
and account_au0_.code=?
and account_au0_.rev=customrevi1_.id
order by
account_au0_.rev asc
after calling getter method of resource it's generating another resource related query like below
/* select
e__
from
com.gspann.itrack.domain.model.staff.Resource_AUD e__
where
e__.originalId.REV.id = (
select
max(e2__.originalId.REV.id)
from
com.gspann.itrack.domain.model.staff.Resource_AUD e2__
where
e2__.originalId.REV.id <= :revision
and e__.originalId.code = e2__.originalId.code
)
and e__.REVTYPE <> :_p0
and e__.originalId.code = :_p1 */ select
resource_a0_.code as code1_48_,
resource_a0_.rev as rev2_48_,
resource_a0_.revtype as revtype3_48_,
resource_a0_.actual_joining_date as actual_j4_48_,
resource_a0_.actual_joining_date_mod as actual_j5_48_,
resource_a0_.email_id as email_id6_48_,
resource_a0_.email_id_mod as email_id7_48_,
resource_a0_.exit_date as exit_dat8_48_,
resource_a0_.exit_date_mod as exit_dat9_48_,
resource_a0_.gender as gender10_48_,
resource_a0_.gender_mod as gender_11_48_,
resource_a0_.name as name12_48_,
resource_a0_.name_mod as name_mo13_48_,
resource_a0_.payroll_id as payroll14_48_,
resource_a0_.payroll_id_mod as payroll15_48_,
resource_a0_.utilization as utiliza16_48_,
resource_a0_.utilization_mod as utiliza17_48_,
resource_a0_.utilization_type as utiliza18_48_,
resource_a0_.utilization_type_mod as utiliza19_48_,
resource_a0_.base_loc_id as base_lo20_48_,
resource_a0_.base_loc_id_mod as base_lo21_48_,
resource_a0_.deputed_loc_id as deputed22_48_,
resource_a0_.deputed_loc_id_mod as deputed23_48_,
resource_a0_.designation_id as designa24_48_,
resource_a0_.designation_id_mod as designa25_48_,
resource_a0_.emp_status_id as emp_sta26_48_,
resource_a0_.emp_status_id_mod as emp_sta27_48_,
resource_a0_.emp_type_code as emp_typ28_48_,
resource_a0_.emp_type_code_mod as emp_typ29_48_,
resource_a0_.image_id as image_i30_48_,
resource_a0_.image_id_mod as image_i31_48_
from
resources_aud resource_a0_
where
resource_a0_.rev=(
select
max(resource_a1_.rev)
from
resources_aud resource_a1_
where
resource_a1_.rev<=?
and resource_a0_.code=resource_a1_.code
)
and resource_a0_.revtype<>?
and resource_a0_.code=?
below is the code to fetch the revisions
AuditReader auditReader = this.getAuditReader();
return auditReader.createQuery().forRevisionsOfEntity(clazz, selectedEntitiesOnly, selectDeletedEntities);
My question is, why it's referring to audit table instead of main table?
Thank in advance

Hibernate Filter being ignored

My application uses Hibernate 5.02 and Wildfly 10 with a PostgreSQL 9.5 database. I'm trying to enable a filter on a #OneToMany collection held within an entity that is constructed via a NamedQuery. Unfortunately, it seems as if the filter is just ignored. Here are the different components, redacted for ease of reading.
#NamedNativeQueries({
#NamedNativeQuery(
name = "getAnalystProcess",
query = "SELECT * FROM analysis.analystprocess WHERE id = :processId",
resultClass = AnalystProcessEntity.class
)})
#FilterDef(
name = "analystProcessUnanalyzedMsgsFilter",
parameters = { #ParamDef(name = "processIds", type = "integer"), #ParamDef(name = "analystIds", type = "integer") })
#Filter(name = "analystProcessUnanalyzedMsgsFilter", condition = "analystprocess_id IN (:processIds) AND id NOT IN (SELECT msg_id FROM analysis.analyzedmsg WHERE analyst_id IN (:analystIds) AND analystprocess_id IN (:processIds)) ORDER BY process_msg_id")
#Entity
#Table(name = "analystprocess", schema = "analyst")
public class AnalystProcessEntity implements JPAEntity {
public static final String GET_PROCESS = "getAnalystProcess";
public static final String MSG_FILTER = "analystProcessUnanalyzedMsgsFilter";
public static final String MSG_FILTER_PROC_ID_PARAM = "processIds";
public static final String MSG_FILTER_ANALYST_ID_PARAM = "analystIds";
private static final long serialVersionUID = 1L;
...
#OneToMany(fetch = FetchType.LAZY, orphanRemoval = true, mappedBy = "process")
#OrderColumn(name = "process_msg_id")
#LazyCollection(LazyCollectionOption.EXTRA)
private List<MsgEntity> msgList;
#Entity
#Table(name = "msg", schema = "analyst")
public class MsgEntity implements JPAEntity {
...
#ManyToOne(cascade = CascadeType.ALL, optional = false)
#JoinColumn(name = "analystprocess_id", referencedColumnName = "id")
private AnalystProcessEntity process;
#Column(name = "process_msg_id")
private Integer processMsgId;
private void buildAnalystProcess() {
LOG.info("Building AnalystProcessEntity");
analystUser.getJdbcSession().enableFilter(AnalystProcessEntity.MSG_FILTER)
.setParameter(AnalystProcessEntity.MSG_FILTER_PROC_ID_PARAM, analystProcessId)
.setParameter(AnalystProcessEntity.MSG_FILTER_ANALYST_ID_PARAM, analystUser.getId());
Query query = analystUser.getJdbcSession().getNamedQuery(AnalystProcessEntity.GET_PROCESS)
.setParameter("processId", analystProcessId);
// Query query = analystUser.getJdbcSession().createNativeQuery("SELECT * FROM analysis.analystprocess WHERE id = :processId")
// .setParameter("processId", analystProcessId)
// .addEntity(AnalystProcessEntity.class);
analystProcess = (AnalystProcessEntity) query.getSingleResult();
CREATE TABLE analysis.analystprocess (
id SERIAL PRIMARY KEY,
name TEXT NOT NULL UNIQUE,
description TEXT,
created_date TIMESTAMP NOT NULL DEFAULT now(),
...
);
CREATE TABLE analysis.msg (
id SERIAL PRIMARY KEY,
analystprocess_id INTEGER NOT NULL REFERENCES analysis.analystprocess(id) ON DELETE CASCADE ON UPDATE CASCADE,
process_msg_id INTEGER NOT NULL,
constraint tbl_statusid_analystprocessid unique(status_id, analystprocess_id)
);
As seen above, I have also tried the filter on constructing the AnalystProcessEntity class via createNativeQuery instead of getNamedQuery and no luck.
I also added a defaultCondition with hardcoded values into the #FilterDef just to see if it would execute the default condition and it still didn't.
I've tried the #Filter above the entity definition as well as above the class definition. I even came across a blog post which made it sound like the condition references entity fields (variable names) and not table fields (column names). Trying to stick to Java naming conventions in the Entity and Postgres naming conventions in the table, so I tried switching the references in the condition and to no avail.
I have sql logging turned on in Hibernate and the condition doesn't show up anywhere, as if it's just simply being ignored.
Any help would be greatly appreciated!
So, the problem was that I had the #FilterDef applied to the wrong class. It was my presumption that because I was constructing the AnalystProcessEntity which holds the MsgEntity collection (which I am trying to filter), that the #FilterDef would be applied to the AnalystProcessEntity class. Instead, it needs to be applied to the entity that it's actually filtering (hindsight being 20/20, that's pretty obvious).
Also, the actual condition needed to be modified to use complete references within the sub-select query.
I hope this helps someone at some point...
#NamedNativeQueries({
#NamedNativeQuery(
name = "getAnalystProcess",
query = "SELECT * FROM analysis.analystprocess WHERE id = :processId",
resultClass = AnalystProcessEntity.class
)})
#Filter(name = "analystProcessUnanalyzedMsgsFilter", condition = "id NOT IN (SELECT amsg.msg_id FROM analysis.analyzedmsg amsg WHERE amsg.analyst_id IN (:analystIds) AND amsg.analystprocess_id IN (:processIds))")
#Entity
#Table(name = "analystprocess", schema = "analyst")
public class AnalystProcessEntity implements JPAEntity {
public static final String GET_PROCESS = "getAnalystProcess";
public static final String MSG_FILTER = "analystProcessUnanalyzedMsgsFilter";
public static final String MSG_FILTER_PROC_ID_PARAM = "processIds";
public static final String MSG_FILTER_ANALYST_ID_PARAM = "analystIds";
private static final long serialVersionUID = 1L;
...
#OneToMany(fetch = FetchType.LAZY, orphanRemoval = true, mappedBy = "process")
#OrderColumn(name = "process_msg_id")
#LazyCollection(LazyCollectionOption.EXTRA)
private List<MsgEntity> msgList;
#FilterDef(
name = "analystProcessUnanalyzedMsgsFilter",
parameters = { #ParamDef(name = "processIds", type = "integer"), #ParamDef(name = "analystIds", type = "integer") })
#Entity
#Table(name = "msg", schema = "analyst")
public class MsgEntity implements JPAEntity {
...
#ManyToOne(cascade = CascadeType.ALL, optional = false)
#JoinColumn(name = "analystprocess_id", referencedColumnName = "id")
private AnalystProcessEntity process;
#Column(name = "process_msg_id")
private Integer processMsgId;
Additionally, I ran into another problem with null's appearing in the collection, despite the fact that I am using an #OrderColumn, which I thought fixed that issue. It seems that with the use of the #Filter, null's are inserted in place of what ended up being filtered OUT (excluded).

Default Sort on a Spring Data JPA Repository Method with Custom Query and Pageable Parameter

I have the following repository method that works exactly the way I need it to iff the user provides a sort column in the page parameter:
public interface IdentityRepository extends JpaRepository<Identity, String> {
#Query("select distinct ident from Identity ident left outer join ident.authorities authority "
+ "where ("
+ "(:src is null or ident.source = :src) and "
+ "(:org is null or ident.organization = :org) and "
+ "(:auth is null or authority.authority = :auth) and "
+ "(:authSrc is null or authority.authoritySource = :authSrc))")
#RestResource(path="filter")
public Page<Identity> findWithFilter(
#Param("src") String source,
#Param("org") String org,
#Param("auth") Authority auth,
#Param("authSrc") AuthoritySource authSrc,
Pageable page);
...
}
If the caller provides a page count, but not a sort column, they will get back the correct number of results when retrieving all the pages. However, many of the entities will be duplicated, so even though the result count is correct, many expected entities are missing and others are duplicated (or triplicated).
What I'm wondering is if there is a way to provide a default sort column and direction if the user does not specify one. I've learned that #EnableSpringDataWebSupport can help here, but we're not using Spring MVC, so I don't have any controllers to attach the #SortDefaults to. We are using Spring Data Rest though. Also, I've tried changing the method name to findWithFilterOrderByIdAsc, but that did not seem to help. Ran across this issue in the Spring JIRA, which I believe is exactly what I need, but until it's resolved, does anyone know of a work around?
Here's my entity...
#Entity
#Table(name = "identity", indexes = { #Index(columnList = "user_id", unique = true) })
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
#Audited
public class Identity implements Serializable, Identifiable<String> {
/**
* The unique identifier for this identity within the IDD application.
*/
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "IDDUidGenerator")
#GenericGenerator(name = "IDDUidGenerator")
private String id;
/**
* The name of the identity provider wherein this identity is originally defined.
*/
#Column(name = "source")
private String source = INTERNAL_SOURCE;
/**
* The unique identifier for this identity within the customer's identity provider.
*/
#NotNull
#Column(name = "user_id", nullable = false, unique = true)
private String userId;
/**
* The roles this identity is authorized to perform.
*/
#OneToMany(fetch = FetchType.EAGER, mappedBy = "identity", cascade = CascadeType.ALL, orphanRemoval = true)
private Set<IdentityAuthority> authorities = new HashSet<>();
...
}
And its sub-entity...
#Entity
#Table(name = "identity_authority")
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
#Audited
public class IdentityAuthority implements Serializable, Identifiable<Long> {
private static final long serialVersionUID = -5315412946768343445L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#XmlTransient
#JsonIgnore
private Long id;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "identity_id", nullable = false)
#XmlTransient
#JsonIgnore
private Identity identity;
#Enumerated(EnumType.STRING)
#Column(name = "authority", length = 20, nullable = false)
private Authority authority;
#Enumerated(EnumType.STRING)
#Column(name = "authority_source", length = 30, nullable = false)
private AuthoritySource authoritySource;
...
}
Here's the test case I ran to demonstrate the problem...
#Test
public void testPagedRequestsReturnAllResults() {
// Create identities
String source = "One Hundred Identities Generator";
int numIdentities = 100;
int pageSize = 5;
List<Identity> input = new ArrayList<>();
for (int i=0; i<numIdentities; i++) {
Identity identity = new Identity();
identity.setUserId(UUID.randomUUID().toString());
identity.setSource(source);
input.add(identity);
}
// Save identities
List<Identity> output = repository.saveBulk(input);
Set<String> savedIds = collectIds(output, null);
assertThat(savedIds.size()).isEqualTo(numIdentities);
// Test Sorted Find Filter with Paging (THIS PASSES)
Pageable pageRequest = new PageRequest(0, pageSize, new Sort(Direction.ASC, "id"));
Set<String> foundPagedIds = new HashSet<>();
do {
Page<Identity> page = repository.findOrderByIdAsc(source, null, null, null, pageRequest);
List<Identity> foundIdentities = page.getContent();
foundPagedIds = collectIds(foundIdentities, foundPagedIds);
pageRequest = page.nextPageable();
} while (pageRequest != null);
assertThat(foundPagedIds.size()).isEqualTo(numIdentities);
assertThat(foundPagedIds).isEqualTo(savedIds);
// Test Unsorted Find Filter with Paging (THIS FAILS)
pageRequest = new PageRequest(0, pageSize);
foundPagedIds = new HashSet<>();
do {
Page<Identity> page = repository.findOrderByIdAsc(source, null, null, null, pageRequest);
List<Identity> foundIdentities = page.getContent();
foundPagedIds = collectIds(foundIdentities, foundPagedIds);
pageRequest = page.nextPageable();
} while (pageRequest != null);
assertThat(foundPagedIds.size()).isEqualTo(numIdentities);
assertThat(foundPagedIds).isEqualTo(savedIds);
}

How to use JPA criteriaBuilder to search on attributes in a collection of sub-attributes

I have an Entity that maps to a table defined this way:
#Entity
#Table(name = "cmmn_calendar_evnt")
public class CommonCalendarEvent implements java.io.Serializable
{
private Integer cevId;
private Set<CommonCalendarEventPart> commonCalendarEventParts = new HashSet<CommonCalendarEventPart>(0)
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "CEV_ID", unique = true, nullable = false)
public Integer getCevId()
{
return this.cevId;
}
#OneToMany(cascade = CascadeType.ALL, fetch = FetchType.LAZY, mappedBy = "commonCalendarEvent")
public Set<CommonCalendarEventPart> getCommonCalendarEventParts()
{
return this.commonCalendarEventParts;
}
}
and CommonCalendarEventPart is defined like this:
#Entity
#Table(name = "cmmn_calendar_evnt_part")
public class CommonCalendarEventPart implements java.io.Serializable
{
private static final long serialVersionUID = 1L;
private Integer ceeId;
private CommonCalendarEvent commonCalendarEvent;
private PartParticipant partParticipant;
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "CEE_ID", unique = true, nullable = false)
public Integer getCeeId()
{
return this.ceeId;
}
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "CEE_CEV_ID", nullable = false)
public CommonCalendarEvent getCommonCalendarEvent()
{
return this.commonCalendarEvent;
}
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "CEE_PPT_ID", nullable = false)
public PartParticipant getPartParticipant()
{
return this.partParticipant;
}
}
and finally:
#Entity
#Table(name = "part_participant")
public class PartParticipant implements java.io.Serializable
{
private static final long serialVersionUID = 1L;
private Integer pptId;
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "PPT_ID", unique = true, nullable = false)
public Integer getPptId()
{
return this.pptId;
}
}
I want to use the CriteriaBuilder to generate a query finding all CommonCalendarEvent for a specific Participant ID.
In Hql it would look something like this: (although I have not confirmed that this Hql is correct either)
"from commonCalendarEvent cce where :pptId in (cce.commonCalendarEventParts.partParticipant.pptId)"
I've tried some approaches of what I thought were intuitive attempts at writing a criteriaBuilder approach, but my attempts have resulted in errors ranging from:
“unexpected end of subtree” to just implementation errors.
.....
CriteriaBuilder builder = getEntityManager().getCriteriaBuilder();
CriteriaQuery<CommonCalendarEvent> criteria = builder.createQuery(CommonCalendarEvent.class);
Root<CommonCalendarEvent> root = criteria.from(CommonCalendarEvent.class);
Fetch<CommonCalendarEvent, CommonCalendarEventPart> evf = root.fetch(CommonCalendarEvent_.commonCalendarEventParts, JoinType.LEFT);
Join<CommonCalendarEvent, CommonCalendarEventPart> evj = (Join<CommonCalendarEvent, CommonCalendarEventPart>) evf;
Join<CommonCalendarEventPart, PartParticipant> evpj = evj.join(CommonCalendarEventPart_.partParticipant);
List<Predicate> pred = new ArrayList<Predicate>();
pred.add(builder.equal(evpj.get(PartParticipant_.pptId), pptId));
criteria.where(builder.and(pred.toArray(new Predicate[] {})));
return getEntityManager().createQuery(criteria).getResultList();
.............
above yields an "unexpected end of subtree" error.
Any Help is appreciated.
+1 for using Lazy initialization. The JPA model is Object, or Entity oriented, so you need to get used to thinking in those terms. A PartParticipant is not identified by its id in JPA, but by the object itself. Assuming you have a list of participants:
PartParticipant pp = em.find(PartParticipant.class, 2);
List<PartParticipant> pps = new ArrayList<PartParticipant>();
pps.add(pp);
Then you pass that list to the queries. In JPQL:
TypedQuery<CommonCalendarEvent> cev = em.createQuery("select cev from CommonCalendarEvent cev join fetch cev.commonCalendarEventParts cce where cce.partParticipant in :pps", CommonCalendarEvent.class);
List<CommonCalendarEvent> cevs = cev.setParameter("pps", pps).getResultList();
Notice the fetch is needed to prevent LazyInitializationExceptions.
Knowing the JPQL, the CriteriaQuery should follow pretty much the same:
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<CommonCalendarEvent> q = cb.createQuery(CommonCalendarEvent.class);
Root<CommonCalendarEvent> r = q.from(CommonCalendarEvent.class);
Join<CommonCalendarEvent, CommonCalendarEventPart> j = r.join("commonCalendarEventParts");
r.fetch("commonCalendarEventParts");
q.select(r).where(j.get("partParticipant").in(pps));
List<CommonCalendarEvent> rs = em.createQuery(q).getResultList();
You don't need to do anything special with the fetch other than execute it. As you can see, the query uses the PartParticipant Id.
select
commoncale0_.CEV_ID as CEV_ID1_0_0_,
commoncale1_.CEE_ID as CEE_ID1_1_1_,
commoncale1_.CEE_CEV_ID as CEE_CEV_2_1_1_,
commoncale1_.CEE_PPT_ID as CEE_PPT_3_1_1_,
commoncale1_.CEE_CEV_ID as CEE_CEV_2_0_0__,
commoncale1_.CEE_ID as CEE_ID1_1_0__
from cmmn_calendar_evnt commoncale0_
inner join cmmn_calendar_evnt_part commoncale1_ on commoncale0_.CEV_ID=commoncale1_.CEE_CEV_ID
where commoncale1_.CEE_PPT_ID in (?)
Fetch<CommonCalendarEvent, CommonCalendarEventPart> evf is not necessary, and the first join statement should be corrected:
Join<CommonCalendarEvent, CommonCalendarEventPart> evj =
root.join(CommonCalendarEvent_.commonCalendarEventParts);
The rest of the query seems correct.