หน้าเว็บ

วันจันทร์ที่ 7 มกราคม พ.ศ. 2556

การสร้าง Entity class และเรียนรู้การ Map Entity Relationship : java

Entity class คืออะไร?

        ถ้าจะว่าไปมันก็คือการ Map จาก Java Class => Database Table นั่นเองครับ  คือเราไม่จำเป็นต้องเขียนคำสั่ง SQL เพื่อ Create Table เอง  เราแค่เขียน java class ขึ้นมา  จากนั้น framework มันก็จะจัดการแปลงไปเป็น  table ให้เราเองโดยอัตโนมัติ  เวลาที่เราดึงค่ามาจาก database มันก็จะแปลงจาก row มาเป็น object ให้เราเอง  อาจจะเรียกว่าการทำ ORM หรือ Object Relational Mapping  นั่นเองครับ  เรามาดูวิธีการสร้างกันเลยดีกว่า


หลักการสร้าง Entity Class (EclipseLink)
  1. เป็น java class ตั้งชื่อว่าอะไรก็ได้ (ให้สื่อกับความหมายนั้นๆ)
  2. implements Serializable
  3. มี annotation @Entiry อยู่บน class
  4. Entity Class ต้องมี default constructor ที่ไม่มี parameter เท่านั้น  เช่น public Category(){ }
  5. ในกรณีที่ต้องการกำหนดค่าบางอย่างของ Table  ให้ใช้ annotation @Table  เช่นการกำหนดชื่อให้ table จะใช้ attribute name  แต่ถ้าไม่ได้กำหนดชื่อ  default table name จะเป็นชื่อของ entity class นั้นๆ การตั้งชื่อ table ให้ระวังเรื่อง reserve word ของ database แต่ละเจ้า
  6. กำหนด attribute
  7. จะต้องมี attribute อย่างน้อย 1 attribute ที่มี annotation @Id เพื่อบอกว่าเป็น primary key
  8. มี method getter และ setter เพื่อ get และ set ค่าให้กับแต่ละ attribute
  9. ในการกำหนดค่าแต่ละ attribute หรือแต่ละ column ให้ใช้ annotation @Colum อยู่บน attribute นั้นๆ
  10. การ map relationshipให้สร้าง attribute ที่มี data type เป็น Entity Class ที่ต้องการ map ด้วย   จากนั้นก็ใส่ annotation Mapping ต่างๆ  ไว้บน attribute นั้นๆ เช่น
    1. @OneToOne
    2. @OneToMany
    3. @ManyToOne
    4. @ManyToMany
หลักการคร่าวๆ มีประมาณนี้ครับ  ต่อไปลองมาสร้างดูครับ


ตัวอย่างที่ 1 ยังไม่มี foreign key หรือการ Map ใดๆ ทั้งสิ้น
1. เขียน java class ขึ้นมา  สมมติผมจะสร้างเป็น Entity UserForum (Map to Table UserForum)


UserForum.java
package com.blogspot.na5cent.model;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToMany;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.TableGenerator;
import javax.persistence.Temporal;
import javax.persistence.Version;

/**
 *
 * @author Redcrow
 */
@Table(name = "USER_FORUM") //เป็นการตั้งชื่อ table ว่าให้ชื่อ USER_FORUM
@Entity //เป็นการประกาศว่า java class นี้เป็น entity class เพื่อให้ framework มาอ่านข้อมูลของ class นี้ไปสร้างเป็น table
public class UserForum implements Serializable {

    @Id //ระบุว่า field นี้เป็น primary key
    //@TableGenerator เป็นการระบุว่าให้ database ทำการสร้าง id หรือ primary key ให้เองโดยอัตโนมัติ โดย ให้ squence id นั้นเก็บไว้ที่ table ORPHANDRG_SEQ  ที่มีค่าของ column NAME เท่ากับ USER_FORUM 
    @TableGenerator(name = "UserForum_GEN", table = "ORPHANDRG_SEQ", pkColumnName = "NAME", valueColumnName = "VALUE", pkColumnValue = "USER_FORUM")
    @GeneratedValue(generator = "UserForum_GEN", strategy = GenerationType.TABLE)
    @Column(name = "USER_FORUM_ID") //กำหนดให้ column นี้มีชื่อว่า USER_FORUM_ID ถ้าไม่กำหนดมันจะเป็น ID ให้เองโดยอัตโนมัติ
    private Integer id;

