Thứ Sáu, 21 tháng 10, 2016

Dùng StAX Parser để xây dựng ứng dụng như trên cơ sở dữ liệu – CRUD

Dùng StAX Parser để xây dựng ứng dụng như trên cơ sở dữ liệu – CRUD

Mục đích: Chủ đề của bài này giới thiệu về bộ StAX (Streaming API for XML) Parser dùng để thực hiện tất cả thao tác trên tài liệu XML từ tìm kiếm đến thêm, xóa, sửa trên tài liệu tương tự như với thao tác trên cơ sở dữ liệu. Với tính đơn giản hơn SAX, dễ sử dụng không đòi hỏi logic cao như SAX và cực kỳ tiết kiệm bộ nhớ cùng với khả năng xử lý tài liệu XML cực kỳ lớn mà không ảnh hưởng đến bộ nhớ trong quá trình xử lý. Hơn thế nữa, khả năng kết hợp với công nghệ khác để xử lý một phần tài liệu XML trở thành Object cần xử lý và đưa lại tài liệu xử lý, cụ thể như kết hợp StAX với JAXB để xử lý một phần tài liệu XML. Chúng tôi sẽ hướng dẫn quí vị xây dựng một ứng dụng web đầy đủ sử dụng StAX để thấy được tính hiệu quả của nó cùng với sự kết hợp với  JAXB để cho thấy khả năng xử lý một phần tài liệu XML thành Object khi ứng dụng đang thực thi.

Yêu cầu về kiến thức cơ bản

Kiến thức tổng quát

  • StAX – là bộ parser thực hiện parsing tài liệu XML theo cơ chế như sau
    • Khi tài liệu XML được đưa vào bộ parser, toàn bộ tài liệu được kiểm tra well-formed
    • Khi nội dung tài liệu kiểm tra xong và đóng, toàn bộ tài liệu được truy cập dưới dạng Stream và được nạp vào bộ nhớ từng phần tùy theo tiến trình duyệt tài liệu
    • Sau đó, bộ StAX parser sẽ thực hiện việc đọc các thành phần theo chiều đi tới trong stream tùy theo sự điều khiển của ứng dụng và lấy giá trị theo yêu cầu của người dùng
    • StAX parser có đặc tính tối ưu là bộ parse dạng pull, nó cho phép người sử dụng điều khiển quá trình xử lý. Nó cung cấp khá năng cho người dùng có thể dừng bộ parser bất cứ khi nào, có thể bỏ qua một số phần tử nhất định, có thể tạm dừng quá trình parse, có thể tiếp tục quá trình parse đã dừng
    • StAX parser hỗ trợ multitple thread đối với ứng dụng và hỗ trợ nhiều người thao tác cùng một lúc
    • Cú pháp API của StAX rất đơn giản bởi vì nó hỗ trợ di chuyển trong Stream và đưa ra dữ liệu dạng tổng quát nhất cho người sử dụng
    • StAX là bộ parser duy nhất hỗ trợ cơ chế đọc và ghi trên tài liệu XML
    • StAX hỗ trợ cơ chế đọc tài liệu XML cực kỳ lớn và hỗ trợ xử lý với tốc độ duyệt khá tốt và phù hợp với thiết bị có bộ nhớ nhỏ và ít.
  • Một số điều cần lưu ý khi thực hiện với bộ SAX
    • StAX hỗ trợ cơ chế sử dụng cho đa người dùng – multiple thread – trong quá trình xử lý nhưng StAX API được implement hạn chế trong việc xử lý vừa thao tác đọc và ghi trên cùng một file tài liệu XML
    • Theo cơ chế bản chất của hệ điều hành, theo cách con trỏ FILE trong ngôn ngữ C/C++, chúng ta đọc file là nạp lên trên bộ nhớ và xử lý trên các vùng bộ đệm – buffer – và sau đó mới đồng bộ hay ghi xuống file vật lý trên đĩa
    • Tuy nhiên, các implement StAX trong Java với cơ chế đọc Stream và hỗ trợ xử lý tài liệu cực kỳ lớn nên quá trình thao tác trên cùng file của chúng ta có thể gây vấn đề vì việc load một phần và đồng bộ dẫn đến một khái niệm đọc và ghi là 02 con trỏ song song đang trỏ cùng một file để đọc và ghi. Điều này dẫn đến chương trình sẽ phát sinh lỗi trong quá trình thực thi của viêc đọc
    • Do vậy, giái pháp là chúng ta phải đọc và thao tác trên một file với stream mở tương ứng và ghi trên một file tạm với stream tương ứng. Khi quá trình thực hiện hoàn tất, chúng ta sẽ đóng 02 stream đọc và ghi. Sau đó, chúng ta thực hiện hủy file gốc và đổi tên file tạm thành file gốc

