关于Jorm的主键ID生成
> 引言
在Jorm中,主键的生成策略主要有AUTO、UUID、GUID、FOREIGN、SEQUENCE、INCREMENT、IDENTITY、ASSIGNED,下面分别来讲述这几种策略的应用场景
> GenerationType.AUTO
Jorm的默认主键策略,自动增长型,自增步长为1,适用数据类型int,long,如:
private int id // 默认策略就是AUTO,故可以不写主键策略
或
@Id(GenerationType.AUTO) // 默认策略可以省去不写的哦~
private int id
> GenerationType.INCREMENT
顾名思义,增长型,适用数据类型int,long。自增步长为1
1> 使用默认自增步长1,如:
@Id(GenerationType.INCREMENT)
@Column("item_id")
private long id;
2> 使用自定义步长,如:
@Id(value = GenerationType.INCREMENT, incrementBy=3) // 这里自增步长为3,注意写法
private int id;
> GenerationType.IDENTITY
对于那些实现了自动增长的数据库,可以使用IDENTITY,如MySQL,SQL Server,PostreSQL,前提是
MySQL数据库中建表语句定义了主键为:id(你的主键列名) int NOT NULL AUTO_INCREMENT 或
id(你的主键列名) bigint NOT NULL AUTO_INCREMENT
SQL Server数据库中建表语句定义了主键为:id int identity(xx, xx) 如此类似
PostreSQL数据库中建表语句定义了主键为:id bigserial 或 id serial
使用例子
@Id(GenerationType.IDENTITY)
@Column("id")
private long sid;
> GenerationType.UUID
与数据库无关的策略,适用数据类型:字符串类型,适用所有数据库,长度须大于或等于32
@Id(GenerationType.UUID)
private String id;
> GenerationType.GUID
与UUID有点类似,不过这个id值是又数据库来生成的,适用于数据库MySQL、PostgreSQL、SQL Server、Oracle等
@Id(GenerationType.GUID)
private String id;
> GenerationType.FOREIGN
适用于一对一关系中引用了另一个对象的主键作为自己的主键的情形,如:
@Id(GenerationType.FOREIGN)
@Column("identity_number")
private String identity;
> GenerationType.SEQUENCE
这个不用多说,应用于Oracle、H2、PostgreSQL等有sequence序列功能的数据库
> GenerationType.ASSIGNED
用户自定义生成,需要由程序员手工给主键主动赋值
项目地址:http://javaclub.sourceforge.net/jorm.html
下载地址:http://sourceforge.net/projects/javaclub/files/jorm/
2011年10月9日 #
使用Jorm进行批量操作
直接上代码吧:
> Demo one
public void batch_op_one() {
session = Jorm.getSession();
JdbcBatcher batcher = session.createBatcher();
batcher.addBatch("delete from t_id_auto");
batcher.addBatch("delete from t_incre");
batcher.addBatch("delete from t_user");
batcher.execute();
session.beginTransaction();
long start;
try {
start = System.currentTimeMillis();
String sql = "INSERT INTO t_user(sex,age,career,name,id) VALUES(?,?,?,?,?)";
for (int i = 0; i < 100000; i++) {
batcher.addBatch(sql, new Object[] {"男", Numbers.random(98), Strings.random(10), Strings.fixed(6), (i+1) });}
String sqlx = "INSERT INTO t_id_auto(name, id) VALUES(?, ?)";
for (int i = 0; i < 100000; i++) {
batcher.addBatch(sqlx, new Object[] {Strings.fixed(6), (i+1)});
if(i > 200) {
//Integer.parseInt("kkk");
}
}
batcher.execute();
System.out.println(System.currentTimeMillis() - start);
} catch (Exception e) {
session.rollback();
} finally {
session.endTransaction();
session.close();
}
}
> Demo two
public void batch_op_two() {
session = Jorm.getSession();
session.beginTransaction();
session.clean(User.class);
JdbcBatcher batcher = session.createBatcher();
batcher.setBatchSize(500);// 指定每批处理的记录数
User u;
int times = 20 * 100;
long start = System.currentTimeMillis();
for(int i = 0; i < times; i++) {
String sex = (i % 2 == 0 ? "男" : "女");
u = new User(Strings.fixed(6), sex, Numbers.random(100), Strings.random(16));
batcher.save(u);
}
batcher.execute();
session.endTransaction();
long cost = (System.currentTimeMillis() - start);
System.out.println("Total:" + cost);
System.out.println("Each:" + (float) cost / times);
session.close();
}
项目地址:http://javaclub.sourceforge.net/jorm.html
下载地址: http://sourceforge.net/projects/javaclub/files/jorm/
2011年9月27日 #
关系型数据库对对象继承关系的解决方案
关系数据库不支持继承,我们可以做如下的映射,这些映射都是牺牲关系模式的范式基础的
1, 用一个表包含所有继承层次的所有字段,然后标识列来标示是哪个类。这种映射方法最简单,但是是违反规范化的,而且有些字段要强制为NULL值,无法保证关系数据模型的数据完整性,这种映射方式性能最高,最简单。
2, 每个具体类一张表(意思就是父类不需要表),所有父属性在具体类表中重复,这种映射如果要查询父类要全部扫描子类表,而且一旦父类变化,这些字表要全部变化。
3, 每个类一张表,表里只包含所属类的属性,然后子类和父类共享外键,这种映射避免了第2种的可怕的修改,但是查询的时候要执行连接。
2011年9月25日 #
高并发数据库自增主键分析
在一般情况下,在新增领域对象后,都需要获取对应的主键值。使用应用层来维护主键,在一定程度上有利于程序性能的优化和应用移植性的提高。在采用数据库自增主键的方案里,如果JDBC驱动不能绑定新增记录对应的主键,就需要手工执行查询语句以获取对应的主键值,对于高并发的系统,这很容易返回错误的主键。通过带缓存的DataFieldMaxValueIncrementer,可以一次获取批量的主键值,供多次插入领域对象时使用,它的执行性能是很高的。
我们经常使用数据的自增字段作为表主键,也即主键值不在应用层产生,而是在新增记录时,由数据库产生。这样,应用层在保存对象前并不知道对象主键值,而必须在保存数据后才能从数据库中返回主键值。在很多情况下,我们需要获取新对象持久化后的主键值。在Hibernate等ORM框架,新对象持久化后,Hibernate会自动将主键值绑定到对象上,给程序的开发带来了很多方便。
在JDBC 3.0规范中,当新增记录时,允许将数据库自动产生的主键值绑定到Statement或PreparedStatement中。
使用Statement时,可以通过以下方法绑定主键值: int executeUpdate(String sql, int autoGeneratedKeys)
也可以通过Connection创建绑定自增值的PreparedStatement: PreparedStatement prepareStatement(String sql, int autoGeneratedKeys)
当autoGeneratedKeys参数设置为Statement.RETURN_GENERATED_KEYS值时即可绑定数据库产生的主键值,设置为Statement.NO_GENERATED_KEYS时,不绑定主键值。下面的代码演示了Statement绑定并获取数据库产生的主键值的过程:
Statement stmt = conn.createStatement();
String sql = "INSERT INTO t_topic(topic_title,user_id) VALUES(‘测试主题’,’123’)";
stmt.executeUpdate(sql,Statement.RETURN_GENERATED_KEYS); // ①指定绑定表自增主键值
ResultSet rs = stmt.getGeneratedKeys();
if( rs.next() ) {
intkey = rs.getInt(); // ②获取对应的表自增主键值
}
Spring利用这一技术,提供了一个可以返回新增记录对应主键值的方法: int update(PreparedStatementCreator psc, KeyHolder generatedKeyHolder) ,其中第二个参数类型org.springframework.jdbc.support.KeyHolder,它是一个回调接口,Spring使用它保存新增记录对应的主键,该接口的接口方法描述如下:
Number getKey() throws InvalidDataAccessApiUsageException;
当仅插入一行数据,主键不是复合键且是数字类型时,通过该方法可以直接返回新的主键值。如果是复合主键,或者有多个主键返回时,该方法抛出 InvalidDataAccessApiUsageException。该方法是最常用的方法,因为一般情况下,我们一次仅插入一条数据并且主键字段类型为数字类型;
如果是复合主键,则列名和列值构成Map中的一个Entry。如果返回的是多个主键,则抛出InvalidDataAccessApiUsageException异常;
Map getKeys() throws InvalidDataAccessApiUsageException;
如果返回多个主键,即PreparedStatement新增了多条记录,则每一个主键对应一个Map,多个Map构成一个List。
List getKeyList():
Spring为KeyHolder接口指代了一个通用的实现类GeneratedKeyHolder,该类返回新增记录时的自增长主键值。假设我们希望在新增论坛板块对象后,希望将主键值加载到对象中,则可以按以下代码进行调整:
public voidaddForum(final Forum forum) {
final String sql = "INSERT INTO t_forum(forum_name,forum_desc) VALUES(?,?)";
KeyHolder keyHolder = newGeneratedKeyHolder(); // ①创建一个主键执有者
getJdbcTemplate().update(newPreparedStatementCreator() {
public PreparedStatement createPreparedStatement(Connection conn) throws SQLException {
PreparedStatement ps = conn.prepareStatement(sql);
ps.setString(1, forum.getForumName());
ps.setString(2, forum.getForumDesc());
returnps;
}
}, keyHolder);
forum.setForumId(keyHolder.getKey().intValue()); // ②从主键执有者中获取主键
}
这样,在调用addForum(Forum forum)新增forum领域对象后,forum将拥有对应的主键值,方便后继的使用。在JDBC 3.0之前的版本中,PreparedStatement不能绑定主键,如果采用表自增键(如MySQL的auto increment或SQLServer的identity)将给获取正确的主键值带来挑战——因为你必须在插入数据后,马上执行另一条获取新增主键的查询语句。下面给出了不同数据库获取最新自增主键值的查询语句:
Hibernate 中的 IdentifierGenerator
1) Assigned
主键由外部程序负责生成,无需Hibernate参与。
2) hilo
通过hi/lo 算法实现的主键生成机制,需要额外的数据库表保存主键生成历史状态。
3) seqhilo
与hilo 类似,通过hi/lo 算法实现的主键生成机制,只是主键历史状态保存在Sequence中,适用于支持Sequence的数据库,如Oracle。
4) increment
主键按数值顺序递增。此方式的实现机制为在当前应用实例中维持一个变量,以保存着当前的最大值,之后每次需要生成主键的时候将此值加1作为主键。 这种方式可能产生的问题是:如果当前有多个实例访问同一个数据库,那么由于各个实例各自维护主键状态,不同实例可能生成同样的主键,从而造成主键重复异常。因此,如果同一数据库有多个实例访问,此方式必须避免使用。
5) identity
采用数据库提供的主键生成机制。如DB2、SQL Server、MySQL中的主键生成机制。
6) sequence
采用数据库提供的sequence 机制生成主键。如Oralce 中的Sequence。
7) native
由Hibernate根据底层数据库自行判断采用identity、hilo、sequence其中一种作为主键生成方式。
8) uuid.hex
由Hibernate基于128 位唯一值产生算法生成16 进制数值(编码后以长度32 的字符串表示)作为主键。
9) uuid.string
与uuid.hex 类似,只是生成的主键未进行编码(长度16)。在某些数据库中可能出现问题(如PostgreSQL)。
10) foreign
使用外部表的字段作为主键。一般而言,利用uuid.hex方式生成主键将提供最好的性能和数据库平台适应性。
另外由于常用的数据库,如Oracle、DB2、SQLServer、MySql 等,都提供了易用的主键生成机制(Auto-Increase 字段或者Sequence)。我们可以在数据库提供的主键生成机制上,采用generator-class=native的主键生成方式。不过值得注意的是,一些数据库提供的主键生成机制在效率上未必最佳,
大量并发insert数据时可能会引起表之间的互锁。数据库提供的主键生成机制,往往是通过在一个内部表中保存当前主键状态(如对于自增型主键而言,此内部表中就维护着当前的最大值和递增量), 之后每次插入数据会读取这个最大值,然后加上递增量作为新记录的主键,之后再把这个新的最大值更新回内部表中,这样,一次Insert操作可能导致数据库内部多次表读写操作,同时伴随的还有数据的加锁解锁操作,这对性能产生了较大影响。 因此,对于并发Insert要求较高的系统,推荐采用uuid.hex 作为主键生成机制。
如果需要采用定制的主键生成算法,则在此处配置主键生成器,主键生成器须实现org.hibernate.id.IdentifierGenerator 接口
关键词: Hibernate 主键 主键生成方式 IdentifierGenerator
2011年9月23日 #
使用Jorm处理Oracle存储过程返回多个游标
> 引言
日常开发中,使用Oracle存储过程,有时候会处理返回多个游标的情况,下面介绍使用 Jorm 框架来处理这一情况
> 数据库准备
1.表
CREATE
TABLE
`t_user` (
`id`
int
(
11
)
NOT
NULL
,
`name`
varchar
(
50
)
DEFAULT
NULL
,
`sex`
char
(
4
)
DEFAULT
NULL
,
`age`
int
(
11
)
DEFAULT
NULL
,
`career`
varchar
(
100
)
DEFAULT
NULL
,
PRIMARY
KEY
(`id`)
) ENGINE
=
InnoDB
DEFAULT
CHARSET
=
utf8;
2.存储过程
--
定义存储过程
CREATE
OR
REPLACE
PROCEDURE
pro_query_users
(
--
参数IN表示输入参数,OUT表示输入参数,类型可以使用任意Oracle中的合法类型。
in_id
IN
NUMBER
,
out_cursor_one OUT package_one.cursor_one,
out_cursor_two OUT package_two.cursor_two
)
AS
--
定义变量
vs_id_value
NUMBER
;
--
变量
BEGIN
--
用输入参数给变量赋初值。
vs_id_value:
=
in_id;
OPEN
out_cursor_one
FOR
SELECT
*
FROM
t_user
WHERE
id
>
vs_id_value;
OPEN
out_cursor_two
FOR
SELECT
*
FROM
t_user
WHERE
name
LIKE
'
%a%
'
;
--
错误处理部分。OTHERS表示除了声明外的任意错误。SQLERRM是系统内置变量保存了当前错误的详细信息。
Exception
WHEN
OTHERS
Then
ROLLBACK
;
Return
;
End
pro_query_users;
> 代码
1.实体类 User.java
import
org.javaclub.jorm.annotation.Entity;
import
org.javaclub.jorm.annotation.Id;
import
org.javaclub.jorm.annotation.NoColumn;
import
org.javaclub.jorm.annotation.PK;
@Entity(table
=
"
t_user
"
, lazy
=
true
)
@PK(value
=
"
id
"
)
public
class
User {
@Id
private
int
id;
private
String name;
private
String sex;
private
Integer age;
private
String career;
@NoColumn
private
int
kvalue;
public
User() {
super
();
}
public
User(String name, String sex, Integer age, String[] career) {
super
();
this
.name
=
name;
this
.sex
=
sex;
this
.age
=
age;
this
.career
=
career;
}
public
int
getId() {
return
id;
}
public
void
setId(
int
id) {
this
.id
=
id;
}
public
String getName() {
return
name;
}
public
void
setName(String name) {
this
.name
=
name;
}
public
String getSex() {
return
sex;
}
public
void
setSex(String sex) {
this
.sex
=
sex;
}
public
Integer getAge() {
return
age;
}
public
void
setAge(Integer age) {
this
.age
=
age;
}
public
String[] getCareer() {
return
career;
}
public
void
setCareer(String[] career) {
this
.career
=
career;
}
public
int
getKvalue() {
return
kvalue;
}
public
void
setKvalue(
int
kvalue) {
this
.kvalue
=
kvalue;
}
public
String toString() {
StringBuffer sb
=
new
StringBuffer();
sb.append(
"
[
"
+
id
+
"
,
"
+
name
+
"
,
"
+
sex
+
"
,
"
+
age
+
"
,
"
+
career
+
"
]
"
);
return
sb.toString();
}
}
2.测试
import
java.sql.CallableStatement;
import
java.sql.ResultSet;
import
java.sql.SQLException;
import
java.sql.Types;
import
java.util.ArrayList;
import
java.util.List;
import
org.javaclub.jorm.Jorm;
import
org.javaclub.jorm.Session;
import
org.javaclub.jorm.common.Numbers;
import
org.javaclub.jorm.common.Strings;
import
org.javaclub.jorm.demos.entity.User;
import
org.javaclub.jorm.jdbc.callable.ProcedureCaller;
import
org.javaclub.jorm.jdbc.sql.SqlParams;
import
org.junit.AfterClass;
import
org.junit.Assert;
import
org.junit.BeforeClass;
import
org.junit.Test;
/**
* ProcedureTest
*
*
@author
Gerald Chen
*
@version
$Id: ProcedureTest.java 2011-8-25 下午06:18:17 Exp $
*/
public
class
ProcedureTest {
static
Session session;
@BeforeClass
public
static
void
setUpBeforeClass()
throws
Exception {
session
=
Jorm.getSession();
}
@AfterClass
public
static
void
destroyAfterClass() {
Jorm.free();
}
@Test
public
void
save_user() {
session.clean(User.
class
);
User user
=
null
;
for
(
int
i
=
0
; i
<
1600
; i
++
) {
String sex
=
(i
%
2
==
0
?
"
男
"
:
"
女
"
);
user
=
new
User(Strings.fixed(
5
), sex, Numbers.random(
98
), Strings.random(
8
));
session.save(user);
}
}
@Test
public
void
oracle_load_two_cursor() {
save_user();
final
String pro
=
"
{call pro_query_users(?, ?, ?)}
"
;
final
List
<
User
>
gtIdUsers
=
new
ArrayList
<
User
>
();
final
List
<
User
>
likeNameUsers
=
new
ArrayList
<
User
>
();
session.call(
new
ProcedureCaller() {
public
CallableStatement prepare()
throws
SQLException {
CallableStatement cs
=
this
.getSession().getConnection().prepareCall(pro);
cs.setInt(
1
,
20
);
cs.registerOutParameter(
2
,oracle.jdbc.OracleTypes.CURSOR);
cs.registerOutParameter(
3
,oracle.jdbc.OracleTypes.CURSOR);
return
cs;
}
public
String callback(CallableStatement cs)
throws
SQLException {
cs.execute();
ResultSet rsOne
=
(ResultSet) cs.getObject(
2
);
//
返回第一个游标
ResultSet rsTwo
=
(ResultSet) cs.getObject(
3
);
//
返回第二个游标
while
(rsOne
!=
null
&&
rsOne.next()) {
gtIdUsers.add(session.getPersister().toBean(rsOne, User.
class
));
}
while
(rsTwo
!=
null
&&
rsTwo.next()) {
likeNameUsers.add(session.getPersister().toBean(rsTwo, User.
class
));
}
return
null
;
}
});
Assert.assertTrue(gtIdUsers.size()
>
0
);
System.out.println(gtIdUsers.size()
+
"
=>
"
+
gtIdUsers);
Assert.assertTrue(likeNameUsers.size()
>
0
);
System.out.println(likeNameUsers.size()
+
"
=>
"
+
likeNameUsers);
}