    @Version //เป็นการกำหนด version ของ row เพื่อป้องกันปัญหา optimistic locking ที่อาจจะเกิดขึ้น optimistic locking คือปัญหาการ update ข้อมูลเก่าทับข้อมูลที่ใหม่กว่า ซึ่งมันไม่ควรที่จะเกิดขึ้น
    private Integer version;

    //information
    @Column(unique=true) //คือการกำหนดว่าให้ column นี้เป็น unique  มันก็คือ SQL unique นั่นเอง
    private String userName;

    @Column(length=30, nullable=false) //กำหนดความยาวตัวอักษรเป็น 30 ตัวอักษร และห้ามเป็น null
    private String fullName; //ในกรณีนี้ถ้าไม่ได้ใส่ @Column  ชื่อ column นี้จะเป็น FULLNAME เองโดยอัตโนมัติ

    private String email;

    private String image;

    @Temporal(javax.persistence.TemporalType.TIMESTAMP) //เป็นการ map ให้ java date => time stamp ของ database
    private Date joinDate;

    private Boolean enable = false;

    private Boolean hidden = false;

    public UserForum() {
    }

    public UserForum(Integer id) {
        this.id = id;
    }
    
    //getter and setter method
  
    @Override
    public String toString() {
        return fullName;
    }

    //hash code กับ equal ใช้สำหรับการ compare object 
    @Override
    public int hashCode() {
        int hash = 3;
        hash = 29 * hash + Objects.hashCode(this.id);
        return hash;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        final UserForum other = (UserForum) obj;
        if (!Objects.equals(this.id, other.id)) {
            return false;
        }
        return true;
    }
}

ตัวอย่างที่ 2 การ Map One to One  
ตัวอย่างนี้  ผมขอใช้ Entity Class ใหม่น่ะครับ


Staff.java
package com.na5cent.blogspot.model;

import java.io.Serializable;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.ManyToOne;
import javax.persistence.OneToOne;
import javax.persistence.PrimaryKeyJoinColumn;
import javax.persistence.Table;
import org.eclipse.persistence.annotations.ReadOnly;

/**
 *
 * @author Redcrow
 */
@Entity
@ReadOnly //เป็นการกำหนดว่า Entity นี้หรือ table นี้สามารถอ่านข้อมูลได้เพียงอย่างเดียวเท่านั้น
@Table(name = "STAFFS")
public class Staff implements Serializable {

    @Id
    @Column(name = "STAFF_ID")
    private String staffId;

    @Version
    private Integer version;
    private String staffName;
    private String account;
    private String password;
 
    ...
    ...
    ...
}

User.java
package com.blogspot.na5cent.model;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.OneToOne;
import javax.persistence.Table;
import javax.persistence.Version;

/**
 *
 * @author Redcrow
 */
@Entity
@Table(name = "USERS")
public class User implements Serializable {

    @Id
    @Column(name = "USER_ID")
    @GeneratedValue(strategy = GenerationType.AUTO) //Generate Auto ตัวนี้ใช้ในการ test เท่านั้น
    private Integer id;

    @Version
    private Integer version;

    @OneToOne //อันนี้คือ foreign key ครับ  ซึ่งชี้ไปที่ primay key ของ table STAFFS ซึ่งก็คือ STAFF_ID นั่นเอง
    private Staff staff;

    private String name;

    private String userName;

    private String userType;

    //getter and setter method

    @Override
    public int hashCode() {
        int hash = 7;
        hash = 59 * hash + (this.id != null ? this.id.hashCode() : 0);
        return hash;
    }

    @Override
    public boolean equals(Object obj) {
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        final User other = (User) obj;
        if (this.id != other.id && (this.id == null || !this.id.equals(other.id))) {
            return false;
        }
        return true;
    }
}

