LEVEL 4.2

Enterprise Multi-Sheet Data Splitting & POI SXSSF Pipeline

Quiz Progress: 0 / 15
Score: 0%
⚡

Warm-up Quick Fire: Retention Check (from 4.1 Excel Sanitization)

Anti-Forgetting Protocol

สถานการณ์จำลอง: ในการตั้งค่า Pixel-Perfect Export ของ JasperReports 7.0.x หากคุณมี Detail Band ที่ขยายตัวได้ตามเนื้อหา (stretchType="containerHeight") แต่พบว่าแถวใน Microsoft Excel ถูกล็อคความสูงแบบตายตัวจนตัวอักษรบรรทัดล่างถูกตัดทิ้ง (Row Height Clipping) คุณต้องกำหนด Export Property ตัวใดให้กับตัวรายงาน?

4.2 Contextual Anchoring: The Multi-Sheet Architecture Dilemma

Why simple pagination breaks down in Enterprise Big-Data Spreadsheets

🚨 The 1,048,576 Row Cliff & POI Memory Spike

การสร้างรายงานทางการเงินหรือธุรกรรม Transaction รายปี (General Ledger) มักมีข้อมูลเกินหลายแสนแถว หากพยายามยัดลงใน Single Sheet ระบบจะชนข้อจำกัดขีดสุดของ Excel (.xlsx รองรับ $1,048,576$ แถว) และหาก Export Engine ไม่ได้ทำงานในโหมด Streaming (Apache POI SXSSF) ตัว Java DOM Tree จะสะสมวัตถุเซลล์หลายสิบล้านโหนดบน Old Gen Heap จนเกิด java.lang.OutOfMemoryError: Java heap space ทันที

⚠️ Dynamic Sheet Name Collision & Excel Corruption

ผู้บริหารต้องการแยกแท็บตาม Department หรือ Business Unit แต่เมื่อข้อมูลมีชื่อสาขาซ้ำกัน หรือมีอักขระต้องห้ามของ Microsoft Excel เช่น / \ ? * : [ ] หรือชื่อยาวเกิน 31 อักขระ POI จะพ่นข้อยกเว้น หรือสร้างไฟล์ที่ทำให้ Excel แจ้งเตือน "We found a problem with some content in 'report.xlsx'..." จนผู้ใช้งานสูญเสียความเชื่อมั่น

🛡️ The JasperReports 7.0.x Defense Paradigm

ในเวอร์ชัน 7.0.x สถาปัตยกรรมรองรับการตัดแท็บผ่าน Group Pagination (isStartNewPage="true") ผสานคุณสมบัติ net.sf.jasperreports.export.xls.sheet.name ระดับ Element Property Expression ที่คำนวณแบบ Dynamic ตอน Filler วนลูป โดยทำงานร่วมกับ Apache POI SXSSF Disk Swapping ได้ 100%

🏛️ Dynamic Core Pillars (4 เสาหลักการแยกแท็บข้อมูลระดับ Enterprise)

Pillar 1

Group-Triggered Page Splitting

การสั่งให้ Engine สร้างแท็บใหม่ต้องพึ่งพา Logical Page Break ของ JRVerticalFiller ผ่านแอตทริบิวต์ isStartNewPage="true" ในระดับ <group> หรือใส่ <element kind="break" type="Page"/>

Analogy: Database Cursor Windowing
Pillar 2

Dynamic Sheet Name Resolution

การผูกชื่อแท็บแบบ Real-time ด้วย Property Expression: net.sf.jasperreports.export.xls.sheet.name วางไว้ใน Group Header Element เพื่อให้ Exporter อ่านค่าและ Apply ลง Apache POI Sheet ณ จังหวะ Render Page

Analogy: Dynamic Kafka Partition Naming
Pillar 3

Hard Row-Count Auto-Splitting

การป้องกันไฟล์แตกเมื่อข้อมูลทะลุขีดจำกัด ด้วย net.sf.jasperreports.export.xls.max.rows.per.sheet Engine จะคำนวณแถวสะสม และทำการ Force New Sheet พร้อมรันหมายเลข Index ต่อท้ายอัตโนมัติ

Analogy: S3 Log File Chunking / Rolling
Pillar 4

SXSSF Streaming & Temp Offload

การเปิดใช้งาน JRXlsxExporter ร่วมกับ Row Access Window (เช่น 100 แถวบน RAM) เพื่อ Flush แถวก่อนหน้าลง .gz ในดิสก์ชั่วคราว ป้องกัน Garbage Collection Thrashing

Analogy: L1 CPU Cache Spilling to Swap
STEP 1

Engine Internals: How JRVerticalFiller Interacts with JRXlsxExporter

เมื่อรายงานถูก Execute ผ่าน JasperFillManager.fillReport(...) ตัว JRVerticalFiller จะประมวลผลข้อมูลทีละ Record หากมี Group Break ที่กำหนด isStartNewPage="true" ตัว Filler จะทำการปิด JRPrintPage เดิม และเริ่มหน้าพิมพ์ใหม่ โดยเก็บ Print Elements ทั้งหมดไว้ในหน่วยความจำ (หรือ Virtualizer Paged File)

