{"id":11629,"date":"2022-09-20T14:00:47","date_gmt":"2022-09-20T14:00:47","guid":{"rendered":"https:\/\/predictly.se\/complex-jpa-queries-with-specifications\/"},"modified":"2024-05-08T11:46:40","modified_gmt":"2024-05-08T11:46:40","slug":"complex-jpa-queries-with-specifications","status":"publish","type":"post","link":"https:\/\/predictly.se\/en\/complex-jpa-queries-with-specifications\/","title":{"rendered":"Complex JPA Queries with Specifications"},"content":{"rendered":"<p>As shown in my previous article on searching using JPA, <a href=\"http:\/\/staging.predictly.se\/jpa-sokningar-med-spring-data-example\/\">Examples<\/a> is a fast and easy way to search for records in a database. But it comes with limitations, most notably it can&#8217;t search in child collections. To support this, we have to turn to JPA Specifications.<\/p>\n<p>In this article, I will use the <a href=\"https:\/\/dev.mysql.com\/doc\/employee\/en\/employees-introduction.html\" target=\"_blank\" rel=\"noopener noreferrer\">MySQL Employees<\/a> database via the docker container <a href=\"https:\/\/hub.docker.com\/r\/genschsa\/mysql-employees\" target=\"_blank\" rel=\"noopener noreferrer\">genschsa\/mysql-employees<\/a>, see my <a href=\"http:\/\/staging.predictly.se\/spring-data-jpa-batchjobb-med-streams\/\">previous post<\/a> for relevant instructions about this container. Entity model for <i>Employee<\/i> is given below.<\/p>\n<pre class=\"brush: java; title: ; notranslate\" title=\"\">\n@Entity\n@Table(name = &quot;employees&quot;)\n@Data\n@Builder\n@AllArgsConstructor\n@NoArgsConstructor\npublic class Employee {\n    \n  @Id\n  @Column(name = &quot;emp_no&quot;)\n  private int employeeId;\n\n  @Column(name = &quot;birth_date&quot;)\n  private LocalDate birthDate;\n    \n  @Column(name = &quot;first_name&quot;)\n  private String firstName;\n    \n  @Column(name = &quot;last_name&quot;)\n  private String lastName;\n    \n  @Column(name = &quot;gender&quot;, columnDefinition = &quot;ENUM(&#039;M&#039;, &#039;F&#039;)&quot;, nullable = false)\n  @Enumerated(EnumType.STRING)\n  Private Gender Gender;\n\n  @Column(name = &quot;hire_date&quot;)\n  private LocalDate hireDate;\n\n  @OneToMany(mappedBy = &quot;employeeId&quot;, cascade = CascadeType.ALL, orphanRemoval = true)\n  private Set&amp;lt;Salary&amp;gt; salaries;\n\n  @OneToMany(mappedBy = &quot;employeeId&quot;, cascade = CascadeType.ALL, orphanRemoval = true)\n  private Set&amp;lt;Title&amp;gt; titles;\n}\n<\/pre>\n<p><i>Title<\/i> shown below, <i>Salary<\/i> has an identical structure with <i>from<\/i> and <i>to<\/i>.<\/p>\n<pre class=\"brush: java; title: ; notranslate\" title=\"\">\n@Data\n@Entity\n@Table(name = &quot;titles&quot;)\n@IdClass(TitleKey.class)\npublic class Title {\n\n  @Id\n  @Column(name = &quot;emp_no&quot;)\n  private int employeeId;\n    \n  @Id\n  @Column(name = &quot;title&quot;)\n  private String title;\n    \n  @Id\n  @Column(name = &quot;from_date&quot;)\n  private LocalDate from;\n    \n  @Column(name = &quot;to_date&quot;)\n  private LocalDate to;\n}\nview raw\n<\/pre>\n<p>Let&#8217;s assume we have a use case where we want to find employees with a simple search term, i.e. passing a String would search for employees using the names and that we also want to support explicit filters such as <i>birthDate<\/i>, <i>hireDate<\/i>, <i>title<\/i> and <i>salary<\/i>.<\/p>\n<h6>Repository and Specification<\/h6>\n<p>To perform searches using Specifications, we must first adapt our JPA Repository to extend from <i>JpaSpecificationExecutor<\/i> and create a <i>Specification<\/i>.<\/p>\n<pre class=\"brush: java; title: ; notranslate\" title=\"\">\npackage se.predictly.support.employee.persistence;\n\nimport org.springframework.data.jpa.repository.JpaRepository;\nimport org.springframework.data.jpa.repository.JpaSpecificationExecutor;\nimport org.springframework.stereotype.Repository;\n\nimport se.predictly.support.employee.persistence.model.Employee;\n\n@Repository\npublic interface EmployeeRepository extends\n  JpaRepository&amp;lt;Employee, Integer&amp;gt;, JpaSpecificationExecutor&amp;lt;Employee&amp;gt; {\n\n}\n<\/pre>\n<p>And the secret sauce, our Specification.<\/p>\n<pre class=\"brush: java; title: ; notranslate\" title=\"\">\n@Data\n@Builder\npublic class EmployeeSpecification implements Specification&amp;lt;Employee&amp;gt; {\n\n  private static final LocalDate CURRENTLY_ACTIVE_ENTRY = LocalDate.of(9999, 1, 1);\n\n  private LocalDate birthDate;\n  private LocalDate hireDate;\n  private Integer salary;\n  private String title;\n\n  private String searchTerm;\n\n  @Override\n  public Predicate toPredicate(Root&amp;lt;Employee&amp;gt; root, CriteriaQuery&amp;lt;?&amp;gt; query, CriteriaBuilder cb) {\n\n  Predicate birthDatePred = ofNullable(birthDate)\n  .map(b -&amp;gt; equals(cb, root.get(&quot;birthDate&quot;), birthDate))\n  .orElse(null);\n  Predicate hireDatePred = ofNullable(hireDate)\n  .map(h -&amp;gt; equals(cb, root.get(&quot;hireDate&quot;), hireDate))\n  .orElse(null);\n\n  Predicate salaryPred = salaryPredicate(root, cb);\n  Predicate titlePred = titlePredicate(root, cb);\n\n  if (nonNull(salaryPred) || nonNull(titlePred)) {\n  query.distinct(true);\n  }\n\n  Predicate searchPred = null;\n  if (StringUtils.isNoneBlank(searchTerm)) {\n  Predicate firstNamePred = like(cb, root.get(&quot;firstName&quot;), searchTerm);\n  Predicate lastNamePred = like(cb, root.get(&quot;lastName&quot;), searchTerm);\n\n  searchPred = cb.or(firstNamePred, lastNamePred);\n  }\n\n  List&amp;lt;Predicate&amp;gt; predicates = new ArrayList&amp;lt;&amp;gt;();\n\n  ofNullable(birthDatePred).ifPresent(predicates::add);\n  ofNullable(hireDatePred).ifPresent(predicates::add);\n  ofNullable(salaryPred).ifPresent(predicates::add);\n  ofNullable(titlePred).ifPresent(predicates::add);\n  ofNullable(searchPred).ifPresent(predicates::add);\n\n  return cb.and(predicates.toArray(new Predicate&#x5B;predicates .size()]));\n  }\n\n  private Predicate salaryPredicate(Root&amp;lt;Employee&amp;gt; root, CriteriaBuilder cb) {\n  if (isNull(salary)) {\n  return null;\n  }\n\n  Join&amp;lt;Employee, Title&amp;gt; salaryJoin = root.join(&quot;salaries&quot;, JoinType.INNER);\n\n  int salaryLow = Double.valueOf(salary * 0.9).intValue();\n  int salaryHigh = Double.valueOf(salary * 1.1).intValue();\n\n  return cb.and(\n  between(cb, salaryJoin.get(&quot;salary&quot;), salaryLow, salaryHigh),\n  equals(cb, salaryJoin.get(&quot;to&quot;), CURRENTLY_ACTIVE_ENTRY));\n  }\n\n  private Predicate titlePredicate(Root&amp;lt;Employee&amp;gt; root, CriteriaBuilder cb) {\n  if (isAllBlank(title)) {\n  return null;\n  }\n\n  Join&amp;lt;Employee, Title&amp;gt; titleJoin = root.join(&quot;titles&quot;, JoinType.INNER);\n\n  return cb.and(\n  like(cb, titleJoin.get(&quot;title&quot;), title),\n  equals(cb, titleJoin.get(&quot;to&quot;), CURRENTLY_ACTIVE_ENTRY));\n  }\n\n  private Predicate equals(CriteriaBuilder cb, Path&amp;lt;Object&amp;gt; field, Object value) {\n  return cb.equal(field, value);\n  }\n\n  private Predicate like(CriteriaBuilder cb, Path&amp;lt;String&amp;gt; field, String searchTerm) {\n  return cb.like(cb.lower(field), &quot;%&quot; + searchTerm.toLowerCase() + &quot;%&quot;);\n  }\n\n  private Predicate between(CriteriaBuilder cb, Path&amp;lt;Integer&amp;gt; field, int min, int max) {\n  return cb.between(field, min, max);\n  }\n}\n<\/pre>\n<p>Quite a lot to unpack here, let&#8217;s go through it step by step.<\/p>\n<p><i>Line #17 and #20 &#8211; Predicate<\/i><\/p>\n<p>Specifications works by combining predicates and our job is to create them according to our use case. Here we specify that if <i>birthDate<\/i> or <i>hireDate<\/i> is set, it should match using an equal statement.<\/p>\n<p><i>Line #24 and #50 &#8211; Salary<\/i><\/p>\n<p>If salary is set, we should find employees that currently match the provided salary. But since forcing users to specify an exact salary is bad design, we check for matches between 90% and 110% of the user provided value.<\/p>\n<p>First we create a Join between our <i>Employee<\/i> and <i>Salary<\/i> and specify what type of join operation should be performed, in this case an <b>INNER<\/b> join should be performed so that we find an employee that has this salary.<\/p>\n<p>Finally, on line #60 we create a predicate saying that the employee must have a salary between min and max and that it should only consider the currently active salary, modeled <i>as<\/i> having a value of 9999-01-01.<\/p>\n<p><i>Line #25 and #65 &#8211; Title<\/i><\/p>\n<p>Similar to salary but we perform a <i>like<\/i> search instead.<\/p>\n<p><i>Line #31 &#8211; Search Term<\/i><\/p>\n<p>Finally we check if the user has provided a <i>searchTerm<\/i> which we use to match against both first and last name. This is done by creating two individual predicates, one for each attribute, and then combine to create one or predicate.<\/p>\n<p><i>Line #48 &#8211; Predicate result<\/i><\/p>\n<p>The last step is to create a final predicate specifying that a row in the database must match all individual predicates, i.e. <b>and<\/b>.<\/p>\n<h6>Creating and Using our Specification<\/h6>\n<pre class=\"brush: java; title: ; notranslate\" title=\"\">\n@Autowired\nprivate EmployeeRepository employeeRepo;\n\n@Transactional(readOnly = true)\npublic List&amp;lt;EmployeeVO&amp;gt; listEmployeesUsingSpecification(int page, int pageSize, String sortField, Sort.Direction sortDirection, LocalDate birthDate,\n  LocalDate hireDate, String title, Integer salary, String searchTerm) {\n\n  Pageable pageable = pageable(page, pageSize, sortField, sortDirection);\n\n  Specification&amp;lt;Employee&amp;gt; spec = EmployeeSpecification.builder()\n  .birthDate(birthDate)\n  .hireDate(hireDate)\n  .salary(salary)\n  .title(title)\n  .searchTerm(searchTerm)\n  .build();\n\n  Page&amp;lt;Employee&amp;gt; employees = employeeRepo.findAll(spec, pageable);\n\n  return employees.stream()\n  .map(EmployeeMapper::map)\n  .collect(Collectors.toList());\n}\n\nprivate Pageable pageable(int page, int pageSize, String sortField, Direction sortDirection) {\n  return PageRequest.of(page, pageSize, sortDirection, sortField);\n}\n<\/pre>\n<h6>Generated SQL<\/h6>\n<p>If we run this code, we get the following SQL<\/p>\n<pre class=\"brush: sql; title: ; notranslate\" title=\"\">\n-- searching using only searchTerm\nselect\n  emp_no,\n  birth_date,\n  first_name,\n  gender,\n  hire_date,\n  last_name  \nfrom\n  employees  \nwhere\n  lower(first_name) like ?  \n  or lower(last_name) like ?  \norder by\n  emp_no desc limit ?\n \n-- searching using title, salary and searchTerm\nselect\n  distinct employee.emp_no, -- distinct issued since we need to perform join\n  employee.birth_date,\n  employee.first_name,\n  employee.gender,\n  employee.hire_date,\n  employee.last_name  \nfrom\n  employees employee  \ninner join\n  salaries salary  \n  on employee.emp_no=salary.emp_no  \ninner join\n  titles title  \n  on employee.emp_no=title.emp_no  \nwhere\n  (salary.salary between 45000 and 55000)  \n  and salary.to_date=?  \n  and (lower(title.title) like ?)  \n  and titleto_date=?  \n  and (lower(employee.first_name) like ? or lower(employee.last_name) like ?)\norder by\n  employee.emp_no desc limit ?\n<\/pre>\n<p>As can be seen, our Specification has been used to generate the conditions we expect in the SQL issued towards the database. A few caveats are very important to take note of.<\/p>\n<h6>Distinct passed on to database<\/h6>\n<p>Once we perform a join we need to instruct Hibernate to not return an instance for each row in the result; this is because the join will create multiple rows in the result set.<\/p>\n<p>What we actually want here is for Hibernate\/JPA to not return an instance for each row but when using a Specification, we can&#8217;t provide a hint to JPA not to send the distinct on to the database<i>(HINT_PASS_DISTINCT_THROUGH<\/i>).<\/p>\n<h6>Paging increases load on database<\/h6>\n<p>If the result contains more rows than what fits in the requested page, a second query with <i>count<\/i> and <i>distinct<\/i> is issued to find the total number of rows. This could create heavy load on the database so care need to be taken that it isn&#8217;t overloaded.<\/p>\n<h6>n+1 loading child entities<\/h6>\n<p>If the call on line #21 to <i>EmployeeMapper::map<\/i> would map all salaries and titles for the current employee, JPA would issue one more <i>select<\/i> statement towards the database for each <i>Employee<\/i> in the result. This has the potential to overload the database and it degrades response times considerably.<\/p>\n<p>This is because our Specification does not contain a fetch<i>(root.fetch()<\/i>), this is possible to add but it will not work with paging since the <i>count<\/i> query isn&#8217;t compatible with the <i>fetch<\/i>.<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Perform complex and dynamic queries of JPA entities that span child collections using Specifications<\/p>\n","protected":false},"author":6,"featured_media":11469,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"inline_featured_image":false,"cybocfi_hide_featured_image":"","footnotes":""},"categories":[120,119,117,118],"tags":[],"class_list":["post-11629","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-application","category-java-en","category-jpa-en","category-spring-en"],"yoast_head":"<!-- This site is optimized with the Yoast SEO plugin v26.6 - https:\/\/yoast.com\/wordpress\/plugins\/seo\/ -->\n<title>Complex JPA Queries with Specifications &#8211; Predictly<\/title>\n<meta name=\"robots\" content=\"index, follow, max-snippet:-1, max-image-preview:large, max-video-preview:-1\" \/>\n<link rel=\"canonical\" href=\"https:\/\/predictly.se\/en\/complex-jpa-queries-with-specifications\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Complex JPA Queries with Specifications &#8211; Predictly\" \/>\n<meta property=\"og:description\" content=\"Perform complex and dynamic queries of JPA entities that span child collections using Specifications\" \/>\n<meta property=\"og:url\" content=\"https:\/\/predictly.se\/en\/complex-jpa-queries-with-specifications\/\" \/>\n<meta property=\"og:site_name\" content=\"Predictly\" \/>\n<meta property=\"article:publisher\" content=\"https:\/\/www.facebook.com\/predictly.se\" \/>\n<meta property=\"article:published_time\" content=\"2022-09-20T14:00:47+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2024-05-08T11:46:40+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/predictly.se\/wp-content\/uploads\/2022\/09\/keyboard.png\" \/>\n\t<meta property=\"og:image:width\" content=\"1429\" \/>\n\t<meta property=\"og:image:height\" content=\"888\" \/>\n\t<meta property=\"og:image:type\" content=\"image\/png\" \/>\n<meta name=\"author\" content=\"Patrik H\u00f6rlin\" \/>\n<meta name=\"twitter:card\" content=\"summary_large_image\" \/>\n<meta name=\"twitter:creator\" content=\"@predictly_se\" \/>\n<meta name=\"twitter:site\" content=\"@predictly_se\" \/>\n<meta name=\"twitter:label1\" content=\"Written by\" \/>\n\t<meta name=\"twitter:data1\" content=\"Patrik H\u00f6rlin\" \/>\n\t<meta name=\"twitter:label2\" content=\"Est. reading time\" \/>\n\t<meta name=\"twitter:data2\" content=\"7 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/predictly.se\/en\/complex-jpa-queries-with-specifications\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/predictly.se\/en\/complex-jpa-queries-with-specifications\/\"},\"author\":{\"name\":\"Patrik H\u00f6rlin\",\"@id\":\"https:\/\/predictly.se\/en\/#\/schema\/person\/57c5342be26b4569def7315855fa23c3\"},\"headline\":\"Complex JPA Queries with Specifications\",\"datePublished\":\"2022-09-20T14:00:47+00:00\",\"dateModified\":\"2024-05-08T11:46:40+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/predictly.se\/en\/complex-jpa-queries-with-specifications\/\"},\"wordCount\":1454,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\/\/predictly.se\/en\/#organization\"},\"image\":{\"@id\":\"https:\/\/predictly.se\/en\/complex-jpa-queries-with-specifications\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/predictly.se\/wp-content\/uploads\/2022\/09\/keyboard.png\",\"articleSection\":[\"Application\",\"Java\",\"JPA\",\"Spring\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\/\/predictly.se\/en\/complex-jpa-queries-with-specifications\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/predictly.se\/en\/complex-jpa-queries-with-specifications\/\",\"url\":\"https:\/\/predictly.se\/en\/complex-jpa-queries-with-specifications\/\",\"name\":\"Complex JPA Queries with Specifications &#8211; Predictly\",\"isPartOf\":{\"@id\":\"https:\/\/predictly.se\/en\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/predictly.se\/en\/complex-jpa-queries-with-specifications\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/predictly.se\/en\/complex-jpa-queries-with-specifications\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/predictly.se\/wp-content\/uploads\/2022\/09\/keyboard.png\",\"datePublished\":\"2022-09-20T14:00:47+00:00\",\"dateModified\":\"2024-05-08T11:46:40+00:00\",\"breadcrumb\":{\"@id\":\"https:\/\/predictly.se\/en\/complex-jpa-queries-with-specifications\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/predictly.se\/en\/complex-jpa-queries-with-specifications\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/predictly.se\/en\/complex-jpa-queries-with-specifications\/#primaryimage\",\"url\":\"https:\/\/predictly.se\/wp-content\/uploads\/2022\/09\/keyboard.png\",\"contentUrl\":\"https:\/\/predictly.se\/wp-content\/uploads\/2022\/09\/keyboard.png\",\"width\":1429,\"height\":888},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/predictly.se\/en\/complex-jpa-queries-with-specifications\/#breadcrumb\",\"itemListElement\":[{\"@type\":\"ListItem\",\"position\":1,\"name\":\"Home\",\"item\":\"https:\/\/predictly.se\/en\/\"},{\"@type\":\"ListItem\",\"position\":2,\"name\":\"Okategoriserad\",\"item\":\"https:\/\/predictly.se\/en\/insikter\/okategoriserad\/\"},{\"@type\":\"ListItem\",\"position\":3,\"name\":\"Complex JPA Queries with Specifications\"}]},{\"@type\":\"WebSite\",\"@id\":\"https:\/\/predictly.se\/en\/#website\",\"url\":\"https:\/\/predictly.se\/en\/\",\"name\":\"Predictly\",\"description\":\"Professional IT services\",\"publisher\":{\"@id\":\"https:\/\/predictly.se\/en\/#organization\"},\"potentialAction\":[{\"@type\":\"SearchAction\",\"target\":{\"@type\":\"EntryPoint\",\"urlTemplate\":\"https:\/\/predictly.se\/en\/?s={search_term_string}\"},\"query-input\":{\"@type\":\"PropertyValueSpecification\",\"valueRequired\":true,\"valueName\":\"search_term_string\"}}],\"inLanguage\":\"en-US\"},{\"@type\":\"Organization\",\"@id\":\"https:\/\/predictly.se\/en\/#organization\",\"name\":\"Predictly\",\"url\":\"https:\/\/predictly.se\/en\/\",\"logo\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/predictly.se\/en\/#\/schema\/logo\/image\/\",\"url\":\"https:\/\/predictly.se\/wp-content\/uploads\/2022\/10\/Logotype1-mobil.svg\",\"contentUrl\":\"https:\/\/predictly.se\/wp-content\/uploads\/2022\/10\/Logotype1-mobil.svg\",\"width\":532,\"height\":96,\"caption\":\"Predictly\"},\"image\":{\"@id\":\"https:\/\/predictly.se\/en\/#\/schema\/logo\/image\/\"},\"sameAs\":[\"https:\/\/www.facebook.com\/predictly.se\",\"https:\/\/x.com\/predictly_se\",\"https:\/\/www.linkedin.com\/company\/predictly\/\"]},{\"@type\":\"Person\",\"@id\":\"https:\/\/predictly.se\/en\/#\/schema\/person\/57c5342be26b4569def7315855fa23c3\",\"name\":\"Patrik H\u00f6rlin\",\"image\":{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/predictly.se\/en\/#\/schema\/person\/image\/\",\"url\":\"https:\/\/secure.gravatar.com\/avatar\/60d93834b6a9c87c5625621e882e4a7c538383fb9323297804eb2473d122e9c8?s=96&d=mm&r=g\",\"contentUrl\":\"https:\/\/secure.gravatar.com\/avatar\/60d93834b6a9c87c5625621e882e4a7c538383fb9323297804eb2473d122e9c8?s=96&d=mm&r=g\",\"caption\":\"Patrik H\u00f6rlin\"}}]}<\/script>\n<!-- \/ Yoast SEO plugin. -->","yoast_head_json":{"title":"Complex JPA Queries with Specifications &#8211; Predictly","robots":{"index":"index","follow":"follow","max-snippet":"max-snippet:-1","max-image-preview":"max-image-preview:large","max-video-preview":"max-video-preview:-1"},"canonical":"https:\/\/predictly.se\/en\/complex-jpa-queries-with-specifications\/","og_locale":"en_US","og_type":"article","og_title":"Complex JPA Queries with Specifications &#8211; Predictly","og_description":"Perform complex and dynamic queries of JPA entities that span child collections using Specifications","og_url":"https:\/\/predictly.se\/en\/complex-jpa-queries-with-specifications\/","og_site_name":"Predictly","article_publisher":"https:\/\/www.facebook.com\/predictly.se","article_published_time":"2022-09-20T14:00:47+00:00","article_modified_time":"2024-05-08T11:46:40+00:00","og_image":[{"width":1429,"height":888,"url":"https:\/\/predictly.se\/wp-content\/uploads\/2022\/09\/keyboard.png","type":"image\/png"}],"author":"Patrik H\u00f6rlin","twitter_card":"summary_large_image","twitter_creator":"@predictly_se","twitter_site":"@predictly_se","twitter_misc":{"Written by":"Patrik H\u00f6rlin","Est. reading time":"7 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/predictly.se\/en\/complex-jpa-queries-with-specifications\/#article","isPartOf":{"@id":"https:\/\/predictly.se\/en\/complex-jpa-queries-with-specifications\/"},"author":{"name":"Patrik H\u00f6rlin","@id":"https:\/\/predictly.se\/en\/#\/schema\/person\/57c5342be26b4569def7315855fa23c3"},"headline":"Complex JPA Queries with Specifications","datePublished":"2022-09-20T14:00:47+00:00","dateModified":"2024-05-08T11:46:40+00:00","mainEntityOfPage":{"@id":"https:\/\/predictly.se\/en\/complex-jpa-queries-with-specifications\/"},"wordCount":1454,"commentCount":0,"publisher":{"@id":"https:\/\/predictly.se\/en\/#organization"},"image":{"@id":"https:\/\/predictly.se\/en\/complex-jpa-queries-with-specifications\/#primaryimage"},"thumbnailUrl":"https:\/\/predictly.se\/wp-content\/uploads\/2022\/09\/keyboard.png","articleSection":["Application","Java","JPA","Spring"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/predictly.se\/en\/complex-jpa-queries-with-specifications\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/predictly.se\/en\/complex-jpa-queries-with-specifications\/","url":"https:\/\/predictly.se\/en\/complex-jpa-queries-with-specifications\/","name":"Complex JPA Queries with Specifications &#8211; Predictly","isPartOf":{"@id":"https:\/\/predictly.se\/en\/#website"},"primaryImageOfPage":{"@id":"https:\/\/predictly.se\/en\/complex-jpa-queries-with-specifications\/#primaryimage"},"image":{"@id":"https:\/\/predictly.se\/en\/complex-jpa-queries-with-specifications\/#primaryimage"},"thumbnailUrl":"https:\/\/predictly.se\/wp-content\/uploads\/2022\/09\/keyboard.png","datePublished":"2022-09-20T14:00:47+00:00","dateModified":"2024-05-08T11:46:40+00:00","breadcrumb":{"@id":"https:\/\/predictly.se\/en\/complex-jpa-queries-with-specifications\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/predictly.se\/en\/complex-jpa-queries-with-specifications\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/predictly.se\/en\/complex-jpa-queries-with-specifications\/#primaryimage","url":"https:\/\/predictly.se\/wp-content\/uploads\/2022\/09\/keyboard.png","contentUrl":"https:\/\/predictly.se\/wp-content\/uploads\/2022\/09\/keyboard.png","width":1429,"height":888},{"@type":"BreadcrumbList","@id":"https:\/\/predictly.se\/en\/complex-jpa-queries-with-specifications\/#breadcrumb","itemListElement":[{"@type":"ListItem","position":1,"name":"Home","item":"https:\/\/predictly.se\/en\/"},{"@type":"ListItem","position":2,"name":"Okategoriserad","item":"https:\/\/predictly.se\/en\/insikter\/okategoriserad\/"},{"@type":"ListItem","position":3,"name":"Complex JPA Queries with Specifications"}]},{"@type":"WebSite","@id":"https:\/\/predictly.se\/en\/#website","url":"https:\/\/predictly.se\/en\/","name":"Predictly","description":"Professional IT services","publisher":{"@id":"https:\/\/predictly.se\/en\/#organization"},"potentialAction":[{"@type":"SearchAction","target":{"@type":"EntryPoint","urlTemplate":"https:\/\/predictly.se\/en\/?s={search_term_string}"},"query-input":{"@type":"PropertyValueSpecification","valueRequired":true,"valueName":"search_term_string"}}],"inLanguage":"en-US"},{"@type":"Organization","@id":"https:\/\/predictly.se\/en\/#organization","name":"Predictly","url":"https:\/\/predictly.se\/en\/","logo":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/predictly.se\/en\/#\/schema\/logo\/image\/","url":"https:\/\/predictly.se\/wp-content\/uploads\/2022\/10\/Logotype1-mobil.svg","contentUrl":"https:\/\/predictly.se\/wp-content\/uploads\/2022\/10\/Logotype1-mobil.svg","width":532,"height":96,"caption":"Predictly"},"image":{"@id":"https:\/\/predictly.se\/en\/#\/schema\/logo\/image\/"},"sameAs":["https:\/\/www.facebook.com\/predictly.se","https:\/\/x.com\/predictly_se","https:\/\/www.linkedin.com\/company\/predictly\/"]},{"@type":"Person","@id":"https:\/\/predictly.se\/en\/#\/schema\/person\/57c5342be26b4569def7315855fa23c3","name":"Patrik H\u00f6rlin","image":{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/predictly.se\/en\/#\/schema\/person\/image\/","url":"https:\/\/secure.gravatar.com\/avatar\/60d93834b6a9c87c5625621e882e4a7c538383fb9323297804eb2473d122e9c8?s=96&d=mm&r=g","contentUrl":"https:\/\/secure.gravatar.com\/avatar\/60d93834b6a9c87c5625621e882e4a7c538383fb9323297804eb2473d122e9c8?s=96&d=mm&r=g","caption":"Patrik H\u00f6rlin"}}]}},"_links":{"self":[{"href":"https:\/\/predictly.se\/en\/wp-json\/wp\/v2\/posts\/11629","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/predictly.se\/en\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/predictly.se\/en\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/predictly.se\/en\/wp-json\/wp\/v2\/users\/6"}],"replies":[{"embeddable":true,"href":"https:\/\/predictly.se\/en\/wp-json\/wp\/v2\/comments?post=11629"}],"version-history":[{"count":1,"href":"https:\/\/predictly.se\/en\/wp-json\/wp\/v2\/posts\/11629\/revisions"}],"predecessor-version":[{"id":11641,"href":"https:\/\/predictly.se\/en\/wp-json\/wp\/v2\/posts\/11629\/revisions\/11641"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/predictly.se\/en\/wp-json\/wp\/v2\/media\/11469"}],"wp:attachment":[{"href":"https:\/\/predictly.se\/en\/wp-json\/wp\/v2\/media?parent=11629"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/predictly.se\/en\/wp-json\/wp\/v2\/categories?post=11629"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/predictly.se\/en\/wp-json\/wp\/v2\/tags?post=11629"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}