jpa学习笔记-ejb-03jpa主键生成策略总结


1 JPA 学习笔记-EJB-03JPA 主键生成策略总结 刘岩 Email:suhuanzheng7784877@163.com 总结一下关于 JPA 的主键生成策略,JPA 是用@GeneratedValue 标记来注释的。一般的 我把主键生成分成两大类。第一个就是简单的单字段主键类型,一个就是复杂的复合主键类 型。我们分 2 种情况分别讨论。 第一种单字段主键类型,看上去简单,无非就是一个 id 字段呗,实际上这个主键字段 在 JPA,还有任何的 ORM 框架中都是有很多种生成策略的。 一般是如下 4 种: 1. AUTO:自动自增生成 2. TABLE:自定义表生成器 3. Identity:像 MS SQL 支持 Identity 字段的生成主键策略 4. Sequence:像 Oracle 支持 Sequence 的生成主键策略 1:自动生成主键 AUTO AUTO 是默认的主键生成策略,使用了 AUTO 策略,JPA 会根据不同的数据库类型来 实现自增策略。比如 MySql 的表主键是自增 1 的,那么此策略就会按照数据库的自增 1 策 略,每次插入数据库记录的时候都会自增 1。Java 代码片段如下: @Id @GeneratedValue(strategy=GenerationType.AUTO) private Integer id; 2:自定义表生成器 TABLE 自定义表生成器是在数据库中再建立一张新的表,这张表不是业务表,而是一张特殊表, 这张表是专门用来管理整个数据库的主键的具体值的。在数据库中建立一张表如下图所示: 那么相应的 Java 片段如下所示: @Id @GeneratedValue(strategy = GenerationType.TABLE, generator = "myTablePK") @TableGenerator( name = "myTablePK", table = "tb_pk", pkColumnName = "table_PK_name", pkColumnValue = "tb_contact_pk", 2 valueColumnName = "table_PK_value", allocationSize = 1) private Integer id; 下面对@TableGenerator 标记的属性说明一下: Name:引用@GeneratedValue 里面的 generator 的值 Table:数据库中专门用于生成主键的特殊表的表名 pkColumnName:tb_pk 表中哪个字段代表了被服务(需要生成主键表)的表名 valueColumnName:指定此辅助表的哪个字段存储了主键的具体值 pkColumnValue:指 明 一 个主 键 名 称,依靠这个名称可以在这张特殊表中找到唯一的 table_PK_value 的值,而这个值就是具体的主键值。换句话说,数据库有几个业务表,这张 表就应该有几条记录才对(除去复合主键的情况) allocationSize:代表自增数 下面自己再写一个测试方法,这次注释掉主键 set 那一句,代码如下: /** * 保存ContactEO实体单元测试 * * @throws NamingException */ public void test03() throws NamingException { // 建立实体 ContactEO contactEO = new ContactEO(); //注释掉主键这句话 //contactEO.setId(2); contactEO.setName("测试主键table策略"); contactEO.setMessage("表策略"); contactEO.setEmail("suhuanzheng77848@163.com"); contactEO.setPrice(9999999911.12); // 对byte属性的赋值 try { // 读取本机一个文件 File file = new File("c://22.jpg"); if (file != null) { FileInputStream fis = new FileInputStream(file); if (fis != null) { int len = fis.available(); byte[] xml = new byte[len]; 3 fis.read(xml); // 赋值 contactEO.setPicture(xml); } } } catch (Exception e) { e.printStackTrace(); } // 获得应用服务上下文 Context ctx = getInitialContext(); Object object = ctx.lookup("ContactServiceImpl/remote"); // 接口 IContactService contactService = (IContactService) PortableRemoteObject .narrow(object, IContactService.class); // 调用接口保存方法 contactService.saveContact(contactEO); } 执行后,数据库记录如下: tb_contact 记录 可以看到 id=4 的是 tb_pk 表辅助生成的值。 tb_pk 表记录如下 4 可以看到,tb_pk 这张表的 table_PK_value 的值改变了。 3:数据库序列 Sequence 像 Oracle 这种数据库支持序列生成主键的策略,那么就可以使用 JPA 的 Sequence 作为 主键的生成策略。代码如下: @Id @Column(name = "id") @GeneratedValue(strategy = GenerationType.SEQUENCE) public Integer getId() { return id; } MySql 暂时不支持序列这种主键策略,所以遇到 MySql 这种数据库的时候,不能使用 SEQUENCE 主键策略。 4:Identity 生成器 有些数据库支持 Identity 字段作为生成器,MS SQL 支持,主键创建 Identity,实体标注 如下: @Id @Column(name = "id") @GeneratedValue(strategy=GenerationType.IDENTITY) public Integer getId() { return id; } 单字段主键的问题,我们暂时先讨论这里,下面我们一起看看复合主键。 表结构如下图所示: 根据这个表对应生成的实体对下代码如下: package eo; import javax.persistence.Column; 5 import javax.persistence.Embeddable; /** * TbStudentsId entity. @author MyEclipse Persistence Tools */ @Embeddable public class TbStudentsId implements java.io.Serializable { // Fields private String name; private String no; // Constructors /** default constructor */ public TbStudentsId() { } /** full constructor */ public TbStudentsId(String name, String no) { this.name = name; this.no = no; } // Property accessors @Column(name = "name", nullable = false) public String getName() { return this.name; } public void setName(String name) { this.name = name; } @Column(name = "no", nullable = false) public String getNo() { return this.no; } public void setNo(String no) { this.no = no; } 6 public boolean equals(Object other) { if ((this == other)) return true; if ((other == null)) return false; if (!(other instanceof TbStudentsId)) return false; TbStudentsId castOther = (TbStudentsId) other; return ((this.getName() == castOther.getName()) || (this.getName() != null && castOther.getName() != null && this.getName().equals( castOther.getName()))) && ((this.getNo() == castOther.getNo()) || (this.getNo() != null && castOther.getNo() != null && this.getNo().equals( castOther.getNo()))); } public int hashCode() { int result = 17; result = 37 * result + (getName() == null ? 0 : this.getName().hashCode()); result = 37 * result + (getNo() == null ? 0 : this.getNo().hashCode()); return result; } } package eo; import javax.persistence.AttributeOverride; import javax.persistence.AttributeOverrides; import javax.persistence.Column; import javax.persistence.EmbeddedId; import javax.persistence.Entity; import javax.persistence.Table; /** * TbStudents entity. @author MyEclipse Persistence Tools */ 7 @Entity @Table(name = "tb_students", catalog = "ejbjpa") public class TbStudents implements java.io.Serializable { // Fields private TbStudentsId id; private String info; // Constructors /** default constructor */ public TbStudents() { } /** minimal constructor */ public TbStudents(TbStudentsId id) { this.id = id; } /** full constructor */ public TbStudents(TbStudentsId id, String info) { this.id = id; this.info = info; } // Property accessors @EmbeddedId @AttributeOverrides( { @AttributeOverride(name = "name", column = @Column(name = "name", nullable = false)), @AttributeOverride(name = "no", column = @Column(name = "no", nullable = false)) }) public TbStudentsId getId() { return this.id; } public void setId(TbStudentsId id) { this.id = id; } @Column(name = "info") public String getInfo() { return this.info; 8 } public void setInfo(String info) { this.info = info; } } TbStudentsId 类是主键类,TbStudents 是实体类。TbStudentsId 是作为 TbStudents 的一个特殊属性存在的。代码还是以嵌入式的(@Embeddable)方式表现主键的。 下面写个 DAO 接口 package dao; import javax.ejb.Remote; import eo.TbStudents; @Remote public interface ITbStudentsService { public void save (TbStudents tbStudents); } 实现类如下: package dao.impl; import javax.ejb.Stateless; import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; import dao.ITbStudentsService; import eo.TbStudents; @Stateless public class TbStudentsServiceImpl implements ITbStudentsService { @PersistenceContext(unitName = "JPAEJBPU") private EntityManager entityManager; @Override public void save(TbStudents tbStudents) { entityManager.persist(tbStudents); } } 按照前文章节部署到 JBOSS 上面后,写一个测试代码,代码如下: public void test04() { 9 try { Context ctx = getInitialContext(); Object object = ctx.lookup("TbStudentsServiceImpl/remote"); ITbStudentsService tbStudentsService = (ITbStudentsService) PortableRemoteObject .narrow(object, ITbStudentsService.class); //对主键对象赋值 TbStudentsId id = new TbStudentsId(); id.setName("Klose"); id.setNo("11"); //对实体赋值 TbStudents tbStudents = new TbStudents(); tbStudents.setId(id); tbStudents.setInfo("拜仁"); tbStudentsService.save(tbStudents); } catch (NamingException e) { e.printStackTrace(); } } 第一次执行此方法后:结果数据库如下: 之后我们不改变任何代码在执行一遍此测试方法,就会发生以下主键重复异常: Caused by: java.sql.BatchUpdateException: Duplicate entry 'Klose-11' for key 'PRIMARY' 稍微改变一下代码: id.setName("Ballck"); 结果如下: 10 name 和 no 不能同时相同,否则会发生主键重复异常。 小结: 借用冯曼菲小姐的著作总结主键经验如下: l.SEQUENCE,IDENTITY 两种策略由于针对的是特殊的一些数据库,所以如果在需求前期, 未确定系统要支持的数据库类型时,最好不要使用。因为一旦更改数据库类型时,例如从 Oracle 变更为 MySQL 时,此时使用的 Sequence 策略就不能使用了,这时候需要变更设置,会变得很 麻烦。 2.AUTO 自动生成策略虽然能够自动生成主键,但主键的生成规则是 JPA 的实现者来确定的, 一旦使用了这种策略,用户没有办法控制,比如说初识值是多少,每次累加值是多少,这些只能 靠 JPA 默认的实现来生成。对于比较简单的主键,对主键生成策略要求少时,采用的这种方式 比较好。 3.TABLE 生成策略是将主键的值持久化在数据库中表中,因为只要是关系型的数据库,都可以 创建一个表,来专门保存生成的值,这样就消除了不同数据库之间的不兼容性。另外,这种策略 也可是设置具体的生成策略,又弥补了 AUTO 策略的不足。所以,这种策略既能保证支持多种数 据库,又有一定的灵活性,建议使用该策略。 4.若以上的 4 种主键生成策略仍不满足需求,这时可以通过一定的规则来设置主键的值。例如 利用 UUID 作为主键,也是常用的策略之一,此时就需要在程序中自动生成,然后设置到实体的 主键上,而不能通过 JPA 的主键生成策略来实现了。 总之,在选择主键生成策略时,要根据需求,选择最合适的策略。
还剩9页未读

继续阅读

下载pdf到电脑,查找使用更方便

pdf的实际排版效果,会与网站的显示效果略有不同!!

需要 3 金币 [ 分享pdf获得金币 ] 0 人已下载

下载pdf

pdf贡献者

zhyp29

贡献于2014-05-25

下载需要 3 金币 [金币充值 ]
亲,您也可以通过 分享原创pdf 来获得金币奖励!
下载pdf