ณ จังหวะ Export ด้วย JRXlsxExporter พารามิเตอร์ net.sf.jasperreports.export.xls.one.page.per.sheet=true จะสั่งการลูป for (JRPrintPage page : jasperPrint.getPages()) ให้ทำการสร้าง POI Sheet ใหม่ขึ้นมา 1 แท็บต่อ 1 Logical Page!

Production Hazard Root Cause Mechanism Symptom / Exception Architectural Defense
Duplicate Sheet Name Crash Field ที่นำมาตั้งชื่อ Sheet มีค่าซ้ำกันใน Record ต่าง Group (เช่น ลูกค้ามี Order 2 ช่วงเวลา) IllegalArgumentException: The workbook already contains a sheet named '...' ผูก Expression โดยใส่ Unique ID หรือสร้าง Variable นับ Sequence: $F{DeptName} + "_" + $V{REPORT_COUNT}
Excel Illegal Characters Violation ชื่อ Sheet มีอักขระต้องห้ามตามสเปก OpenXML: : \ / ? * [ ] IllegalArgumentException: Invalid char (/) found at index (...) in sheet name Sanitize ข้อมูลใน Expression ด้วย Regex: $F{DeptName}.replaceAll("[\\\\/\\?\\*\\[\\]:]", "_")
Sheet Name Length Exceeded ข้อจำกัดของ Excel BIFF8/OpenXML ไม่อนุญาตให้ชื่อแท็บเกิน 31 ตัวอักษร IllegalArgumentException: sheetName length must be between 1 and 31 Truncate ด้วย String substring: $F{Name}.length() > 30 ? $F{Name}.substring(0, 30) : $F{Name}
Memory Exhaustion (OOM) on Large Multi-sheet ใช้ HSSFWorkbook (XLS โบราณ) หรือใช้ SXSSFWorkbook โดยไม่ได้ตั้งค่า Temp File Swap java.lang.OutOfMemoryError: Java heap space during ZipOutputStream flush ตั้งค่า JVM Properties net.sf.jasperreports.export.xls.use.temporary.file=true และกำหนด Storage Path ใน Container
STEP 2

Strict 7.0.x Architecture Implementation

100% Unified Element Model

โค้ดสถาปัตยกรรมด้านล่างคือมาตรฐาน Enterprise สำหรับ JasperReports 7.0.3+ ที่ใช้แบ่งแท็บข้อมูลตามฝ่าย (DepartmentGroup) โดยแต่ละแท็บจะมีชื่อตามรหัสแผนก พร้อมฟอกชื่อให้อยู่ในขอบเขต 31 ตัวอักษรและปราศจากอักขระต้องห้าม:

production_multisheet_template.jrxml (JasperReports 7.0.x Schema)
<?xml version="1.0" encoding="UTF-8"?>
<jasperReport xmlns="http://jasperreports.sourceforge.net/jasperreports"
              xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
              xsi:schemaLocation="http://jasperreports.sourceforge.net/jasperreports http://jasperreports.sourceforge.net/xsd/jasperreport.xsd"
              name="MultiSheetEnterpriseReport"
              pageWidth="595" pageHeight="842"
              columnWidth="555" leftMargin="20" rightMargin="20" topMargin="20" bottomMargin="20"
              uuid="c3f81e2e-8e4a-4a61-9c8e-32b0a1a01234">

    <!-- Global Export Properties for POI XLSX Multi-Sheet Splitting -->
    <property name="net.sf.jasperreports.export.xls.one.page.per.sheet" value="true"/>
    <property name="net.sf.jasperreports.export.xls.remove.empty.space.between.rows" value="true"/>
    <property name="net.sf.jasperreports.export.xls.remove.empty.space.between.columns" value="true"/>
    <property name="net.sf.jasperreports.export.xls.detect.cell.type" value="true"/>
    <property name="net.sf.jasperreports.export.xls.white.page.background" value="false"/>
    <property name="net.sf.jasperreports.export.xls.use.temporary.file" value="true"/>

    <field name="departmentId" class="java.lang.String"/>
    <field name="departmentName" class="java.lang.String"/>
    <field name="employeeCode" class="java.lang.String"/>
    <field name="employeeName" class="java.lang.String"/>
    <field name="salary" class="java.math.BigDecimal"/>

    <!-- Group definition driving the sheet split -->
    <group name="DepartmentGroup" isStartNewPage="true" isResetPageNumber="true">
        <expression><![CDATA[$F{departmentId}]]></expression>
        <groupHeader>
            <band height="55">
                <!-- Sheet Name Controller: Attached to group header text field -->
                <element kind="textField" x="0" y="0" width="555" height="30">
                    <propertyExpression name="net.sf.jasperreports.export.xls.sheet.name">
                        <![CDATA[
                            ($F{departmentId} + "_" + $F{departmentName})
                                .replaceAll("[\\\\/\\?\\*\\[\\]:]", "_")
                                .length() > 31 
                                ? ($F{departmentId} + "_" + $F{departmentName}).replaceAll("[\\\\/\\?\\*\\[\\]:]", "_").substring(0, 31)
                                : ($F{departmentId} + "_" + $F{departmentName}).replaceAll("[\\\\/\\?\\*\\[\\]:]", "_")
                        ]]>
                    </propertyExpression>
                    <!-- Freeze panes below the header table -->
                    <property name="net.sf.jasperreports.export.xls.freeze.row" value="3"/>
                    <textElement>
                        <font size="14" isBold="true"/>
                    </textElement>
                    <expression><![CDATA["Division: " + $F{departmentName} + " (" + $F{departmentId} + ")"]]></expression>
                </element>

                <!-- Column Headers -->
                <element kind="staticText" x="0" y="35" width="120" height="20">
                    <textElement><font isBold="true"/></textElement>
                    <text><![CDATA[Employee Code]]></text>
                </element>
                <element kind="staticText" x="120" y="35" width="285" height="20">
                    <textElement><font isBold="true"/></textElement>
                    <text><![CDATA[Employee Name]]></text>
                </element>
                <element kind="staticText" x="405" y="35" width="150" height="20">
                    <textElement textAlignment="Right"><font isBold="true"/></textElement>
                    <text><![CDATA[Base Salary]]></text>
                </element>
            </band>
        </groupHeader>
    </group>

    <detail>
        <band height="20">
            <element kind="textField" x="0" y="0" width="120" height="20">
                <expression><![CDATA[$F{employeeCode}]]></expression>
            </element>
            <element kind="textField" x="120" y="0" width="285" height="20">
                <expression><![CDATA[$F{employeeName}]]></expression>
            </element>
            <element kind="textField" x="405" y="0" width="150" height="20">
                <textElement textAlignment="Right"/>
                <expression><![CDATA[new java.text.DecimalFormat("#,##0.00").format($F{salary})]]></expression>
            </element>
        </band>
    </detail>
