1. 程式人生 > >MySQL主鍵自動生成和生成器表以及JPA主鍵對映

MySQL主鍵自動生成和生成器表以及JPA主鍵對映

MySQL主鍵自動生成

表設計

MySQL有許多主鍵生成策略,其中很常見的一種是自動生成。一般情況下,主鍵型別是BIGINT UNSIGNED,自動生成主鍵的關鍵詞是AUTO_INCREMENT。
CREATE TABLE Stock (
	id BIGINT UNSIGNED NOT NULL PRIMARY KEY AUTO_INCREMENT,
    NO VARCHAR(255) NOT NULL,
    name VARCHAR(255) NOT NULL,
    price DECIMAL(6,2) NOT NULL,
	UNIQUE KEY Stock_NO (NO),
    INDEX Stock_Name(name)
) ENGINE = InnoDB;

JPA主鍵對映

@Entity
@Table(name = "Stock", uniqueConstraints = {
        @UniqueConstraint(name = "Stock_NO", columnNames = { "NO" })
},
indexes = {
        @Index(name = "Stock_Name", columnList = "name")
})
public class Stock {
	private long id;
    private String no;
    private String name;
    private double price;
    
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
	public long getId() {
		return id;
	}
	public void setId(long id) {
		this.id = id;
	}
 @GeneratedValue(strategy = GenerationType.IDENTITY):實體主鍵生成策略是自動生成,相容MySQL主鍵自動生成策略,關鍵詞是AUTO_INCREMENT。若是MySQL主鍵沒有指定AUTO_INCREMENT,報出以下異常。
javax.persistence.PersistenceException: org.hibernate.exception.GenericJDBCException: could not execute statement

Caused by: org.hibernate.exception.GenericJDBCException: could not execute statement

Caused by: java.sql.SQLException: Field 'id' doesn't have a default value
uniqueConstraints = {
        @UniqueConstraint(name = "Stock_NO", columnNames = { "NO" })
},
indexes = {
        @Index(name = "Stock_Name", columnList = "name")
}:建立唯一性索引,索引名字是Stock_NO,針對列是NO,建立索引,索引名字是Stock_Name,針對列是name。只有啟動了模式生成,索引生成的配置才能生效。啟用模式生成,在配置檔案persistence.xml做如下配置:
<properties>
            <property name="javax.persistence.schema-generation.database.action"
                      value="drop-and-create" />
        </properties>
模式生成雖然很方便,能自動生成表結構,但是,由它生成的表結構不總是最佳的,而且還不能保證是正確的。因此,作為最佳實踐,不建議在生產環境啟用模式生成,手工維護表機構。禁用模式生成,在配置檔案persistence.xml做如下配置:
 <properties>
            <property name="javax.persistence.schema-generation.database.action"
                      value="none" />
        </properties>

生成器表

主鍵的生成策略是生成器表,這種策略不常見,一般用於遺留資料庫使用JPA。否則的話,主鍵的生成策略一般會選擇自動生成(GenerationType.IDENTITY)或是序列生成(GenerationType.SEQUENCE)。往目標表插入一條資料之間,JPA實現者從生成器表選擇一條關於目標表的主鍵記錄,該記錄儲存目標表的主鍵。JPA實現者增大該主鍵值,然後把該主鍵增大之前的那個值插入目標表。

MySQL生成器表

CREATE TABLE CreatorKey (
  TableName VARCHAR(64) NOT NULL PRIMARY KEY,
  KeyValue BIGINT UNSIGNED NOT NULL,
  INDEX CreatorKey_Table_Values (TableName, KeyValue)
) ENGINE = InnoDB;

MySQL目標表

CREATE TABLE Student (
  id BIGINT UNSIGNED NOT NULL PRIMARY KEY,
  name VARCHAR(100) NOT NULL,
  INDEX Student_name (name)
) ENGINE = InnoDB;

JPA主鍵對映

@Entity
@Table
public class Student {
	private long id;
	private String name;

	@Id
    @GeneratedValue(strategy = GenerationType.TABLE,
            generator = "studentGenerator")
    @TableGenerator(name = "studentGenerator", table = "creatorkey",
            pkColumnName = "TableName", pkColumnValue = "Publishers",
            valueColumnName = "KeyValue", initialValue = 1,
            allocationSize = 1)
    @Column(name = "studentId")
	public long getId() {
		return id;
	}
name :主鍵生成策略定義的名字;table:生成器表在資料庫中的名字;pkColumnName:生成器表的主鍵列的名字;pkColumnValue:生成器表主鍵列的值;valueColumnName:生成器表值列的名字;initialValue:生成器表初始值;allocationSize:生成器表數值遞增或遞減幅度。generator:主鍵生成策略定義的名字,該屬性與name屬性保持一致;generator = "studentGenerator":使用生成器表的主鍵生成策略。

主鍵@TableGenerator的屬性initialValue,allocationSize

根據原始碼,註解@TableGenerator的屬性initialValue、allocationSize是可選的,並且預設值分別是0、50。
  /** 
     * (Optional) The initial value to be used to initialize the column
     * that stores the last value generated.
     */
    int initialValue() default 0;

    /**
     * (Optional) The amount to increment by when allocating id 
     * numbers from the generator.
     */
    int allocationSize() default 50;
但是,根據實際測試結果,情況並非如源程式碼表示的那樣。清空生成器表creatorkey、目標表student,去掉屬性initialValue、allocationSize,執行持久化操作。
 Student student = new Student();
            student.setName("張三");
            manager.persist(student);
得到的結果卻是這樣的。                                                      從上圖可以看出生成器表初始值並非為0,遞增或遞減幅度並非為50。而且每次重啟web server後,初始值和遞增幅度都是不確定的。更重要的是,主鍵的生成已經和生成器表失去了聯絡,KeyValue一致停留在某個值,不會變化。因此,建議在寫註解@TableGenerator時,雖然屬性 initialValue、 allocationSize是可選的,但要明確為這兩個屬性指定數值。令人驚訝的是,即使是明確為這兩個屬性指定數值,很多時候,也會出現上述的問題。經測試,把initialValue、 allocationSize都設定為1時,執行正常。

主鍵@TableGenerator的範圍

根據原始碼,在同一個持久化單元內,@TableGenerator的主鍵生成策略定義是全域性的,可以被其他實體引用。
/**
 * Defines a primary key generator that may be 
 * referenced by name when a generator element is specified for 
 * the {@link GeneratedValue} annotation. A table generator 
 * may be specified on the entity class or on the primary key 
 * field or property. The scope of the generator name is global 
 * to the persistence unit (across all generator types).

但是,根據實際測試結果,情況並非如源程式碼表示的那樣。即使在同一持久化單元內,@TableGenerator的主鍵生成策略定義只對定義它的實體生效。
@Entity
@Table
public class Book implements Serializable
{
    private long id;
    @Id
    @GeneratedValue(strategy = GenerationType.TABLE,
            generator = "studentGenerator")
    public long getId()
    {
        return this.id;
    }

    public void setId(long id)
    {
        this.id = id;
    }
啟動web server,報出如下異常。
javax.persistence.PersistenceException: [PersistenceUnit: EntityMappings] Unable to build Hibernate SessionFactory
Caused by: org.hibernate.AnnotationException: Unknown Id.generator: studentGenerator
org.hibernate.AnnotationException: Unknown Id.generator: studentGenerator
若在實體Book加上對主鍵生成策略的定義,就執行正常。
@Entity
@Table
public class Book implements Serializable
{
    private long id;

    @Id
    @GeneratedValue(strategy = GenerationType.TABLE,
            generator = "studentGenerator")
    @TableGenerator(name = "studentGenerator", table = "creatorkey",
            pkColumnName = "TableName", pkColumnValue = "Publishers",
            valueColumnName = "KeyValue", initialValue = 1, allocationSize=1)
    public long getId()
    {
        return this.id;
    }

    public void setId(long id)
    {
        this.id = id;
    }