Perform native batch UPDATE with EclipseLink - jpa

Dear fellow programmers,
i have been given the task to update about 10 000 - 100 000 records in an Oracle 11g database EACH minute. The current state of those records are held in a global ArrayList so i don't need to SELECT all records on every update from the DB. A scheduler updates those records in the ArrayList at the beginning of each minute and then starts to update the records in the database.
I cannot change this fact, it is a customer requirement.
To achieve high performance, those updates should be done by using the native batch update feature.
I am using a TomEE plume 7.0.2 application server with EclipseLink 2.6.3 (this version is included with TomEE).
Code:
#PersistenceContext(unitName = "MES_Tables")
private EntityManager em;
...
#Schedule(second="0", minute="*", hour="*", persistent=false)
public void startUpdate(){
Query q = em.createNativeQuery(
"UPDATE " +
"SCHEMA.PROPERTIES_GRP_CONT " +
"SET " +
"STRVAL = ? " + //<-- SQL-Param
"WHERE " +
"STATES_ID = 1 " +
"AND PROPERTIES_ID = ? " + //<-- SQL-Param
"AND PROPERTIES_GRP_ID = ?"); //<-- SQL-Param
for(BatchInfo bi : biList){
int rowsUpdated = q
.setParameter(1, Long.toString(bi.getLifetime()))
.setParameter(2, bi.getPropertiesId())
.setParameter(3, bi.getBatchId())
.executeUpdate();
}
}
Unfortunately those updates are executed as single updates and no batching is happening. So 10 000 updates are taking about 40-50 seconds.
To my understanding the EntityManager (em) should automatically create batch updates if you execute multiple updates within a single for each loop.
Even simplifying the SQL UPDATE to a statement without any parameters, so that always the same update is executed, did not change the fact that single updates were executed.
persistence.xml
<?xml version="1.0" encoding="UTF-8"?>
<persistence version="2.1"
xmlns="http://xmlns.jcp.org/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/persistence http://xmlns.jcp.org/xml/ns/persistence/persistence_2_1.xsd">
<persistence-unit name="MES_Tables" transaction-type="JTA">
<jta-data-source>MES_Connection</jta-data-source>
<exclude-unlisted-classes>false</exclude-unlisted-classes>
<properties>
<property name="javax.persistence.schema-generation.database.action" value="none" />
<property name="eclipselink.ddl-generation" value="none" />
<property name="eclipselink.logging.level" value="WARNING" />
<property name="eclipselink.logging.level.sql" value="FINE" />
<property name="eclipselink.logging.parameters" value="true" />
<property name="javax.persistence.query.timeout" value="1800000" />
<property name="eclipselink.jdbc.connections.wait-timeout" value="1800000" />
<property name="eclipselink.jdbc.batch-writing" value="JDBC" />
<property name="eclipselink.jdbc.batch-writing.size" value="600" />
<property name="eclipselink.logging.logger" value="mes.core.logging.EclipseLinkLogger"/>
</properties>
</persistence-unit>
</persistence>
To test if batch updating is working at all, i refactored the code to use a managed JPA entity instead of the native SQL UPDATE. The problem here is, that i need to perform a em.merge(entity) on each entity for it to be managed again. This is because the entities become unmanaged after committing (which is happening each minute in the scheduler).
This causes 10 000 slow SELECTs (30-40 seconds). After those SELECTs are finished, EclipseLink performs a fast batch update (3-4 seconds).
The last days i was trying to prevent EclipseLink from performing those SELECTs and just issue the update but without luck. From another stackoverflow post i found a method to do updates without the SELECT:
Perform UPDATE without SELECT in eclipselink
EntityManagerImpl emImpl = ((EntityManagerImpl) em.getDelegate());
UnitOfWork uow = emImpl.getUnitOfWork();
AbstractSession as = uow.getParent();
for(BatchInfo bi : biList)
as.updateObject(bi);
This unfortunately did not work also because of the following exception:
org.eclipse.persistence.internal.sessions.IsolatedClientSession cannot be cast to org.eclipse.persistence.internal.sessions.UnitOfWorkImpl
I am out of options now and hopefully someone of you can give me a hint where to look at and solve this problem. It would be greatly appreciated.
I would rather have the native batch update working than the manipulating EclipseLink to not perform any SELECTs on merge.