Mô hình tổng quát

Figure 3: StAX operation tại http://www.developerfusion.com/article/84523/stax-the-odds-with-woodstox/

  • InfoSet – XML Information Set
    • Đây là một khái niệm thể hiện một thành phần mà bộ StAX sẽ duyệt qua, một InfoSet chứa đựng một element, một attribute, thậm chí một document, ... và phải tuân thủ đầy đủ luật well-formed của một tài liệu XML
  • StAX cung cấp 02 API để hỗ trợ xử lý dữ liệu
  • StAX Cursor API
    • API cho xem mỗi thành phần trong tài liệu XML như là một event
    • API này sử dụng một con trỏ có kiểu dữ liệu kiểu int, tại mỗi thời điểm con trỏ này trỏ đến một infoSet trong Stream và người sử dụng sẽ dựa trên kết quả con trỏ trả về để thực hiện xử lý
    • Khi cần xử lý, người sử dụng sẽ đem các thành phần trong infoSet về dưới dạng String để xử lý
    • Đây là một ưu điểm của StAX, đó là cực kỳ tiết kiệm bộ nhớ và khi cần xử lý mới đem về và cách thức nạp để xử lý cực kỳ nhanh. Tuy nhiên, đây cũng là khuyết điểm của StAX cursor vì khi cần construct thành các object để trao đổi giữa các resource với nhau thì đòi hỏi tốn thời gian construct object
    • Để tạo ra StAX Cursor chúng ta phải thông qua class XMLInputFactory để từ đó tạo thành XMLStreamReader để hỗ trợ đọc dữ liệu

XMLInputFactory factory = XMLInputFactory.newInstance();

XMLStreamReader reader = factory.createXMLStreamReader(inputStream);

    • Các hàm cơ bản thường dùng của StAX Cursor API

Methods

Mô tả

int next()

Trả về cursor của event tiếp theo

int nextTag()

Nhảy tới event tiếp theo bỏ tất cả các white space, COMMENT, hay PROCESSING_INSTRUCTION, cho đến khi gặp START_ELEMENT hay END_ELEMENT

Chỉ áp dụng methods này khi giữa các infoSet là các whitespace, comment hay processing instruction

boolean hasNext()

Xác định stream đang duyệt còn hay là hết

int getEventType()

Trả về kiểu dữ liệu mà cursor đang trỏ tới

String getLocalName()

Trả về tên node của START_ELEMENT, END_ELEMENT hay ENTITY_REFERENCE

String getText()

Trả về giá trị của CHARACTERS, COMMENT, ENTITY REFERENCE

void close()

Đóng stream khi hoàn tất

 

    • Để thực hiện ghi dữ liệu Cursor cung cấp đối tượng XMLStreamWriter
      • Đối tượng này được tạo từ class XMLOutputFactory

XMLOutputFactory factory = XMLOutputFactory.newInstance();

XMLStreamWriter writer = factory.createXMLStreamWriter(outputStream);

      • Cách thức ghi của StreamWriter là ghi tuần tự từng thành phần infoSet cơ bản theo qui định của XML và không thực hiện kiểm tra well-formed trong quá trình ghi.
      • Các hàm cơ bản của bộ writer

Methods

Mô tả

writeStartDocument

Ghi XML declaration. Với giá trị mặc định version là 1.0

writeStartElement(String localName)

Ghi start element

writeCharacters (String text)

Ghi giá trị text

writeEndElement

Ghi end element

writeAttribute(String localName, String value)

Ghi attribute

writeEndDocument

Ghi end document

flush

Đồng bộ dữ liệu xuống file, áp dụng khi sử dụng trên ứng dụng web. Ứng dụng desktop sẽ được tự động đồng bộ

close

Đóng stream writer

 

  • StAX Iterator API
    • Thực hiện chuyển đổi stream thành các object event trên bộ nhớ. Do vậy, quá trình này tốn nhiều thời gian hơn StAX Cursor và tốn nhiều bộ nhớ hơn. Tuy nhiên, StAX Iterator hỗ trợ để giao tiếp giữa các resource của ứng dụng và các ứng dụng khác một cách nhanh chóng thông qua Object
    • Cách tạo StAX Iterator tương tự như StAX Cursor nhưng kiểu xử lý của chúng bây giờ là XMLEventReader

XMLInputFactory factory = XMLInputFactory.newInstance();

XMLEventReader reader = factory.createXMLEventReader(inputStream);

    • Kiểu dữ liệu trả về không phải là dạng integer như là Cursor mà nó sẽ là kiểu XMLEvent
    • Các hàm thông dụng được sử dụng trong Iterator để đọc dữ liệu

Methods

Mô tả

int getEventType

Trả về kiểu dữ liệu của Event đang xử lý

boolean hasNext

Kiểm tra xem có còn event trong stream xử lý nữa hay không

XMLEvent nextEvent

Di chuyển đến object Event tiếp theo

XMLEvent nextTag

Tương tự như nextTag của StAX Cursor

XMLEvent peek

Xác định event tiếp theo trong chuỗi Stream đang xử lý nhưng không di chuyển đến event tiếp theo trong Stream. Trong khi nextEvent sẽ di chuyển đến event tiếp theo trong Stream

void close

Đóng parse khi xử lý xong

Boolean isXXX()

Dùng để kiểm tra loại dữ liệu của Event đang xử lý. Ví dụ như isStartElement, isEndElement, isCharacter, …

    • Để ghi dữ liệu xuống tài liệu XML, tương tự như cursor, iterator cung cấp XMLEventWriter

XMLOutputFactory factory = XMLOutputFactory.newInstance();

XMLEventWriter writer = factory.createXMLEventWriter(outputStream);

      • Cơ chế thực hiện có khác biệt với Cursor, theo cách phải tạo ra một phần tử XML theo định nghĩa để tạo ra một event object. Sau đó, dùng methods add để đưa event vào trong Streams

XMLEvent event = factory.createXxx(các tham số);

writer.add(event);

      • Tương tự cách ghi của cursor, iterator cũng ghi tuần tự và không kiểm tra well-formed
  • Kết hợp giữa StAX Cursor và StaX Iterator
    • Mỗi bộ API đều có ưu điểm và khuyết điểm. Do vậy, người ta đã tận dụng ưu điểm của 02 bộ này theo ngữ nghĩa, duyệt tài liệu XML lớn cực kỳ nhanh – tiết kiệm bộ nhớ và khi cần giao tiếp thì lấy là kiểu event object
    • Điều này có nghĩa là người ta sẽ sử dụng cursor để duyệt dữ liệu và iterator để lấy dữ liệu xử lý cho ứng dụng
    • Vấn đề đặt ra lớn nhất đó là làm sao chuyển đổi từ cursor sang iterator? Câu trả lời là bộ API đã cung cấp một adapter XMLEventAllocator để hỗ trợ chuyển đổi từ dạng cursor trong iterator khi thực hiện thao tác với dữ liệu
    • Bên cạnh đó, API cũng cung cấp method asXxx() như asStartElement, asEndElement, ... để hỗ trợ ép kiểu dữ liệu từ cursor thành dạng iterator
    • Ví dụ
      • Tài liệu XML xử lý

      • Code xử lý kết hợp với ngữ nghĩa
        • Duyệt tài liệu sử dụng cursor
        • Sử dụng phương thức getXMLEvent với bộ adapter convert cursor thành event
        • Sau đó, dùng method asXxx để ép về kiểu thực tế của Iterator
        • Xử lý kết quả

      • Kết quả khi thực thi

