注:本文提到了为何不用数据库的自增特性.
概述
在一般情况下,在新增领域对象后,都需要获取对应的主键值。使用应用层来维护主键,在一定程度上有利于程序性能的优化和应用移植性的提高。在采用数据库自增主键的方案里,如果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() ) {
int key = rs.getInt();②获取对应的表自增主键值
}
Spring利用这一技术,提供了一个可以返回新增记录对应主键值的方法:
int update(PreparedStatementCreator psc, KeyHolder generatedKeyHolder)
org.springframework.jdbc.support.KeyHolder是一个回调接口,Spring使用它保存新增记录对应的主键,该接口的接口方法描述如下:
Number getKey() throws InvalidDataAccessApiUsageException
当仅插入一行数据,主键不是复合键且是数字类型时,通过该方法可以直接返回新的主键值。如果是复合主键,或者有多个主键返回时,该方法抛出InvalidDataAccessApiUsageException。该方法是最常用的方法,因为一般情况下,我们一次仅插入一条数据并且主键字段类型为数字类型;
Map getKeys() throws InvalidDataAccessApiUsageException
如果是复合主键,则列名和列值构成Map中的一个Entry。如果返回的是多个主键,则该方法抛出InvalidDataAccessApiUsageException异常;
List getKeyList():
如果返回多个主键,即PreparedStatement新增了多条记录,则每一个主键对应一个Map,多个Map构成一个List。
Spring为KeyHolder接口指代了一个通用的实现类GeneratedKeyHolder,该类返回新增记录时的自增长主键值。假设我们希望在新增论坛板块对象后,希望将主键值加载到对象中,则可以按以下代码进行调整:
public void addForum(final Forum forum) {
final String sql = "INSERT INTO t_forum(forum_name,forum_desc) VALUES(?,?)";
KeyHolder keyHolder = new GeneratedKeyHolder();①创建一个主键执有者
getJdbcTemplate().update(new PreparedStatementCreator(){
public PreparedStatement createPreparedStatement(Connection conn)
throws SQLException {
PreparedStatement ps = conn.prepareStatement(sql);
ps.setString(1, forum.getForumName());
ps.setString(2, forum.getForumDesc());
return ps;
}
},keyHolder);
forum.setForumId(keyHolder.getKey().intValue());②从主键执有者中获取主键
}
这样,在调用addForum(final Forum forum)新增forum领域对象后,forum将拥有对应的主键值,方便后继的使用。
在JDBC 3.0之前的版本中,PreparedStatement不能绑定主键,如果采用表自增键(如MySql的auto increment或SqlServer的identity)将给获取正确的主键值带来挑战——因为你必须在插入数据后,马上执行另一条获取新增主键的查询语句。表 1给出了不同数据库获取最新自增主键值的查询语句:
表 1 不同数据库获取新增加的主键值
数据库 |
获取新增主键的查询语句 |
DB2 |
IDENTITY_VAL_LOCAL() |
Informix |
SELECT dbinfo('sqlca.sqlerrd1') FROM <TABLE> |
Sybase |
SELECT @@IDENTITY |
SqlServer |
SELECT SCOPE_IDENTITY()或SELECT @@IDENTITY |
MySql |
SELECT LAST_INSERT_ID() |
HsqlDB |
CALL IDENTITY() |
Cloudscape |
IDENTITY_VAL_LOCAL() |
Derby |
IDENTITY_VAL_LOCAL() |
PostgreSQL |
SELECT nextval('<TABLE>_SEQ') |
如果数据库的并发率很高,比如在插入记录后执行查询主键之前,数据库又执行了若干条插入记录的SQL语句,这时,通过表 1 返回的主键值就是最后一条插入语句的主键值,而非我们希望的主键值了。所以使用查询语句获取表自增键值是不安全的,这也是为什么有些数据库(如Oracle、Firebird)故意不提供自增键,而只提供序列的原因,序列强制要求你在新增记录前,先获取主键值。Oracle通过SELECT <SEQUENCE_NAME>.nextval FROM DUAL获取序列的下一个值,而FireBird通过SELECT GEN_ID(<SEQUENCE_NAME> 1) FROM RDB$DATABASE获取序列的下一个值。在10.4.1小节中,我们还将讲解应用层自增键的相关知识。
应用层产生主键
Spring JDBC提供了自增键以及行集的支持,自增键对象让我们可以不依赖数据库的自增键,在应用层为新记录提供主键值。在JDK 1.4中引入了RowSet,它允许在连接断开的情况下操作数据,在这节里,我们将介绍如何在Spring JDBC中使用RowSet。
自增键的使用
一般数据库都提供了自增键的功能,如MySql的auto_increment、SqlServerr的identity字段等。Spring允许你在应用层产生主键值,为此定义了org.springframework.jdbc.support.incrementer.DataFieldMaxValueIncrementer接口,提供两种产生主键的方案:第一,通过序列产生主键;第二,通过表产生主键。根据主键产生方式和数据库的不同,Spring提供了若干实现类,如图 1所示:
图 1 DateFieldValueIncrementer继承类图
根据不同的主键产生方式,可能需要配置表名、主键字段名或序列名等信息。下面,我们以Oracle和MySql为例分别讲解使用序列及表字段产生主键值的方式。
DataFieldMaxValueIncrementer接口定义了3个获取下一个主键值的方法:
int nextIntValue():获取下一个主键值,主键数据类型为int;
long nextLongValue():获取下一个主键值,主键数据类型为long;
String nextStringValue():获取下一个主键值,主键数据类型为String;
在其抽象实现类AbstractDataFieldMaxValueIncrementer中,提供了几个重要的属性,其中incrementerName定义序列或主键表的名称;如果返回的主键是String类型,则paddingLength属性可能会派上用场,它允许你指定返回主键的长度,不足的部分前面补0。
HsqlMaxValueIncrementer和MySQLMaxValueIncrementer两个主键值产生器基于表进行工作。通过columnName属性定义主键列的名字,通过cacheSize属性定义缓存的主键个数,当内存中的主键值用完后,产生器将一次性获取cacheSize个主键,这样可以减少数据访问的次数,提高应用的性能。
我们通过DateFieldValueIncrementer从数据库中获取主键值来弥补这个缺陷。首先,调整PostJdbcDao的代码,添加DateFieldValueIncrementer属性,并通过它从序列中得到下一个主键值:
代码清单 13 使用DateFieldValueIncrementer产生主键
public class PostJdbcDao extends JdbcDaoSupport implements PostDao {
private DataFieldMaxValueIncrementer incre; ①主键值产生器
public void addPost(final Post post) {
…
getJdbcTemplate().execute(
sql,new AbstractLobCreatingPreparedStatementCallback(
this.lobHandler) {
protected void setValues(PreparedStatement ps,
LobCreator lobCreator) throws SQLException {
ps.setInt(1, incre.nextIntValue());②获取下一个主键值
…
}
});
}
…//省略get/setter方法
}
在②处,我们通过incre.nextIntValue()获取下一个主键值。
以序列方式产生主键值
在Oracle数据库中创建一个seq_post_id序列,使用这个序列为t_post提供主键值,以下是创建seq_post_id的脚本:
create sequence seq_post_id
increment by 1
start with 1;
接着,调整Spring的配置,使用OracleSequenceMaxValueIncrementer作为主键产生器:
<bean id="incre" class="org.springframework.jdbc.support.incrementer.OracleSequenceMaxValueIncrementer">
<property name="incrementerName" value="seq_post_id"/> ①指定序列名
<property name="dataSource" ref="dataSource"/> ②设置数据源
</bean>
<bean id="postDao" parent="dao" class="com.baobaotao.dao.jdbc.PostJdbcDao">
<property name="lobHandler" ref="oracleLobHandler"/>
<property name="incre" ref="incre"/> ③添加主键主键产生器
</bean>
以表方式产生主键值
在Mysql中创建一张用于维护t_post主键的t_post_id表,以下是创建该表及插入初始化的SQL脚本:
create table t_post_id(sequence_id int) type = MYISAM;
insert into t_post_id values(0);
由于主键维护表的并发访问量很大,所以最好将其声明为MYISAM类型,此外需要为该表提供初始值,以便后续主键值在此之上进行递增。
调整为MySql数据库后,我们仅需要对Spring配置进行小小的调整就可以了:
<bean id="incre"
class="org.springframework.jdbc.support.incrementer.MySQLMaxValueIncrementer">
<property name="incrementerName" value="t_post_id"/> ①设置维护主键的表名
<property name="columnName" value="sequence_id"/>②用于生成主键值的列名
<property name="cacheSize" value="10"/> ③缓存大小
<property name="dataSource" ref="dataSource"/>
</bean>
<bean id="postDao" parent="dao" class="com.baobaotao.dao.jdbc.PostJdbcDao">
<property name="lobHandler" ref="defaultLobHandler"/>
<property name="incre" ref="incre"/>
</bean>
incrementerName和columnName都很容易理解,cacheSize决定一次返回的主键个数,这里我们设置为10。当第一次通过MySQLMaxValueIncrementer# nextIntValue()获取主键值时,MySQLMaxValueIncrementer将使t_post_id. sequence_id递增10,而后续9次调用nextIntValue()方法时,都从缓存中获取主键值。直到第10次再次调用nextIntValue()方法时,才会再次将t_post_id. sequence_id字段值递增10,如此循环反复。
小结
主键的生产方式从产生地点上可以分为应用层产生和数据库产生两种方式。应用层借助数据库的序列或表产生主键,这种方式可以保证程序的可移植性和安全性,同时可以通过缓存机制提高运行效率。有些数据库支持数据表自增键的主键产生机制,在JDBC 3.0以前的版本中,无法通过Statement自动获取新增记录的对应主键。这时需要在插入数据后,马上执行一条数据库相关的主键获取SQL语句以得到对应的主键值,在数据库高并发的情况下,有可能获取到不正确的主键值。在这种情况下,在插入数据前事先在应用层准备好主键值是很好的备选方案。
分享到:
相关推荐
可能使用了关系型数据库管理系统(如MySQL、Oracle或SQL Server),根据数据建模的结果创建了相应的表结构,包括主键、外键、索引等设计,以支持高效的数据查询和操作。此外,数据库设计还涉及到了事务处理、并发...
这个系统充分体现了Java语言在企业级应用中的优势,通过合理的架构设计和功能模块划分,实现了数据的精确跟踪和业务流程的自动化。 1. **Java 技术栈** - **JDBC**:Java Database Connectivity,用于连接数据库,...
- 使用索引优化查询性能,合理设计主键和外键,确保数据一致性。 5. **微信小程序集成** - 微信小程序作为前端展示,利用微信提供的SDK进行用户认证和支付功能。 - 小程序与后端API通信,实现数据同步,提供移动...
- **数据完整性**:为了保证数据的准确性和一致性,数据库需要设置合适的约束,如主键约束、外键约束、唯一性约束等。 3. **代码实现** - **前端界面**:用户友好的界面设计,包括商品管理、供应商管理、订单处理...
4. **报表统计**:生成各类报表,如库存盘点报告、销售分析报告等,为管理者决策提供数据支持。 5. **权限控制**:根据角色分配不同的操作权限,如普通员工只能查看和操作自己的订单,而管理员则能进行全局管理。 ...
此外,数据库设计的关键是表的结构和关系,比如商品表、供应商表、库存表和订单表等,它们之间通过主键和外键建立关联,以实现数据的一致性和完整性。 【进销存管理】 进销存,即进货、销售和库存管理,是商业运营...
主键、外键的设置以及适当的数据类型选择对系统性能至关重要。 5. **功能模块**: - 入库管理:记录商品的入库信息,如数量、时间、供应商等,确保入库流程的透明化。 - 出库管理:管理商品的出库请求,包括出库...
同时,会列出各个表的字段设计,包括主键、外键、数据类型等。 4. **功能模块设计**: - **商品管理**:包括商品的添加、修改、删除,以及查询功能,可能涉及到库存预警等。 - **库存管理**:记录商品的入库、...
根据项目需求,开发仓库管理系统可能选用Java、Python、C#等编程语言,配合Spring Boot、Django、ASP.NET等框架,快速构建功能模块。前端则可能使用HTML、CSS、JavaScript,配合Vue.js、React.js等框架,提供良好的...
物资管理系统是一种基于数据库技术的信息管理应用,主要用于存储、管理和跟踪组织内部的各种物资资源。在数据库课程设计中,这样的系统能够帮助学生深入理解数据库原理、设计模式以及实际开发流程。以下是对这个系统...
2. 数据库表结构:详细列出每个表的设计,包括字段名、数据类型、主键、外键等,确保数据的一致性和完整性。 五、详细设计 详细设计阶段关注用户界面和具体功能的实现: 1. 登录界面:设计用户登录验证机制,可能...
设计合理的数据库结构对于数据的存储和查询至关重要,这包括表的字段定义、数据类型选择、主键与外键的设定以及索引优化。 2. **用户界面设计**:系统应具有直观易用的用户界面,以便操作员进行商品入库、出库操作...