在用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:

  1. @Entity
  2. @Table(name = "TEACHER")
  3. public class Teacher extends BaseDomain {
  4. @Id()
  5. @GeneratedValue(strategy = GenerationType.IDENTITY)
  6. @Column(name = "ID")
  7. private Long id;
  8.  
  9. @Column
  10. private String name;
  11.  
  12. @OneToMany(mappedBy = "teacher")
  13. private List<Student> students;
  14.  
  15. //getter&setter...
  16. }

student:

  1. @Entity
  2. @Table(name = "STUDENT")
  3. public class Student extends BaseDomain {
  4.  
  5. @Id()
  6. @GeneratedValue(strategy = GenerationType.IDENTITY)
  7. @Column(name = "ID")
  8. private Long id;
  9.  
  10. @Column
  11. private String name;
  12.  
  13. @ManyToOne
  14. @JoinColumn(name = "tid")
  15. private Teacher teacher;
  16.  
  17. //getter&setter...
  18. }

数据库生成表结果:

  1. CREATE TABLE public”.”student (
  2. id int8 DEFAULT nextval(‘student_id_seq’::regclass) NOT NULL,
  3. name varchar(255) COLLATE default”,
  4. teacher_id int8,
  5. CONSTRAINT student_pkey PRIMARY KEY (“id”),
  6. CONSTRAINT fk3y5qg5r9ewc48x7ek8lx5ua8h FOREIGN KEY (“teacher_id”) REFERENCES public”.”teacher > (“id”) ON DELETE NO ACTION ON UPDATE NO ACTION
  7. )
  8. WITH (OIDS=FALSE)
  9. ;
  10. ALTER TABLE public”.”student OWNER TO postgres”;

可以看到设置了外键”fk3y5qg5r9ewc48x7ek8lx5ua8h” FOREIGN KEY (“teacher_id”)

2.只在student端加上@ForeignKey

student

  1. @Entity
  2. @Table(name = "STUDENT")
  3. public class Student extends BaseDomain {
  4.  
  5. @Id()
  6. @GeneratedValue(strategy = GenerationType.IDENTITY)
  7. @Column(name = "ID")
  8. private Long id;
  9.  
  10. @Column
  11. private String name;
  12.  
  13. @ManyToOne
  14. @JoinColumn(name = "tid",foreignKey = @ForeignKey(name = "none",value = ConstraintMode.NO_CONSTRAINT))
  15. private Teacher teacher;
  16.  
  17. //setter&getter
  18. }

加上该注解之后,根据这个注解说明是可以去掉外键关联关系,但发现加上后然并卵,外键还是没有去掉。这里需要说明其中@ForeignKey的value值由如下代码所示几种情况:

  1. /**
  2. * Used to control the application of a constraint.
  3. *
  4. * @since JPA 2.1
  5. */
  6. public enum ConstraintMode {
  7. /**
  8. * Apply the constraint.
  9. */
  10. CONSTRAINT,
  11. /**
  12. * Do not apply the constraint.
  13. */
  14. NO_CONSTRAINT,
  15. /**
  16. * Use the provider-defined default behavior.
  17. */
  18. PROVIDER_DEFAULT
  19. }

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

在一的这端加上@org.hibernate.annotations.ForeignKey(name = “none”)这个被jpa废弃的注解。加上之前在student中设置的@ForeignKey(注意这个是javax.persistence包下的),可以去掉外键关联
teacher:

  1. @Entity
  2. @Table(name = "TEACHER")
  3. public class Teacher extends BaseDomain {
  4. @Id()
  5. @GeneratedValue(strategy = GenerationType.IDENTITY)
  6. @Column(name = "ID")
  7. private Long id;
  8.  
  9. @Column
  10. private String name;
  11.  
  12. @OneToMany(mappedBy = "teacher")
  13. @org.hibernate.annotations.ForeignKey(name = "none")
  14. private List<Student> students;
  15.  
  16. //getter&setter...
  17. }

结果:

  1. CREATE TABLE public”.”student (
  2. id int8 DEFAULT nextval(‘student_id_seq’::regclass) NOT NULL,
  3. createdtime timestamp(6),
  4. name varchar(255) COLLATE default”,
  5. version int4,
  6. tid int8,
  7. CONSTRAINT student_pkey PRIMARY KEY (“id”)
  8. )
  9. WITH (OIDS=FALSE)
  10. ;
  11. ALTER TABLE public”.”student OWNER TO postgres”;

可以看到student表中原来关联teacher的外键没了,说明该注解起作用。

4.需要注意的坑

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

  1. @OneToMany(mappedBy = "teacher")
  2. @org.hibernate.annotations.ForeignKey(name = "none")
  3. 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的源码:

  1. @Target({})
  2. @Retention(RUNTIME)
  3. public @interface ForeignKey {
  4. /**
  5. * (Optional) The name of the foreign key constraint. Defaults to a provider-generated name.
  6. *
  7. * @return The foreign key name
  8. */
  9. String name() default "";
  10.  
  11. /**
  12. * (Optional) The foreign key constraint definition. Default is provider defined. If the value of
  13. * disableForeignKey is true, the provider must not generate a foreign key constraint.
  14. *
  15. * @return The foreign key definition
  16. */
  17. String foreignKeyDefinition() default "";
  18.  
  19. /**
  20. * (Optional) Used to specify whether a foreign key constraint should be generated when schema generation is in effect.
  21. */
  22. ConstraintMode value() default ConstraintMode.CONSTRAINT;
  23. }

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

2.teacher(1这端)没有student列表或者student列表被@Transient所修饰,像这样:

  1. @OneToMany(mappedBy = "teacher")
  2. @Transient
  3. 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类如下:

  1. import org.hibernate.dialect.PostgreSQL9Dialect;
  2. /**
  3. * 不生成外键,通过类似于SQL注入的方法,为每个数据库修改创建外键的SQL
  4. */
  5. public class PostgreSQL9DialectWithoutFK extends PostgreSQL9Dialect {
  6. @Override
  7. public String getAddForeignKeyConstraintString(
  8. String constraintName,
  9. String[] foreignKey,
  10. String referencedTable,
  11. String[] primaryKey,
  12. boolean referencesPrimaryKey) {
  13. // 设置foreignkey对应的列值可以为空
  14. return " alter "+ foreignKey[0] +" set default null " ;
  15. }
  16. }
  1. import org.hibernate.dialect.Oracle10gDialect;
  2. /**
  3. * 不生成外键,通过类似于SQL注入的方法,为每个数据库修改创建外键的SQL
  4. */
  5. public class Oracle10gDialectWithoutFK extends Oracle10gDialect {
  6. @Override
  7. public String getAddForeignKeyConstraintString(
  8. String constraintName,
  9. String[] foreignKey,
  10. String referencedTable,
  11. String[] primaryKey,
  12. boolean referencesPrimaryKey) {
  13. // 通过修改外键列的默认值,而不是添加外键,避免生成外键
  14. return " modify "+ foreignKey[0] +" default null " ;
  15. }
  16. }

在重写生成外键SQL时考虑过使用SQL注入的方式在创建完外键后,再删除外键,但是这种方式比较复杂,做了一点后就放弃了。

创建了重写的Dialect类,通过hibernate.dialect=Oracle10gDialectWithoutFK配置后,在生成数据库表时外键策略就会生效。

另:

  • 关于是否在数据库中生成外键的讨论如下图,具体的讨论内容可以按关键字搜索相关内容。

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

JPA 不生成外键的更多相关文章

  1. Entity Framework code first设置不在数据库中生成外键

    你现在用的EF是什么版本?我用EF6,你可以重写SqlServerMigrationSqlGenerator的生成外键和更新外键的方法,把不需要的表都过滤掉不就ok了? public class Ex ...

  2. EntityFramework Code First 构建外键关系,数据库不生成外键约束

    创建 ExtendedSqlGenerator类 public class ExtendedSqlGenerator : SqlServerMigrationSqlGenerator { #regio ...

  3. jpa无外键配置

    在用jpa这种orm框架时,有时我们实体对象存在关联关系,但实际的业务场景可能不需要用jpa来控制数据库创建数据表之间的关联约束,这时我们就需要消除掉数据库表与表之间的外键关联.但jpa在处理建立外键 ...

  4. cdm 生成pdm时, 外键的命名规则

    在CDM 生成PDM时,生成的外键默认的规则是:父表名称的前三个字母+"_"+主键 为子类的外键,可是在一些情况,很不习惯用 父表的前三个字母命名,需要用自己的规则来生成外键,此时 ...

  5. Springboot结合Jpa的外键使用

    当我们写项目的时候,总有些奇奇怪怪的理由,非让你连表查询,其实最好的就是什么都不连,数据库完全解耦 但我们还是要学习下Jpa怎么根据外键查询 (这里说下Jpa+springboot的感觉,刚开始就感觉 ...

  6. MS SQL巡检系列——检查外键字段是否缺少索引

    前言感想:一时兴起,突然想写一个关于MS SQL的巡检系列方面的文章,因为我觉得这方面的知识分享是有价值,也是非常有意义的.一方面,很多经验不足的人,对于巡检有点茫然,不知道要从哪些方面巡检,另外一方 ...

  7. Entity Framework 4.1 - Code First 指定外键名称

    Entity Framework 4.1 中,生成外键的方式有以下几种: 1-指定导航属性,会自动生成外键,命名规则为:“表名_主键名”2-默认情况下与导航属性的主键名称相同的字段会自动被标记为外键, ...

  8. EF Code First 导航属性 与外键

    一对多关系 项目中最常用到的就是一对多关系了.Code First对一对多关系也有着很好的支持.很多情况下我们都不需要特意的去配置,Code First就能通过一些引用属性.导航属性等检测到模型之间的 ...

  9. Code First 指定外键名称

    指定类外键有注释(DataAnnotation)和FluentAPI两种方式, 目前掌握的知识我们先把DataAnnotation可用的四种方式总结如下 第一种方法: //1-指定导航属性,会自动生成 ...

随机推荐

  1. 早期(编译器)优化--javac编译器

    java语言的“编译期”其实是一段“不确定”的操作过程,可能是指一个前端编译器把.java变成.class的过程,也可能是指虚拟机的后端运行期编译器(JLT)把字节码转变成机器码的过程,也有可能是使用 ...

  2. cvc-complex-type.2.4.a: Invalid content was found starting with element 'property'. One of '{"http:// www.springframework.org/schema/beans":import, "http:// www.springframework.org/schema/beans":a

    因为property并未被<bean class="">标签包围, 所以要用bean标签包围即可

  3. unity8个入门代码

    01,基本碰撞检测代码 function OnCollisionEnter(theCollision:Collision){ if(theCollision.gameObject.name==&quo ...

  4. float类型数保留一位小数

    float类型数保留一位小数 float a = 2.5f; float b = 1.2f; System.out.println(a/b); System.out.println((float)(M ...

  5. db2 codepage

    首先分两个层面,DB2 CODEPAGE和OS CODEPAGE(DB2SET DB2CODEPAGE相当于设定了当前实例的OS的CODEPAGE) Linux系统查看CODEPAGE的方法:在终端输 ...

  6. 【转载】IntelliJ IDEA 内存优化最佳实践

    本文转自 http://blog.oneapm.com/apm-tech/426.html [编者按]本文作者在和同事的一次讨论中发现,对 IntelliJ IDEA 内存采用不同的设置方案,会对 I ...

  7. Linux kernel engineer--trace

    http://oliveryang.net/ https://github.com/yangoliver

  8. Revit API创建标注NewTag

    start ;             )                 {                     eId = item;                 }            ...

  9. android:制作 Nine-Patch 图片

    它是一种被特殊处理过的 png 图片,能够指定哪些区域可以被拉伸而 哪些区域不可以. 那么 Nine-Patch 图片到底有什么实际作用呢?我们还是通过一个例子来看一下吧.比如 说项目中有一张气泡样式 ...

  10. firedac数据集数据序列为JSON

    firedac数据集数据序列为JSON FIREDAC数据库引擎充分地考虑了跨平台和跨语言的支持. 因此,FIREDAC数据集可以序列为BIN\XML\JSON,三种格式. firedac数据集数据序 ...