I have a JPA Repository for my Document entity. And I have a problem with one long method, where I want to achieve something like full text search across multiple tables. Document Entity has some OneToMany relationships.
public interface DocumentRepository extends JpaRepository<Document, Long>, JpaSpecificationExecutor<Document> {
Page<Document> findByInstanceNumberContainingIgnoreCaseOrAttachmentsNameContainingIgnoreCaseOrDocumentIdEqualsOrHeaderAuthorNameContainingIgnoreCaseOrCommentsCommentContainingIgnoreCaseOrFormFieldsSvalueContainingIgnoreCaseOrItemListsRowsCellsSvalueContainingIgnoreCaseOrderByInstanceNumber(Pageable pageable, String instanceNumber, String attachmentName, Integer documentId, String authorName, String comment, String formContent, String cellContent);
}
Unfortunately, that method returns multiplied and duplicated results. For example, currently I have 120 records in my Documents table, but I am getting 10485638 results in this query.
Here is something what was generated and used to fetch results
select count(document0_.id) as col_0_0_ from documents document0_ left outer join attachments attachment1_ on document0_.id=attachment1_.document_id left outer join headers header2_ on document0_.header_id=header2_.id left outer join authors author3_ on header2_.author_id=author3_.id left outer join comments comments4_ on document0_.id=comments4_.document_id left outer join form_fields formfields5_ on document0_.id=formfields5_.document_id left outer join item_list itemlists6_ on document0_.id=itemlists6_.document_id left outer join rows rows7_ on itemlists6_.id=rows7_.item_list_id left outer join cells cells8_ on rows7_.id=cells8_.row_id where upper(document0_.instance_number) like upper('%TEST%') escape '\' or upper(attachment1_.name) like upper('%TEST%') escape '\' or document0_.external_document_id=123 or upper(author3_.name) like upper('%TEST%') escape '\' or upper(comments4_.comment) like upper('%TEST%') escape '\' or upper(formfields5_.svalue) like upper('%TEST%') escape '\' or upper(cells8_.svalue) like upper('%TEST%') escape '\''
What can I do to fix that issue and get only unique documents? I need paging, so I can not use Set here.
Edit:
Document Entity class (without getters, setters and constructors):
#Entity
#ToString
#AllArgsConstructor
#NoArgsConstructor
#Table(name = "documents", indexes = {
#Index(columnList = "external_document_id", unique = true),
#Index(columnList = "instance_number")
})
public class Document {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "documents_generator")
#SequenceGenerator(name="documents_generator", sequenceName = "documents_seq", allocationSize=50)
#Column(name = "id", nullable = false)
private Long id;
#Column(name = "external_document_id", nullable = false, unique=true)
private Integer documentId;
#Column(name = "version", nullable = false)
private Integer version;
#Column(name = "instance_number", nullable = false)
private String instanceNumber;
#ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name = "task_id")
private Task tasks;
#ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name = "header_id")
private Header header;
#OneToMany(mappedBy = "document", cascade = CascadeType.PERSIST)
#ToString.Exclude
#EqualsAndHashCode.Exclude
private Set<Comment> comments = new LinkedHashSet<>();
#OneToMany(mappedBy = "document", cascade = CascadeType.PERSIST)
#ToString.Exclude
#EqualsAndHashCode.Exclude
private Set<Attachment> attachments = new LinkedHashSet<>();
#OneToMany(mappedBy = "document", cascade = CascadeType.PERSIST)
#ToString.Exclude
#EqualsAndHashCode.Exclude
private Set<FormField> formFields = new LinkedHashSet<>();
#OneToMany(mappedBy = "document", cascade = CascadeType.PERSIST)
#ToString.Exclude
#EqualsAndHashCode.Exclude
private Set<ItemList> itemLists = new LinkedHashSet<>();
}
Related
i'm kinda struggling mapping the following schema with hibernate
table_a (A1_ID,A2_ID) --> PK = (A1_ID, A2_ID)
table_b (A1_ID, A2_ID, B1_ID) --> PK =(A1_ID, A2_ID, B1_ID)
where table_b's A1_ID and A2_ID should be foreingkey referencing respective table_A's columns
There is a one-to-many from TABLE_A to TABLE_B where TABLE_B's primary key is partially shared with TABLE_A's primary key
What I've tried so far
#Data
#Entity
#Table(name = "table_a")
#IdClass(TableA.TableAKey.class)
public class TableA {
#Id
#Column(name = "A1_ID)
private String a1_id;
#Id
#Column(name = "A2_ID)
private String a2_id;
#OneToMany(mappedBy = "tableA",fetch = FetchType.EAGER, cascade = CascadeType.ALL)
private List<TableB> tableB;
#Data
static class TableAKey implements Serializable {
private String a1_id;
private String a2_id
}
}
**CHILD ENTITY**
#Data
#Entity
#Table(name = "table_b")
#IdClass(TableB.TableBKey.class)
public class TableB {
#Id
#Column(name = "B1_ID)
private String b1_id;
#Id
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumns({
#JoinColumn(name = "a1_id", insertable = false, updatable = false),
#JoinColumn(name = "a2_id", insertable = false, updatable = false)
)}
private TableA tableA;
#Column(name = "A1_ID)
private String a1_id;
#Column(name = "A2_ID)
private String a2_id;
#Data
static class TableAKey implements Serializable {
private String b1_id;
private TableA tableA;
}
}
I was expecting i could be able to do something like this:
TableA tableA = new TableA();
t.setA1_id("a1id");
t.setA2_id("a2id");
TableB tableB = new TableB();
tableB.setB1Id("b1Id");
tableA.setTableB(Arrays.asList(tableB));
tableARepository.save(tableA);
And the code above I was expecting to "magically" perform the following insert at DB
INSERT INTO table_A (A1_ID,A2_ID) VALUES ('a1id',a2id');
INSERT INTO table_B (A1_ID,A2_ID, B1_ID) VALUES ('a1id',a2id','b1id')
but instead i get a "the column index is out of range: n, number of columns n-1".
I also tried with some embeddedId approach, using referenceColumnName but nothing.
Am I doing something wrong in the mapping or in the object creation process?
The problem is a lot similar to the following
https://hibernate.atlassian.net/browse/HHH-14340
I've been struggling with a namedquery for a few days. The named query has an inner join to a 2nd table. One added complexity is that the primary key on the 2nd table is a composite key. I have the two tables simplified here:
Table: aname
nameIdx number(9),
firstName varchar2(40),
lastName varchar2(40),
primary key is nameIdx
Table: aname_role
nameIdx number(9), --foreign key to name table
nameType char(2),
inactiveFlag char(1)
composite primary key is on nameIdx and nameType
I am trying to emulate the following sql query in JPQL:
select * from aname n
left join aname_role nr on n.nameidx=nr.nameidx
where nr.nametype='5'
and nr.inactiveflag='N';
This query works as expected in Oracle returning many records. In Java I have these JPA entities:
#Entity
#Table(name="ANAME")
#NamedQueries({
#NamedQuery(name = "AName.findActiveSalesPersons", query = "SELECT a FROM AName a LEFT JOIN a.aNameRoleList r WHERE r.inactiveflag='N' and r.ANameRolePK.nametype='5' ")})
public class AName implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Basic(optional = false)
#NotNull
#Column(name = "NAMEIDX")
private Integer nameidx;
#Column(name = "FIRSTNAME")
private String firstname;
#Column(name = "LASTNAME")
private String lastname;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "aName")
private List<ANameRole> aNameRoleList;
//getters and setters here
and
#Entity
#Table(name = "ANAME_ROLE")
public class ANameRole implements Serializable {
private static final long serialVersionUID = 1L;
#EmbeddedId
protected ANameRolePK aNameRolePK;
#Basic(optional = false)
#NotNull
#Column(name = "INACTIVEFLAG")
private Character inactiveflag;
#JoinColumn(name = "NAMEIDX", referencedColumnName = "NAMEIDX", insertable = false, updatable = false)
#ManyToOne(optional = false)
private AName aName;
//getters and setters here
There is also a primary key class ANameRolePK
#Embeddable
public class ANameRolePK implements Serializable {
#Basic(optional = false)
#NotNull
#Column(name = "NAMEIDX")
private int nameidx;
#Basic(optional = false)
#NotNull
#Size(min = 1, max = 2)
#Column(name = "NAMETYPE")
private String nametype;
//getters and setters here
With this setup, including the named query specified in the AName entity above, the following returns an empty result list:
em.createNamedQuery("AName.findActiveSalesPersons").getResultList();
Can anyone point me to what I am doing wrong in this named query?
SELECT a FROM AName a LEFT JOIN a.aNameRoleList r WHERE r.inactiveflag='N' and r.aNameRolePK.nametype='5'
Thanks,
Steve
By default, at least using Hibernate, the default fetch type is Lazy, so you need to do a join fetch instead of a join. Also, you should have select distinct. Try:
SELECT distinct a FROM AName a LEFT JOIN fetch a.aNameRoleList r WHERE r.inactiveflag='N' and r.aNameRolePK.nametype='5'
References: Default fetch type for one-to-one, many-to-one and one-to-many in Hibernate
After more testing, I realized the join was working, but not the "r.aNameRolePK.nametype='5'". But if I changed that to "r.aNameRolePK.nameidx=1" it works. So, it was just the nametype field, which we have defined as a char(2) in the database. The problem is with the spaces in a char field and it is discussed here: Java NamedQuery String Problem. It looks like the recommended way to resolve this is to implement an EclipseLink SessionCustomizer. For testing I changed the named query to
SELECT a
FROM AName a LEFT JOIN a.aNameRoleList r
WHERE r.inactiveflag='N' and trim(trailing from r.aNameRolePK.nametype)=5
This returns the expected records.
openjpa is complaining about an incorrect argument for a JPA query that EclipseLink properly handles. EclipseLink returns the set of validation messages for the motor.
Two questions:
1) Is my query wrong and EclipseLink is kindly handling it anyway?
2) Any suggestions on how to restructure the query for openjpa?
Thanks for thinking about my question!
Query
SELECT m.valMessages FROM ThreePhaseMotorInput m WHERE m.id = :id
Actual openjpa exception
Caused by: <openjpa-2.3.0-r422266:1540826 nonfatal user error> org.apache.openjpa.persistence.ArgumentException:
Query projections cannot include array, collection, or map fields.
Invalid query: "SELECT m.valMessages FROM ThreePhaseMotorInput m WHERE m.id = :id"
at org.apache.openjpa.kernel.ExpressionStoreQuery$AbstractExpressionExecutor.assertNotContainer(ExpressionStoreQuery.java:328)
at org.apache.openjpa.kernel.ExpressionStoreQuery$DataStoreExecutor.<init>(ExpressionStoreQuery.java:770)
at org.apache.openjpa.kernel.ExpressionStoreQuery.newDataStoreExecutor(ExpressionStoreQuery.java:179)
at org.apache.openjpa.kernel.QueryImpl.createExecutor(QueryImpl.java:749)
at org.apache.openjpa.kernel.QueryImpl.compileForDataStore(QueryImpl.java:707)
at org.apache.openjpa.kernel.QueryImpl.compileForExecutor(QueryImpl.java:689)
at org.apache.openjpa.kernel.QueryImpl.compile(QueryImpl.java:589)
at org.apache.openjpa.persistence.EntityManagerImpl.createNamedQuery(EntityManagerImpl.java:1038)
at org.apache.openjpa.persistence.EntityManagerImpl.createNamedQuery(EntityManagerImpl.java:1017)
ThreePhaseMotorInput mapping
public class ThreePhaseMotorInput implements IThreePhaseMotorInput, Serializable {
private static final long serialVersionUID = 8084370807289186987L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Version
private Integer version;
private Integer status;
#OneToOne(cascade = CascadeType.ALL, optional = true, targetEntity = UnapprovedThreePhaseMotor.class)
#JoinColumn(name = "unapproved_id")
private IThreePhaseMotor unapprovedMotor;
#OneToOne(cascade = CascadeType.ALL, optional = true, targetEntity = ApprovedThreePhaseMotor.class)
#JoinColumn(name = "approved_id")
private IThreePhaseMotor approvedMotor;
#OneToMany(orphanRemoval = true, cascade = CascadeType.ALL, fetch = FetchType.LAZY, targetEntity = ValidationMessage.class)
#JoinColumn(name = "input_id", referencedColumnName = "id", nullable = false)
#OrderColumn(name = "idx")
private List<IValidationMessage> valMessages;
ValidationMessage mapping
public class ValidationMessage implements Serializable, IValidationMessage {
private static final long serialVersionUID = 8765213112015434057L;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "record_id")
private Long recordId;
#Column(name = "field_name")
private String fieldName;
#Column(name = "validation_msg")
private String validationMsg;
private Integer status;
#Column(name = "fail_field")
private String failField;
#Column(name = "error_source")
private Integer errorSource;
Check http://docs.oracle.com/javaee/6/tutorial/doc/bnbuf.html#bnbvx - SELECT clause: A SELECT clause cannot specify a collection-valued expression. For example, the SELECT clause p.teams is invalid because teams is a collection.
But you can use valMessages for INNER/OUTER join and select IValidationMessage entities trough it, e.g.:
SELECT ivm
FROM ThreePhaseMotorInput tpmi
INNER JOIN tpmi.valMessages ivm
WHERE tpmi.id = :id
How do I create an efficient JPA Criteria query to select a list of entities only if they exist in a join table? For example take the following three tables:
create table user (user_id int, lastname varchar(64));
create table workgroup (workgroup_id int, name varchar(64));
create table user_workgroup (user_id int, workgroup_id int); -- Join Table
The query in question (what I want JPA to produce) is:
select * from user where user_id in (select user_id from user_workgroup where workgroup_id = ?);
The following Criteria query will produce a similar result, but with two joins:
CriteriaBuilder cb = getEntityManager().getCriteriaBuilder();
CriteriaQuery<User> cq = cb.createQuery(User.class);
Root<User> root = cq.from(User.class);
cq.select(root);
Subquery<Long> subquery = cq.subquery(Long.class);
Root<User> subroot = subquery.from(User.class);
subquery.select(subroot.<Long>get("userId"));
Join<User, Workgroup> workgroupList = subroot.join("workgroupList");
subquery.where(cb.equal(workgroupList.get("workgroupId"), ?));
cq.where(cb.in(root.get("userId")).value(subquery));
getEntityManager().createQuery(cq).getResultList();
The fundamental problem seems to be that I'm using the #JoinTable annotation for the USER_WORKGROUP join table instead of a separate #Entity for the join table so it doesn't seem I can use USER_WORKGROUP as a Root in a criteria query.
Here are the entity classes:
#Entity
public class User {
#Id
#Column(name = "USER_ID")
private Long userId;
#Column(name = "LASTNAME")
private String lastname;
#ManyToMany(mappedBy = "userList")
private List<Workgroup> workgroupList;
}
#Entity
public class Workgroup {
#Id
#Column(name = "WORKGROUP_ID")
private Long workgroupId;
#Column(name = "NAME")
private String name;
#JoinTable(name = "USER_WORKGROUP", joinColumns = {
#JoinColumn(name = "WORKGROUP_ID", referencedColumnName = "WORKGROUP_ID", nullable = false)}, inverseJoinColumns = {
#JoinColumn(name = "USER_ID", referencedColumnName = "USER_ID", nullable = false)})
#ManyToMany
private List<User> userList;
}
As far as I know, JPA essentially ignores the join table. The JPQL that you do would be
select distinct u from user u join u.workgroupList wg where wg.name = :wgName
for the Criteria query, you should be able to do:
Criteria c = session.createCriteria(User.class, "u");
c.createAlias("u.workgroupList", "wg");
c.add(Restrictions.eq("wg.name", groupName));
c.setResultTransformer(Criteria.DISTINCT_ROOT_ENTITY);
there's no need to worry about the middle join table.
I have two Entites
#Entity
public Report()
#Id
#Column(name = "REPORT_ID")
private long id;
#JsonIgnore
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(
name="reports_projects",
joinColumns={#JoinColumn(name="report_id", referencedColumnName="REPORT_ID")},
inverseJoinColumns={#JoinColumn(name="project", referencedColumnName="PROJECT_ID")})
private List<Project> projects;
second is:
#Entity(name = "projects")
public class Project
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "PROJECT_ID")
// seems like spring's jpa has issue hanlde "_" between the words
private long id;
#Column(name = "CODE", nullable = false)
private String code;
#Column(name = "DESCRIPTION", nullable = false)
private String description;
#Column(name = "CREATION_DATE", nullable = false)
private Date creationDate;
i'm tring to query reports by projects.code
tried few stuff like
#Query("select reports from org.jpp.domain.quicksearch.ReportQS reports inner join reports.projects p where p.code in :code")
And
CriteriaBuilder cb = em.getCriteriaBuilder();
CriteriaQuery<QuickSearchResult> query = cb.createQuery(QuickSearchResult.class);
Metamodel m = em.getMetamodel();
EntityType<ReportQS> ReportQSMetaModel = m.entity(ReportQS.class);
Root<ReportQS> reportsQS = query.from(ReportQS.class);
Root<Project> projects = query.from(Project.class);
Join<ReportQS, Project> joinReportsProjects = reportsQS.join("projects");
Predicate condition = cb.equal(projects.get("code"),"gnrl");
query.select(reportsQS).where(condition);
TypedQuery<QuickSearchResult> q = em.createQuery(query);
I get empty result for both of the queries
Any idea how to get this to work ?
Thanks in advance,
Oak
Try following code:
String query = "select r from ReportQS r join r.projects p where p.code = :code";
List<ReportQS> reports = em.createQuery(query,ReportQS.class).setParameter("code","grnl").getResultList();
Make sure that ReportQS is name of entity class (in your sample code you have different class name and different entity name used in query).