在一个关系数据库中,所有的数据都是存储在表里的,每一个表都有一个主键(Primary Key)。对大多数的用户输入数据来讲,主键需要由系统以序列号的方式产生(大多数),而不是由操作人员给出。
某些关系数据库引擎提供某种序列键生成机制。如SQL Server允许每一个表内可以有一个自动编号列。Oracle提供Sequence对象,可以提供序列键值。
主键:它的值用于惟一地标识表中的某一条记录
但某些数据库引擎则没有相应的机制,如Sybase。这时就需要我们自己去生成主键序列号。通常的做法是使用一个表来存储所有的主键最大值。这个表包含两个列,一个列存放键名,另一个列存放键值.
主键生成器的使用环境
1.主键自动增长序列,这时需要一个主键生成器
2.大数据量表中,查询主键最大值,我们需要一个主键生成器跟踪数据表中的主键状态,当需要查询数据表中的记录数时,只需要查询一下主键维护表就可以迅速得出结果.
主键生成器的代码(Java)实现
/**
* id生成器
* @author Administrator
*
*/
public class IdGenerator {
/**
* 根据表名生成该表的序列
* @param tableName
* @return 返回生成的序列
*/
public static int generate(String tableName) {
//使用数据库的悲观锁for update
String sql = "select value from t_table_id where table_name=? for update";
Connection conn = null;
PreparedStatement pstmt = null;
ResultSet rs = null;
int value = 0;
try {
conn = DbUtil.getConnection();
//开始事务
DbUtil.beginTransaction(conn);
pstmt = conn.prepareStatement(sql);
pstmt.setString(1, tableName);
rs = pstmt.executeQuery();
if (!rs.next()) {
throw new RuntimeException();
}
value = rs.getInt("value");
value++; //自加
modifyValueField(conn, tableName, value);
//提交事务
DbUtil.commitTransaction(conn);
}catch(Exception e) {
e.printStackTrace();
//回滚事务
DbUtil.rollbackTransaction(conn);
throw new RuntimeException();
}finally {
DbUtil.close(rs);
DbUtil.close(pstmt);
DbUtil.resetConnection(conn); //重置Connection的状态
DbUtil.close(conn);
}
return value;
}
/**
* 根据表名更新序列字段的值
* @param conn
* @param tableName
* @param value
*/
private static void modifyValueField(Connection conn, String tableName, int value)
throws SQLException {
String sql = "update t_table_id set value=? where table_name=?";
PreparedStatement pstmt = null;
try {
pstmt = conn.prepareStatement(sql);
pstmt.setInt(1, value);
pstmt.setString(2, tableName);
pstmt.executeUpdate();
}finally {
DbUtil.close(pstmt);
}
}
}
上述代码中,需要注意的是,主键生成器的多线程问题使用了"for updat"悲观锁来解决.悲观锁是采用数据库机制实现的,数据被锁住之后其他用户将无法查看,直到锁释放,只有提交或回滚事务锁才会释放.for update语句只能放到select语句中,因为查询时把数据锁住才有意义.悲观锁的使用仅是将查询的一条数据锁住,而不是整个主键维护表.