ตัวอย่างที่ 3 การ Map One To Many หรือ Many To One


Post.java
package com.blogspot.na5cent.model;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Lob;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.TableGenerator;
import javax.persistence.Temporal;
import javax.persistence.Version;
import org.apache.commons.lang3.StringUtils;

/**
 *
 * @author Redcrow
 */
@Table(name = "POST_FORUM")
@Entity
public class Post implements Serializable {

    @Id
    @TableGenerator(name = "Post_GEN", table = "ORPHANDRG_SEQ", pkColumnName = "NAME", valueColumnName = "VALUE", pkColumnValue = "POST_FORUM")
    @GeneratedValue(generator = "Post_GEN", strategy = GenerationType.TABLE)
    @Column(name = "POST_ID")
    private Integer id;
 
    @Version
    private Integer version;
 
    //information
    private String title;
 
    @Lob //ฟิลด์นี้ คือการเก็บข้อมูลเป็น Large Object Binary
    private String content;
 
    @Temporal(javax.persistence.TemporalType.TIMESTAMP)
    private Date postDate;
 
    @ManyToOne //เป็น foreign key ที่ชี้ไปที่ primary key ของ table CATEGORY ซึ่งก็คือ CATEGORY_ID

    private Category category;
 
    @OneToMany(mappedBy = "post")//เป็น bidirectional mapping ที่ map กลับไปยัง table COMMENT_FORUM ฟิลด์ COMMENT_ID 
    private List<Comment> comments;

    //getter and setter method
    ...
    ...
    ...
}

Comment.java
package com.blogspot.na5cent.model;

import java.io.Serializable;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.Lob;
import javax.persistence.ManyToOne;
import javax.persistence.Table;
import javax.persistence.TableGenerator;
import javax.persistence.Temporal;
import javax.persistence.Version;
import org.apache.commons.lang.StringUtils;

/**
 *
 * @author Redcrow
 */
@Table(name = "COMMENT_FORUM")
@Entity
public class Comment implements Serializable {

    @Id
    @TableGenerator(name = "Comment_GEN", table = "ORPHANDRG_SEQ", pkColumnName = "NAME", valueColumnName = "VALUE", pkColumnValue = "COMMENT_FORUM")
    @GeneratedValue(generator = "Comment_GEN", strategy = GenerationType.TABLE)
    @Column(name = "COMMENT_ID")
    private Integer id;
 
    @Version
    private Integer version;
 
    //information
    @Lob
    private String content;
 
    @ManyToOne //เป็น foreign key ที่ชี้ไปที่ primary key ของ table POST_FORUM ซึ่งก็คือ POST_FORUM_ID
    private Post post;
 
    @Temporal(javax.persistence.TemporalType.TIMESTAMP)
    private Date commentDate;

    ...
    ...
    ...
}

Category.java
package com.blogspot.na5cent.model;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.TableGenerator;
import javax.persistence.Version;
import org.hibernate.annotations.Fetch;
import org.hibernate.annotations.FetchMode;

/**
 *
 * @author Redcrow
 */
@Table(name = "CATEGORY")
@Entity
public class Category implements Serializable {

    @Id
    @TableGenerator(name = "Category_GEN", table = "ORPHANDRG_SEQ", pkColumnName = "NAME", valueColumnName = "VALUE", pkColumnValue = "CATEGORY")
    @GeneratedValue(generator = "Category_GEN", strategy = GenerationType.TABLE)
    @Column(name = "CATEGORY_ID")
    private Integer id;
 
    @Version
    private Integer version;
 
    //information
    @Column(name = "CATEGORY_NAME")
    private String categoryName;
 

    //เป็น bidirectional mapping ที่ชี้กลับไปที่ table POST_FORUM ฟิดล์ POST_ID
    @OneToMany(mappedBy = "category" , fetch= FetchType.LAZY) 
    //FetchType.LAZY คือ ตอนที่ดึงข้อมูลของ Category มาไม่ต้องเอา post ทั้งหมดมาด้วย  แต่ถ้าอยากให้ post ทั้งหมดมาด้วยก็ต้องใช้ FetchType.EAGER
    private List<Post> posts;

