数据主键生成映射问题
今天在将监控管理工程底层的持久化实现从hibernate换成JPA时还是遇到问题了,EntityManager在调用persist时报出了
feng-15:37:15,620 ERROR [STDERR] javax.persistence.PersistenceException: org.hibernate.PersistentObjectException: detached entity passed to persist: ejbModule.domain.targetconfig.TTargetConfig
feng-15:37:15,620 ERROR [STDERR] at org.hibernate.ejb.AbstractEntityManagerImpl.throwPersistenceException(AbstractEntityManagerImpl.java:629)
feng-15:37:15,620 ERROR [STDERR] at org.hibernate.ejb.AbstractEntityManagerImpl.persist(AbstractEntityManagerImpl.java:218)
feng-15:37:15,620 ERROR [STDERR] at org.jboss.ejb3.entity.TransactionScopedEntityManager.persist(TransactionScopedEntityManager.java:182)
feng-15:37:15,620 ERROR [STDERR] at ejbModule.persistence.common.AbstractDaoImpl.save(AbstractDaoImpl.java:348)
feng-15:37:15,620 ERROR [STDERR] at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
...
feng-15:37:15,625 ERROR [STDERR] Caused by: org.hibernate.PersistentObjectException: detached entity passed to persist: ejbModule.domain.targetconfig.TTargetConfig
feng-15:37:15,625 ERROR [STDERR] at org.hibernate.event.def.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:79)
feng-15:37:15,625 ERROR [STDERR] at org.hibernate.event.def.DefaultPersistEventListener.onPersist(DefaultPersistEventListener.java:38)
feng-15:37:15,625 ERROR [STDERR] at org.hibernate.impl.SessionImpl.firePersist(SessionImpl.java:618)
feng-15:37:15,625 ERROR [STDERR] at org.hibernate.impl.SessionImpl.persist(SessionImpl.java:592)
feng-15:37:15,625 ERROR [STDERR] at org.hibernate.impl.SessionImpl.persist(SessionImpl.java:596)
feng-15:37:15,625 ERROR [STDERR] at org.hibernate.ejb.AbstractEntityManagerImpl.persist(AbstractEntityManagerImpl.java:212)
feng-15:37:15,627 ERROR [STDERR] ... 66 more
后来才发现了是由于实体的主键属性上加了注解如下:
@javax.persistence.Id @javax.persistence.Column(name="TARGET_CONFIG_ID",scale=0) @javax.persistence.GeneratedValue(generator="project-assigned") @org.hibernate.annotations.GenericGenerator(name="project-assigned",strategy="assigned") public Long getTargetConfigId() { return this.targetConfigId; }
这样加的注解,在直接用hibernate的session进行操作时,是不会有问题的,但是用JPA的EntityManager接口进行操作时,就报了上面的错误。
在JPA中,可以为entity bean手工生成主键,也可以让persistence provider为你代劳。当你想让persistence provider生成主键时,就必须使用注解@javax.persistence.GeneratedValue。
@javax.persistence.GeneratedValue的定义如下
package javax.persistence; @Target({METHOD, FIELD}) @Retention(RUNTIME) public @interface GeneratedValue{ GenerationType strategy() default AUTO; String generator() default ""; } public enum GenerationType{ TABLE, SEQUENCE, IDENTITY, AUTO }
在监控管理工程中,所有的主键的生成都是手工为entity bean生成主键,再通过主键属性的set方法设置进entity bean里的,所以在使用JPA方式操作时,本来就不应该使用GeneratedValue这个注解。
在如上面忘记修改注解的情况下,JPA认定我的主键需要persistence provider来生成,而且生成策略使用默认即GenetionType.AUTO。所以在我已经手工为entity bean设置了主键属性的值,再通过EntityManager接口的persist方法进行游离对象的持久化时,就报出了开头的那个错误。
修改后的注解如下
@javax.persistence.Id @javax.persistence.Column(name="TARGET_CONFIG_ID",scale=0) public Long getTargetConfigId() { return this.targetConfigId; }
----------------------------------------------大家一起来分隔---------------------------------------------------------
关于主键生成,现在工程里都是通过用一张数据库表:SEQ表,来进行主键的生成。
每次需要往数据库里插入新数据时,都需要从这张SEQ表里查找出当前已使用主键的数值,再对这个当前数值进行操作,得到一个不同于当前已经使用的主键数值(现在使用的方法是进行加1累加),最后再更新这张SEQ表的当前已使用主键值。这样就得到了一个还没进行使用的主键数值。
可是这样的问题是每次插入数据时,都得跟数据库进行生成主键的操作往返,无疑增加了数据库的负担,在大量并发地插入数据时,这样的问题更为明显。
后来这张SEQ表由于并发访问大经常被锁定了,导致程序无法对其进行操作。在进行了使用BEAN管理事务的方法对生成主键的代码进行事务管理,还在stateless bean里加synchronized进行并发控制...都不起作用的情况下,最后对SEQ表进行了分表,一张SEQ表分成了A_SEQ,B_SEQ,C_SEQ表等等,各个模块对生成主键并发访问量大的另外访问这些分出来的SEQ表,减少对同一张SEQ表访问量。这样下来才基本解决了锁表问题,可是以后会不会再出现,我也不知道了......
就这个问题,后来我有考虑过使用数据库的触发器来自动生成主键值,可是被P总否定了,理由是数据量太大时,使用触发器会使数据库的操作变慢...我没经历过这么大量数据的测试,也不知道是不是会这样,反正否决不了他做的决定...
所以现在数据主键的生成还是通过程序查表来进行操作,下一步我想考虑通过使用@GeneratedValue来设置使用表生成器进行主键生成。