一、 JPA通用策略生成器
通过annotation来映射hibernate实体的,基于annotation的hibernate主键标识为@Id,
其生成规则由@GeneratedValue设定的.这里的@id和@GeneratedValue都是JPA的标准用法,
JPA提供四种标准用法,由@GeneratedValue的源代码可以明显看出.
public enumGenerationType{
TABLE,
SEQUENCE,
IDENTITY,
AUTO
}
JPA提供的四种标准用法为TABLE,SEQUENCE,IDENTITY,AUTO.
TABLE:使用一个特定的数据库表格来保存主键。
SEQUENCE:根据底层数据库的序列来生成主键,条件是数据库支持序列。
IDENTITY:主键由数据库自动生成(主要是自动增长型)
AUTO:主键由程序控制。
1、TABLE
@Id
@GeneratedValue(strategy = GenerationType.TABLE,generator="payablemoney_gen")
@TableGenerator(name = "pk_gen",
table="tb_generator",
pkColumnName="gen_name",
valueColumnName="gen_value",
pkColumnValue="PAYABLEMOENY_PK",
allocationSize=1
)
这里应用表tb_generator,定义为
CREATE TABLE tb_generator(
id NUMBER NOT NULL,
gen_name VARCHAR2(255)NOT NULL,
gen_value NUMBER NOTNULL,
PRIMARY KEY(id)
)
INSERT INTO tb_generator(id, gen_name, gen_value) VALUES(1,'PAYABLEMOENY_PK', 1);
在主键生成后,这条纪录的value值,按allocationSize递增
2、SEQUENCE
@Id
@GeneratedValue(strategy =GenerationType.SEQUENCE,generator="payablemoney_seq")
@SequenceGenerator(name="payablemoney_seq",sequenceName="seq_payment")
3、IDENTITY
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
4、AUTO
@Id
@GeneratedValue(strategy = GenerationType.AUTO)
在指定主键时,如果不指定主键生成策略,默认为AUTO。
二、hibernate主键策略生成器
hibernate提供多种主键生成策略,有点是类似于JPA,有的是hibernate特有:
assigned: 在插入数据的时候主键由程序处理(很常用),无需Hibernate参与。如果要由程序代码来指定主键,就采有这种.这是 <generator>元素没有指定时的默认生成策略。等同于JPA中的AUTO。
increment: 插入数据的时候hibernate会给主键添加一个自增的主键,但是一个hibernate实例就维护一个计数器,所以在多个实例运行的时候不能使用这个方法。
对 long ,short 或 int 的数据列生成自动增长主键。increment主键生成方式的特点是与底层数据库无关性,大部分数据库如 Mysql,MSSQL和ORACLE等都支持increament生成方式。此方式的实现机制为在当前应用实例中维持一个变量,以保存着当前的最大值,之后每次需要生成主键的 时候将此值加1作为主键。increment方式的不足之处是当多个线程并发对数据库表进行写操作时,可能出现相同的主键值,发生主键重复的冲突,因此多线程并发操作时,不应该使用此方法。
identity: 使用SQL Server 和MySQL 的自增字段,这个方法不能放到 Oracle 中,Oracle不支持自增字段,要设定sequence(MySQL 和 SQL Server 中很常用)。
等同于JPA中的INDENTITY。
如果数据列的类型是 long, short 或 int ,可使用主键生成器生成自动增长Hibernate主键。与底层数据库有关,要求数据库支持identify,如MySQL中是 auto_increment,SQL Server中是Identify.支持的数据库有MySQL,SQL Server,DB2,Sybase和HypersonicSQL.(好像不支持oracle) 无需Hibernate和用户的干涉,使用较为方便,但不便于在不同的数据库之间移植程序。identity的优点是不会发生increment方式的并发错做问题。数据库涉及到的表要设置自动增长。
sequence: 调用底层数据库的序列来生成主键,要设定序列名,不然hibernate无法找到。
hilo: 使用hi/lo 算法生成策略,要在数据库中建立一张额外的表,默认表名为hibernate_unique_key,默认字段为integer类型,名称是 next_hi(比较少用)。这样生成的标识符只在特定的数据库是唯一的,在使用JTA(Java分布式事务) 获得链接或用户自定义提供的链接中,不要使用这种生成器。hilo方式需要维护表信息,因此对数据的影响的要率会造成一定影响。
seqhilo: 通过hilo算法实现,但是主键历史保存在Sequence中,适用于支持 Sequence 的数据库,如 Oracle(比较少用)
native: 由Hibernate根据不同的数据库方言,自行判断采用identity、hilo、sequence其中一种作为Hibernate主键生成方式,native的 优点是与底层性无关,便于不同数据库之间的移植,由Hibernate根据不同数据库选择主键的生成方式。对于 oracle 采用Sequence 方式,对于MySQL 和 SQL Server采用identity(自增主键生成机制),native就是将主键的生成工作交由数据库完成,hibernate不管(很常用)。
uuid: 采用128位的uuid算法生成主键,uuid被编码为一个32位16进制数字的字符串。占用空间大(字符串类型)。
select: 使用触发器生成主键(主要用于早期的数据库主键生成机制,少用)。
foreign: 使用另外一个相关联的对象的主键。通常和<one-to-one>联合起来使用。
guid: 采用数据库底层的guid算法机制,对应MYSQL的uuid()函数,SQL Server的newid()函数,ORACLE的rawtohex(sys_guid())函数等。
uuid.hex: 看uuid,建议用uuid替换。
sequence-identity: sequence策略的扩展,采用立即检索策略来获取sequence值,需要JDBC3.0和JDK4以上(含1.4)版本
hibernate提供了多种生成器供选择,基于Annotation的方式通过@GenericGenerator实现.
hibernate每种主键生成策略提供接口org.hibernate.id.IdentifierGenerator的实现类,如果要实现自定义的主键生成策略也必须实现此接口.
public interface IdentifierGenerator {
/**
* The configurationparameter holding the entity name
*/
public static finalString ENTITY_NAME = "entity_name";
/**
* Generate a new identifier.
* @param session
* @param object theentity or toplevel collection for which the id is being generated
* @return a newidentifier
* @throwsHibernateException
*/
public Serializablegenerate(SessionImplementor session, Object object)
throwsHibernateException;
}
dentifierGenerator提供一generate方法,generate方法返回产生的主键.
三、@GenericGenerator
自定义主键生成策略,由@GenericGenerator实现。
hibernate在JPA的基础上进行了扩展,可以用一下方式引入hibernate独有的主键生成策略,就是通过@GenericGenerator 加入的。
比如说,JPA标准用法
@Id
@GeneratedValue(GenerationType.AUTO)
就可以用hibernate特有以下用法来实现
@GeneratedValue(generator ="paymentableGenerator")
@GenericGenerator(name = "paymentableGenerator",strategy = "assigned")
@GenericGenerator的定义:
@Target({PACKAGE, TYPE, METHOD, FIELD})
@Retention(RUNTIME)
public @interface GenericGenerator {
/**
* unique generator name
*/
String name();
/**
* Generator strategyeither a predefined Hibernate
* strategy or a fullyqualified class name.
*/
String strategy();
/**
* Optional generatorparameters
*/
Parameter[] parameters()default {};
}
name属性指定生成器名称。
strategy属性指定具体生成器的类名。
parameters得到strategy指定的具体生成器所用到的参数。
对于这些hibernate主键生成策略和各自的具体生成器之间的关系,在 org.hibernate.id.IdentifierGeneratorFactory中指定了,
static {
GENERATORS.put("uuid", UUIDHexGenerator.class);
GENERATORS.put("hilo", TableHiLoGenerator.class);
GENERATORS.put("assigned", Assigned.class);
GENERATORS.put("identity",IdentityGenerator.class);
GENERATORS.put("select", SelectGenerator.class);
GENERATORS.put("sequence", SequenceGenerator.class);
GENERATORS.put("seqhilo", SequenceHiLoGenerator.class);
GENERATORS.put("increment", IncrementGenerator.class);
GENERATORS.put("foreign", ForeignGenerator.class);
GENERATORS.put("guid", GUIDGenerator.class);
GENERATORS.put("uuid.hex", UUIDHexGenerator.class); //uuid.hexis deprecated
GENERATORS.put("sequence-identity", SequenceIdentityGenerator.class);
}
上面十二种策略,加上native,hibernate一共默认支持十三种生成策略。
1、native
@GeneratedValue(generator ="paymentableGenerator")
@GenericGenerator(name = "paymentableGenerator",strategy = "native")
2、uuid
@GeneratedValue(generator = "paymentableGenerator")
@GenericGenerator(name = "paymentableGenerator",strategy = "uuid")
3、hilo
@GeneratedValue(generator ="paymentableGenerator")
@GenericGenerator(name = "paymentableGenerator",strategy = "hilo")
4、assigned
@GeneratedValue(generator ="paymentableGenerator")
@GenericGenerator(name = "paymentableGenerator",strategy = "assigned")
5、identity
@GeneratedValue(generator ="paymentableGenerator")
@GenericGenerator(name = "paymentableGenerator",strategy = "identity")
6、select
@GeneratedValue(generator ="paymentableGenerator")
@GenericGenerator(name="paymentableGenerator",strategy="select", parameters = { @Parameter(name = "key",value = "idstoerung") })
7、sequence
@GeneratedValue(generator = "paymentableGenerator")
@GenericGenerator(name = "paymentableGenerator",strategy = "sequence", parameters = { @Parameter(name = "sequence", value ="seq_payablemoney") })
8、seqhilo
@GeneratedValue(generator ="paymentableGenerator")
@GenericGenerator(name = "paymentableGenerator",strategy = "seqhilo", parameters = { @Parameter(name ="max_lo", value = "5") })
9、increment
@GeneratedValue(generator ="paymentableGenerator")
@GenericGenerator(name = "paymentableGenerator",strategy = "increment")
10、foreign
@GeneratedValue(generator = "idGenerator")
@GenericGenerator(name = "idGenerator", strategy ="foreign", parameters = { @Parameter(name = "property",value = "employee") })
注意:直接使用@PrimaryKeyJoinColumn 报错
@OneToOne(cascade = CascadeType.ALL)
@PrimaryKeyJoinColumn
例如
@Entity
public class Employee {
@Id Integer id;
@OneToOne@PrimaryKeyJoinColumn
EmployeeInfo info;
...
}
应该为
@Entity
public class Employee {
@Id
@GeneratedValue(generator = "idGenerator")
@GenericGenerator(name ="idGenerator", strategy = "foreign", parameters = {@Parameter(name = "property", value = "info") })
Integer id;
@OneToOne
EmployeeInfo info;
...
}
11、guid
@GeneratedValue(generator ="paymentableGenerator")
@GenericGenerator(name = "paymentableGenerator",strategy = "guid")
12、uuid.hex
@GeneratedValue(generator ="paymentableGenerator")
@GenericGenerator(name = "paymentableGenerator",strategy = "uuid.hex")
13、sequence-identity
@GeneratedValue(generator ="paymentableGenerator")
@GenericGenerator(name = "paymentableGenerator",strategy = "sequence-identity", parameters = { @Parameter(name = "sequence", value ="seq_payablemoney") })
四、通过@GenericGenerator自定义主键生成策略
如果实际应用中,主键策略为程序指定了就用程序指定的主键(assigned),没有指定就从sequence中取。
明显上面所讨论的策略都不满足,只好自己扩展了,集成assigned和sequence两种策略。
public class AssignedSequenceGenerator extends SequenceGeneratorimplements PersistentIdentifierGenerator, Configurable {
private StringentityName;
public voidconfigure(Type type, Properties params, Dialect dialect) throwsMappingException {
entityName =params.getProperty(ENTITY_NAME);
if (entityName==null){
throw newMappingException("no entity name");
}
super.configure(type,params, dialect);
}
public Serializablegenerate(SessionImplementor session, Object obj)throws HibernateException{
Serializable id =session.getEntityPersister( entityName, obj ).getIdentifier( obj,session.getEntityMode() );
if (id==null) {
id =super.generate(session, obj);
}
return id;
}
}
@GeneratedValue(generator ="paymentableGenerator")
@GenericGenerator(name = "paymentableGenerator",strategy = "AssignedSequenceGenerator",parameters = { @Parameter(name= "sequence", value = "seq_payablemoney") })
public final class IdentifierGeneratorFactory {
//注册所有支持的ID生成策略
private static finalHashMap GENERATORS = new HashMap();
static {
GENERATORS.put( "uuid",UUIDHexGenerator.class );
GENERATORS.put( "hilo",TableHiLoGenerator.class );
GENERATORS.put( "assigned",Assigned.class );
GENERATORS.put( "identity",IdentityGenerator.class );
GENERATORS.put( "select",SelectGenerator.class );
GENERATORS.put( "sequence",SequenceGenerator.class );
GENERATORS.put( "seqhilo",SequenceHiLoGenerator.class );
GENERATORS.put( "increment",IncrementGenerator.class );
GENERATORS.put( "foreign",ForeignGenerator.class );
GENERATORS.put( "guid",GUIDGenerator.class );
GENERATORS.put( "uuid.hex",UUIDHexGenerator.class ); // uuid.hex is deprecated
GENERATORS.put("sequence-identity", SequenceIdentityGenerator.class );
}
public staticIdentifierGenerator create(String strategy, Type type, Properties params,Dialect dialect)throws MappingException {
try {
Class clazz = getIdentifierGeneratorClass(strategy, dialect );
...
}
public static ClassgetIdentifierGeneratorClass(String strategy, Dialect dialect) {
Class clazz = ( Class ) GENERATORS.get(strategy );
...
}
}
显然create() 方法是用于创建ID生成器的, 而且用到了参数strategy和dialect. Hibernate在初始化SessionFactory的时候就会准备这些ID生成器. 见以下代码
SessionFactoryImpl(){
Iterator classes =cfg.getClassMappings();
while (classes.hasNext() ) {
PersistentClassmodel = (PersistentClass) classes.next();
if (!model.isInherited() ) {
IdentifierGeneratorgenerator =model.getIdentifier().createIdentifierGenerator(settings.getDialect(),settings.getDefaultCatalogName(),settings.getDefaultSchemaName(),(RootClass)model);
identifierGenerators.put(model.getEntityName(), generator );
}
}
model.getIdentifier().createIdentifierGenerator() 最终会引用到
returnIdentifierGeneratorFactory.create(identifierGeneratorStrategy,getType(),params,dialect);
1) uuid: 是采用128位的算法生成惟一值,支持大部分的数据库
public Serializable generate(SessionImplementor session, Objectobj) {
return newStringBuffer(36)
.append( format( getIP()) ).append(sep)
.append( format(getJVM() ) ).append(sep)
.append( format(getHiTime() ) ).append(sep)
.append( format(getLoTime() ) ).append(sep)
.append( format(getCount() ) ) //注: 每次加1, JVM内唯一, 通过synchronized来保证实现
.toString();
}
protected String format(int intval) {
String formatted =Integer.toHexString(intval);
StringBuffer buf = newStringBuffer("00000000");
buf.replace(8-formatted.length(), 8, formatted );
return buf.toString();
}
protected String format(short shortval) {
String formatted =Integer.toHexString(shortval);
StringBuffer buf = newStringBuffer("0000");
buf.replace(4-formatted.length(), 4, formatted );
return buf.toString();
2)GUID: 通过使用数据库本身的uuid算法来实现
public class GUIDGenerator implements IdentifierGenerator {
public Serializablegenerate(SessionImplementor session, Object obj)
throwsHibernateException {
final String sql =session.getFactory().getDialect().getSelectGUIDString();
...
}
假如getDialect()返回的是MySQLDialect,则返回的是
public String getSelectGUIDString() {
return "selectuuid()";
}
但是如果不支持 uuid的数据库,则抛出异常
public String getSelectGUIDString() {
throw newUnsupportedOperationException( "dialect does not support GUIDs" );
}
3)increment:
public class IncrementGenerator implements IdentifierGenerator,Configurable {
...
public synchronized Serializable generate(SessionImplementorsession, Object object) throws HibernateException {
if (sql!=null) {
getNext( session ); //注:使用了一个sql: "select max(" +column + ") from " + buf.toString();
}
returnIdentifierGeneratorFactory.createNumber(next++, returnClass);
}
}
留意这里对generate方法使用了同步,可想如果所有ID都通过hibernate创建, 则是安全的...
4) foreign key 简而言之, 就是要取到关联的对象的ID
foreign-key的配置稍微繁琐一点, 附上一个例子:对于帖子(Post)和评论(Comment), Comment表有一个外键fk_post, comment.post_id关联到Post.id.那么在Comment.hbm.xml中就可以这样配置
Comment.hbm.xml
------------------------------------------------------------
<hibernate-mapping package="work">
<classname="Comment" lazy="false">
<idname="id">
<generatorclass="foreign">
<paramname="property">post</param>
</generator>
</id>
...
<many-to-onename="post" column="post_id"></many-to-one>
</class>
</hibernate-mapping>
hibernate源代码:
------------------------------------------------------------
public Serializable generate(SessionImplementorsessionImplementor, Object object)
throws HibernateException {
//注:这里object是Comment对象
Session session =(Session) sessionImplementor;
//注:这里associatedObject是 Post对象
Object associatedObject= sessionImplementor.getFactory()
.getClassMetadata( entityName )
.getPropertyValue( object, propertyName, session.getEntityMode() );
if ( associatedObject== null ) {
throw newIdentifierGenerationException(
"attempted toassign id from null one-to-one property: " +
propertyName
);
}
EntityType type =(EntityType) sessionImplementor.getFactory()
.getClassMetadata(entityName )
.getPropertyType(propertyName );
Serializable id;
try {
id =ForeignKeys.getEntityIdentifierIfNotUnsaved(
type.getAssociatedEntityName(),
associatedObject,
sessionImplementor
);
}
catch(TransientObjectException toe) {
id = session.save(type.getAssociatedEntityName(), associatedObject );
//注:尝试保存该对象来生成ID, 这个操作可能触发一系列其他的东西,如事件, 缓存写入等等
}
if (session.contains(object) ) {
//abort the save (theobject is already saved by a circular cascade)
returnIdentifierGeneratorFactory.SHORT_CIRCUIT_INDICATOR;
//throw newIdentifierGenerationException("save associated object first, or disablecascade for inverse association");
}
return id;
}
5) Identity: 利用数据库的自增长方式来生成ID
相比前面的策略, 这是很有意思的ID生成策略, 因为hibernate并不能在insert前预先获得ID, 而是在insert后,依赖于JDBC API的PreparedStatement.getGeneratedKeys()方法来取得ID, 该方法返回的是一个ResultSet, 只有一列, 名称为GENERATED_KEY. 所以Hibernate也是采用一种后置处理的方式: 即在调用到IdentifierGenerator.getnerate()方法的时候(其实这个时候的实现是IdentityGenerator类) , 直接返回一个Serilizable对象--IdentifierGeneratorFactory.POST_INSERT_INDICATOR.接着, 在我们使用session.save(object)方法的时候, 会判断save操作的类型是否为IdentifierGeneratorFactory.POST_INSERT_INDICATOR,再进行相应处理.
save部分代码量太大, 免了.看一些关键的.
先是"奇怪"的generate()方法
public abstract class AbstractPostInsertGenerator implementsPostInsertIdentifierGenerator {
public Serializablegenerate(SessionImplementor s, Object obj) {
returnIdentifierGeneratorFactory.POST_INSERT_INDICATOR;
}
}
public class IdentityGenerator extends AbstractPostInsertGenerator{
.. //没有覆盖generate()
}
然后是session.save()对应的事件监听器 AbstractSaveEventListener 的saveWithGeneratedId()
protected Serializable saveWithGeneratedId(...){
...
if ( generatedId == null ) {
throw newIdentifierGenerationException( "null id generated for:" +entity.getClass() );
}else if ( generatedId ==IdentifierGeneratorFactory.SHORT_CIRCUIT_INDICATOR ) {
returnsource.getIdentifier( entity );
}else if ( generatedId ==IdentifierGeneratorFactory.POST_INSERT_INDICATOR ) {
return performSave(entity, null, persister, true, anything, source, requiresImmediateIdAccess );
}
...
}
该方法一直执行到protected SerializableperformSaveOrReplicate(...)方法的
if ( useIdentityColumn ) {
EntityIdentityInsertAction insert = new EntityIdentityInsertAction(values, entity, persister, source, shouldDelayIdentityInserts);
if (!shouldDelayIdentityInserts ) {
log.debug("executing identity-insert immediately" );
source.getActionQueue().execute(insert ); //这里有文章,hibernate已先把先前的操作先转换成sql执行
经过N多处理后,最后回到刚才提到的JDBC API上. 在IdentityGenerator.GetGeneratedKeysDelegate子类中
public Serializable executeAndExtract(PreparedStatement insert)throws SQLException {
insert.executeUpdate();
ResultSet rs = null;
try {
rs =insert.getGeneratedKeys();
returnIdentifierGeneratorFactory.getGeneratedIdentity(rs,persister.getIdentifierType());
...
}
一种方法是通过hibernate传统的xml映射方式去调用
<class name="com.test.User" table="proctab">
<id name="id" column="id">
<generator class="native"/>
</id>
<property name="name" column="name" type="string" />
<property name="age" column="age" type="integer" />
</class>
<sql-query name="getUser" callable="true">
<return alias="user" class="com.test.User">
<return-property name="id" column="id" />
<return-property name="name" column="name" />
<return-property name="age" column="age" />
</return>
{call proc()}
</sql-query>
Session ss= HibernateSessionFactory.getSession()
List li=ss.getNamedQuery("getUser").list(); //要求你的存储过程必须能返回记录集,否则要出错
ss.close();
类似jdbc的方法
Session session =HibernateSessionFactory.getSession();
Connection conn = session.connection();
ResultSet rs =null;
CallableStatement call = conn.prepareCall("{Call proc()}");
rs = call.executeQuery();
rs.close();
session.close()
还有就是通过createSQLQuery来实现
SQLQuery query = session.createSQLQuery("{Call proc(?)}");
query.setString(0, 参数);
List list =query.list();
如果你的存储过程是完成非查询任务就应该在配置文件用以下三个标签
<sql-insert callable="true">{call createPerson (?, ?)}</sql-insert>
<sql-delete callable="true">{? = call deletePerson (?)}</sql-delete>
<sql-update callable="true">{? = call updatePerson (?, ?)}</sql-update>
有一点不好的地方就是调用存储过程获取记录集时,不能对Query使用
setFirstResult(int)和 setMaxResults(int)方法来分页