    ...
    ...
    ...
}

ตัวอย่างที่ 4 การ map Many To Many



Category.java
package com.blogspot.na5cent.model;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.JoinColumn;
import javax.persistence.JoinTable;
import javax.persistence.ManyToMany;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.TableGenerator;
import javax.persistence.Version;
import org.hibernate.annotations.Fetch;
import org.hibernate.annotations.FetchMode;

/**
 *
 * @author Redcrow
 */
@Table(name = "CATEGORY")
@Entity
public class Category implements Serializable {

    @Id
    @TableGenerator(name = "Category_GEN", table = "ORPHANDRG_SEQ", pkColumnName = "NAME", valueColumnName = "VALUE", pkColumnValue = "CATEGORY")
    @GeneratedValue(generator = "Category_GEN", strategy = GenerationType.TABLE)
    @Column(name = "CATEGORY_ID")
    private Integer id;
    @Version
    private Integer version;
    //information
    @Column(name = "CATEGORY_NAME")
    private String categoryName;
 
 
    //join
    @ManyToMany(fetch= FetchType.EAGER)//ใช้ keyword ManyToMany
    //FetchType.EAGER คือการโหลดข้อมูล UserForum ที่เกี่ยวกับ Category นี้มาทั้งหมด เมื่อมีการใหลด Category นี้ขึ้นมาใช้งาน 
    //JoinTable เป็นการสร้าง table กลางขึ้นมาครับ โดยให้มีชื่อว่า ANSWER  ซึ่งประกอบไปด้วย primary key 2 ฟิลด์ คือ ฟิลด์ CATEGORY ที่ชี้ไปที่ CATEGORY_ID หรือ primary key ของ table CATEGORY  และฟิลด์ USER_FORUM ที่ชี้ไปที่ USER_FORUM_ID หรือ primary key ของ table USER_FORUM
    @JoinTable(name = "ANSWER",
    joinColumns = {
        @JoinColumn(name = "CATEGORY", referencedColumnName = "CATEGORY_ID")},
    inverseJoinColumns = {
        @JoinColumn(name = "USER_FORUM", referencedColumnName = "USER_FORUM_ID")})
    private List<UserForum> answers;

    //ตรงนี้ได้กล่าวไปแล้วในตัวอย่างที่ 3 
    @OneToMany(mappedBy = "category", fetch= FetchType.LAZY)
    private List<Post> posts;

 ...
 ...
 ...
}

UserForum.java
package com.blogspot.na5cent.model;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.Objects;
import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToMany;
import javax.persistence.OneToMany;
import javax.persistence.Table;
import javax.persistence.TableGenerator;
import javax.persistence.Temporal;
import javax.persistence.Version;

/**
 *
 * @author Redcrow
 */
@Table(name = "USER_FORUM")
@Entity
public class UserForum implements Serializable {

    @Id
    @TableGenerator(name = "UserForum_GEN", table = "ORPHANDRG_SEQ", pkColumnName = "NAME", valueColumnName = "VALUE", pkColumnValue = "USER_FORUM")
    @GeneratedValue(generator = "UserForum_GEN", strategy = GenerationType.TABLE)
    @Column(name = "USER_FORUM_ID")
    private Integer id;
    @Version
    private Integer version;
 
    //information
    @Column(unique=true)
    private String userName;
 
    @Column(length=30, nullable=false)
    private String password;
 
    //join
    //เป็น bidirectional mapping แบบ  many to many ที่ชี้กลับไปที่ table Category ฟิดล์ answers และให้เป็น FetchType.EAGER
    //ตัว ฟิดล์ answers เป็น case sensitive ฝั่งนั้นเป็นยังไง  ฝั่งนี้ต้องพิมพ์แบบนั้น  ตัวเล็กตัวใหญ่มีผลครับ
    @ManyToMany(mappedBy = "answers", fetch = FetchType.EAGER)
    private List<Category> categories;

    ...
    ...
    ...
}

4 ความคิดเห็น: