首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 數據庫知識 >  >> DB2 資料庫 >> DB2 教學

DB2 pureXML 與 Ruby on Rails 實戰:構建靈活的 Team Room 應用(二)

     編輯:DB2 教學

系列回顧與本篇目標

上一篇我們用 Ruby on Rails + DB2 建立了 Team Room 基礎版:會員註冊、文檔上傳、分類訂閱、郵件通知。本篇將引入 DB2 pureXML 特性,實現:

  1. 用戶認證增強(唯一 userid + SHA-256 加鹽密碼)
  2. 訂閱關係重構:支援「多用戶訂閱同一主題」的多對多模型
  3. 利用 XML 列存儲半結構化文檔元數據,並用 SQL/XML 混合查詢

步驟 1:用戶模型安全升級

1.1 生成遷移腳本

cd D:/rails/teamroom
ruby script/generate migration add_user_credentials_columns

1.2 編輯遷移文件 db/migrate/008_add_user_credentials_columns.rb

class AddUserCredentialsColumns < ActiveRecord::Migration
  def self.up
    add_column :users, :userid, :string, limit: 8, null: false
    add_column :users, :hash_passwd, :string, null: false
    add_column :users, :salt, :string, null: false
    add_index :users, :userid, unique: true
  end

  def self.down
    remove_column :users, :userid
    remove_column :users, :hash_passwd
    remove_column :users, :salt
  end
end

1.3 執行遷移

rake db:migrate

1.4 模型層加密邏輯 app/models/user.rb

require 'digest/sha2'
class User < ActiveRecord::Base
  attr_accessor :password
  validates_uniqueness_of :userid
  validates_confirmation_of :password
  validates_length_of :password, within: 6..40

  before_save :encrypt_password

  def self.authenticate(userid, password)
    user = find_by_userid(userid)
    return nil unless user
    expected = Digest::SHA2.hexdigest(user.salt + password)
    (user.hash_passwd == expected) ? user : nil
  end

  private
  def encrypt_password
    return if password.blank?
    self.salt = SecureRandom.hex(16)
    self.hash_passwd = Digest::SHA2.hexdigest(salt + password)
  end
end

步驟 2:訂閱關係重構為多對多

2.1 業務痛點

原設計:Subscription belongs_to Subject,導致一個 Subject 只能屬於一個 Subscription,現實中多用戶想訂閱同一技術主題(如「DB2 pureXML」)卻無法實現。

2.2 新增中間表遷移

ruby script/generate migration create_subscriptions_subjects_join_table
class CreateSubscriptionsSubjectsJoinTable < ActiveRecord::Migration
  def self.up
    create_table :subscriptions_subjects, id: false do |t|
      t.integer :subscription_id, null: false
      t.integer :subject_id, null: false
    end
    add_index :subscriptions_subjects, [:subscription_id, :subject_id], unique: true
  end

  def self.down
    drop_table :subscriptions_subjects
  end
end

2.3 模型關聯調整

# app/models/subscription.rb
class Subscription < ActiveRecord::Base
  belongs_to :user
  has_and_belongs_to_many :subjects, join_table: 'subscriptions_subjects'
end

# app/models/subject.rb
class Subject < ActiveRecord::Base
  has_and_belongs_to_many :subscriptions, join_table: 'subscriptions_subjects'
end

步驟 3:引入 pureXML 存儲文檔元數據

3.1 為 documents 表添加 XML 列

ruby script/generate migration add_metadata_xml_to_documents
class AddMetadataXmlToDocuments < ActiveRecord::Migration
  def self.up
    execute "ALTER TABLE documents ADD COLUMN metadata XML"
  end

  def self.down
    execute "ALTER TABLE documents DROP COLUMN metadata"
  end
end

3.2 XML 文檔結構設計

<metadata>
  <author>Zhang San</author>
  <tags>
    <tag>DB2</tag>
    <tag>pureXML</tag>
    <tag>Ruby</tag>
  </tags>
  <version>1.2</version>
  <custom_fields>
    <field name="confidentiality">internal</field>
    <field name="review_status">approved</field>
  </custom_fields>
</metadata>

3.3 Rails 模型封裝 XML 讀寫 app/models/document.rb

class Document < ActiveRecord::Base
  belongs_to :subject
  belongs_to :user

  # 將 Ruby Hash 序列化為 XML 字符串寫入 DB
  def metadata=(hash)
    return super(nil) if hash.nil?
    builder = Nokogiri::XML::Builder.new(encoding: 'UTF-8') do |xml|
      xml.metadata {
        xml.author hash[:author]
        xml.tags { hash[:tags].each { |t| xml.tag t } } if hash[:tags]
        xml.version hash[:version]
        if hash[:custom_fields]
          xml.custom_fields {
            hash[:custom_fields].each { |k,v| xml.field(v, name: k) }
          }
        end
      }
    end
    super(builder.to_xml)
  end

  # 讀取時解析 XML 為 Hash
  def metadata
    return nil if super().blank?
    doc = Nokogiri::XML(super())
    {
      author: doc.at_xpath('//author')&.text,
      tags: doc.xpath('//tag').map(&:text),
      version: doc.at_xpath('//version')&.text,
      custom_fields: doc.xpath('//custom_fields/field').each_with_object({}) { |n,h| h[n['name']] = n.text }
    }
  end
end

步驟 4:SQL/XML 混合查詢實戰

4.1 場景:查找「標籤包含 pureXML」且「作者為 Zhang San」的文檔

# 純 Ruby 方式(全表掃描,效率低)
Document.all.select { |d| d.metadata&.dig(:tags)&.include?('pureXML') && d.metadata&.dig(:author) == 'Zhang San' }

# 推薦:DB2 原生 XMLExists + XMLQuery 推下去
sql = <<-SQL
  SELECT d.*, XMLQUERY('$m/metadata/author/text()' PASSING d.metadata AS "m") AS author_name
  FROM documents d
  WHERE XMLEXISTS('$m/metadata/tags/tag[. = "pureXML"]' PASSING d.metadata AS "m")
    AND XMLQUERY('$m/metadata/author/text()' PASSING d.metadata AS "m") = 'Zhang San'
SQL
Document.find_by_sql(sql)

4.2 關鍵函數速查

函數用途示例
XMLEXISTSWHERE 子句中判斷 XPath 是否匹配XMLEXISTS('$m//tag[.="DB2"]' PASSING metadata AS "m")
XMLQUERYSELECT 列表中提取標量值XMLQUERY('$m/metadata/version/text()' PASSING metadata AS "m")
XMLTABLE將 XML 序列展開為關係表XMLTABLE('$m/metadata/tags/tag' PASSING metadata AS "m" COLUMNS tag VARCHAR(50) PATH '.')

控制器與視圖微調

  • UsersController 新增 register/login 動作,使用 User.authenticate
  • DocumentsController#create 接收表單 tags[]、custom_fields 並賦值 @document.metadata = params[:metadata]
  • 視圖中用 check_box_tag 'tags[]', 'DB2' 等渲染標籤多選

總結與下一步

本篇完成了:

  1. 用戶認證安全加固(userid 唯一 + SHA-256 加鹽)
  2. 訂閱關係從 1:N 升級為 M:N,符合真實業務
  3. 引入 pureXML 列存儲半結構化元數據,配合 SQL/XML 推算下推,避免全表掃描

下一篇將探討:XML 索引建立、XQuery 複雜報表、版本控制與審計日誌 等進階主題。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved