{"id":11659,"date":"2022-09-20T16:13:58","date_gmt":"2022-09-20T16:13:58","guid":{"rendered":"https:\/\/predictly.se\/spring-data-jpa-batch-jobs-with-streams\/"},"modified":"2024-05-08T11:46:41","modified_gmt":"2024-05-08T11:46:41","slug":"spring-data-jpa-batch-jobs-with-streams","status":"publish","type":"post","link":"https:\/\/predictly.se\/en\/spring-data-jpa-batch-jobs-with-streams\/","title":{"rendered":"Spring Data JPA &#8211; batch jobs with Streams"},"content":{"rendered":"<p>Spring Data JPA is great. It saves us time to focus on what&#8217;s important while not sacrificing much in terms of usability and performance. I have now been using Spring Data JPA in a batch and ETL setup for more than one year and learned a lot in the process. Time to share some findings!<\/p>\n<p>The first and most important finding when working with streams is that <b>you can&#8217;t just swap your return type from List to Stream<\/b> in your repository interface! Doing so will create a lot of unwanted side effects and often not produce the result you expected.<\/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>. The JPA model of the Employee entity is shown 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<h6>Container performance issue<\/h6>\n<p>A quick note on the docker container. Once started, the statistics of the database I used for this article was incorrect leading to very poor performance in joins. If you want to replicate or test the findings of this article, the following SQL statement is necessary:<\/p>\n<pre class=\"brush: sql; title: ; notranslate\" title=\"\">select * from mysql.innodb_table_stats where database_name = &#039;employees&#039;;<\/pre>\n<p>Check that the values of n_rows is correct for each table, especially for <i>salaries<\/i>. If it says 0, run this query and then check again:<\/p>\n<pre class=\"brush: sql; title: ; notranslate\" title=\"\">analyze table salaries;<\/pre>\n<h6>Working with unbounded Result Sets<\/h6>\n<p><a href=\"https:\/\/spring.io\/projects\/spring-data-jpa\" target=\"_blank\" rel=\"noopener noreferrer\">Spring Data JPA<\/a> repositories in their default setup expects to return instances of either <i>Optional<\/i> or <i>List<\/i>, meaning that the result set will be lifted into memory and mapped to your model object. This works well for many use cases, but when the result set becomes very large (&gt; 100,000 records) or the result set size isn&#8217;t known in advance, memory will become a problem. Enter <i>Stream<\/i><\/p>\n<h6>Creating and Closing a Stream<\/h6>\n<p>Creating a Stream is extremely easy thanks to Spring Data JPA which supports this out of the box.<\/p>\n<pre class=\"brush: java; title: ; notranslate\" title=\"\">\n@Repository\npublic interface EmployeeRepository extends JpaRepository&amp;lt;Employee, Integer&amp;gt; {\n\n  Stream&amp;lt;Employee&amp;gt; findByHireDateBetween(LocalDate from, LocalDate to);\n}\n<\/pre>\n<p>This can then be used in our code as follows<\/p>\n<pre class=\"brush: java; title: ; notranslate\" title=\"\">\n@Autowired\nprivate EmployeeRepository employeeRepo;\n\n@Transactional(readOnly = true)\npublic void exportEmployees(LocalDate hireDateFrom, LocalDate hireDateTo) {\n  try (Stream&amp;lt;Employee&amp;gt; employees = employeeRepo.findByHireDateBetween(hireDateFrom, hireDateTo)) {\n  employees.forEach(this::mapAndWrite);\n  }\n}\n\nprivate void mapAndWrite(Employee from) {\n  EmployeeVO vo = EmployeeMapper.map(from);\n  writeToFile(vo);\n}\n<\/pre>\n<p><i>Line #1 &#8211; Any stream operation must be wrapped in a transaction<\/i><br \/>\nSpring will throw an exception otherwise<\/p>\n<p><i>Line #3 &#8211; A stream is created by Spring Data JPA but <b>must be closed by you<\/b><\/i><br \/>\nA stream keeps a cursor to the result set open since it can&#8217;t know when the result set has been consumed. If not closed you will run out of cursors in the database. Use try-with-resources or call <i>close<\/i> on the stream.<\/p>\n<p><i>Line #4 &#8211; Forward operations only<\/i><br \/>\nA stream can only be consumed once<\/p>\n<p>If we run the code it will work and we will get a Stream to work with, but it will take a long time to complete as we increase the gap between <i>hireDateFrom<\/i> and <i>hireDateTo<\/i>. Using SQL logging we can see that for each employee, a second and third call is made to collect all <i>salaries<\/i> and all <i>titles<\/i> for that employee. In our case, this is triggered via the call to <i>EmployeeMapper::map<\/i>. This is normal JPA and Hibernate behavior, lazy loading child collections when they are needed.<\/p>\n<p>The problem we often face is that if we are turning to use Streams, we often also need all or many of the entity&#8217;s child collections populated.<\/p>\n<h6>Joining child collections<\/h6>\n<p>Child collections can be populated during the initial query but special care must be taken to avoid pitfalls that are the result of switching to using streams.<\/p>\n<pre class=\"brush: java; title: ; notranslate\" title=\"\">\n@Repository\npublic interface EmployeeRepository extends JpaRepository&amp;lt;Employee, Integer&amp;gt; {\n\n  @Query(&quot;SELECT DISTINCT e from Employee e &quot;\n  + LEFT JOIN FETCH e.salaries &quot;\n  + LEFT JOIN FETCH e.titles &quot;\n  + &quot;WHERE e.hireDate BETWEEN ?1 AND ?2 &quot;\n  + &quot;ORDER BY e.employeeId&quot;)\n  @QueryHints(value = {\n  @QueryHint(name = HINT_FETCH_SIZE, value = &quot;&quot; + Integer.MIN_VALUE),\n  @QueryHint(name = HINT_CACHEABLE, value = &quot;false&quot;),\n  @QueryHint(name = HINT_READONLY, value = &quot;true&quot;),\n  @QueryHint(name = HINT_PASS_DISTINCT_THROUGH, value = &quot;false&quot;)\n  })  \n  Stream&amp;lt;Employee&amp;gt; findByHireDateBetween(LocalDate from, LocalDate to);\n}\n<\/pre>\n<p>Let&#8217;s start by examining the query.<\/p>\n<p><i>Line #4 &#8211; Remove duplicates in stream via DISTINCT<\/i><br \/>\nBy default, JPA and Hibernate will return an instance for each row returned in the result set. Since we are now joining multiple tables, the id of an employee will appear many times. Even though each instance of an employee is identical, it will appear as many times in the stream as the join produces rows for that employee.<\/p>\n<p><i>Line #5-6 &#8211; FETCH JOIN to eagerly load child collections<\/i><br \/>\nBy eagerly loading these collections, JPA\/Hibernate understands that it does not need to populate the collections once accessed. Fetching multiple child collections works as long as they are represented with a <i>Set<\/i> in the entity and not with a <i>List<\/i>. If they are represented as a <i>List<\/i>, Hibernate will throw a <i>MultipleBagFetchException<\/i> (note on performance from the Hibernate expert <a href=\"https:\/\/vladmihalcea.com\/hibernate-multiplebagfetchexception\/\" target=\"_blank\" rel=\"noopener noreferrer\">Vlad Mihalcea<\/a>)<\/p>\n<p><i>Line #8 &#8211; ORDER BY to remove partial results<\/i><br \/>\nHow does Hibernate know that an entity is ready in the stream when each Employee will result in multiple rows in the result set due to the join? Hibernate solves this by looking at the id attribute(s) of the entity, as soon as they change in the result set, an entity will become available in our stream.<br \/>\nThis means that if we don&#8217;t order our result on the primary entity&#8217;s id attribute(s), we will end up with partial results since there is no guarantee that the database will keep rows relating to the same Employee together.<\/p>\n<h6>Instructing Hibernate with QueryHints<\/h6>\n<p>To make full use of streams in JPA, we need to provide additional information.<\/p>\n<p><i>Line #10 &#8211; Enable streaming via HINT_FETCH_SIZE<\/i><br \/>\n<b>This instruction is crucial<\/b> to make use of and benefit from streams in Spring Data JPA. Without this, the underlying JDBC driver will actually still load all entities from the result set into memory even though we have declared the return object to be a <i>Stream<\/i>.<\/p>\n<p>Even more strange is that the value is specific for different drivers. For MySQL it must have a value of <i>Integer.MIN_VALUE<\/i>, for PostgreSQL it must have a value but it can use any positive integer, e.g. &#8220;<i>500<\/i>&#8220;.<\/p>\n<p><i>Line #13 &#8211; Remove duplicates in stream via HINT_PASS_DISTINCT_THROUGH<\/i><br \/>\nThis instruction informs Spring Data JPA\/Hibernate not to pass a <i>DISTINCT<\/i> statement to the database via SQL. Instead it will interpret the <i>DISTINCT<\/i> statement in our JPQL as an instruction to Hibernate not to return the same entity one time for each row returned, i.e. it is used in conjunction with the instruction regarding <i>DISTINCT<\/i> explained above.<\/p>\n<h6>Managing memory when consuming a Stream<\/h6>\n<p>Using the repository above we are ready to consume and process each Employee via a Stream. However, if we invoke <i>exportEmployees<\/i> as above we will eventually run into an <i>OutOfMemoryError<\/i>.<br \/>\nThe reason for this is that even though we are streaming Employee entities from the database and have marked the query and transaction as read only, Hibernate keeps track of all entities in it&#8217;s persistence context. After going through a few thousand records, the heap will be full of these entities.<br \/>\nTo resolve this we have to handle each entity in the stream in a different manner and most importantly tell Hibernate that it shouldn&#8217;t keep track of the entity in the persistence context once we are done with it.<\/p>\n<pre class=\"brush: java; title: ; notranslate\" title=\"\">\n@Autowired\nprivate EmployeeRepository employeeRepo;\n\n@Autowired\nprivate EntityManager entityManager;\n\n@Transactional(readOnly = true)\npublic void exportEmployees(LocalDate hireDateFrom, LocalDate hireDateTo) {\n  try (Stream&amp;lt;Employee&amp;gt; employees = employeeRepo.findByHireDateBetween(hireDateFrom, hireDateTo)) {\n  employees.forEach(this::mapWriteAndDetach);\n  }\n}\n\nprivate void mapWriteAndDetach(Employee from) {\n  EmployeeVO vo = EmployeeMapper.map(from);\n  writeToFile(vo);\n  this.entityManager.detach(from);\n}\n<\/pre>\n<p><i>Line #11 &#8211; Remove entity from persistence context<\/i><br \/>\nWith this instruction, we let Hibernate know that we are done with this instance and it should no longer keep track of it. Once Hibernate releases the object from it&#8217;s persistence context, the JVM can successfully free the memory during the next GC.<\/p>\n<h6>Conclusion<\/h6>\n<p>Spring Data JPA and Hibernate are great tools but they were never intended to be used in batch or ETL settings. With some careful setup they can actually be used with great success for these kinds of purposes, making it possible to reuse a lot of the awesome features in Spring Data JPA and Hibernate while rapidly processing millions of entities without causing runtime errors.<\/p>\n<p>Kudos to Daniel Hommrich<\/p>\n","protected":false},"excerpt":{"rendered":"<p>Spring Data JPA is great for rapid development but special care must be taken when it comes to handling large data sets<\/p>\n","protected":false},"author":6,"featured_media":11450,"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-11659","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>Spring Data JPA - batch jobs with Streams &#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\/spring-data-jpa-batch-jobs-with-streams\/\" \/>\n<meta property=\"og:locale\" content=\"en_US\" \/>\n<meta property=\"og:type\" content=\"article\" \/>\n<meta property=\"og:title\" content=\"Spring Data JPA - batch jobs with Streams &#8211; Predictly\" \/>\n<meta property=\"og:description\" content=\"Spring Data JPA is great for rapid development but special care must be taken when it comes to handling large data sets\" \/>\n<meta property=\"og:url\" content=\"https:\/\/predictly.se\/en\/spring-data-jpa-batch-jobs-with-streams\/\" \/>\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-20T16:13:58+00:00\" \/>\n<meta property=\"article:modified_time\" content=\"2024-05-08T11:46:41+00:00\" \/>\n<meta property=\"og:image\" content=\"https:\/\/predictly.se\/wp-content\/uploads\/2022\/09\/stream-2.jpg\" \/>\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\/jpeg\" \/>\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=\"8 minutes\" \/>\n<script type=\"application\/ld+json\" class=\"yoast-schema-graph\">{\"@context\":\"https:\/\/schema.org\",\"@graph\":[{\"@type\":\"Article\",\"@id\":\"https:\/\/predictly.se\/en\/spring-data-jpa-batch-jobs-with-streams\/#article\",\"isPartOf\":{\"@id\":\"https:\/\/predictly.se\/en\/spring-data-jpa-batch-jobs-with-streams\/\"},\"author\":{\"name\":\"Patrik H\u00f6rlin\",\"@id\":\"https:\/\/predictly.se\/en\/#\/schema\/person\/57c5342be26b4569def7315855fa23c3\"},\"headline\":\"Spring Data JPA &#8211; batch jobs with Streams\",\"datePublished\":\"2022-09-20T16:13:58+00:00\",\"dateModified\":\"2024-05-08T11:46:41+00:00\",\"mainEntityOfPage\":{\"@id\":\"https:\/\/predictly.se\/en\/spring-data-jpa-batch-jobs-with-streams\/\"},\"wordCount\":1547,\"commentCount\":0,\"publisher\":{\"@id\":\"https:\/\/predictly.se\/en\/#organization\"},\"image\":{\"@id\":\"https:\/\/predictly.se\/en\/spring-data-jpa-batch-jobs-with-streams\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/predictly.se\/wp-content\/uploads\/2022\/09\/stream-2.jpg\",\"articleSection\":[\"Application\",\"Java\",\"JPA\",\"Spring\"],\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"CommentAction\",\"name\":\"Comment\",\"target\":[\"https:\/\/predictly.se\/en\/spring-data-jpa-batch-jobs-with-streams\/#respond\"]}]},{\"@type\":\"WebPage\",\"@id\":\"https:\/\/predictly.se\/en\/spring-data-jpa-batch-jobs-with-streams\/\",\"url\":\"https:\/\/predictly.se\/en\/spring-data-jpa-batch-jobs-with-streams\/\",\"name\":\"Spring Data JPA - batch jobs with Streams &#8211; Predictly\",\"isPartOf\":{\"@id\":\"https:\/\/predictly.se\/en\/#website\"},\"primaryImageOfPage\":{\"@id\":\"https:\/\/predictly.se\/en\/spring-data-jpa-batch-jobs-with-streams\/#primaryimage\"},\"image\":{\"@id\":\"https:\/\/predictly.se\/en\/spring-data-jpa-batch-jobs-with-streams\/#primaryimage\"},\"thumbnailUrl\":\"https:\/\/predictly.se\/wp-content\/uploads\/2022\/09\/stream-2.jpg\",\"datePublished\":\"2022-09-20T16:13:58+00:00\",\"dateModified\":\"2024-05-08T11:46:41+00:00\",\"breadcrumb\":{\"@id\":\"https:\/\/predictly.se\/en\/spring-data-jpa-batch-jobs-with-streams\/#breadcrumb\"},\"inLanguage\":\"en-US\",\"potentialAction\":[{\"@type\":\"ReadAction\",\"target\":[\"https:\/\/predictly.se\/en\/spring-data-jpa-batch-jobs-with-streams\/\"]}]},{\"@type\":\"ImageObject\",\"inLanguage\":\"en-US\",\"@id\":\"https:\/\/predictly.se\/en\/spring-data-jpa-batch-jobs-with-streams\/#primaryimage\",\"url\":\"https:\/\/predictly.se\/wp-content\/uploads\/2022\/09\/stream-2.jpg\",\"contentUrl\":\"https:\/\/predictly.se\/wp-content\/uploads\/2022\/09\/stream-2.jpg\",\"width\":1429,\"height\":888},{\"@type\":\"BreadcrumbList\",\"@id\":\"https:\/\/predictly.se\/en\/spring-data-jpa-batch-jobs-with-streams\/#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\":\"Spring Data JPA &#8211; batch jobs with Streams\"}]},{\"@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":"Spring Data JPA - batch jobs with Streams &#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\/spring-data-jpa-batch-jobs-with-streams\/","og_locale":"en_US","og_type":"article","og_title":"Spring Data JPA - batch jobs with Streams &#8211; Predictly","og_description":"Spring Data JPA is great for rapid development but special care must be taken when it comes to handling large data sets","og_url":"https:\/\/predictly.se\/en\/spring-data-jpa-batch-jobs-with-streams\/","og_site_name":"Predictly","article_publisher":"https:\/\/www.facebook.com\/predictly.se","article_published_time":"2022-09-20T16:13:58+00:00","article_modified_time":"2024-05-08T11:46:41+00:00","og_image":[{"width":1429,"height":888,"url":"https:\/\/predictly.se\/wp-content\/uploads\/2022\/09\/stream-2.jpg","type":"image\/jpeg"}],"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":"8 minutes"},"schema":{"@context":"https:\/\/schema.org","@graph":[{"@type":"Article","@id":"https:\/\/predictly.se\/en\/spring-data-jpa-batch-jobs-with-streams\/#article","isPartOf":{"@id":"https:\/\/predictly.se\/en\/spring-data-jpa-batch-jobs-with-streams\/"},"author":{"name":"Patrik H\u00f6rlin","@id":"https:\/\/predictly.se\/en\/#\/schema\/person\/57c5342be26b4569def7315855fa23c3"},"headline":"Spring Data JPA &#8211; batch jobs with Streams","datePublished":"2022-09-20T16:13:58+00:00","dateModified":"2024-05-08T11:46:41+00:00","mainEntityOfPage":{"@id":"https:\/\/predictly.se\/en\/spring-data-jpa-batch-jobs-with-streams\/"},"wordCount":1547,"commentCount":0,"publisher":{"@id":"https:\/\/predictly.se\/en\/#organization"},"image":{"@id":"https:\/\/predictly.se\/en\/spring-data-jpa-batch-jobs-with-streams\/#primaryimage"},"thumbnailUrl":"https:\/\/predictly.se\/wp-content\/uploads\/2022\/09\/stream-2.jpg","articleSection":["Application","Java","JPA","Spring"],"inLanguage":"en-US","potentialAction":[{"@type":"CommentAction","name":"Comment","target":["https:\/\/predictly.se\/en\/spring-data-jpa-batch-jobs-with-streams\/#respond"]}]},{"@type":"WebPage","@id":"https:\/\/predictly.se\/en\/spring-data-jpa-batch-jobs-with-streams\/","url":"https:\/\/predictly.se\/en\/spring-data-jpa-batch-jobs-with-streams\/","name":"Spring Data JPA - batch jobs with Streams &#8211; Predictly","isPartOf":{"@id":"https:\/\/predictly.se\/en\/#website"},"primaryImageOfPage":{"@id":"https:\/\/predictly.se\/en\/spring-data-jpa-batch-jobs-with-streams\/#primaryimage"},"image":{"@id":"https:\/\/predictly.se\/en\/spring-data-jpa-batch-jobs-with-streams\/#primaryimage"},"thumbnailUrl":"https:\/\/predictly.se\/wp-content\/uploads\/2022\/09\/stream-2.jpg","datePublished":"2022-09-20T16:13:58+00:00","dateModified":"2024-05-08T11:46:41+00:00","breadcrumb":{"@id":"https:\/\/predictly.se\/en\/spring-data-jpa-batch-jobs-with-streams\/#breadcrumb"},"inLanguage":"en-US","potentialAction":[{"@type":"ReadAction","target":["https:\/\/predictly.se\/en\/spring-data-jpa-batch-jobs-with-streams\/"]}]},{"@type":"ImageObject","inLanguage":"en-US","@id":"https:\/\/predictly.se\/en\/spring-data-jpa-batch-jobs-with-streams\/#primaryimage","url":"https:\/\/predictly.se\/wp-content\/uploads\/2022\/09\/stream-2.jpg","contentUrl":"https:\/\/predictly.se\/wp-content\/uploads\/2022\/09\/stream-2.jpg","width":1429,"height":888},{"@type":"BreadcrumbList","@id":"https:\/\/predictly.se\/en\/spring-data-jpa-batch-jobs-with-streams\/#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":"Spring Data JPA &#8211; batch jobs with Streams"}]},{"@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\/11659","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=11659"}],"version-history":[{"count":1,"href":"https:\/\/predictly.se\/en\/wp-json\/wp\/v2\/posts\/11659\/revisions"}],"predecessor-version":[{"id":11671,"href":"https:\/\/predictly.se\/en\/wp-json\/wp\/v2\/posts\/11659\/revisions\/11671"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/predictly.se\/en\/wp-json\/wp\/v2\/media\/11450"}],"wp:attachment":[{"href":"https:\/\/predictly.se\/en\/wp-json\/wp\/v2\/media?parent=11659"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/predictly.se\/en\/wp-json\/wp\/v2\/categories?post=11659"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/predictly.se\/en\/wp-json\/wp\/v2\/tags?post=11659"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}