没有主键和一般用途的 JPA 数字生成器
JPA number generators without primary key and general use
环境:OpenJPA2.x、Tomcat webapp、Mariadb,但可能会更改。这不是 Hibernate 或 Spring webapp。
我已经阅读了一些主题,例如:
Hibernate JPA Sequence (non-Id)
我有几个实体 类 有 someNumber 非主键字段,有些有 someNumber 和 someNumberB 双列。字段有一个约束 UNIQUE(someNumber) 和 UNIQUE(someNumberB),主键组合是 PRIMARY(server_id, code)。在提交行插入之前我需要一个数值。
如果我了解其他主题,我将无法使用 JPA @generator 标记。我被迫实施一种老式的实用方法。这是我做的一个方法,它需要一个新的数据库连接,所以它总是 运行 在一个单独的事务中。
public synchronized static long getGeneratorValue(String genName, int incStep) {
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
try {
if (genName==null) genName="app";
// try few times before give up, another node may have updated a value in-between. Create a new transaction from db connection pool.
for(int idx=0; idx<3; idx++) {
conn = createConnection(false); // autocommit=false
stmt = conn.createStatement();
rs = stmt.executeQuery(String.format("Select value From generators Where name='%s'", genName));
if (!rs.next()) throw new IllegalArgumentException("Invalid generator name " + genName);
if (incStep==0)
return rs.getLong("value"); // return an existing value
long oldValue = rs.getLong("value");
long newValue = oldValue+incStep;
int rowCount = stmt.executeUpdate(String.format("Update generators Set value=%d Where name='%s' and value=%d", newValue, genName, oldValue));
if (rowCount>0) {
conn.commit();
return newValue;
}
close(rs, stmt, conn);
conn=null;
}
throw new IllegalArgumentException("Obtaining a generator value failed " + genName);
} catch(Exception ex) {
try { conn.rollback(); } catch(Exception e){}
if (ex instanceof IllegalArgumentException) throw (IllegalArgumentException)ex;
else throw new IllegalArgumentException(ex.getMessage(), ex);
} finally {
if (conn!=null) close(rs, stmt, conn);
}
}
我对此并不完全满意,尤其是故障保护 foreach_loop 针对另一个 Tomcat 节点同时更新生成器值。此循环可能会在繁忙的工作负载上失败。
我可以使用 DB auto_increment 列作为通用数字生成器吗,我想它可以容忍更好的并发性?如果这将数据库锁定到 MariaDB,MySQL 或类似的数据库,我现在可以接受它。
值必须是用于遗留用途的数字字段,我不能使用 GUID 字符串值。
我在考虑使用 DB auto_increment 专栏并想出了这个实用函数。我可能会选择这个实现,或者 Whosebug 社区是否有更好的技巧可用?
CREATE TABLE generatorsB (
value bigint UNSIGNED NOT NULL auto_increment,
name varchar(255) NOT NULL,
PRIMARY KEY(value)
) ENGINE=InnoDB DEFAULT CHARACTER SET utf8 DEFAULT COLLATE utf8_swedish_ci;
// name=any comment such as an entityClass name, no real application use
public static long getGeneratorBValue(String name) {
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
try {
String sql = String.format("Insert Into generatorsB (name) Values('%s')", name);
conn = createConnection(false); // autocommit=false
stmt = conn.createStatement();
int rowCount = stmt.executeUpdate(sql, Statement.RETURN_GENERATED_KEYS);
if (rowCount>0) {
rs = stmt.getGeneratedKeys();
if (rs.next()) {
long newValue = rs.getLong(1);
if (newValue % 5 == 0) {
// delete old rows every 5th call, we don't need generator table rows
sql = "Delete From generatorsB Where value < "+ (newValue-5);
stmt.close();
stmt = conn.createStatement();
stmt.executeUpdate(sql, Statement.NO_GENERATED_KEYS);
}
conn.commit();
return newValue;
}
}
throw new IllegalArgumentException("Obtaining a generator value failed");
} catch(Exception ex) {
try { conn.rollback(); } catch(Exception e){}
if (ex instanceof IllegalArgumentException) throw (IllegalArgumentException)ex;
else throw new IllegalArgumentException(ex.getMessage(), ex);
} finally {
if (conn!=null) close(rs, stmt, conn);
}
}
环境:OpenJPA2.x、Tomcat webapp、Mariadb,但可能会更改。这不是 Hibernate 或 Spring webapp。
我已经阅读了一些主题,例如: Hibernate JPA Sequence (non-Id)
我有几个实体 类 有 someNumber 非主键字段,有些有 someNumber 和 someNumberB 双列。字段有一个约束 UNIQUE(someNumber) 和 UNIQUE(someNumberB),主键组合是 PRIMARY(server_id, code)。在提交行插入之前我需要一个数值。
如果我了解其他主题,我将无法使用 JPA @generator 标记。我被迫实施一种老式的实用方法。这是我做的一个方法,它需要一个新的数据库连接,所以它总是 运行 在一个单独的事务中。
public synchronized static long getGeneratorValue(String genName, int incStep) {
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
try {
if (genName==null) genName="app";
// try few times before give up, another node may have updated a value in-between. Create a new transaction from db connection pool.
for(int idx=0; idx<3; idx++) {
conn = createConnection(false); // autocommit=false
stmt = conn.createStatement();
rs = stmt.executeQuery(String.format("Select value From generators Where name='%s'", genName));
if (!rs.next()) throw new IllegalArgumentException("Invalid generator name " + genName);
if (incStep==0)
return rs.getLong("value"); // return an existing value
long oldValue = rs.getLong("value");
long newValue = oldValue+incStep;
int rowCount = stmt.executeUpdate(String.format("Update generators Set value=%d Where name='%s' and value=%d", newValue, genName, oldValue));
if (rowCount>0) {
conn.commit();
return newValue;
}
close(rs, stmt, conn);
conn=null;
}
throw new IllegalArgumentException("Obtaining a generator value failed " + genName);
} catch(Exception ex) {
try { conn.rollback(); } catch(Exception e){}
if (ex instanceof IllegalArgumentException) throw (IllegalArgumentException)ex;
else throw new IllegalArgumentException(ex.getMessage(), ex);
} finally {
if (conn!=null) close(rs, stmt, conn);
}
}
我对此并不完全满意,尤其是故障保护 foreach_loop 针对另一个 Tomcat 节点同时更新生成器值。此循环可能会在繁忙的工作负载上失败。
我可以使用 DB auto_increment 列作为通用数字生成器吗,我想它可以容忍更好的并发性?如果这将数据库锁定到 MariaDB,MySQL 或类似的数据库,我现在可以接受它。
值必须是用于遗留用途的数字字段,我不能使用 GUID 字符串值。
我在考虑使用 DB auto_increment 专栏并想出了这个实用函数。我可能会选择这个实现,或者 Whosebug 社区是否有更好的技巧可用?
CREATE TABLE generatorsB (
value bigint UNSIGNED NOT NULL auto_increment,
name varchar(255) NOT NULL,
PRIMARY KEY(value)
) ENGINE=InnoDB DEFAULT CHARACTER SET utf8 DEFAULT COLLATE utf8_swedish_ci;
// name=any comment such as an entityClass name, no real application use
public static long getGeneratorBValue(String name) {
Connection conn = null;
Statement stmt = null;
ResultSet rs = null;
try {
String sql = String.format("Insert Into generatorsB (name) Values('%s')", name);
conn = createConnection(false); // autocommit=false
stmt = conn.createStatement();
int rowCount = stmt.executeUpdate(sql, Statement.RETURN_GENERATED_KEYS);
if (rowCount>0) {
rs = stmt.getGeneratedKeys();
if (rs.next()) {
long newValue = rs.getLong(1);
if (newValue % 5 == 0) {
// delete old rows every 5th call, we don't need generator table rows
sql = "Delete From generatorsB Where value < "+ (newValue-5);
stmt.close();
stmt = conn.createStatement();
stmt.executeUpdate(sql, Statement.NO_GENERATED_KEYS);
}
conn.commit();
return newValue;
}
}
throw new IllegalArgumentException("Obtaining a generator value failed");
} catch(Exception ex) {
try { conn.rollback(); } catch(Exception e){}
if (ex instanceof IllegalArgumentException) throw (IllegalArgumentException)ex;
else throw new IllegalArgumentException(ex.getMessage(), ex);
} finally {
if (conn!=null) close(rs, stmt, conn);
}
}