拜托,Hibernate主键生成策略sequence能不能别再用在String类型属性上
一、主键生成策略-sequence简介
sequence又称为序列,适用于long、short或int类型的主键,Hibernate根据底层数据库序列生成标识符。只有支持序列的数据库才能使用该生成策略(如Oralce、DB2等,),MySQL数据库本身不支持序列,固无法使用到sequence生成策略。此处以Oracle数据库为例简单描述下Hibernate如何配置并使用序列作为主键。
- Oracle数据库创建序列
create sequence TIGER_SEQ minvalue 0 maxvalue 999999999999999999999999999 start with 10000 increment by 1 cache 20; /
- Hibernate映射文件(实体类类名.hbm.xml文件)配置序列
<id name="urid"> <column name="urid"/> <generator class="sequence"> <param name="sequence">TIGER_SEQ</param>
</generator>
</id>
二、编写背景
应客户方要求,在项目部署中间件要求使用weblogic12C,导致必须升级Spring框架到Spring4,相对应的Hibernate版本也由3.2.5.ga升级成3.6.10.Final。由于现有代码是祖传代码,之前设计Hibernate映射文件中使用了序列的主键对应的实体类却是String类型的,而升级后的版本只认long、shot和int类型,String类型的主键直接不支持了。直接改实体类对应的类型不现实,改动量太大而且容易改漏,只能硬着头皮去下载两个版本的Hibernate源码进行研究。
三、刨根问底
根据下载的源码首先定位到处理主键生成策略的类在org\hibernate\id\下,按文件名含义很容易就找到了序列相关处理类SequenceGenerator.java(大牛写的框架命名还是挺规范的)。
- 3.6.10.Final版本的SequenceGenerator.java文件源码
1 import java.io.Serializable; 2 import java.sql.PreparedStatement; 3 import java.sql.ResultSet; 4 import java.sql.SQLException; 5 import java.util.Properties; 6 7 import org.slf4j.Logger; 8 import org.slf4j.LoggerFactory; 9 import org.hibernate.HibernateException; 10 import org.hibernate.MappingException; 11 import org.hibernate.cfg.ObjectNameNormalizer; 12 import org.hibernate.exception.JDBCExceptionHelper; 13 import org.hibernate.dialect.Dialect; 14 import org.hibernate.engine.SessionImplementor; 15 import org.hibernate.mapping.Table; 16 import org.hibernate.type.Type; 17 import org.hibernate.util.PropertiesHelper; 18 19 /** 20 * <b>sequence</b><br> 21 * <br> 22 * Generates <tt>long</tt> values using an oracle-style sequence. A higher 23 * performance algorithm is <tt>SequenceHiLoGenerator</tt>.<br> 24 * <br> 25 * Mapping parameters supported: sequence, parameters. 26 * 27 * @see SequenceHiLoGenerator 28 * @see TableHiLoGenerator 29 * @author Gavin King 30 */ 31 public class SequenceGenerator implements PersistentIdentifierGenerator, Configurable { 32 private static final Logger log = LoggerFactory.getLogger(SequenceGenerator.class); 33 34 /** 35 * The sequence parameter 36 */ 37 public static final String SEQUENCE = "sequence"; 38 39 /** 40 * The parameters parameter, appended to the create sequence DDL. 41 * For example (Oracle): <tt>INCREMENT BY 1 START WITH 1 MAXVALUE 100 NOCACHE</tt>. 42 */ 43 public static final String PARAMETERS = "parameters"; 44 45 private String sequenceName; 46 private String parameters; 47 private Type identifierType; 48 private String sql; 49 50 protected Type getIdentifierType() { 51 return identifierType; 52 } 53 54 public void configure(Type type, Properties params, Dialect dialect) throws MappingException { 55 ObjectNameNormalizer normalizer = ( ObjectNameNormalizer ) params.get( IDENTIFIER_NORMALIZER ); 56 sequenceName = normalizer.normalizeIdentifierQuoting( 57 PropertiesHelper.getString( SEQUENCE, params, "hibernate_sequence" ) 58 ); 59 parameters = params.getProperty( PARAMETERS ); 60 61 if ( sequenceName.indexOf( '.' ) < 0 ) { 62 final String schemaName = normalizer.normalizeIdentifierQuoting( params.getProperty( SCHEMA ) ); 63 final String catalogName = normalizer.normalizeIdentifierQuoting( params.getProperty( CATALOG ) ); 64 sequenceName = Table.qualify( 65 dialect.quote( catalogName ), 66 dialect.quote( schemaName ), 67 dialect.quote( sequenceName ) 68 ); 69 } 70 else { 71 // if already qualified there is not much we can do in a portable manner so we pass it 72 // through and assume the user has set up the name correctly. 73 } 74 75 this.identifierType = type; 76 sql = dialect.getSequenceNextValString( sequenceName ); 77 } 78 79 public Serializable generate(SessionImplementor session, Object obj) { 80 return generateHolder( session ).makeValue(); 81 } 82 83 protected IntegralDataTypeHolder generateHolder(SessionImplementor session) { 84 try { 85 PreparedStatement st = session.getBatcher().prepareSelectStatement( sql ); 86 try { 87 ResultSet rs = st.executeQuery(); 88 try { 89 rs.next(); 90 IntegralDataTypeHolder result = buildHolder(); 91 result.initialize( rs, 1 ); 92 if ( log.isDebugEnabled() ) { 93 log.debug("Sequence identifier generated: " + result); 94 } 95 return result; 96 } 97 finally { 98 rs.close(); 99 } 100 } 101 finally { 102 session.getBatcher().closeStatement(st); 103 } 104 105 } 106 catch (SQLException sqle) { 107 throw JDBCExceptionHelper.convert( 108 session.getFactory().getSQLExceptionConverter(), 109 sqle, 110 "could not get next sequence value", 111 sql 112 ); 113 } 114 } 115 116 protected IntegralDataTypeHolder buildHolder() { 117 return IdentifierGeneratorHelper.getIntegralDataTypeHolder( identifierType.getReturnedClass() ); 118 } 119 120 public String[] sqlCreateStrings(Dialect dialect) throws HibernateException { 121 String[] ddl = dialect.getCreateSequenceStrings(sequenceName); 122 if ( parameters != null ) { 123 ddl[ddl.length - 1] += ' ' + parameters; 124 } 125 return ddl; 126 } 127 128 public String[] sqlDropStrings(Dialect dialect) throws HibernateException { 129 return dialect.getDropSequenceStrings(sequenceName); 130 } 131 132 public Object generatorKey() { 133 return sequenceName; 134 } 135 136 public String getSequenceName() { 137 return sequenceName; 138 } 139 140 }
- 3.2.5.ga版本的SequenceGenerator.java文件源码
1 //$Id: SequenceGenerator.java 9686 2006-03-27 16:47:06Z steve.ebersole@jboss.com $ 2 package org.hibernate.id; 3 4 import java.io.Serializable; 5 import java.sql.PreparedStatement; 6 import java.sql.ResultSet; 7 import java.sql.SQLException; 8 import java.util.Properties; 9 10 import org.apache.commons.logging.Log; 11 import org.apache.commons.logging.LogFactory; 12 import org.hibernate.HibernateException; 13 import org.hibernate.MappingException; 14 import org.hibernate.exception.JDBCExceptionHelper; 15 import org.hibernate.dialect.Dialect; 16 import org.hibernate.engine.SessionImplementor; 17 import org.hibernate.mapping.Table; 18 import org.hibernate.type.Type; 19 import org.hibernate.util.PropertiesHelper; 20 21 /** 22 * <b>sequence</b><br> 23 * <br> 24 * Generates <tt>long</tt> values using an oracle-style sequence. A higher 25 * performance algorithm is <tt>SequenceHiLoGenerator</tt>.<br> 26 * <br> 27 * Mapping parameters supported: sequence, parameters. 28 * 29 * @see SequenceHiLoGenerator 30 * @see TableHiLoGenerator 31 * @author Gavin King 32 */ 33 34 public class SequenceGenerator implements PersistentIdentifierGenerator, Configurable { 35 36 /** 37 * The sequence parameter 38 */ 39 public static final String SEQUENCE = "sequence"; 40 41 /** 42 * The parameters parameter, appended to the create sequence DDL. 43 * For example (Oracle): <tt>INCREMENT BY 1 START WITH 1 MAXVALUE 100 NOCACHE</tt>. 44 */ 45 public static final String PARAMETERS = "parameters"; 46 47 private String sequenceName; 48 private String parameters; 49 private Type identifierType; 50 private String sql; 51 52 private static final Log log = LogFactory.getLog(SequenceGenerator.class); 53 54 public void configure(Type type, Properties params, Dialect dialect) throws MappingException { 55 sequenceName = PropertiesHelper.getString(SEQUENCE, params, "hibernate_sequence"); 56 parameters = params.getProperty(PARAMETERS); 57 String schemaName = params.getProperty(SCHEMA); 58 String catalogName = params.getProperty(CATALOG); 59 60 if (sequenceName.indexOf( '.' ) < 0) { 61 sequenceName = Table.qualify( catalogName, schemaName, sequenceName ); 62 } 63 64 this.identifierType = type; 65 sql = dialect.getSequenceNextValString(sequenceName); 66 } 67 68 public Serializable generate(SessionImplementor session, Object obj) 69 throws HibernateException { 70 71 try { 72 73 PreparedStatement st = session.getBatcher().prepareSelectStatement(sql); 74 try { 75 ResultSet rs = st.executeQuery(); 76 try { 77 rs.next(); 78 Serializable result = IdentifierGeneratorFactory.get( 79 rs, identifierType 80 ); 81 if ( log.isDebugEnabled() ) { 82 log.debug("Sequence identifier generated: " + result); 83 } 84 return result; 85 } 86 finally { 87 rs.close(); 88 } 89 } 90 finally { 91 session.getBatcher().closeStatement(st); 92 } 93 94 } 95 catch (SQLException sqle) { 96 throw JDBCExceptionHelper.convert( 97 session.getFactory().getSQLExceptionConverter(), 98 sqle, 99 "could not get next sequence value", 100 sql 101 ); 102 } 103 104 } 105 106 public String[] sqlCreateStrings(Dialect dialect) throws HibernateException { 107 String[] ddl = dialect.getCreateSequenceStrings(sequenceName); 108 if ( parameters != null ) { 109 ddl[ddl.length - 1] += ' ' + parameters; 110 } 111 return ddl; 112 } 113 114 public String[] sqlDropStrings(Dialect dialect) throws HibernateException { 115 return dialect.getDropSequenceStrings(sequenceName); 116 } 117 118 public Object generatorKey() { 119 return sequenceName; 120 } 121 122 public String getSequenceName() { 123 return sequenceName; 124 } 125 126 }
上面两个源码文件比对下发现,3.6.10.Final版本调用的是buildHolder()方法中的getIntegralDataTypeHolder方法进行处理,而getIntegralDataTypeHolder方法是直接就把序列对应的属性类型当成了long、shot和int类型(或对应包装类),这是导致将序列用在String类型属性上出错的根本原因。
三、对症下药
经过源码分析发现,升级到3.6.10.Final版本后只需要改动SequenceGenerator类中的generate方法,进行兼容性处理即可,具体改造点如下(红色加粗):
1 /* 2 * Hibernate, Relational Persistence for Idiomatic Java 3 * 4 * Copyright (c) 2010, Red Hat Inc. or third-party contributors as 5 * indicated by the @author tags or express copyright attribution 6 * statements applied by the authors. All third-party contributions are 7 * distributed under license by Red Hat Inc. 8 * 9 * This copyrighted material is made available to anyone wishing to use, modify, 10 * copy, or redistribute it subject to the terms and conditions of the GNU 11 * Lesser General Public License, as published by the Free Software Foundation. 12 * 13 * This program is distributed in the hope that it will be useful, 14 * but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY 15 * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License 16 * for more details. 17 * 18 * You should have received a copy of the GNU Lesser General Public License 19 * along with this distribution; if not, write to: 20 * Free Software Foundation, Inc. 21 * 51 Franklin Street, Fifth Floor 22 * Boston, MA 02110-1301 USA 23 */ 24 package org.hibernate.id; 25 26 import java.io.Serializable; 27 import java.sql.PreparedStatement; 28 import java.sql.ResultSet; 29 import java.sql.SQLException; 30 import java.util.Properties; 31 32 import org.hibernate.id.factory.IdentifierGeneratorFactory; 33 import org.slf4j.Logger; 34 import org.slf4j.LoggerFactory; 35 import org.hibernate.HibernateException; 36 import org.hibernate.MappingException; 37 import org.hibernate.cfg.ObjectNameNormalizer; 38 import org.hibernate.exception.JDBCExceptionHelper; 39 import org.hibernate.dialect.Dialect; 40 import org.hibernate.engine.SessionImplementor; 41 import org.hibernate.mapping.Table; 42 import org.hibernate.type.Type; 43 import org.hibernate.util.PropertiesHelper; 44 45 /** 46 * <b>sequence</b><br> 47 * <br> 48 * Generates <tt>long</tt> values using an oracle-style sequence. A higher 49 * performance algorithm is <tt>SequenceHiLoGenerator</tt>.<br> 50 * <br> 51 * Mapping parameters supported: sequence, parameters. 52 * 53 * @see SequenceHiLoGenerator 54 * @see TableHiLoGenerator 55 * @author Gavin King 56 */ 57 public class SequenceGenerator implements PersistentIdentifierGenerator, Configurable { 58 private static final Logger log = LoggerFactory.getLogger(SequenceGenerator.class); 59 60 /** 61 * The sequence parameter 62 */ 63 public static final String SEQUENCE = "sequence"; 64 65 /** 66 * The parameters parameter, appended to the create sequence DDL. 67 * For example (Oracle): <tt>INCREMENT BY 1 START WITH 1 MAXVALUE 100 NOCACHE</tt>. 68 */ 69 public static final String PARAMETERS = "parameters"; 70 71 private String sequenceName; 72 private String parameters; 73 private Type identifierType; 74 private String sql; 75 76 protected Type getIdentifierType() { 77 return identifierType; 78 } 79 80 public void configure(Type type, Properties params, Dialect dialect) throws MappingException { 81 ObjectNameNormalizer normalizer = ( ObjectNameNormalizer ) params.get( IDENTIFIER_NORMALIZER ); 82 sequenceName = normalizer.normalizeIdentifierQuoting( 83 PropertiesHelper.getString( SEQUENCE, params, "hibernate_sequence" ) 84 ); 85 parameters = params.getProperty( PARAMETERS ); 86 87 if ( sequenceName.indexOf( '.' ) < 0 ) { 88 final String schemaName = normalizer.normalizeIdentifierQuoting( params.getProperty( SCHEMA ) ); 89 final String catalogName = normalizer.normalizeIdentifierQuoting( params.getProperty( CATALOG ) ); 90 sequenceName = Table.qualify( 91 dialect.quote( catalogName ), 92 dialect.quote( schemaName ), 93 dialect.quote( sequenceName ) 94 ); 95 } 96 else { 97 // if already qualified there is not much we can do in a portable manner so we pass it 98 // through and assume the user has set up the name correctly. 99 } 100 101 this.identifierType = type; 102 sql = dialect.getSequenceNextValString( sequenceName ); 103 } 104 105 public Serializable generate(SessionImplementor session, Object obj) { 106 if(identifierType.getReturnedClass()==String.class){ 107 try { 108 109 PreparedStatement st = session.getBatcher().prepareSelectStatement(sql); 110 try { 111 ResultSet rs = st.executeQuery(); 112 try { 113 rs.next(); 114 Serializable result =rs.getString( 1 ); 115 if ( log.isDebugEnabled() ) { 116 log.debug("Sequence identifier generated: " + result); 117 } 118 return result; 119 } 120 finally { 121 rs.close(); 122 } 123 } 124 finally { 125 session.getBatcher().closeStatement(st); 126 } 127 128 } 129 catch (SQLException sqle) { 130 throw JDBCExceptionHelper.convert( 131 session.getFactory().getSQLExceptionConverter(), 132 sqle, 133 "could not get next sequence value", 134 sql 135 ); 136 } 137 }else{ 138 return generateHolder( session ).makeValue(); 139 } 140 } 141 142 protected IntegralDataTypeHolder generateHolder(SessionImplementor session) { 143 try { 144 PreparedStatement st = session.getBatcher().prepareSelectStatement( sql ); 145 try { 146 ResultSet rs = st.executeQuery(); 147 try { 148 rs.next(); 149 IntegralDataTypeHolder result = buildHolder(); 150 result.initialize( rs, 1 ); 151 if ( log.isDebugEnabled() ) { 152 log.debug("Sequence identifier generated: " + result); 153 } 154 return result; 155 } 156 finally { 157 rs.close(); 158 } 159 } 160 finally { 161 session.getBatcher().closeStatement(st); 162 } 163 164 } 165 catch (SQLException sqle) { 166 throw JDBCExceptionHelper.convert( 167 session.getFactory().getSQLExceptionConverter(), 168 sqle, 169 "could not get next sequence value", 170 sql 171 ); 172 } 173 } 174 175 protected IntegralDataTypeHolder buildHolder() { 176 return IdentifierGeneratorHelper.getIntegralDataTypeHolder( identifierType.getReturnedClass() ); 177 } 178 179 public String[] sqlCreateStrings(Dialect dialect) throws HibernateException { 180 String[] ddl = dialect.getCreateSequenceStrings(sequenceName); 181 if ( parameters != null ) { 182 ddl[ddl.length - 1] += ' ' + parameters; 183 } 184 return ddl; 185 } 186 187 public String[] sqlDropStrings(Dialect dialect) throws HibernateException { 188 return dialect.getDropSequenceStrings(sequenceName); 189 } 190 191 public Object generatorKey() { 192 return sequenceName; 193 } 194 195 public String getSequenceName() { 196 return sequenceName; 197 } 198 199 }
四、小结
此次并未对Hibernate源码进行深入剖析,只是业务实际场景上碰到了就想办法解决。当遇到框架受限时别急着放弃,重写源码或许是一条很好的出路。