</jasperReport>
STEP 3

Interactive Architecture Workspace (Split-View Studio)

Lab 4.2.1: Dynamic Group Multi-Sheet & Sheet Sanitization

Target: Excel (.xlsx)

โจทย์สถาปัตยกรรม: บริษัทมีรายงานยอดขายหลายภูมิภาค (Region: North, South, East, West) คุณต้องกำหนด Group ให้เริ่มหน้าใหม่ทุกครั้งที่เปลี่ยน Region และตั้งชื่อ Sheet แต่ละ Sheet ตามชื่อ Region นั้นๆ โดยต้องแน่ใจว่าแท็กทั้งหมดเป็น JasperReports 7.0.x Unified Element Model

  • ต้องประกาศ <property name="net.sf.jasperreports.export.xls.one.page.per.sheet" value="true"/>
  • ต้องมี <group name="..." isStartNewPage="true">
  • ต้องใช้แท็ก <element kind="textField" ...> เท่านั้น (ห้ามใช้แท็กโบราณ <textField>)
  • ต้องมี <propertyExpression name="net.sf.jasperreports.export.xls.sheet.name">
Spreadsheet Multi-Tab Layout Preview
GLOBAL ENTERPRISE REGIONAL PERFORMANCE Confidential - Finance Division
Region ID: RG-NORTH
Quarter: Q4-2026
Status: AUDITED
North_Division South_Division East_Division West_Division +
⚠️ Crash Simulator (Test Engine Resilience)

ทดลองจำลองสถานการณ์ความผิดพลาดที่เกิดขึ้นจริงบน Production เพื่อสังเกตพฤติกรรมของ JasperReports Engine:

JRXML 7.0.x Live Linter
Awaiting Validation
Click "Verify 7.0.x Architecture" to run AST and Regex rules.
ARTIFACT PROMPTS

Production Image Specification Prompts (Visual Validation)

Commercial Grade Standard
1. Architectural Flowchart (Vector)

"2D Vector architectural diagram, modern dark slate background #0b0f19. JasperReports 7.0.3+ Engine Internal Pipeline: JRVerticalFiller streaming rows into Apache POI SXSSFWorkbook, showing isStartNewPage trigger triggering new Sheet, evaluation of net.sf.jasperreports.export.xls.sheet.name, and temp disk swap gzip files under /tmp, crisp typography, 8k resolution."

2. Lab 4.2.1 Output (Commercial Spreadsheet)

"Realistic high-end Microsoft Excel spreadsheet workbook for enterprise finance, white clean grid, tab bar at bottom showing distinct tabs: 'North_Division', 'South_Division', 'East_Division', 'West_Division'. Top rows freeze pane with corporate header, clean currency formatting $1,250,400.00, sharp sans-serif font, no blueprint outlines, production-ready."

3. Lab 4.2.2 Output (Large Dataset Chunks)

"Realistic enterprise Excel accounting report with multiple numbered volume sheets: 'GL_Chunk_1', 'GL_Chunk_2', 'GL_Chunk_3', showing transaction logs, debit/credit balanced columns, ledger IDs, subtle alternating zebra row striping, sharp professional financial presentation."

EXAM ARENA

15 Architectural Mastery Quizzes

เจาะลึกกลไก Engine, POI SXSSF, Classloading, JVM Memory และ Production Edge Cases