After searching for a long time and trying different approaches (thanks to Chris) i found the simplest solution if you want to stay on the native side of JPA:
#Schedule(second="0", minute="*", hour="*", persistent=false)
public void startUpdate(){
String updateSql =
"UPDATE " +
"SCHEMA.PROPERTIES_GRP_CONT " +
"SET " +
"STRVAL = ? " + //<-- SQL-Param
"WHERE " +
"STATES_ID = 1 " +
"AND PROPERTIES_ID = ? " + //<-- SQL-Param
"AND PROPERTIES_GRP_ID = ?"; //<-- SQL-Param
java.sql.Connection connection = em.unwrap(java.sql.Connection.class);
PreparedStatement prepStatement = connection.prepareStatement(updateSql);
for(BatchInfo bi : biList){
prepStatement.setString(1, Long.toString(bi.getLifetime()));
prepStatement.setLong(2, bi.getPropertiesId());
prepStatement.setLong(3, bi.getBatchId());
prepStatement.addBatch();
}
prepStatement.executeBatch();
}
Warning: the important part (em.unwrap) may be EclipseLink specific and require JPA 2.1 or higher!

Related

Hybris custom Category itemtype not syncing

I've created my own Itemtype extending Category:
<itemtype code="BrandCategory" extends="Category">
<attributes>
<attribute qualifier="hide" type="java.lang.Boolean">
<persistence type="property"/>
<defaultvalue>java.lang.Boolean.FALSE</defaultvalue>
<modifiers read="true" write="true" optional="false" search="true"/>
</attribute>
</attributes>
</itemtype>
However, when I assign this category to any Product that already has other categories and I do a catalog synchronization, all the categories do copy to online except the custom ones (BrandCategory).
How can I fix this bug?
you need to update SyncAttributeDescriptorConfig and it can be done either via Backoffice or via Impex.
"#%groovy%
def query = '''SELECT {pk} FROM {<CustomJOBName>CatalogVersionSyncJob}'''
def syncJobs = flexibleSearchService.search(query).result
//forcing all sync jobs to create sync descriptors, if not created
syncJobs.each { syncJob -> syncJob.getSyncAttributeConfigurations() }
"
UPDATE GenericItem[processor = de.hybris.platform.commerceservices.impex.impl.ConfigPropertyImportProcessor]; pk[unique = true]
$attribute = attributeDescriptor(enclosingType(code), qualifier)[unique = true]
UPDATE SyncAttributeDescriptorConfig[batchmode = true]; $attribute ; includedInSync
; BrandCategory:hide ; true
UPDATE AttributeDescriptor; enclosingType(code)[unique = true]; qualifier[unique = true]; unique
; BrandCategory ; catalogVersion ; true
To run groovy in Impex, please add this property to local. properties.
Disable legacy scripting (makes groovy work at impex)
impex.legacy.scripting=false
or run impex via enabling the code execution.
Try adding the new type(i.e BrandCategory) into your product synchronization job as a root type as depicted in below image:

Jasper Report PDFs Are Not PDF/UA Compliant