Áp dụng lý thuyết StAX để xây dựng ứng dụng CRUD trên file XML

  • Nội dung yêu cầu của ứng dụng
    • Chỉ cho sinh viên có đúng id và password cùng với status khác dropout mới được vào hệ thống
    • Sau khi login thành công, sinh viên có thể search các sinh viên tùy theo status được nhập vào. Kết quả sẽ được hiển thị ra dạng lưới
    • Trên lưới kết quả, người dùng có thể xóa một dòng mà họ lựa chọn, dữ liệu phải được cập nhật xuống XML và lưới cũng được cập nhật
    • Tương tự, người dùng có thể sửa tên lớp, địa chỉ của một dòng cụ thể trên lưới và lưới cùng dữ liệu dưới file XML phải được cập nhật
    • Người dùng có thể tạo thêm sinh viên mới với trạng thái mặc định là break. Dữ liệu phải được cập nhật xuống file XML và người dùng tạo mới có thể login vào hệ thống thông qua trang login
  • Tài liệu XML xử lý có dạng như sau

  • Các nội dung thực hiện theo từng bước như sau
    • Xây dựng chức năng Login theo yêu cầu. Ở đây, chúng ta sẽ áp dụng StAX Cursor để xử lý
      • Bước 1: Xây dựng thư viện hỗ trợ parsing tài liệu XML sử dụng StAX Cursor
        • Các bước tạo và xây dựng ứng dụng web, quí vị tự thực hiện (tham khảo tại địa chỉ http://www.kieutrongkhanh.net/search/label/Servlet%26JSP) hay tham khảo xây dựng ứng dụng sử dụng DOM (tham khảo lại tại địa chỉ http://www.kieutrongkhanh.net/2016/08/gioi-thieu-ve-dom-inh-nghia-dom-api-va.html)
        • Tập tin XML được đặt ngay tại thư mục WEB-INF
        • Tham số của hàm này là một input Stream cần xử lý. Chúng ta không truyền tên file trực tiếp để có thể handle stream trực tiếp bên ngoài khi cần xử lý tiếp và có thể đóng mở trong mọi trường hợp cả khi ứng dụng bị lỗi trong quá trình thực thi

      • Bước 2: Xây dựng giao diện thực hiện login

      • Bước 3: Tạo các thư viện hỗ trợ để lấy attribute và text value của các element trong quá trình xử lý
        • Để lấy được attribute chúng ta cần phải duyệt stream đến start Element và kiểm tra tên node trùng khớp và lấy ra giá trị của attribute
        • Method để lấy trị attribute trong API đòi hỏi cần có namespace. Nếu tài liệu chúng ta không có namespace thì chúng ta sẽ truyền “” hay là null. Mặc định JDK mới là truyền “”, chúng ta khi thực hiện ứng dụng và nếu kết quả ra sai, quí vị tự động truyền null
        • Method này truyền vào một reader đang xử lý, tên element, tên uri namespace, tên attribute

        • Để lấy text value của một element, chúng ta cũng làm tương tự. Tuy nhiên, khi đã tìm được đúng element, chúng ta sẽ phải dời con trỏ đến vị trí tiếp theo – đó chính là text của node – để lấy trị. Bên cạnh đó, theo nguyên lấy xử lý trong XML, chúng ta nên dịch chuyển con trỏ đến end element để hoàn tất xử lý cho một node
        • Method này truyền vào một reader đang cần xử lý và tên element cần lấy trị

      • Bước 4: Thực hiện xử lý trong Servlet để đưa ra kết quả
        • Tạo ProcessServlet

    • Xây dựng LoginStAXServlet
      • Tạo Stream
      • Di chuyển Stream, tìm Start Element với giá trị là student
      • Lấy id, kiểm tra
      • Nếu đúng, lấy các element để lưu trữ fullname
      • Lấy element password, kiểm tra
      • Nếu đúng, lấy status, kiểm tra
      • Nếu thỏa điều kiện, thì lưu trữ trên scope, dừng bộ parser và đưa ra kết quả
      • Nếu duyệt đến hết không có thì đưa ra kết quả không hợp lệ

      • Bước 5: Clean and Build Project, Deploy, và chạy thử ứng dụng
        • Login với user đúng: id, password đúng và status là studying

 

        • Login với user đúng: id, password đúng và status là break

 

        • Login với user đúng nhưng status là drop out

 

    • Login với user không tồn tại

 

  • Chúng ta vừa hoàn tất xong chức năng login. Chúng ta sẽ tiếp tục với chứng năng search của StAX
    • Trong chức năng Search này, chúng ta sẽ áp dụng StAX Iterator để duyệt object cho thuận lợi
    • Bước 1: Xây dựng thư viện parse StAX Iterator và các methods để lấy attribute và lấy text node của element tương tự như cursor
      • Parse StAX Iterator

      • Lấy attribute của một node

      • Lấy text node của một element

    • Bước 2: Xây dựng đối tượng DTO để đón nhận kết quả khi xử lý Search trả về

    • Bước 3: Xử lý search trên Servlet để chuyển sang trang trình bày kết quả
      • Tạo Stream
      • Duyệt từng event lấy tất cả giá trị và lưu trữ
      • Đến event element status, thực hiện kiểm tra, nếu thỏa điều kiện tìm kiếm thì lưu thành DTO và lưu trong list kết quả trả về
      • Chuyển sang trang search.jsp để trình bày giao diện

      • Xử lý kết quả trên trang trình bày

    • Bước 4: Thực hiện clean and Build Project, Deploy và test chức năng search
      • Login thành công đến chức năng Search

      • Nhập giá trị để search
        • Search có kết quả

        • Search không tìm thấy

  • Chúng ta vừa hoàn thành xong chức năng Search. Chúng ta sẽ tiếp tục với chức năng Delete trực tiếp trên lưới
    • Bước 1: Bổ sung thư viện để thực hiện xóa một node trong tài liệu XML sử dụng StAX
      • Cơ chế được áp dụng ở đây đó là chúng ta thực hiện mở Stream để duyệt như bình thường
      • Bên cạnh đó, chúng ta sẽ mở stream ghi đến một file tạm để thực hiện ghi các giá trị thỏa điều kiện vào stream tạm
      • Chúng ta duyệt từng event và kiểm tra điều kiện. Một khi một đối tượng thỏa điều kiện nghĩa là đối tượng đó sẽ bị xóa, chúng ta sẽ bật cờ không cho ghi vào trong stream output từ start element cho đến khi gặp node end element
      • Các trường hợp còn lại sẽ ghi trực tiếp event duyệt vào trong stream ghi
      • Sau khi quá trình duyệt hết stream. Chúng ta đóng tất cả stream lại
      • Thực hiện xóa file gốc mà chúng ta đã duyệt đi, sau đó, đổi tên file tạm thành tên file gốc để hoàn tất quá trình xóa node
      • Tham số truyền vào là giá trị điều kiện cần xóa, tên file XML và đường dẫn đến nơi chưa file

    • Bước 2: Thực hiện xử lý trên Servlet
      • Thực hiện gọi method xóa node và gọi chức năng Search lại lần nữa để trình bày giao diện

    • Bước 3: Clean and Build, Deploy, và Test ứng dụng
      • Màn hình lưới sau khi search

      • Click Delete

      • Kết quả trên thực tế

      • Dòng trống là dòng bị xóa
    • Chúng ta vừa hoàn thành chức năng Delete. Chúng ta sẽ thực hiện tiếp tục chức năng Update trên lưới
      • Bước 1:  Bổ sung thư viện hỗ trợ Update
        • Tương tự như delete, chúng ta cũng mở stream đọc và stream ghi.
        • Tuy nhiên, StAX là dạng forward only mà update đòi hỏi phải xử lý giữa các node con. Do vậy, ở đây chúng ta sẽ áp dụng JAXB để thực hiện việc update dữ liệu
        • Cơ chế thực hiện
          • Chúng ta thực hiện quá trình xác định event tiếp theo trong stream là gì nhưng không di chuyển thực sự trong stream bằng cách dùng method peek.
          • Thông qua peek, chúng ta xác định được event của chúng ta đang xử lý, chúng ta sẽ convert stream đang xử lý trở thành Object thông qua JAXB với cách thức unmarshall
          • Khi có được object, chúng ta thực hiện kiểm tra các thành object thỏa điều kiện, chúng ta cập nhật trạng thái của object
          • Sau khi việc cập nhật hoàn tất, chúng ta sẽ thực hiện marshall object trở lại vào trong stream
          • Thực hiện quá trình ghi stream với event thông qua writer và di chuyển đến event tiếp theo cho đến khi duyệt hết stream

      • Bước 2: Tạo Servlet để thực hiện xử lý update và gọi chức năng Search lại lần nữa để cập nhật giao diện

      • Bước 3: Clean and Build, Deploy và test lại project với chức năng Update

        • Chỉnh sửa giá trị, và nhấn Update

        • File được cập nhật giá trị sau khi lưới được cập nhật như sau

    • Chúng ta vừa hoàn thành xong chức năng Update, chúng ta hoàn thành chức năng cuối cùng là Insert
      • Bước 1: bổ sung thư viện hỗ trợ Insert
        • Chúng ta cũng sẽ thực hiện tương tự như update là phối hợp với JAXB để tạo object mới rồi marshall vào Stream để ghi vào file
        • Ở đây, chúng ta sẽ tìm endElement của rootElement mà không di chuyển trong stream sử dụng peek. Ngữ nghĩa ở đây theo khái niệm là khi chúng ta gặp phần tử đóng của root element thì chúng ta chèn node mới vào trước đó
        • Do vậy, khi event tiếp theo last element, chúng ta tạo JAXB Object và marshall vào trong stream và ghi thẳng xuống file thông qua writer, rồi cuối cùng mới di chuyển đến event tiếp theo và ghi event vào stream

        • Bước 2: Bổ sung giao diện cho phần tạo mới node

        • Bước 3: Tạo Servlet để xử lý

      • Bước 4: Clean and Build, Deploy và Test

      • Click Create New Student

      • Login lại lần nữa sẽ ok
      • Mở file được cập nhật như sau

  • Cấu trúc của toàn bộ thư mục project như sau

Chúc mừng quí vị đã hoàn tất và nắm được mô hình parser của StAX và cách vận dụng để tạo ra ứng dụng thao tác tương tự như trên DB và sự kết hợp với JAXB để cho thấy tính tối ưu của JAXB. Việc lựa chọn parser là tùy theo nghiệp vụ và nội dung xử lý của từng ứng dụng khác nhau và tùy theo đặc tính của parser. Bên cạnh đó, sự phối hợp với công nghệ khác sẽ làm parser trở nên mạnh hơn so với bản chất của chính nó.

Chúng tôi hy vọng nội dung của bài này giúp ích quí vị.

Rất mong sự góp ý chân thành và chia sẻ của quí vị về vấn đề này. Hẹn gặp lại quý vị ở các chủ đề khác liên quan đến XML.

Không có nhận xét nào:

Đăng nhận xét