JPA 不生成外鍵


在用jpa這種orm框架時,有時我們實體對象存在關聯關系,但實際的業務場景可能不需要用jpa來控制數據庫創建數據表之間的關聯約束,這時我們就需要消除掉數據庫表與表之間的外鍵關聯。
但jpa在處理建立外鍵時存在一些問題,在stackoverflow上搜索了相關的jpa創建實體對象關聯關系但不建立外鍵這一系列問題后,發現這個是jpa在處理外鍵時存在一定的bug,官方給出的答復是在hibernate 5.x會解決掉這個問題,但是經驗證5.x的版本這個問題依舊存在。下面給出這個問題的解釋以及這個問題如何解決。

下面會以techer和student對象來舉例,teacher和student存在一對多關系,一個teacher關聯多個student。

1.teacher與student設置外鍵關系

teacher和student之間通過@OneToMany和@ManyToOne建立外鍵關聯關系
teacher:

@Entity
@Table(name = "TEACHER")
public class Teacher extends BaseDomain {
    @Id()
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "ID")
    private Long id;

    @Column
    private String name;

    @OneToMany(mappedBy = "teacher")
    private List<Student> students;
    
    //getter&setter...
}

 


student: 

@Entity
@Table(name = "STUDENT")
public class Student extends BaseDomain {

    @Id()
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "ID")
    private Long id;

    @Column
    private String name;

    @ManyToOne
    @JoinColumn(name = "tid")
    private Teacher teacher;

    //getter&setter...
}

 


數據庫生成表結果: 

CREATE TABLEpublic”.”student” (
“id” int8 DEFAULT nextval(‘student_id_seq’::regclass) NOT NULL,
“name” varchar(255) COLLATE “default”,
“teacher_id” int8,
CONSTRAINT “student_pkey” PRIMARY KEY (“id”),
CONSTRAINT “fk3y5qg5r9ewc48x7ek8lx5ua8h” FOREIGN KEY (“teacher_id”) REFERENCESpublic”.”teacher” > (“id”) ON DELETE NO ACTION ON UPDATE NO ACTION
)
WITH (OIDS=FALSE)
;
ALTER TABLEpublic”.”student” OWNER TO “postgres”;

 

可以看到設置了外鍵”fk3y5qg5r9ewc48x7ek8lx5ua8h” FOREIGN KEY (“teacher_id”)

2.只在student端加上@ForeignKey

student

@Entity
@Table(name = "STUDENT")
public class Student extends BaseDomain {

    @Id()
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "ID")
    private Long id;

    @Column
    private String name;

    @ManyToOne
    @JoinColumn(name = "tid",foreignKey = @ForeignKey(name = "none",value = ConstraintMode.NO_CONSTRAINT))
    private Teacher teacher;

    //setter&getter
}

 


加上該注解之后,根據這個注解說明是可以去掉外鍵關聯關系,但發現加上后然並卵,外鍵還是沒有去掉。這里需要說明其中@ForeignKey的value值由如下代碼所示幾種情況: 

 

/**
 * Used to control the application of a constraint.
 *
 * @since JPA 2.1
 */
public enum ConstraintMode {
    /**
     * Apply the constraint.
     */
    CONSTRAINT,
    /**
     * Do not apply the constraint.
     */
    NO_CONSTRAINT,
    /**
     * Use the provider-defined default behavior.
     */
    PROVIDER_DEFAULT
}

 

 

3.在teacher端加入@org.hibernate.annotations.ForeignKey(name = “none”)

在一的這端加上@org.hibernate.annotations.ForeignKey(name = “none”)這個被jpa廢棄的注解。加上之前在student中設置的@ForeignKey(注意這個是javax.persistence包下的),可以去掉外鍵關聯
teacher:

@Entity
@Table(name = "TEACHER")
public class Teacher extends BaseDomain {
    @Id()
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Column(name = "ID")
    private Long id;

    @Column
    private String name;

    @OneToMany(mappedBy = "teacher")
    @org.hibernate.annotations.ForeignKey(name = "none")
    private List<Student> students;
    
    //getter&setter...
}

 


結果: 

CREATE TABLEpublic”.”student” (
“id” int8 DEFAULT nextval(‘student_id_seq’::regclass) NOT NULL,
“createdtime” timestamp(6),
“name” varchar(255) COLLATE “default”,
“version” int4,
“tid” int8,
CONSTRAINT “student_pkey” PRIMARY KEY (“id”)
)
WITH (OIDS=FALSE)
;
ALTER TABLEpublic”.”student” OWNER TO “postgres”;

 

可以看到student表中原來關聯teacher的外鍵沒了,說明該注解起作用。

4.需要注意的坑

1.如果teacher(1的這端)有student列表(多的這端),像這樣:

@OneToMany(mappedBy = "teacher")
@org.hibernate.annotations.ForeignKey(name = "none")
private List<Student> students;

 


如果要去掉外鍵關聯關系,student端也需要像在2小結提到樣需要加上@JoinColumn(name = “tid”,foreignKey = @ForeignKey(name = “none”,value = ConstraintMode.NO_CONSTRAINT)),但此時你會發現其中@ForeignKey中value的值不管你設置為ConstraintMode.NO_CONSTRAINT還是ConstraintMode.CONSTRAINT,數據庫都不會設置外鍵(這個是特么的真奇怪)。但是就是不管怎樣,你就是不能不設置@ForeignKey,並且你還必須要設置其中的值不能為默認值,不然就是要生成外鍵。這里貼下@ForeignKey的源碼: 

 

@Target({})
@Retention(RUNTIME)
public @interface ForeignKey {
    /**
     * (Optional) The name of the foreign key constraint.  Defaults to a provider-generated name.
     *
     * @return The foreign key name
     */
    String name() default "";

    /**
     * (Optional) The foreign key constraint definition.  Default is provider defined.  If the value of
     * disableForeignKey is true, the provider must not generate a foreign key constraint.
     *
     * @return The foreign key definition
     */
    String foreignKeyDefinition() default "";

    /**
     * (Optional) Used to specify whether a foreign key constraint should be generated when schema generation is in effect.
     */
    ConstraintMode value() default ConstraintMode.CONSTRAINT;
}

 

真的是X了狗了。。。我表示久久不能理解。。。 

2.teacher(1這端)沒有student列表或者student列表被@Transient所修飾,像這樣:

 

@OneToMany(mappedBy = "teacher")
@Transient
private List<Student> students;

 

那么也是無論你在student端設置ConstraintMode的值,都不會設置外鍵.but!!!你就是不能不在student端(多的這端)設置@JoinColumn(name=”tid”,foreignKey=@ForeignKey(name=”none”,value=ConstraintMode.NO_CONSTRAINT)),否則也是會生成外鍵 

總結

所以要使數據表中沒有外鍵關聯關系。
1.當兩邊都有關聯關系字段,1的這端利用@org.hibernate.annotations.ForeignKey(name = “none”),多的那端在JoinColumn中加上foreignKey = @ForeignKey(name = “none”,value = ConstraintMode.NO_CONSTRAINT)

2.當只有多的那端有關聯字段,一的那段沒有關聯字段或者關聯字段被@Transient所修飾,請在多的那端在JoinColumn中加上foreignKey = @ForeignKey(name = “none”,value = ConstraintMode.NO_CONSTRAINT)

最后需要說明的是@org.hibernate.annotations.ForeignKey(name = “none”)這個注解之后可能會在之后的版本會被直接移除掉,所以更新jar包的時候需要注意下。

參考資料:
①.https://hibernate.atlassian.net/browse/HHH-8805
②.https://hibernate.atlassian.net/browse/HHH-8862

 

 

親測可用;

 

第二種辦法:

 

 

  關於如何禁用Hibernate生成外鍵,網上有使用設置ForeignKey(name="null")方式,使Hibernate不生成外鍵關聯,但是需要在每個關聯關系上設置,比較繁瑣,很難統一控制保證數據庫中不存在外鍵關聯。而且經測試在@JoinColumn設置foreignkey=@ForeignKey(name="null")不會生成外鍵,在@JoinTable中此種設置方式還是可以生成外鍵。 

       下面提供一種禁用Hibernate外鍵的方式,在創建數據庫表時不生成外鍵關聯,但是個人感覺還不是最好的解決方案,希望多多指教。

       思路:因為Hibernate為了處理不同數據庫SQL的差異,為每個數據庫定義了dialect,在執行SQL時會由dialect類的方法中獲取相應的SQL,所以可以通過重寫dialect類中生成外鍵SQL的方法不生成數據庫外鍵關聯。分別重寫的Postgresql數據庫和Oracle數據庫的Dialect類如下:

Java代碼   收藏代碼
import org.hibernate.dialect.PostgreSQL9Dialect;  
/** 
 * 不生成外鍵,通過類似於SQL注入的方法,為每個數據庫修改創建外鍵的SQL 
 */  
public class PostgreSQL9DialectWithoutFK extends PostgreSQL9Dialect {  
    @Override  
    public String getAddForeignKeyConstraintString(  
            String constraintName,  
            String[] foreignKey,  
            String referencedTable,  
            String[] primaryKey,  
            boolean referencesPrimaryKey) {  
//      設置foreignkey對應的列值可以為空  
        return " alter "+ foreignKey[0] +" set default null " ;  
    }  
}  

 

Java代碼  
import org.hibernate.dialect.Oracle10gDialect;  
/** 
 * 不生成外鍵,通過類似於SQL注入的方法,為每個數據庫修改創建外鍵的SQL 
 */  
public class Oracle10gDialectWithoutFK extends Oracle10gDialect {  
    @Override  
    public String getAddForeignKeyConstraintString(  
            String constraintName,  
            String[] foreignKey,  
            String referencedTable,  
            String[] primaryKey,  
            boolean referencesPrimaryKey) {  
//      通過修改外鍵列的默認值,而不是添加外鍵,避免生成外鍵  
        return " modify "+ foreignKey[0] +" default null " ;  
    }  
}  

 

       在重寫生成外鍵SQL時考慮過使用SQL注入的方式在創建完外鍵后,再刪除外鍵,但是這種方式比較復雜,做了一點后就放棄了。

 

       創建了重寫的Dialect類,通過hibernate.dialect=Oracle10gDialectWithoutFK配置后,在生成數據庫表時外鍵策略就會生效。

      另:

  • 關於是否在數據庫中生成外鍵的討論如下圖,具體的討論內容可以按關鍵字搜索相關內容。

    • JPA中@JoinColumn不生成外鍵的配置
    • postgresql中alter語法如下


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM