1》hibernate/JPA實現復合主鍵的思路:是將所有的主鍵屬性封裝在一個主鍵類中,提供給需要復合主鍵的實體類使用。
2》主鍵類的幾點要求:
1. 使用復合主鍵的實體類必須實現Serializable接口。 必須實現Serializable接口的原因很簡單,我們查找數據的時候是根據主鍵查找的。打開Hibernate的幫助文檔我們可以找到get與load方法的聲明形式如下: Object load(Class theClass,Serializable id) Object get(Class theClass,Serializable id) 當我們查找復合主鍵類的對象時,需要傳遞主鍵值給get()或load()方法的id參數,而id參數只能接收一個實現了Serializable接口的對象。而復合主鍵類的主鍵不是一個屬性可以表示的,所以只能先new出復合主鍵類的實例(例如:new People()),然后使用主鍵屬性的set方法將主鍵值賦值給主鍵屬性,然后將整個對象傳遞給get()或load()方法的id參數,實現主鍵值的傳遞,所以復合主鍵的實體類必須實現Serializable接口。 2. 使用復合主鍵的實體類必須重寫equals和hashCode方法。必須重寫equals和hashCode方法也很好理解。這兩個方法使用於判斷兩個對象(兩條記錄)是否相等的。為什么要判斷兩個對象是否相等呢?因為數據庫中的任意兩條記錄中的主鍵值是不能相同的,所以我們在程序中只要確保了兩個對象的主鍵值不同就可以防止主鍵約束違例的錯誤出現。也許這里你會奇怪為什么不使用復合主鍵的實體類不重寫這兩個方法也沒有主鍵違例的情況出現,這是因為使用單一主鍵方式,主鍵值是Hibernate來維護的,它會確保主鍵不會重復,而復合主鍵方式,主鍵值是編程人員自己維護的,所以必須重寫equals和hashCode方法用於判斷兩個對象的主鍵是否相同。 3. 重寫的equals和hashCode方法,只與主鍵屬性有關,普通屬性不要影響這兩個方法進行判斷。這個原因很簡單,主鍵才能決定一條記錄,其他屬性不能決定一條記錄。 4.主鍵類必須有默認的public無參數的構造方法。
3》hibernate/JPA注解方式實現復合主鍵的方式有三種
1、@Embeddable + @Id + @Embedded(@Embeddable 表示這個類可以嵌入到別的類中去,常以表示其他類的某個屬性。@Embedded 它和 @Embeddable 正好相反,它用來表示某個屬性是被嵌入進來的。)
2、@Embeddable + @EmbeddedId(@EmbeddedId = @Embedded + @Id)
3、@IdClass + @Id
==========================================第一種注解方式實現復合主鍵:@Embeddable + @Id + @Embedded====================================================
1》寫一個主鍵類ModelKey【這個主鍵類有點BUG,想要正確的例子,直接看下面】

package com.sxd.swapping.domain; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; import javax.persistence.Column; import javax.persistence.Embeddable; import java.io.Serializable; import java.util.Objects; /** * 本類 作為 復合主鍵類 供某個需要復合主鍵的類使用 * 必須提供 * 1》get/set方法 * 2》無參/全參構造 * 3》重寫equals()、hashCode()方法 * 4》實現Serializable接口 * * 這里重寫的equals()和hashCode()可以在idea中 alt+insert快捷鍵選擇模板進行重寫 */ @Embeddable @Getter @Setter @NoArgsConstructor @AllArgsConstructor public class ModelKey implements Serializable{ private static final long serialVersionUID = -2397232644712659217L; private String key1; private String key2; private String key3; @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; ModelKey modelKey = (ModelKey) o; return Objects.equals(key1, modelKey.key1) && Objects.equals(key2, modelKey.key2) && Objects.equals(key3, modelKey.key3); } @Override public int hashCode() { return Objects.hash(key1, key2, key3); } }
2》實體類ModelEntity使用主鍵類作為復合主鍵

package com.sxd.swapping.domain; import lombok.Getter; import lombok.Setter; import javax.persistence.*; @Entity @Table(name = "model_entity") @Getter @Setter public class ModelEntity{ @Id @Embedded private ModelKey modelKey; @Column(name = "user_name") private String userName; @Column(name = "password") private String password; }
在啟動創建過程中,報錯:

2018-05-14 14:23:51.949 INFO 7156 --- [ restartedMain] org.hibernate.dialect.Dialect : HHH000400: Using dialect: org.hibernate.dialect.MySQL5Dialect Hibernate: drop table if exists hua_yang_area Hibernate: drop table if exists model_entity Hibernate: create table hua_yang_area (id bigint not null auto_increment, create_date datetime not null, create_id varchar(255) not null, uid varchar(255) not null, update_date datetime, update_id varchar(255), area_name varchar(255) not null, area_person bigint not null, primary key (id)) engine=MyISAM Hibernate: create table model_entity (key1 varchar(255) not null, key2 varchar(255) not null, key3 varchar(255) not null, password varchar(255), user_name varchar(255), primary key (key1, key2, key3)) engine=MyISAM 2018-05-14 14:23:52.385 WARN 7156 --- [ restartedMain] o.h.t.s.i.ExceptionHandlerLoggedImpl : GenerationTarget encountered exception accepting command : Error executing DDL via JDBC Statement org.hibernate.tool.schema.spi.CommandAcceptanceException: Error executing DDL via JDBC Statement at org.hibernate.tool.schema.internal.exec.GenerationTargetToDatabase.accept(GenerationTargetToDatabase.java:67) ~[hibernate-core-5.2.16.Final.jar:5.2.16.Final] at org.hibernate.tool.schema.internal.SchemaCreatorImpl.applySqlString(SchemaCreatorImpl.java:440) [hibernate-core-5.2.16.Final.jar:5.2.16.Final] at org.hibernate.tool.schema.internal.SchemaCreatorImpl.applySqlStrings(SchemaCreatorImpl.java:424) [hibernate-core-5.2.16.Final.jar:5.2.16.Final] at org.hibernate.tool.schema.internal.SchemaCreatorImpl.createFromMetadata(SchemaCreatorImpl.java:315) [hibernate-core-5.2.16.Final.jar:5.2.16.Final] at org.hibernate.tool.schema.internal.SchemaCreatorImpl.performCreation(SchemaCreatorImpl.java:166) [hibernate-core-5.2.16.Final.jar:5.2.16.Final] at org.hibernate.tool.schema.internal.SchemaCreatorImpl.doCreation(SchemaCreatorImpl.java:135) [hibernate-core-5.2.16.Final.jar:5.2.16.Final] at org.hibernate.tool.schema.internal.SchemaCreatorImpl.doCreation(SchemaCreatorImpl.java:121) [hibernate-core-5.2.16.Final.jar:5.2.16.Final] at org.hibernate.tool.schema.spi.SchemaManagementToolCoordinator.performDatabaseAction(SchemaManagementToolCoordinator.java:155) [hibernate-core-5.2.16.Final.jar:5.2.16.Final] at org.hibernate.tool.schema.spi.SchemaManagementToolCoordinator.process(SchemaManagementToolCoordinator.java:72) [hibernate-core-5.2.16.Final.jar:5.2.16.Final] at org.hibernate.internal.SessionFactoryImpl.<init>(SessionFactoryImpl.java:312) [hibernate-core-5.2.16.Final.jar:5.2.16.Final] at org.hibernate.boot.internal.SessionFactoryBuilderImpl.build(SessionFactoryBuilderImpl.java:460) [hibernate-core-5.2.16.Final.jar:5.2.16.Final] at org.hibernate.jpa.boot.internal.EntityManagerFactoryBuilderImpl.build(EntityManagerFactoryBuilderImpl.java:892) [hibernate-core-5.2.16.Final.jar:5.2.16.Final] at org.springframework.orm.jpa.vendor.SpringHibernateJpaPersistenceProvider.createContainerEntityManagerFactory(SpringHibernateJpaPersistenceProvider.java:57) [spring-orm-5.0.5.RELEASE.jar:5.0.5.RELEASE] at org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean.createNativeEntityManagerFactory(LocalContainerEntityManagerFactoryBean.java:365) [spring-orm-5.0.5.RELEASE.jar:5.0.5.RELEASE] at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.buildNativeEntityManagerFactory(AbstractEntityManagerFactoryBean.java:390) [spring-orm-5.0.5.RELEASE.jar:5.0.5.RELEASE] at org.springframework.orm.jpa.AbstractEntityManagerFactoryBean.afterPropertiesSet(AbstractEntityManagerFactoryBean.java:377) [spring-orm-5.0.5.RELEASE.jar:5.0.5.RELEASE] at org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean.afterPropertiesSet(LocalContainerEntityManagerFactoryBean.java:341) [spring-orm-5.0.5.RELEASE.jar:5.0.5.RELEASE] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.invokeInitMethods(AbstractAutowireCapableBeanFactory.java:1761) [spring-beans-5.0.5.RELEASE.jar:5.0.5.RELEASE] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.initializeBean(AbstractAutowireCapableBeanFactory.java:1698) [spring-beans-5.0.5.RELEASE.jar:5.0.5.RELEASE] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:579) [spring-beans-5.0.5.RELEASE.jar:5.0.5.RELEASE] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:501) [spring-beans-5.0.5.RELEASE.jar:5.0.5.RELEASE] at org.springframework.beans.factory.support.AbstractBeanFactory.lambda$doGetBean$0(AbstractBeanFactory.java:317) [spring-beans-5.0.5.RELEASE.jar:5.0.5.RELEASE] at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:228) ~[spring-beans-5.0.5.RELEASE.jar:5.0.5.RELEASE] at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:315) [spring-beans-5.0.5.RELEASE.jar:5.0.5.RELEASE] at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:199) [spring-beans-5.0.5.RELEASE.jar:5.0.5.RELEASE] at org.springframework.context.support.AbstractApplicationContext.getBean(AbstractApplicationContext.java:1089) ~[spring-context-5.0.5.RELEASE.jar:5.0.5.RELEASE] at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:859) ~[spring-context-5.0.5.RELEASE.jar:5.0.5.RELEASE] at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:550) ~[spring-context-5.0.5.RELEASE.jar:5.0.5.RELEASE] at org.springframework.boot.web.servlet.context.ServletWebServerApplicationContext.refresh(ServletWebServerApplicationContext.java:140) ~[spring-boot-2.0.1.RELEASE.jar:2.0.1.RELEASE] at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:759) ~[spring-boot-2.0.1.RELEASE.jar:2.0.1.RELEASE] at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:395) ~[spring-boot-2.0.1.RELEASE.jar:2.0.1.RELEASE] at org.springframework.boot.SpringApplication.run(SpringApplication.java:327) ~[spring-boot-2.0.1.RELEASE.jar:2.0.1.RELEASE] at org.springframework.boot.SpringApplication.run(SpringApplication.java:1255) ~[spring-boot-2.0.1.RELEASE.jar:2.0.1.RELEASE] at org.springframework.boot.SpringApplication.run(SpringApplication.java:1243) ~[spring-boot-2.0.1.RELEASE.jar:2.0.1.RELEASE] at com.sxd.swapping.SwappingApplication.main(SwappingApplication.java:10) ~[classes/:na] at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method) ~[na:1.8.0_171] at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62) ~[na:1.8.0_171] at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43) ~[na:1.8.0_171] at java.lang.reflect.Method.invoke(Method.java:498) ~[na:1.8.0_171] at org.springframework.boot.devtools.restart.RestartLauncher.run(RestartLauncher.java:49) ~[spring-boot-devtools-2.0.1.RELEASE.jar:2.0.1.RELEASE] Caused by: com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: Specified key was too long; max key length is 1000 bytes at sun.reflect.NativeConstructorAccessorImpl.newInstance0(Native Method) ~[na:1.8.0_171] at sun.reflect.NativeConstructorAccessorImpl.newInstance(NativeConstructorAccessorImpl.java:62) ~[na:1.8.0_171] at sun.reflect.DelegatingConstructorAccessorImpl.newInstance(DelegatingConstructorAccessorImpl.java:45) ~[na:1.8.0_171] at java.lang.reflect.Constructor.newInstance(Constructor.java:423) ~[na:1.8.0_171] at com.mysql.jdbc.Util.handleNewInstance(Util.java:425) ~[mysql-connector-java-5.1.46.jar:5.1.46] at com.mysql.jdbc.Util.getInstance(Util.java:408) ~[mysql-connector-java-5.1.46.jar:5.1.46] at com.mysql.jdbc.SQLError.createSQLException(SQLError.java:944) ~[mysql-connector-java-5.1.46.jar:5.1.46] at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3976) ~[mysql-connector-java-5.1.46.jar:5.1.46] at com.mysql.jdbc.MysqlIO.checkErrorPacket(MysqlIO.java:3912) ~[mysql-connector-java-5.1.46.jar:5.1.46] at com.mysql.jdbc.MysqlIO.sendCommand(MysqlIO.java:2530) ~[mysql-connector-java-5.1.46.jar:5.1.46] at com.mysql.jdbc.MysqlIO.sqlQueryDirect(MysqlIO.java:2683) ~[mysql-connector-java-5.1.46.jar:5.1.46] at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2482) ~[mysql-connector-java-5.1.46.jar:5.1.46] at com.mysql.jdbc.ConnectionImpl.execSQL(ConnectionImpl.java:2440) ~[mysql-connector-java-5.1.46.jar:5.1.46] at com.mysql.jdbc.StatementImpl.executeInternal(StatementImpl.java:845) ~[mysql-connector-java-5.1.46.jar:5.1.46] at com.mysql.jdbc.StatementImpl.execute(StatementImpl.java:745) ~[mysql-connector-java-5.1.46.jar:5.1.46] at com.zaxxer.hikari.pool.ProxyStatement.execute(ProxyStatement.java:95) ~[HikariCP-2.7.8.jar:na] at com.zaxxer.hikari.pool.HikariProxyStatement.execute(HikariProxyStatement.java) ~[HikariCP-2.7.8.jar:na] at org.hibernate.tool.schema.internal.exec.GenerationTargetToDatabase.accept(GenerationTargetToDatabase.java:54) ~[hibernate-core-5.2.16.Final.jar:5.2.16.Final] ... 39 common frames omitted
所以,需要制定創建復合主鍵的字段長度
只需要保證復合主鍵的長度總和在【1000bytes/8 = 125】以內即可。
修改:【正確的主鍵類ModelKey】

package com.sxd.swapping.domain; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; import javax.persistence.Column; import javax.persistence.Embeddable; import java.io.Serializable; import java.util.Objects; /** * 本類 作為 復合主鍵類 供某個需要復合主鍵的類使用 * 必須提供 * 1》get/set方法 * 2》無參/全參構造 * 3》重寫equals()、hashCode()方法 * 4》實現Serializable接口 * * 這里重寫的equals()和hashCode()可以在idea中 alt+insert快捷鍵選擇模板進行重寫 */ @Embeddable @Getter @Setter @NoArgsConstructor @AllArgsConstructor public class ModelKey implements Serializable{ private static final long serialVersionUID = -2397232644712659217L; @Column(length = 36) private String key1; @Column(length = 36) private String key2; @Column(length = 36) private String key3; @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; ModelKey modelKey = (ModelKey) o; return Objects.equals(key1, modelKey.key1) && Objects.equals(key2, modelKey.key2) && Objects.equals(key3, modelKey.key3); } @Override public int hashCode() { return Objects.hash(key1, key2, key3); } }
再次啟動,成功執行DDL語句,查看數據表,復合主鍵已經創建成功。
==========================================第二種注解方式實現復合主鍵:@Embeddable +@EmbeddedId====================================================
1》ModelKey主鍵表不變

package com.sxd.swapping.domain; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; import javax.persistence.Column; import javax.persistence.Embeddable; import java.io.Serializable; import java.util.Objects; /** * 本類 作為 復合主鍵類 供某個需要復合主鍵的類使用 * 必須提供 * 1》get/set方法 * 2》無參/全參構造 * 3》重寫equals()、hashCode()方法 * 4》實現Serializable接口 * * 這里重寫的equals()和hashCode()可以在idea中 alt+insert快捷鍵選擇模板進行重寫 */ @Embeddable @Getter @Setter @NoArgsConstructor @AllArgsConstructor public class ModelKey implements Serializable{ private static final long serialVersionUID = -2397232644712659217L; @Column(length = 36) private String key1; @Column(length = 36) private String key2; @Column(length = 36) private String key3; @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; ModelKey modelKey = (ModelKey) o; return Objects.equals(key1, modelKey.key1) && Objects.equals(key2, modelKey.key2) && Objects.equals(key3, modelKey.key3); } @Override public int hashCode() { return Objects.hash(key1, key2, key3); } }
2》ModelEntity實體表中使用@EmbeddedId注解

package com.sxd.swapping.domain; import lombok.Getter; import lombok.Setter; import javax.persistence.*; @Entity @Table(name = "model_entity") @Getter @Setter public class ModelEntity{ @EmbeddedId private ModelKey modelKey; @Column(name = "user_name") private String userName; @Column(name = "password") private String password; }
==========================================第三種注解方式實現復合主鍵:@IdClass + @Id====================================================
1》ModelKey主鍵類取消注解@Embeddable

package com.sxd.swapping.domain; import lombok.AllArgsConstructor; import lombok.Getter; import lombok.NoArgsConstructor; import lombok.Setter; import javax.persistence.Column; import java.io.Serializable; import java.util.Objects; /** * 本類 作為 復合主鍵類 供某個需要復合主鍵的類使用 * 必須提供 * 1》get/set方法 * 2》無參/全參構造 * 3》重寫equals()、hashCode()方法 * 4》實現Serializable接口 * * 這里重寫的equals()和hashCode()可以在idea中 alt+insert快捷鍵選擇模板進行重寫 */ @Getter @Setter @NoArgsConstructor @AllArgsConstructor public class ModelKey implements Serializable{ private static final long serialVersionUID = -2397232644712659217L; @Column(length = 32) private String key1; @Column(length = 32) private String key2; @Column(length = 32) private String key3; @Override public boolean equals(Object o) { if (this == o) return true; if (o == null || getClass() != o.getClass()) return false; ModelKey modelKey = (ModelKey) o; return Objects.equals(key1, modelKey.key1) && Objects.equals(key2, modelKey.key2) && Objects.equals(key3, modelKey.key3); } @Override public int hashCode() { return Objects.hash(key1, key2, key3); } }
2》ModelEntity實體類使用@IdClass(value = ModelKey.class)標明使用哪個類作為主鍵類和@Id分別標記主鍵屬性

package com.sxd.swapping.domain; import lombok.Getter; import lombok.Setter; import javax.persistence.*; @IdClass(value = ModelKey.class) @Entity @Table(name = "model_entity") @Getter @Setter public class ModelEntity{ @Id private String key1; @Id private String key2; @Id private String key3; @Column(name = "user_name") private String userName; @Column(name = "password") private String password; }
這里修改了主鍵類三個屬性的長度,可以看到數據庫中,已經修改成功!
===================================最后,實體類的多個字段建立唯一索引,達到復合主鍵的效果=================================
當然,一般情況下不推薦使用復合主鍵,或者不用實現復合主鍵,對實體類的多個字段建立唯一索引,同樣能達到想要的效果
地址:http://www.cnblogs.com/sxdcgaq8080/p/9036249.html
有興趣可以了解一下!!