low level programmer

« 好文推薦 - EJB 3.0 Based... | Main | 好文推薦 - How to Create... »
星期四 六月 05, 2008

EJB3 - JPA - ORM

description

Hibernate大家都用很久了, 各種mapping方式也都瞭若指掌.
不過一方面覺得Hibernate畢竟只是provider, 不太想把Hibernate當成standard用, 雖然他已經這麼強.
一方面也覺得orm太豐富了, 所以常常怎麼試都work.
卻不知道有時候work的結果可能是provider cover了, 有時候是瞎貓碰到死耗子.
記得Hibernate也有說, orm這種東西效能不會差, 但前提是要你用對.
因此能知道到底正確的使用方式是什麼是重要的.
正確的使用 + 不想看Hibernate的說明書, 我只要standard. 所以很多餘的紀錄一下自己看書測試的結果.
orm實在太多了, 已經試很久, 尤其是遇到@SecondaryTable的問題時花更多時間, 平常又要上班.
希望這個對誰有幫助, 主要是對我自己有幫助啦. :-)

reference

EJB3 in Action - CH8 - Object-relational mapping
javax.persistence (Java EE 5)

annotations

  • @Table
    1. 用來設定主table的資訊, 如果要設定額外table可用@SecondaryTable或@SecondaryTables.
    2. 如果沒有@Table會mapping到預設值. (此預設值在書上說: If it's omitted, the entity is assumed to be mapped to a table in the default schema with the same name as the entity class)
    3. 如果有@Table沒有設name屬性, 則table name會設成entity name.
    4. uniqueConstraints用來設定唯一特性的欄位, 此欄位的設定在自動ddl建立table的時候才生效.
  • @Column
    1. 指定persistent property 或 field mapping到的column name.
    2. 如果沒設@Column, 會mapping到預設值, 就是mapping到table上符合property name的欄位.
    3. 當一個欄位是透過@SecondaryTable mapping到其他table時要指定table屬性
    4. 如果insertable/updatable設為false, 則insert/update statement執行時就不會包含這個column, 這屬性可用來處理read only column.
    5. columnDefinition用來指定建立此column時的SQL, 比方說byte[]型態的@Lob會建BLOB, 但是透過columnDefinition可建立CLOB, 如下所示.
      @Column(name = "LOB_XML", columnDefinition="clob")
      @Lob
      public byte[] getSecondaryTable() {
          return secondaryTable;
      }                
                  
  • @Enumerated
    1. 指定一個persistent field被存為enumerated type.
    2. value屬性為EnumType, 有ORDINAL(預設值)和STRING. ORDINAL存enum type在該enum存放的index(從0開始算), STRING就是存enum的字串.
  • @Lob
    1. 如果@Lob放 char[]或String, 對應成CLOB. 若是byte[], 就對應BLOB.
    2. 可搭配@Basic用lazy的方式存取, 不過LOB的lazy存取在EJB3 spec中是optional的, 所以vendor不一定支援LOB的lazy存取.
  • @Temporal
    1. 用來指定型態為java.util.Date, java.util.Calendar的mapping.
    2. value屬性值為TemporalType, 有DATE(mapping到java.sql.Date. storing day, month, year), TIME(mapping到java.sql.Time, storing time and not day, month, year), TIMESTAMP(預設值, mapping到java.sql.Timestamp, storing time, day, month and year).
    3. 如果型態本來就是java.sql.Date, java.sql.Time, java.sql.Timestamp, 就不用@Temporal
  • @SecondaryTable
    1. 指定一個entity中屬性mapping到的第二個table.
    2. 如果沒有@SecondaryTable, 則假設所有的entity屬性都屬於同一個table.
    3. 如果pkJoinColumns沒設定則假設join column都reference到主table的PK, 且有相同的型態.
    4. 如果開啟自動產生schema的功能, Hibernate上有個產生錯誤的方式.
      1. 建立主table, 裡面放一個@OneToOne與一個@Column mapping到@SecondaryTable設定的table.
      2. 主entity程式如下
        @Entity
        @Table(name="MY_USER")
        @SecondaryTable(name="USER_PICTURES", pkJoinColumns=@PrimaryKeyJoinColumn(name="USER_ID"))
        public class MyUser implements Serializable {
            private static final long serialVersionUID = 1L;
            private Long id;
            private BillingInfo billingInfo;
            private byte[] picture;
            
            public void setId(Long id) {
                this.id = id;
            }
        
            @Id
            @Column(name="USER_ID")
            @GeneratedValue(strategy = GenerationType.AUTO)
            public Long getId() {
                return id;
            }
        
            @Column(name="PICTURE", table="USER_PICTURES")
            @Lob
            @Basic(fetch=FetchType.LAZY)
            public byte[] getPicture() {
                return picture;
            }
        
            public void setPicture(byte[] picture) {
                this.picture = picture;
            }
        
            @OneToOne(cascade=CascadeType.ALL)
            @JoinColumn(name="USER_BILLING_ID", referencedColumnName="BILLING_ID")
            public BillingInfo getBillingInfo() {
                return billingInfo;
            }
        
            public void setBillingInfo(BillingInfo billingInfo) {
                this.billingInfo = billingInfo;
            }
        
        }                        
                            
      3. 如此在自動建立schema的時候schema在建立table USER_PICTURES時會用以下的SQL
        create table USER_PICTURES (PICTURE blob, USER_ID number(19,0) not null, primary key (USER_BILLING_ID))
                            
        這段SQL錯誤的地方很明顯, 就在指定的PK不是USER_PICTURES的PK, 而是@OneToOne的FK. 去看source, 好像是在準備SQL statement的時候, 準備到SecondaryTable時, 跑去取org.hibernate.mapping.Constraint的columns, 應該存在的是SecondaryTable的PK, 取到的卻是OneToOne的column.
        不過這沒什麼大不了的, 只要自己建一個對的table他就會mapping正確. 只是我在這地方繞了很久, 希望別太多人像我一樣笨還在那試, 書上本來就說不鼓勵使用自動建立schema的功能了.
  • @GeneratedValue
    1. 和@Id搭配, 提供自動建立PK value的功能
    2. strategy值為GenerationType, 有三種值: AUTO, IDENTITY, SEQUENCE, TABLE
      1. AUTO: 由provider幫你決定最合適的strategy
      2. IDENTITY: 適用於MS-Sql, 因為我用的是Oracle所以用這種的會出exception說我的dialet不支援IDENTITY
      3. SEQUENCE: 用database sequence column
        1. 先用@SequenceGenerator建立一個generator, 再設定@GeneratedValue的generator屬性指定要用哪個generator.
          @Id
          @Column(name="USER_ID")
          @SequenceGenerator(
              name="USER_SEQUENCE_GENERATOR", sequenceName="USER_SEQUENCE", 
              initialValue=1, allocationSize=1
          )
          @GeneratedValue(strategy = GenerationType.SEQUENCE, generator="USER_SEQUENCE_GENERATOR")
          public Long getId() {
              return id;
          }                                
                                      
          這樣就會建立一個sequence.
          create sequence USER_SEQUENCE

          initialValue是出始值, allocationSize是每次增加的值.
        2. 如果沒用@SequenceGenerator, Hibernate會下這個SQL當作generator
          create sequence hibernate_sequence
        3. 注意sequence name在persistece module中是共用的, 所以使用上要小心sequence name的唯一性
      4. TABLE: 用database table存放PK value
        1. 先用@TableGenerator建立generator, 再設定generator指定generator名稱.
          @Id
          @Column(name="USER_ID")
          @TableGenerator(
              name="USER_TABLE_GENERATOR", table="SEQUENCE_GENERATOR_TABLE", 
              pkColumnName="SEQUENCE_NAME", valueColumnName="SEQUENCE_VALUE", 
              pkColumnValue="USER_SEQUENCE", initialValue=1, allocationSize=100
          )
          @GeneratedValue(strategy = GenerationType.TABLE, generator="USER_TABLE_GENERATOR")
          public Long getId() {
              return id;
          }                                
                                      
          這樣就會建一個table來放PK value
          create table SEQUENCE_GENERATOR_TABLE ( 
              SEQUENCE_NAME varchar2(255),  SEQUENCE_VALUE number(10,0) 
          ) 
                                      
          insert into SEQUENCE_GENERATOR_TABLE (SEQUENCE_NAME, SEQUENCE_VALUE) 
          VALUES ('USER_SEQUENCE', 1)                                
                                      
        2. 這方式的好處是一個table可以存放很多個entity的PK值
  • @Embedded
    1. 把一個@Embeddable的class放在entity中加上@Embedded, 就能用到@Embeddable class的屬性.
    2. @Embedded預設是mapping到和所屬entity同一個table, 但也可用@SecondaryTable mapping到不同table, 不過會出現前面提到的錯誤.
    3. @Embedded預設的做法如下
      @Embeddable
      public class EmbeddedEntity implements Serializable, RegistedEntity {
      
          private static final long serialVersionUID = 1L;
      
          private Calendar embeddedTime;
      
          @Column(name="EMBEDDED_TIME")
          @Temporal(TemporalType.TIMESTAMP)
          public Calendar getEmbeddedTime() {
              return embeddedTime;
          }
      
          public void setEmbeddedTime(Calendar embeddedTime) {
              this.embeddedTime = embeddedTime;
          }
          
      }
          
      @Entity
      @Table(name = "ONE")
      public class OneTable implements Serializable {
          ...
          @Embedded
          public EmbeddedEntity getEmbeddedEntity() {
              return embeddedEntity;
          }
          ...
      }
                  
    4. 如果想改寫@Embeddable class mapping到的column可用@AttributeOverrides改寫
      @Embedded
      @AttributeOverrides(@AttributeOverride(
          name = "embeddedTime", 
          column = @Column(name = "DIFF_EMBEDDED_TIME"))
      )
      public EmbeddedEntity getDifferentColumnEmbeddedEntity() {
          return differentColumnEmbeddedEntity;
      }                
                  
  • @JoinColumn
    1. 指定用來join table的column
    2. 指定column name的使用方式如下
      @OneToOne(cascade=CascadeType.ALL)
      @JoinColumn(name="ANOTHER_ONE", referencedColumnName="ID")
      public AnotherOneTable getAnotherOne() {
          return anotherOne;
      }                
                  
      這樣在主entity會有個ANOTHER_ONE欄位, mapping到AnotherOneTable這個entity的ID欄位.
    3. 如果沒指定column name, 產生的column name會以一種方式呈現
      < relationship field/property name >_< name of referenced primary key column >
                  
      如以下範例
      @OneToOne(cascade=CascadeType.ALL)
      @JoinColumn
      public AnotherOneTable getAnotherOne() {
          return anotherOne;
      }
                  
      這樣就會在主table建立一個column: anotherOne_id mapping到AnotherOneTable這個entity的id欄位.
    4. 如果是雙向關係的@OneToOne, 在AnotherOneTable那邊就會設定@OneToOne(mappedBy = "anotherOne"),
      mappedBy這個屬性用在owning side的另一端, 所謂的owning side就是擁有關係的那個table.
      例如 ONE table中有個欄位 ANOTHER_ONE mapping到ANOTHER_ONE_TABLE, 則ONE擁有與ANOTHER_ONE_TABLE的關係, 所以就是owning side.
      所以ANOTHER_ONE_TABLE這邊的entity就要設定mappedBy, 而mappedBy的值就是owning side對備擁有的entity設定的變數名稱.
    5. 注意無法在@OneToOne關係的兩邊都用@JoinColumn
  • @PrimaryKeyJoinColumn
    1. 指定一個用來join其他table的column.
    2. 可用在JOIN的繼承strategy
      @Entity
      @Table(name = "PARENT")
      @Inheritance(strategy = InheritanceType.JOINED)
      @DiscriminatorColumn(discriminatorType=DiscriminatorType.STRING, name="CHILD_TYPE", length=1)
      public class Parent implements Serializable {
          ...
      }
      
      @Entity
      @Table(name="GOOD_CHILD")
      @DiscriminatorValue(value="G")
      @PrimaryKeyJoinColumn(name="ID")
      public class GoodChild extends Parent implements java.io.Serializable {
          ...
      }
                  
    3. @SecondaryTable的join PK
      @Entity
      @Table(name="USERS")
      @SecondaryTable(name="USER_PICTURES", pkJoinColumns=@PrimaryKeyJoinColumn(name="USER_ID"))
      public class MyUser implements Serializable {
          private static final long serialVersionUID = 1L;
          private Long id;
          private byte[] picture;
          
          public void setId(Long id) {
              this.id = id;
          }
      
          @Id
          @Column(name="USER_ID")
          @GeneratedValue(strategy = GenerationType.AUTO)
          public Long getId() {
              return id;
          }
      
          @Column(name="PICTURE", table="USER_PICTURES")
          @Lob
          @Basic(fetch=FetchType.LAZY)
          public byte[] getPicture() {
              return picture;
          }
      
          public void setPicture(byte[] picture) {
              this.picture = picture;
          }
      
      }                
                  
    4. @OneToOne的reference key
      @Entity
      @Table(name = "EMPLOYEE")
      public class Employee implements Serializable {
      
          private static final long serialVersionUID = 1L;
          private Long id;
          private String name;
          private Address address;
      
          public void setId(Long id) {
              this.id = id;
          }
      
          @Id
          @Column(name = "EMPLOYEE_ID")
          @GeneratedValue(strategy = GenerationType.AUTO)
          public Long getId() {
              return id;
          }
      
          @OneToOne(cascade=CascadeType.ALL)
          @PrimaryKeyJoinColumn // 如果PK和referencedColumn名稱一樣就不用指定名稱
          public Address getAddress() {
              return address;
          }
      
          public void setAddress(Address address) {
              this.address = address;
          }
      
          @Column(name="NAME")
          public String getName() {
              return name;
          }
      
          public void setName(String name) {
              this.name = name;
          }
      }
      
      @Entity
      @Table(name = "ADDRESS")
      public class Address implements Serializable {
      
          private static final long serialVersionUID = 1L;
          private Long employeeId;
          private String street;
      
          @Id
          @Column(name="EMPLOYEE_ID")
          public Long getEmployeeId() {
              return employeeId;
          }
      
          public void setEmployeeId(Long employeeId) {
              this.employeeId = employeeId;
          }
      
          @Column(name="STREET")
          public String getStreet() {
              return street;
          }
      
          public void setStreet(String street) {
              this.street = street;
          }
      
      }
                  
      這樣用hibernate自動建立的schema中address會少了FK constraint, 所以我又自己加了一個constraint
      alter table address add constraint FK13246123456 foreign key (employee_id) references employee
      這段書上的說明不多, 不曉得我加了FK constraint之後是不是書上說的@PrimaryKeyJoinColumn使用模式
  • @OneToMany
    @Entity
    public class Programmer implements Serializable {
        private static final long serialVersionUID = 1L;
        private Long id;
        private String name;
        private Set< Certification> certs = new HashSet();
        
        public void setId(Long id) {
            this.id = id;
        }
    
        @Id
        @GeneratedValue(strategy = GenerationType.AUTO)
        public Long getId() {
            return id;
        }
    
        @OneToMany(cascade=CascadeType.ALL, mappedBy="owner")
        public Set< Certification> getCerts() {
            return certs;
        }
    
        public void setCerts(Set< Certification> certs) {
            this.certs = certs;
        }
    
        @Column(name="NAME")
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
    }
    
    @Entity
    public class Certification implements Serializable {
        private static final long serialVersionUID = 1L;
        private Long id;
        private String certName;
        private Programmer owner;
        
        public void setId(Long id) {
            this.id = id;
        }
    
        @Id
        @GeneratedValue(strategy = GenerationType.AUTO)
        public Long getId() {
            return id;
        }
    
        @Column(name="CERT_NAME")
        public String getCertName() {
            return certName;
        }
    
        public void setCertName(String certName) {
            this.certName = certName;
        }
    
        @ManyToOne
        @JoinColumn(name="PROGRAMMER_ID")
        public Programmer getOwner() {
            return owner;
        }
    
        public void setOwner(Programmer owner) {
            this.owner = owner;
        }
    
    }
        
    1. 不能@OneToMany和@ManyToOne都用@JoinColumn, 會有deployment-time error.
    2. 如果沒設定mappedBy就會變unidirectional, 在這的意義就是Programmer.getCerts()會回傳空值
    3. 建議使用雙向, 就是設定mappedBy, 避免要維護兩邊entity.
    4. @OneToMany和@ManyToOne還可以指到自己
      @Entity
      @Table(name="COMPONENT")
      public class Component implements Serializable {
          private static final long serialVersionUID = 1L;
          private Long id;
          private Component parent;
          private List< Component> children = new ArrayList< Component>();
          
          public void setId(Long id) {
              this.id = id;
          }
      
          @Id
          @Column(name="COMPONENT_ID")
          @GeneratedValue(strategy = GenerationType.AUTO)
          public Long getId() {
              return id;
          }
      
          @ManyToOne
          @JoinColumn(name="PARENT_COMPONENT_ID", referencedColumnName="COMPONENT_ID")
          public Component getParent() {
              return parent;
          }
      
          public void setParent(Component parent) {
              this.parent = parent;
          }
      
          @OneToMany(mappedBy="parent", cascade=CascadeType.ALL)
          public List< Component> getChildren() {
              return children;
          }
      
          public void setChildren(List< Component> children) {
              this.children = children;
          }
      
      }                
                  
  • ManyToMany的關係: 使用@JoinTable. @ManyToMany哪邊是owning side都可以, 所以哪邊設mappedBy都可以
    @Entity
    @Table(name="SALES")
    public class Sales implements Serializable {
    
        private static final long serialVersionUID = 1L;
        private Long id;
        private String name;
        private List< Product> products = new ArrayList< Product>();
    
        public void setId(Long id) {
            this.id = id;
        }
    
        @Id
        @Column(name = "SALES_ID")
        @GeneratedValue(strategy = GenerationType.AUTO)
        public Long getId() {
            return id;
        }
    
        @Column(name = "NAME")
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        // 沒設定mappedBy就會是unidirectional
        @ManyToMany(mappedBy="sales", cascade=CascadeType.ALL)
        public List< Product> getProducts() {
            return products;
        }
    
        public void setProducts(List< Product> products) {
            this.products = products;
        }
    }
    
    @Entity
    @Table(name = "PRODUCT")
    public class Product implements Serializable {
    
        private static final long serialVersionUID = 1L;
        private Long id;
        private String name;
        private List< Sales> sales = new ArrayList< Sales>();
    
        public void setId(Long id) {
            this.id = id;
        }
    
        @Id
        @Column(name = "PRODUCT_ID")
        @GeneratedValue(strategy = GenerationType.AUTO)
        public Long getId() {
            return id;
        }
    
        @Column(name = "NAME")
        public String getName() {
            return name;
        }
    
        public void setName(String name) {
            this.name = name;
        }
    
        @ManyToMany(cascade=CascadeType.ALL)
        @JoinTable(name = "PRODUCT_SALES",  // PRODUCT_SALES是中間的table
            // name是中間table的column, 若和現有column同名就不用referencedColumnName
            joinColumns = @JoinColumn(name = "PRODUCT_ID"), 
            inverseJoinColumns = @JoinColumn(name = "SALES_ID")
        )
        public List< Sales> getSales() {
            return sales;
        }
    
        public void setSales(List< Sales> sales) {
            this.sales = sales;
        }
    }
        
  • 繼承: @Inheritance(strategy=InheritanceType.SINGLE_TABLE)
    1. 注意EMPLOYEE_TYPE的@Column要設定成insertable=false, updatable=false. 因為要persist的時候不用設定type就會JPA就會自己判斷DiscriminatorColumn要存什麼進去. 沒設定insertable, updatable反而�����被告知有重複的column存在.
    2. SINGLE_TABLE是EJB3的預設作法, 優點是最快, 缺點是subclass的column不能nullable, 同時SINGLE_TABLE也會產生很多null值, 而且一旦改屬性就要改整個schema.
    3. public enum EmployeeType {
          P, // Programmer
          S  // Sales
      }
      
      @Entity
      @Table(name = "EMPLOYEE")
      @Inheritance(strategy = InheritanceType.SINGLE_TABLE)
      @DiscriminatorColumn(name="EMPLOYEE_TYPE", length=1, discriminatorType=DiscriminatorType.STRING)
      public abstract class Employee implements Serializable {
      
          private static final long serialVersionUID = 1L;
          private Long id;
          private String name;
          private EmployeeType employeeType;
      
          public void setId(Long id) {
              this.id = id;
          }
      
          @Id
          @Column(name = "EMPLOYEE_ID")
          @GeneratedValue(strategy = GenerationType.AUTO)
          public Long getId() {
              return id;
          }
      
          @Column(name="NAME")
          public String getName() {
              return name;
          }
      
          public void setName(String name) {
              this.name = name;
          }
      
          @Column(name="EMPLOYEE_TYPE", insertable=false, updatable=false)
          @Enumerated
          public EmployeeType getEmployeeType() {
              return employeeType;
          }
      
          public void setEmployeeType(EmployeeType employeeType) {
              this.employeeType = employeeType;
          }
      }
      
      @Entity
      @DiscriminatorValue(value="P")
      public class Programmer extends Employee implements Serializable {
          private static final long serialVersionUID = 1L;
          private String lang;
      
          @Column(name="LANG")
          public String getLang() {
              return lang;
          }
      
          public void setLang(String lang) {
              this.lang = lang;
          }
      
      }
      
      @Entity
      @DiscriminatorValue(value="S")
      public class Sales extends Employee implements Serializable {
          private static final long serialVersionUID = 1L;
          private String product;
      
          @Column(name="PRODUCT")
          public String getProduct() {
              return product;
          }
      
          public void setProduct(String product) {
              this.product = product;
          }
      
      }
              
  • 繼承: @Inheritance(strategy=InheritanceType.JOINED)
    1. InheritanceType.JOINED表示subclass mapping到分開的table, 與super class透過JOIN的方式關聯
    2. JOINED是最好的設計方式, 透過index的設定也可提升效能.
    3. superclass與subclass的one-to-one關聯透過@PrimaryKeyJoinColumn設定
    4. JOINED的方式和SINGLE_TABLE不一樣的是需要設定EmployeeType
    5. @Entity
      @Table(name = "EMPLOYEE")
      @Inheritance(strategy = InheritanceType.JOINED)
      @DiscriminatorColumn(name="EMPLOYEE_TYPE", length=1, discriminatorType=DiscriminatorType.STRING)
      public abstract class Employee implements Serializable {
      
          private static final long serialVersionUID = 1L;
          private Long id;
          private String name;
          private EmployeeType employeeType;
      
          public void setId(Long id) {
              this.id = id;
          }
      
          @Id
          @Column(name = "EMPLOYEE_ID")
          @GeneratedValue(strategy = GenerationType.AUTO)
          public Long getId() {
              return id;
          }
      
          @Column(name="NAME")
          public String getName() {
              return name;
          }
      
          public void setName(String name) {
              this.name = name;
          }
      
          @Column(name="EMPLOYEE_TYPE")
          @Enumerated(EnumType.STRING)
          public EmployeeType getEmployeeType() {
              return employeeType;
          }
      
          public void setEmployeeType(EmployeeType employeeType) {
              this.employeeType = employeeType;
          }
      }
      
      @Entity
      @Table(name="PROGRAMMER")
      @DiscriminatorValue(value="P")
      @PrimaryKeyJoinColumn(name="EMPLOYEE_ID")
      public class Programmer extends Employee implements Serializable {
          private static final long serialVersionUID = 1L;
          private String lang;
      
          @Column(name="LANG")
          public String getLang() {
              return lang;
          }
      
          public void setLang(String lang) {
              this.lang = lang;
          }
      
      }
      
      @Entity
      @Table(name="SALES")
      @DiscriminatorValue(value="S")
      @PrimaryKeyJoinColumn(name="EMPLOYEE_ID")
      public class Sales extends Employee implements Serializable {
          private static final long serialVersionUID = 1L;
          private String product;
      
          @Column(name="PRODUCT")
          public String getProduct() {
              return product;
          }
      
          public void setProduct(String product) {
              this.product = product;
          }
      
      }
              
  • @Inheritance(strategy=InheritanceType.TABLE_PER_CLASS)
    1. 這種方式最糟糕, provider也最難實作, EJB3又沒當成必須實作的部份, 所以不建議使用.
    2. 糟糕的地方在他會重複存值, 比方說Employee有name, Programmer也會在table中存name, 導致重複的資料出線.
  • 迴響:

    發表迴響:
    • HTML 語法: 關閉