I want to make PDFs exported by Jasper PDF/UA compliant, but limitations of Jasper are preventing me from doing so. Client is pressuring us to get this done properly.
PDF/UA has a lot of requirements, including but not limited to displaying title and language, embedding fonts, and adding alternate text to images. So far, I have set all the 508 PDF tags, set properties to display title and language, embedded fonts, and added alt text to images all in Jaspersoft Studio. I have also appended the PDF/UA identifier to the output PDF (i.e. after the PDF was generated) via Apache PDFBox. We are using Jaspersoft Studio v6.6.0 coupled with Jasper Reports Library v6.4.0 and Oracle for the DB. From what I've read, Jasper has limited capabilities in this regard due to itext being downgraded back to v2.1.7.js6 because of licensing issues.
<jasperReport xlmns=...>
... // other properties
<property name="net.sf.jasperreports.awt.ignore.missing.font" value="false"/>
<property name="net.sf.jasperreports.export.xls.detect.cell.type" value="false"/>
<property name="net.sf.jasperreports.export.xls.sheet.names.all" value="REPORT SHEET NAME"/>
<property name="net.sjasperreports.default.pdf.font.name" value="Times-Roman"/>
<property name="net.sf.jasperreports.export.xls.ignore.graphics" value="false"/>
<property name="net.sf.jasperreports.default.pdf.embedded" value="true"/>
<property name="net.sf.jasperreports.export.pdf.metadata.title" value="MY REPORT TITLE"/>
<property name="net.sf.jasperreports.export.pdf.display.metadata.title" value="true"/>
<property name="net.sf.jasperreports.export.pdf.tagged" value="true"/>
<property name="net.sf.jasperreports.export.pdf.tag.language" value="EN-US"/>
... // parameters, stored proc call, headings, etc.
<!-- Possible PDF 508 tags to be set on text fields -->
<property name="net.sf.jasperreports.export.pdf.tag.table" value="start"/>
<property name="net.sf.jasperreports.export.pdf.tag.th" value="full"/>
<property name="net.sf.jasperreports.export.pdf.tag.tr" value="start">
<property name="net.sf.jasperreports.export.pdf.tag.td" value="full">
<property name="net.sf.jasperreports.export.pdf.tag.tr" value="end">
<property name="net.sf.jasperreports.export.pdf.tag.table" value="start"/>
...
</jasperReport>
... // other imports
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.pdmodel.common.PDMetadata;
import org.apache.xmpbox.XMPMetadata;
import org.apache.xmpbox.schema.XMPSchema;
import org.apache.xmpbox.xml.XmpSerializer;
... // more imports
public class ReportResult {
... // other methods
/*
* #param pdf - The pdf instance created from BAOS
* #param title - Document
* #return BAOS containing metadata (UA-identifier, title)
*/
private ByteArrayOutputStream appendXMPMetaData(PDDocument pdf, String title) throws TransformerException, IOException {
XMPMetadata xmp = XMPMetadata.createXMPMetadata();
xmp.createAndAddDublinCoreSchema();
xmp.getDublinCoreSchema().setTitle(title);
xmp.getDublinCoreSchema().setDescription(title);
xmp.createAndAddPDFAExtensionSchemaWithDefaultNS();
xmp.getPDFExtensionSchema().addNamespace("http://www.aiim.org/pdfa/ns/schema#", "pdfaSchema");
xmp.getPDFExtensionSchema().addNamespace("http://www.aiim.org/pdfa/ns/property#", "pdfaProperty");
xmp.getPDFExtensionSchema().addNamespace("http://www.aiim.org/pdfua/ns/id/", "pdfuaid");
XMPSchema uaSchema = new XMPSchema(XMPMetadata.createXMPMetadata(),
"pdfaSchema", "pdfaSchema", "pdfaSchema");
uaSchema.setTextPropertyValue("schema", "PDF/UA Universal Accessibility Schema");
uaSchema.setTextPropertyValue("namespaceURI", "http://www.aiim.org/pdfua/ns/id/");
uaSchema.setTextPropertyValue("prefix", "pdfuaid");
XMPSchema uaProp = new XMPSchema(XMPMetadata.createXMPMetadata(),"pdfaProperty", "pdfaProperty", "pdfaProperty");
uaProp.setTextPropertyValue("name", "part");
uaProp.setTextPropertyValue("valueType", "Integer");
uaProp.setTextPropertyValue("category", "internal");
uaProp.setTextPropertyValue("description", "Indicates, which part of ISO 14289 standard is followed");
uaSchema.addUnqualifiedSequenceValue("property", uaProp);
xmp.getPDFExtensionSchema().addBagValue("schemas", uaSchema);
xmp.getPDFExtensionSchema().setPrefix("pdfuaid");
xmp.getPDFExtensionSchema().setTextPropertyValue("part", "1");
XmpSerializer serializer = new XmpSerializer();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
serializer.serialize(xmp, baos, true);
PDMetadata metadata = new PDMetadata(pdf);
metadata.importXMPMetadata(baos.toByteArray());
pdf.getDocumentCatalog().setMetadata(metadata);
ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream();
pdf.save(byteArrayOutputStream);
pdf.close();
return byteArrayOutputStream;
}
protected void getJasperPDFDoc(ReportConfig reportConfig) throws IOException, TransformerException {
List<ReportParameter> reportParams = reportConfig.getReportParams();
... // cookies and printer config
Map imagesMap = new HashMap();
request.getSession(true).setAttribute("IMAGES_MAP", imagesMap);
ByteArrayOutputStream bs = ReportAccess.Instance.getInstance().generateJasperReport(
getCurrentUserId(), getCurrentUserName(), reportConfig, "PDF",
reportParams, getTmpImageUri(),
imagesMap, rptTemplateLoc);
if (bs != null) {
if (reportConfig.doPrint) {
response.setContentType("text/html");
} else {
log.debug("Got PDF report data");
String fileName = getReportFileName(reportConfig) + ".pdf";
response.setContentType("application/pdf");
String dispositionProperty = "attachment; filename=" + fileName;
response.setHeader("Content-disposition", dispositionProperty);
}
PDDocument pdf = PDDocument.load(new ByteArrayInputStream(bs.toByteArray()));
ByteArrayOutputStream baosWithMetaData = appendXMPMetaData(pdf, reportConfig.getDisplayName());
response.setHeader("Content-length", Integer.toString(baosWithMetaData.size()));
ServletOutputStream os = response.getOutputStream();
baosWithMetaData.writeTo(os);
os.flush();
os.close();
} else {
displayError("PDF");
}
}
... // other methods
}
/* REPORT MANAGER CLASS */
private static void generatePDFDoc(JasperPrint jasperPrint, ByteArrayOutputStream f) {
try {
JasperPrint jr = moveTableOfContents(jasperPrint);
JRPdfExporter exporter = new JRPdfExporter();
exporter.setExporterInput(new SimpleExporterInput(jr));
exporter.setExporterOutput(new SimpleOutputStreamExporterOutput(f));
//configuration
SimplePdfExporterConfiguration configuration = new SimplePdfExporterConfiguration();
configuration.setCompressed(true);
configuration.setTagged(true);
configuration.setTagLanguage("EN-US");
//set configuration
exporter.setConfiguration(configuration);
//export to PDF
exporter.exportReport();
} catch (Exception e) {
log.error(e.getMessage(), e);
}
}
I noticed a handful of errors reported by Adobe's Preflight checker as well as our client, listed below:
Non-standard tag present
Circular Role Map
Unknown Anchor cell appended to top-left of every page
Table is not properly recognized in table-editor view
Images showing my problem(s). Any help in this regard is kindly appreciated.
If you want to make things simpler, but different way, PD4ML v4 can be an option. There is a minimalistic sample on the page: https://pd4ml.tech/pdf-ua/
It uses available structure and meta info from input HTML/CSS to produce a valid tagged PDF/UA.
If the goal is to pass PDF/UA file format validation only (e.g. by Adobe's Preflight checker) it is sufficient just to choose Constants.PDFUA as an output format.
pd4ml.writePDF(fos, Constants.PDFUA);
If the goal is to produce Matterhorn Protocol-compliant PDFs (and pass a validation by PAC3 https://www.access-for-all.ch/en/pdf-lab/pdf-accessibility-checker-pac.html), most probably you would also need to align your input HTML: to add TITLE, ALT and LANG attributes, to make sure table structures and heading hierarchy are consistent etc.

MyBatis batch update for oracle

I am trying to do the batch update in myBatis with oracle Database, where i have to update 10K+ records.
But when i use below
I am getting exception says 'invalid character'.
It is working fine when i do single update statement multiple times.
Please help with your example if you already tried this.
<update id="batchUpdate">
<foreach collection="empList" item="employee" separator=";">
UPDATE Employee
SET EMP_JOBTITLE = #{employee.jobTitle},
EMP_STATUS = #{employee.status}
WHERE Employee_ID = #{employee.empId}
</foreach>
</update>
If you use Oracle:
<update id="batchUpdate">
call
begin
<foreach collection="empList" item="employee" close=";" eparator=";">
UPDATE Employee
SET EMP_JOBTITLE = #{employee.jobTitle},
EMP_STATUS = #{employee.status}
WHERE Employee_ID = #{employee.empId}
</foreach>
end
</update>
I was not able to resolve that error, but came across this post in which a code contributor recommended that the proper way to batch update is to open a session in batch mode and repeatedly call update for a single record.
Here's what works for me:
public void updateRecords(final List<GisObject> objectsToUpdate) {
final SqlSession sqlSession = MyBatisUtils.getSqlSessionFactory().openSession(ExecutorType.BATCH);
try {
final GisObjectMapper mapper = sqlSession.getMapper(GisObjectMapper.class);
for (final GisObject gisObject : objectsToUpdate) {
mapper.updateRecord(gisObject);
}
sqlSession.commit();
} finally {
sqlSession.close();
}
}
Then, remove the foreach in your update and modify it to update a single record (remove the "employee.")

Oracle ADF - Print input form values in console

I am working in JDeveloper 11.1.1.7.0. I am creating a simple form application in ADF.
I need to print the input values of the ADF-form in the console. For this, I used the below code.
System.out.println("It1 : " + it1.getValue());
System.out.println("si1 : " + si1.getValue());
System.out.println("soc1 : " + soc1.getValue());
The input values are getting populated in the console for RichInputText*(it1)* field and RichSelectItem*(si1)* field. But, the same is not working for RichSelectOneChoice*(soc1)* field.
How to print the value that is selected in RichSelectOneChoice*(soc1)* field.
Thanks in advance.
You don't need to bind the component to the managed bean (actually,this is considered as a bad practice), but to bind only the component values.
So, your form should consist of (for example):
<af:inputText value="#{managedBean.inputTextValue}" id="it1" />
<af:selectOneChoice value="#{managedBean.selectOneChoiceValue}" id="soc1">
<af:selectItem itemValue="first" itemLabel="First" id="si1" />
<af:selectItem itemValue="second" itemLabel="Second" id="si2" />
<af:selectItem itemValue="third" itemLabel="Third" id="si3" />
</af:selectOneChoice>
<af:commandButton actionListener="#{managedBean.printValues}" id="cb1" />
And in the managed bean you should have :
private String inputTextValue;
private String selectOneChoiceValue;
//accessors
public void printValues(ActionEvent event) {
System.out.println("Input text value: " + inputTextValue);
System.out.println("Select one choice value: " + selectOneChoiceValue);
}
Set the attribute valuePassThru to true and you should get the value of selectOneChoice as well.
Actually using the method you mentioned you should get the required selected value:
System.out.println("soc1 : " + soc1.getValue());

Need to set the quartz cron expression dynamically

I'm using quartz in my web application (Servlet web app) following is snap of quartz.property file and the quartz.job.xml
quartz.property
#===================================================
# Configure the Job Initialization Plugin
#===================================================
org.quartz.plugin.jobInitializer.class = org.quartz.plugins.xml.XMLSchedulingDataProcessorPlugin
org.quartz.plugin.jobInitializer.fileNames = jobs.xml
org.quartz.plugin.jobInitializer.failOnFileNotFound = true
org.quartz.plugin.jobInitializer.scanInterval = 10
org.quartz.plugin.jobInitializer.wrapInUserTransaction = false
<?xml version='1.0' encoding='utf-8'?>
<job-scheduling-data xmlns="http://www.quartz-scheduler.org/xml/JobSchedulingData"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.quartz-scheduler.org/xml/JobSchedulingData http://www.quartz-scheduler.org/xml/job_scheduling_data_1_8.xsd"
version="1.8">
<schedule>
<job>
<name>my-very-clever-job</name>
<group>MYJOB_GROUP</group>
<description>The job description</description>
<job-class>com.acme.scheduler.job.ReportJob</job-class>
</job>
<trigger>
<cron>
<name>my-trigger</name>
<group>MYTRIGGER_GROUP</group>
<job-name>my-very-clever-job</job-name>
<job-group>MYJOB_GROUP</job-group>
<!-- trigger every night at 4:30 am -->
<cron-expression>0 30 4 * * ?</cron-expression>
</cron>
</trigger>
</schedule>
</job-scheduling-data>
Every thing work fine,in this order. I need to allow user to change the time (cron expression) as the way they want.My question is how do i set the cron expression in dynamically.
CronTrigger cronTrigger = (CronTrigger) stdScheduler
.getTrigger(triggerName,triggerGroupName);
CronTrigger newTriggerIns = new CronTrigger();
newTriggerIns.setJobName(cronTrigger.getJobName());
newTriggerIns.setName(triggerName);
newTriggerIns.setCronExpression(newCronExpression);
stdScheduler.rescheduleJob(triggerName,triggerGroupName,newTriggerIns);
In the above, take the existing trigger instance.
Create one new trigger instance and set cron expression.
Then reschedule the existing instance with new one.
Creating a new Trigger like this doesn't work.
CronTrigger cronTrigger = (CronTrigger) stdScheduler.getTrigger(triggerName,triggerGroupName);
CronTrigger newTriggerIns = new CronTrigger();
newTriggerIns.setJobName(cronTrigger.getJobName());
newTriggerIns.setName(triggerName);
newTriggerIns.setCronExpression(newCronExpression);
stdScheduler.rescheduleJob(triggerName,triggerGroupName,newTriggerIns); //doesn't work
You just have to edit the original trigger like this:
CronTrigger cronTrigger = (CronTrigger) stdScheduler.getTrigger(triggerName,triggerGroupName);
cronTrigger.setCronExpression(newCronExpression);
stdScheduler.rescheduleJob(triggerName,triggerGroupName,cronTrigger);
Use Quartz api. Programmatically take this trigger instance, cast it to CronTrigger instance and use it's setCronExpression to put expression dynamically.