如何以大数据的JAX-RS响应的形式将JPA结果流化/序列化

有时,有必要通过JPA检索大型数据集(例如,超过1,000,000条记录),并将它们填充到java.util.List的单个实例中是有风险的(内存障碍)。 因此,这是一个快速的解决方案,它可以解决JAX-RS REST资源端点如何仍能及时为我们提供响应,而又不会通过“页面”对JPA实体进行流式处理或序列化来打破内存限制的情况。

示例数据库表和JPA实体

数据库表

为了演示如何实现大数据的输出,这是我们可以使用的示例MySQL数据库表。

create database large_data_test_db;
use large_data_test_db;create table generated_uuids (record_no bigint not null auto_increment,uuid varchar(100) not null,datetime_generated datetime not null,primary key(record_no),unique(uuid)
);

JPA实体

接下来,定义代表上述表结构的JPA实体类。

GeneratedUuidEntity.java的代码

package com.developerscrappad;import java.io.Serializable;
import java.util.Date;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.NamedQueries;
import javax.persistence.NamedQuery;
import javax.persistence.Table;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;@Entity
@Table( name = "generated_uuids" )
@NamedQueries( {@NamedQuery( name = "GeneratedUuidEntity.listAll", query = "SELECT u FROM GeneratedUuidEntity u" ),@NamedQuery( name = "GeneratedUuidEntity.queryRecordsSize", query = "SELECT count(u) FROM GeneratedUuidEntity u" )
} )
public class GeneratedUuidEntity implements Serializable {private static final long serialVersionUID = 12312312234234123L;@Id@GeneratedValue( strategy = GenerationType.IDENTITY )@Column( name = "record_no" )private Long recordNo;@Column( name = "uuid" )private String uuid;@Column( name = "datetime_generated" )@Temporal( TemporalType.TIMESTAMP )private Date datetimeGenerated;public GeneratedUuidEntity() {}public GeneratedUuidEntity( Long recordNo ) {this.recordNo = recordNo;}public GeneratedUuidEntity( Long recordNo, String uuid, Date datetimeGenerated ) {this.recordNo = recordNo;this.uuid = uuid;this.datetimeGenerated = datetimeGenerated;}public Long getRecordNo() {return recordNo;}public void setRecordNo( Long recordNo ) {this.recordNo = recordNo;}public String getUuid() {return uuid;}public void setUuid( String uuid ) {this.uuid = uuid;}public Date getDatetimeGenerated() {return datetimeGenerated;}public void setDatetimeGenerated( Date datetimeGenerated ) {this.datetimeGenerated = datetimeGenerated;}@Overridepublic int hashCode() {int hash = 0;hash += ( recordNo != null ? recordNo.hashCode() : 0 );return hash;}@Overridepublic boolean equals( Object object ) {// TODO: Warning - this method won't work in the case the id fields are not setif ( !( object instanceof GeneratedUuidEntity ) ) {return false;}GeneratedUuidEntity other = ( GeneratedUuidEntity ) object;if ( ( this.recordNo == null && other.recordNo != null ) || ( this.recordNo != null && !this.recordNo.equals( other.recordNo ) ) ) {return false;}return true;}@Overridepublic String toString() {return "com.developerscrappad.GeneratedUuidEntity[ recordNo=" + recordNo + " ]";}
}

GeneratedUuidEntity中定义了两个命名查询。 GeneratedUuidEntity.queryRecordsSize用于查询表的总记录数,而GeneratedUuidEntity.listAll用于检索表中的所有记录。

实施JAX-RS REST资源(Java EE方式)

让我们有一个名称为JPAStreamingRESTResource的JAX-RS REST资源类,其中有一个可用的JPA EntityManager(持久性单元名称: JPAStreamingPU )要注入并通过受保护的方法getEntityManager()获得

@Path( "generated-uuids" )
@Stateless( name = "JPAStreamingRESTResource", mappedName = "ejb/JPAStreamingRESTResource" )
public class JPAStreamingRESTResource {@PersistenceContext( unitName = "JPAStreamingPU" )private EntityManager entityManager;protected EntityManager getEntityManager() {return entityManager;}/*** Say "NO" to response caching*/protected Response.ResponseBuilder getNoCacheResponseBuilder( Response.Status status ) {CacheControl cc = new CacheControl();cc.setNoCache( true );cc.setMaxAge( -1 );cc.setMustRevalidate( true );return Response.status( status ).cacheControl( cc );}
}

此外,我们有一个名为getNoCacheResponseBuilder()的方法,该方法用于获取非缓存的javax.ws.rs.core.Response.ResponseBuilder ,这样以后就不会再得到奇怪的缓存结果了。

JPA调用方法

接下来,让我们在资源类中定义两个方法,即:

queryGeneratedUuidRecordsSize() –检索表中的记录总数

private int queryGeneratedUuidRecordsSize() {return getEntityManager().createNamedQuery( "GeneratedUuidEntity.queryRecordsSize", Long.class ).getSingleResult().intValue();
}

listAllGeneratedUuidEntities() –从表中检索所有数据,但具有某些限制条件,例如记录的起始位置(recordPosition)和每次往返数据库的最大记录数(recordsPerRoundTrip)。 目的是“分页”结果,以使结果列表不会过分膨胀。 我们稍后会看到它的作用。

private List<GeneratedUuidEntity> listAllGeneratedUuidEntities( int recordPosition, int recordsPerRoundTrip ) {return getEntityManager().createNamedQuery( "GeneratedUuidEntity.listAll" ).setFirstResult( recordPosition ).setMaxResults( recordsPerRoundTrip ).getResultList();
}

让流开始

现在,让我们实现资源端点方法,至少从理论上讲,该方法可以在不损害大小的情况下检索数据。 此方法将返回JSON响应,其数据格式为:

{"result": [{"record_no": 1,"uuid": "34d99089-3e36-4f00-ab93-846b61771eb3","datetime_generated": "2015-06-28 21:02:23"},…]
}
@GET@Path( "list-all" )@Produces( "application/json" )@TransactionAttribute( TransactionAttributeType.NEVER )public Response streamGeneratedUuids() {// Define the format of timestamp outputSimpleDateFormat df = new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss" );return getNoCacheResponseBuilder( Response.Status.OK ).entity( new StreamingOutput() {// Instruct how StreamingOutput's write method is to stream the data@Overridepublic void write( OutputStream os ) throws IOException, WebApplicationException {int recordsPerRoundTrip = 100;                      // Number of records for every round trip to the databaseint recordPosition = 0;                             // Initial record position indexint recordSize = queryGeneratedUuidRecordsSize();   // Total records found for the query// Start streaming the datatry ( PrintWriter writer = new PrintWriter( new BufferedWriter( new OutputStreamWriter( os ) ) ) ) {writer.print( "{\"result\": [" );while ( recordSize > 0 ) {// Get the paged data set from the DBList<GeneratedUuidEntity> generatedUuidEntities = listAllGeneratedUuidEntities( recordPosition, recordsPerRoundTrip );for ( GeneratedUuidEntity generatedUuidEntity : generatedUuidEntities ) {if ( recordPosition > 0 ) {writer.print( "," );}// Stream the data in Json object formatwriter.print( Json.createObjectBuilder().add( "record_no", generatedUuidEntity.getRecordNo() ).add( "uuid", generatedUuidEntity.getUuid() ).add( "datetime_generated", df.format( generatedUuidEntity.getDatetimeGenerated() ) ).build().toString() );// Increase the recordPosition for every record streamedrecordPosition++;}// update the recordSize (remaining no. of records)recordSize -= recordsPerRoundTrip;}// Done!writer.print( "]}" );}}} ).build();}

电源线说明:

实际上,这很简单。 诀窍是通过重写write()方法来定义匿名类StreamingOutput的表达式,该方法首先通过queryGeneratedUuidRecordsSize()查询总记录大小,然后通过listAllGeneratedUuidEntities()逐页检索记录。 此方法将多次往返于数据库,具体取决于定义的recordsPerRoundTrip值。

JPAStreamingRESTResource.java的完整源代码:

package com.developerscrappad;import java.io.BufferedWriter;
import java.io.IOException;
import java.io.OutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.text.SimpleDateFormat;
import java.util.List;
import javax.ejb.Stateless;
import javax.ejb.TransactionAttribute;
import javax.ejb.TransactionAttributeType;
import javax.json.Json;
import javax.persistence.EntityManager;
import javax.persistence.PersistenceContext;
import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.WebApplicationException;
import javax.ws.rs.core.CacheControl;
import javax.ws.rs.core.Response;
import javax.ws.rs.core.StreamingOutput;@Path( "generated-uuids" )
@Stateless( name = "JPAStreamingRESTResource", mappedName = "ejb/JPAStreamingRESTResource" )
public class JPAStreamingRESTResource {@PersistenceContext( unitName = "JPAStreamingPU" )private EntityManager entityManager;private List<GeneratedUuidEntity> listAllGeneratedUuidEntities( int recordPosition, int recordsPerRoundTrip ) {return getEntityManager().createNamedQuery( "GeneratedUuidEntity.listAll" ).setFirstResult( recordPosition ).setMaxResults( recordsPerRoundTrip ).getResultList();}private int queryGeneratedUuidRecordsSize() {return getEntityManager().createNamedQuery( "GeneratedUuidEntity.queryRecordsSize", Long.class ).getSingleResult().intValue();}protected EntityManager getEntityManager() {return entityManager;}/*** Say "NO" to response caching*/protected Response.ResponseBuilder getNoCacheResponseBuilder( Response.Status status ) {CacheControl cc = new CacheControl();cc.setNoCache( true );cc.setMaxAge( -1 );cc.setMustRevalidate( true );return Response.status( status ).cacheControl( cc );}@GET@Path( "list-all" )@Produces( "application/json" )@TransactionAttribute( TransactionAttributeType.NEVER )public Response streamGeneratedUuids() {// Define the format of timestamp outputSimpleDateFormat df = new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss" );return getNoCacheResponseBuilder( Response.Status.OK ).entity( new StreamingOutput() {// Instruct how StreamingOutput's write method is to stream the data@Overridepublic void write( OutputStream os ) throws IOException, WebApplicationException {int recordsPerRoundTrip = 100;                      // Number of records for every round trip to the databaseint recordPosition = 0;                             // Initial record position indexint recordSize = queryGeneratedUuidRecordsSize();   // Total records found for the query// Start streaming the datatry ( PrintWriter writer = new PrintWriter( new BufferedWriter( new OutputStreamWriter( os ) ) ) ) {writer.print( "{\"result\": [" );while ( recordSize > 0 ) {// Get the paged data set from the DBList<GeneratedUuidEntity> generatedUuidEntities = listAllGeneratedUuidEntities( recordPosition, recordsPerRoundTrip );for ( GeneratedUuidEntity generatedUuidEntity : generatedUuidEntities ) {if ( recordPosition > 0 ) {writer.print( "," );}// Stream the data in Json object formatwriter.print( Json.createObjectBuilder().add( "record_no", generatedUuidEntity.getRecordNo() ).add( "uuid", generatedUuidEntity.getUuid() ).add( "datetime_generated", df.format( generatedUuidEntity.getDatetimeGenerated() ) ).build().toString() );// Increase the recordPosition for every record streamedrecordPosition++;}// update the recordSize (remaining no. of records)recordSize -= recordsPerRoundTrip;}// Done!writer.print( "]}" );}}} ).build();}
}

小心

请记住要调整应用程序服务器的响应连接超时值,以防止REST或Http Client抛出java.io.IOException异常EOF异常。

测试它

要测试这是否有效,只需将表加载仅567条记录即可。 然后,让单元测试调用端点URL,并使用以下单元测试代码将检索到的JSON数据保存到文件中(使用Apache HttpClient):

JPAStreamingUnitTest.java的代码:

package com.developerscrappad;import java.io.File;
import java.io.FileInputStream;
import static org.junit.Assert.*;import java.nio.file.FileSystems;
import java.nio.file.Files;
import java.sql.Connection;
import java.sql.DriverManager;
import java.sql.PreparedStatement;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.UUID;
import javax.json.Json;
import javax.json.JsonArray;
import javax.json.JsonObject;
import javax.json.JsonReader;
import org.apache.http.HttpEntity;
import org.apache.http.client.methods.CloseableHttpResponse;
import org.apache.http.client.methods.HttpGet;
import org.apache.http.impl.client.CloseableHttpClient;
import org.apache.http.impl.client.HttpClients;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;public class JPAStreamingUnitTest {private static final String dbDriverClassname = "com.mysql.jdbc.Driver";private static final String dbUrl = "jdbc:mysql://localhost:3306/large_data_test_db";private static final String username = "username";private static final String password = "password";private static final int numberOfRecords = 567;private static final String jsonResultOutputFilename = "testing123.json";@BeforeClasspublic static void setUpClass() {try {Class.forName( dbDriverClassname );try ( Connection conn = DriverManager.getConnection( dbUrl, username, password ) ) {String insertSQL = "insert into generated_uuids (uuid, datetime_generated) values (?, now())";try ( PreparedStatement stmt = conn.prepareStatement( insertSQL ) ) {for ( int i = 0; i < numberOfRecords; i++ ) {System.out.println( "Inserting row: " + i );stmt.setString( 1, UUID.randomUUID().toString() );stmt.executeUpdate();}}}} catch ( final Exception ex ) {ex.printStackTrace();fail( ex.getMessage() );}}@AfterClasspublic static void tearDownClass() {try {Class.forName( dbDriverClassname );try ( Connection conn = DriverManager.getConnection( dbUrl, username, password ) ) {String truncateSQL = "truncate generated_uuids";conn.createStatement().executeUpdate( truncateSQL );}new File( System.getProperty( "java.io.tmpdir" ), jsonResultOutputFilename ).delete();} catch ( final Exception ex ) {ex.printStackTrace();fail( ex.getMessage() );}}@Testpublic void testJPAStreaming() {String url = "http://localhost:8080/JPAStreaming/rest-api/generated-uuids/list-all/";try {CloseableHttpClient httpclient = HttpClients.createDefault();HttpGet httpGet = new HttpGet( url );try ( CloseableHttpResponse response1 = httpclient.execute( httpGet ) ) {System.out.println( response1.getStatusLine() );HttpEntity entity1 = response1.getEntity();Files.copy( entity1.getContent(), FileSystems.getDefault().getPath( System.getProperty( "java.io.tmpdir" ), jsonResultOutputFilename ) );}// Validatetry ( JsonReader jsonReader = Json.createReader( new FileInputStream( new File( System.getProperty( "java.io.tmpdir" ), jsonResultOutputFilename ) ) ) ) {JsonObject jsonObj = jsonReader.readObject();assertTrue( jsonObj.containsKey( "result" ) );JsonArray jsonArray = jsonObj.getJsonArray( "result" );assertEquals( numberOfRecords, jsonArray.size() );SimpleDateFormat validationDF = new SimpleDateFormat( "yyyy-MM-dd HH:mm:ss" );for ( int i = 0; i < jsonArray.size(); i++ ) {JsonObject generatedUuidJsonObj = jsonArray.getJsonObject( i );int recordNumber = generatedUuidJsonObj.getInt( "record_no" );assertTrue( recordNumber > 0 );try {UUID.fromString( generatedUuidJsonObj.getString( "uuid" ) );} catch ( IllegalArgumentException ex ) {fail( "Invalid UUID format at record number: " + recordNumber );}try {validationDF.parse( generatedUuidJsonObj.getString( "datetime_generated" ) );} catch ( final NullPointerException | ParseException ex ) {fail( "datetime_generated field must not be null and must be of format yyyy-MM-dd HH:mm:ss" );}}}} catch ( final Exception ex ) {ex.printStackTrace();fail( ex.getMessage() );}}
}

我们完成了。 感谢您的阅读,希望对您有所帮助。

翻译自: https://www.javacodegeeks.com/2015/07/how-to-streamserialize-jpa-result-as-jax-rs-response-for-large-data.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/359385.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

Tab标签页接口---使用Intent对象

TabHost tabHost getTabHost();Intent it new Intent();it.setClass(Main.this, DateTimePicker.class);TabSpec spectabHost.newTabSpec("tab1");spec.setContent(it);spec.setIndicator("ら戳㎝丁",getResources().getDrawable(android.R.drawable.i…

java猜数字小游戏_Java实现简单猜数字小游戏

本文实例为大家分享了Java实现猜数字游戏的具体代码&#xff0c;供大家参考&#xff0c;具体内容如下完成猜数字游戏需要实现以下几点&#xff1a;获得一个随机数作为“答案数”&#xff1b; 输入数字&#xff0c;与“答案数”作比较(判断大了&#xff0c;小了&#xff0c;相等…

要看的东西

udp发送、接受 tcp 客户端 服务端 http&#xff08;servlet&#xff09; http post http get web service cookies session 多线程 io流 集合list set map&#xff1a;循环、迭代器 jmeter、soapui、webdriver、python转载于:https://www.cnblogs.com/stay-sober/p/4158837.h…

简单的基准测试:不可变集合VS持久集合

通常&#xff0c;您需要向集合中添加新元素。 因为您是一个优秀而谨慎的开发人员&#xff0c;所以您希望尽可能保持不变。 因此&#xff0c;向不可变集合中添加新元素将意味着您必须创建一个新的不可变集合&#xff0c;其中包含原始集合的所有元素以及新元素。 您可以使用gua…

java list 合并去重_java 怎么把多个list 合并成一个去掉重复的

展开全部示例代码636f707962616964757a686964616f31333361313838:public static void main(String[] args){List list1 new ArrayList();list1.add(1);list1.add(2);list1.add(3);list1.add(4);List list2 new ArrayList();list2.add(1);list2.add(4);list2.add(7);list2.add…

UITextField实现左侧空出一定的边距

就是通过uitextfield的leftview来实现的&#xff0c;同时要设置leftviewmode。 如果设置左右边距&#xff0c;需要再加上rightView功能 -(void)setTextFieldLeftPadding:(UITextField *)textField forWidth:(CGFloat)leftWidth {CGRect frame textField.frame;frame.size.widt…

java软件工程师 英文简历_2017java程序员英文简历范文

2017java程序员英文简历范文简历写完以后&#xff0c;再检查一下你的简历是否回答了以下问题&#xff1a;它是否清楚并能够让雇主尽快知道你的能力?是否写清了你的能力?是否写清了你要求这份工作的.基础?有东西可删除吗?尽力完善你的简历直到最好。2017java程序员英文简历范…

写给java web一年左右工作经验的人

摘要 大学就开始学习web&#xff0c;磕磕绊绊一路走过来&#xff0c;当中得到过开源社区很多的帮助&#xff0c;总结了这些年来的技术积累&#xff0c;回馈给开源社区。 ps&#xff1a;图片都是从网上盗。。。感谢原作者。 ps&#xff1a;文字千真万确都是我自己写的。 在此&am…

FlexyPool如何支持Dropwizard Metrics包重命名

介绍 FlexyPool严重依赖Dropwizard &#xff08;以前是Codahale&#xff09;度量标准来监视连接池的使用情况 。 集成到Dropwizard中后&#xff0c;程序包名称必然会被重命名 。 因此&#xff0c;4.0.0版本将使用io.dropwizard.metrics软件包名称代替com.codahale.metrics 。 …

java网络编程与分布式计算_Java网络编程与分布式计算

基本信息书名:Java网络编程与分布式计算定价&#xff1a;38.00元作者:[澳]赖利出版社&#xff1a;机械工业出版社出版日期&#xff1a;2003-03-00ISBN&#xff1a;9787111115786字数&#xff1a;页码&#xff1a;版次&#xff1a;装帧&#xff1a;开本&#xff1a;编辑推荐内容…

java ref 应用类型_Java四种引用类型

Java四种引用类型在java中&#xff0c;类型就分为两种&#xff0c;基本类型和引用类型或自定义类型。引用类型又分为四种&#xff1a;强引用 StrongReference软引用 SoftReference若引用 WeakReference虚引用 PhantomReference划分这些类型的目的是&#xff1a;是为了更灵活的管…

嵌入式Linux学习笔记

一 嵌入式系统定义&#xff1a; 应用于特定环境的硬件体系。 二 两样非常重要的能力&#xff1a; 1. 掌握各种新概念的能力 2. 调试的能力( 包括软件, 硬件 ) 三 需要的基础知识&#xff1a; 1. 操作系统理论基础 2. 数据结构 3. C,C编程语言 4. 汇编语言 5. Linux基…

java string 内存占用_JVM系列之:String,数组和集合类的内存占用大小

简介之前的文章中&#xff0c;我们使用JOL工具简单的分析过String,数组和集合类的内存占用情况&#xff0c;这里再做一次更详细的分析和介绍&#xff0c;希望大家后面再遇到OOM问题的时候不再抱头痛哭&#xff0c;而是可以有章可循&#xff0c;开始吧。数组先看下JOL的代码和输…

maven osgi_OSGi将Maven与Equinox结合使用

maven osgi很长时间以来&#xff0c;我一直在努力理解OSGi的真正含义。 它已经存在很长时间了&#xff0c;但是没有多少人意识到这一点。 它被炒作是一种非常复杂的技术。 这是我为所有Java开发人员简化的尝试。 简而言之&#xff0c; OSGi是一组规范&#xff0c;这些规范支持模…

java用redis缓存的步骤_详解在Java程序中运用Redis缓存对象的方法|chu

这段时间一直有人问如何在Redis中缓存Java中的List 集合数据&#xff0c;其实很简单&#xff0c;常用的方式有两种&#xff1a;1. 利用序列化&#xff0c;把对象序列化成二进制格式&#xff0c;Redis 提供了 相关API方法存储二进制&#xff0c;取数据时再反序列化回来&#xff…

Linux安装、卸载软件

在linux环境中&#xff0c;尤其是cenos中安装过一些软件&#xff0c;一般是二进制安装与源码安装&#xff0c;现小结一下linux中的安装与卸载。 一、通常Linux应用软件的安装包有三种&#xff1a; 1) tar包&#xff0c;如software-1.2.3-1.tar.gz。它是使用UNIX系统的打包工具t…

java绘制_Java 绘制简单图形的问题

代码能运行成功但东西出不来能帮忙看下代码吗&#xff1f;.O_O.importjava.awt.Color;importjava.awt.Graphics2D;importjavax.swing.JComponent;importjavax.swing.JFrame;importjava.awt.geom....代码 能运行成功 但东西出不来 能帮忙看下代码吗&#xff1f;.O_O.import java…

SonarQube中的Maven项目的单元和集成测试报告

自SonarQube 4.2起。 测试报告不是在Maven构建期间由Sonar Maven插件生成的&#xff08;请参阅SonarQube的博客文章 &#xff09;。 因此&#xff0c;在Sonar Maven插件收集SonarQube服务器的信息之前&#xff0c;必须由另一个插件生成测试报告。 在这里&#xff0c;Jacoco Mav…

IOS-状态栏的简单操作

一、启动时隐藏状态栏 1、在info.plist里面 Status bar is initially hidden 设置为 YES2、在appDelagate里面 设置 [application setStatusBarHidden:NO withAnimation:UIStatusBarAnimationFade]; 二、将所有页面状态栏字体颜色设为白色1.在info.plist中添加一个字段&#x…

python中、文件最重要的功能是( )和接收数据_Python基础语法14个知识点大串讲

来源&#xff1a;Python数据之道Python基础语法大串讲Python 是一门面向对象的编程语言&#xff0c;相信这篇文章对于初学者而言应该会有一个比较大的帮助&#xff0c;下面我们将 Python 中常用的基础语法和函数做了一个汇总&#xff0c;满满的干货&#xff0c;供大家学习。1、…