Spring JDBC-自增键和行集RowSet
来源:互联网 发布:鹏为crm软件 编辑:程序博客网 时间:2024/06/06 02:42
- 概述
- 自增键的使用
- Oracle以序列方式产生主键值
- MySQL以表方式产生主键值
- 如何规划主键方案
- 自增键小结
- 以行集返回数据
- 示例
- 示例源码
概述
Spring JDBC提供了对自增键及行集的支持,自增键对象让用户可以不依赖数据库的自增键,在应用层为新纪录提供主键。
在Java1.4中引入RowSet,它允许在连接断开的情况下操作数据。 这里我们讨论如何在Spring JDBC中使用RowSet。
自增键的使用
一般数据库都提供了自增键的功能,比如MySql的auto_increment , SQL Server的identifty字段等.
Spring允许用户在应用层产生主键值,为此定义了org.springframework.jdbc.supprot.incrementer.DataFieldMaxValueIncrementer
接口 , 提供了两种产生主键的方案,第一是通过序列产生主键,第二是通过表产生主键。 根据主键产生方式及数据库类型的不同,Spring提供了不同的实现类。
DataFieldMaxValueIncrementer继承类图
根据不同的主键产生方式,可能需要配置表名、主键字段或者序列等信息。
DataFieldMaxValueIncrementer接口中定义了3个获取主键值的方法
int nextIntValue():获取下一个主键值,主键值类型为int
long nextLongValue();获取下一个主键值,主键值类型为long
String nextStringValue();获取下一个主键值,主键值类型为String
在其抽象类AbstractDataFieldMaxValueIncrementer中,提供了几个重要属性:
incrementerName:定义序列名后模拟序列表的名称,如果返回的主键值类型是String类型,则paddingLength属性就会派上用场,它允许用户指定返回主键的长度,不足的部分前面补0.
AbstractSequenceMaxAbstractSequence使用标准的数据库序列产生主键值,
而AbstractColumnMaxValueIncrementer使用一张模拟序列的表产生主键值,AbstractColumnMaxValueIncrementer可以通过cacheSize属性指定缓存的主键个数,当内存中主键值用完后,递增器将一次性获取cacheSize个主键,这样可以减少数据库访问的次数,提高应用的性能。
下面分别以Oracle和MySQL为例子,分别阐述下使用序列以及字段产生主键值的方式。
Oracle以序列方式产生主键值
在Oracle数据库中创建artisan表以及artisan_id的序列
-- Create tablecreate table ARTISAN( artisan_id NUMBER, artisan_name VARCHAR2(50))tablespace TAB_CC pctfree 10 initrans 1 maxtrans 255 storage ( initial 16 next 8 minextents 1 maxextents unlimited );-- Create sequence create sequence artisan_id_seqminvalue 1maxvalue 999start with 1increment by 1cache 20;
接着我们调整下,Spring配置文件,使用OracleSequenceMaxValueIncrementer作为主键的产生器
<?xml version="1.0" encoding="UTF-8" ?><beans xmlns="http://www.springframework.org/schema/beans" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:p="http://www.springframework.org/schema/p" xmlns:context="http://www.springframework.org/schema/context" xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd http://www.springframework.org/schema/context http://www.springframework.org/schema/context/spring-context.xsd"> <!-- 扫描类包,将标注Spring注解的类自动转化Bean,同时完成Bean的注入 --> <context:component-scan base-package="com.xgj.dao.dataFieldMaxValueIncrementer"/> <!-- 使用context命名空间,加载数据库的properties文件 --> <context:property-placeholder location="classpath:spring/jdbc.properties" /> <bean id="dataSource" class="org.apache.commons.dbcp.BasicDataSource" destroy-method="close" p:driverClassName="${jdbc.driverClassName}" p:url="${jdbc.url}" p:username="${jdbc.username}" p:password="${jdbc.password}" /> <!-- 配置Jdbc模板 --> <bean id="jdbcTemplate" class="org.springframework.jdbc.core.JdbcTemplate" p:dataSource-ref="dataSource" /> <!-- 配置主键产生器,指定数据源和序列名 --> <bean id="oracleIncre" class="org.springframework.jdbc.support.incrementer.OracleSequenceMaxValueIncrementer" p:dataSource-ref="dataSource" p:incrementerName="artisan_id_seq"/></beans>
业务类
package com.xgj.dao.dataFieldMaxValueIncrementer.oracle.dao;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.jdbc.core.JdbcTemplate;import org.springframework.jdbc.support.incrementer.OracleSequenceMaxValueIncrementer;import org.springframework.stereotype.Repository;import com.xgj.dao.dataFieldMaxValueIncrementer.oracle.domain.Artisan;/** * * * @ClassName: AritsanOracleDaoImpl * * @Description: @Repository标注DAO层,并被Spring管理 * * @author: Mr.Yang * * @date: 2017年9月29日 下午8:39:32 */@Repositorypublic class AritsanOracleDaoImpl implements AritsanOracleDao { private JdbcTemplate jdbcTemplate; private OracleSequenceMaxValueIncrementer oracleIncre; private static final String addArtisanSql = "insert into artisan(artisan_id ,artisan_name) values(?,?)"; /** * * * @Title: setOracleIncre * * @Description: 自动注入OracleSequenceMaxValueIncrementer * * @param oracleIncre * * @return: void */ @Autowired public void setOracleIncre(OracleSequenceMaxValueIncrementer oracleIncre) { this.oracleIncre = oracleIncre; } /** * * * @Title: setJdbcTemplate * * @Description: 自动注入JdbcTemplate * * @param jdbcTemplate * * @return: void */ @Autowired public void setJdbcTemplate(JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; } /** * 使用 oracleIncre.nextIntValue() 作为主键自增长 */ @Override public void addArtisan(Artisan artisan) { jdbcTemplate.update(addArtisanSql, oracleIncre.nextIntValue(), artisan.getArtisanName()); System.out.println("add Artisan successfully"); }}
单元测试
package com.xgj.dao.dataFieldMaxValueIncrementer.oracle.dao;import org.junit.After;import org.junit.Before;import org.junit.Test;import org.springframework.context.support.ClassPathXmlApplicationContext;import com.xgj.dao.dataFieldMaxValueIncrementer.oracle.domain.Artisan;public class OracleSeqIncreaseTest { ClassPathXmlApplicationContext ctx = null; AritsanOracleDaoImpl aritsanOracleDaoImpl = null; @Before public void initContext() { // 启动Spring 容器 ctx = new ClassPathXmlApplicationContext( "classpath:com/xgj/dao/dataFieldMaxValueIncrementer/conf_oracleincreaseId.xml"); aritsanOracleDaoImpl = ctx.getBean("aritsanOracleDaoImpl", AritsanOracleDaoImpl.class); System.out.println("initContext successfully"); } @Test public void queryTeacherById() { for (int i = 0; i < 5; i++) { Artisan artisan = new Artisan(); artisan.setArtisanName("Xiao" + i); aritsanOracleDaoImpl.addArtisan(artisan); } } @After public void closeContext() { if (ctx != null) { ctx.close(); } System.out.println("close context successfully"); }}
日志
2017-09-29 20:41:28,720 INFO [main] (AbstractApplicationContext.java:583) - Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@4a05bc0c: startup date [Fri Sep 29 20:41:28 BOT 2017]; root of context hierarchy2017-09-29 20:41:28,823 INFO [main] (XmlBeanDefinitionReader.java:317) - Loading XML bean definitions from class path resource [com/xgj/dao/dataFieldMaxValueIncrementer/conf_oracleincreaseId.xml]initContext successfullyadd Artisan successfullyadd Artisan successfullyadd Artisan successfullyadd Artisan successfullyadd Artisan successfully2017-09-29 20:41:30,221 INFO [main] (AbstractApplicationContext.java:984) - Closing org.springframework.context.support.ClassPathXmlApplicationContext@4a05bc0c: startup date [Fri Sep 29 20:41:28 BOT 2017]; root of context hierarchyclose context successfully
测试结果
观察ID,是按照定义的序列生成的ID
MySQL以表方式产生主键值
在MySQL数据库中创建一张用于维护artisan主键的artisan_id表
create table artisan_id(sequence_id int) type = MYISAM;insert into artisan_id values(0);
由于主键维护表的并发访问量很大,最好将其声明为MYISAM类型。 此外,需要为该表提供初始值,以便后续主键值在此基础上增长。
Spring配置文件微调
<!-- 配置主键产生器,指定数据源和序列名 --> <bean id="mysqlIncrease" class="org.springframework.jdbc.support.incrementer.MySQLMaxValueIncrementer" p:dataSource-ref="dataSource" p:incrementerName="artisan_id" p:columnName="sequence_id" p:cacheSize="10"/>
p:incrementerName –维护主键的表名
p:columnName 用于生成主键值的列名
p:cacheSize 缓存大小
cacheSize 决定一次返回的主键个数,这里设置为10 ,当第一次通过MySQLMaxValueIncrementer#nextIntValue()方法获取主键时,MySQLMaxValueIncrementer将使artisan_id.sequence_id 递增到10 ,而后9次调用nextIntValue方法时,都从缓存中获取主键值,直到第10次调用nextIntValue()方法时,才会再此将artisan_id.sequence_id递增10 ,如此循环反复.
如何规划主键方案
从主键创建者的角度看,我们可以将主键创建方案分为两类:
- 其一为“应用层主键方案”,新数据的主键分配由应用层负责,如采用UUID或者使用DataFieldMaxValueIncrementer生成主键都属于这一类型;
- 其二为“数据库层主键方案”,新数据的主键分配由数据库负责,即在表结构定义时,将主键设置为auto
increment或通过表的触发器分配主键。
1、数据库层主键方案不足:
其一,它给应用开发带来不便,因为你必须通过一个查询获取新增数据的主键值;
其二,不方便主键值的全局管理和控制,使系统散失灵活性;
其三,不方便数据的整合和迁移。
2、采用应用层主键方案,使用UUID产生主键值,这样可以保证ID的全局唯一性,为后期数据整合带来了便利。
当然,采用UUID也有不好地方,就是UUID是一个36位的字符串,会占用大量的存储空间。
所以另一个候选的方案就是采用分段长整型编码方案,将主键编码分为N段:这样就可以创建一个全局的唯一的整数型的主键值。
这里不能使用DataFieldMaxValueIncrementer,因为DataFieldMaxValueIncrementer只能为一个表创建主键,但道理是相同,我们可以创建一个包含N个字段的主键表,编写一个类似DataFieldMaxValueIncrementer的接口以获取主键值。
自增键小结
在高并发的系统中,如果采用基于序列表的方式创建主键值,则应该考虑两个层面的并发问题:
第一:应用层获取主键的并发问题,Spring的DataFielMaxValueIncrementer实现类已经对获取主键值的代码进行了同步,确保同一JVM内应用不会产生应发问题
第二:全局的并发问题,如果应用是集群部署的,所有集群节点通过同一个序列表获取主键,那么就必须对这张序列表进行乐观锁定(序列表必须添加一个版本或者时间戳字段),以防止集群节点的并发问题。 很可惜的是Spring的DataFielMaxValueIncrementer并灭有对序列表进行乐观锁定。我们只有自己实现DataFielMaxValueIncrementer接口,以解决全局并发的问题。
另外 DataFielMaxValueIncrementer接口只能为一张表提供组件,即每张表都必须配置一个单独的DataFielMaxValueIncrementer,因此比较死板,建议参照DataFielMaxValueIncrementer接口,自行编写一个可为多张表提供主键的接口。
以行集返回数据
行集对象可以绑定一个数据连接并在整个生命周期中维持该连接,在此情况下,该行集对象被称为“连接的行集”。
行集对象还可以先绑定一个数据源,获取数据后就关闭它,这种行集被称为“非连接行集”。 非连接行集可以在断开连接时更改数据,然后重新绑定数据连接,并将对数据的更改同步到数据库中。
JdbcTemplate 为获取基于行集的结果集,提供如下查询方法
- SqlRowSet queryForRowSet(String sql)
- SqlRowSet queryForRowSet(String sql,Object … args)
- SqlRowSet queryForRowSet(String sql,Object[] args ,int[] argTypes)
示例
package com.xgj.dao.rowset.dao;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.jdbc.core.JdbcTemplate;import org.springframework.jdbc.support.rowset.SqlRowSet;import org.springframework.stereotype.Repository;/** * * * @ClassName: AritsanOracleDaoImpl * * @Description: @Repository标注DAO层,并被Spring管理 * * @author: Mr.Yang * * @date: 2017年9月29日 下午10:41:10 */@Repositorypublic class AritsanOracleDaoImpl implements AritsanOracleDao { private JdbcTemplate jdbcTemplate; private static final String selectArtisanByIdSql = "select artisan_name from artisan where artisan_id = ?"; /** * * * @Title: setJdbcTemplate * * @Description: 自动注入JdbcTemplate * * @param jdbcTemplate * * @return: void */ @Autowired public void setJdbcTemplate(JdbcTemplate jdbcTemplate) { this.jdbcTemplate = jdbcTemplate; } @Override public SqlRowSet selectArtisanById(int artisanId) { return jdbcTemplate.queryForRowSet(selectArtisanByIdSql, artisanId); }}
单元测试
package com.xgj.dao.rowset.dao;import org.junit.After;import org.junit.Before;import org.junit.Test;import org.springframework.context.support.ClassPathXmlApplicationContext;import org.springframework.jdbc.support.rowset.SqlRowSet;public class RowSetTest { ClassPathXmlApplicationContext ctx = null; AritsanOracleDaoImpl aritsanOracleDaoImpl = null; @Before public void initContext() { // 启动Spring 容器 ctx = new ClassPathXmlApplicationContext( "classpath:com/xgj/dao/rowset/conf_rowset.xml"); aritsanOracleDaoImpl = ctx.getBean("aritsanOracleDaoImpl", AritsanOracleDaoImpl.class); System.out.println("initContext successfully"); } @Test public void queryTeacherById() { SqlRowSet sqlRowSet = aritsanOracleDaoImpl.selectArtisanById(1); // 这时,数据连接已经断开 while (sqlRowSet.next()) { System.out.println("artisan_name:" + sqlRowSet.getString("artisan_name")); } } @After public void closeContext() { if (ctx != null) { ctx.close(); } System.out.println("close context successfully"); }}
测试结果:
2017-09-29 22:27:14,381 INFO [main] (AbstractApplicationContext.java:583) - Refreshing org.springframework.context.support.ClassPathXmlApplicationContext@70221d9a: startup date [Fri Sep 29 22:27:14 BOT 2017]; root of context hierarchy2017-09-29 22:27:14,500 INFO [main] (XmlBeanDefinitionReader.java:317) - Loading XML bean definitions from class path resource [com/xgj/dao/rowset/conf_rowset.xml]initContext successfullyartisan_name:Xiao02017-09-29 22:27:16,161 INFO [main] (AbstractApplicationContext.java:984) - Closing org.springframework.context.support.ClassPathXmlApplicationContext@70221d9a: startup date [Fri Sep 29 22:27:14 BOT 2017]; root of context hierarchyclose context successfully
在selectArtisanById查询并返回SqlRowSet的结果集后,数据连接已经断开,但是结果集的数据已经保存在SqlRowSet中。 因此,我们仍然可以访问到SqlRowSet的结果集数据。
值的注意的是,RowSet会一次性装载所有的匹配数据,而不像ResultSet一样,分批次返回一批数据(一批的行数为fetchSize).
所以对于大结果集的数据,使用SQLRowSet会造成很大的内存消耗,不过JdbcTemplate的maxSize属性依然会现在SqlRowSet的返回记录数。
示例源码
代码已托管到Github—> https://github.com/yangshangwei/SpringMaster
- Spring JDBC-自增键和行集RowSet
- JDBC:ResultSet和RowSet
- jdbc(RowSet离线结果集)
- jdbc(RowSet离线结果集)
- jdbc(RowSet离线结果集)
- JDBC中Rowset和ResultSet的区别
- jdbc中的离线数据集(RowSet)
- JDBC数据集(RowSet)--- JdbcRowSet
- Spring JDBC对Oracle10g数据库操作时RowSet的问题
- JDBC 结果集的新增功能(RowSet的Insert和Update)
- Java技术回顾之JDBC:ResultSet和RowSet
- Java技术回顾之JDBC:ResultSet和RowSet
- JDBC中的几种结果集ResultSet,RowSet
- Spring JDBC 如何获得和生成主键+Spring JDBC对行集的支持
- spring的JDBC框架中自增键的问题与cacheSize关系
- Spring JDBC 新增记录返回自增主键(实例)
- jdbc初步笔记6 DataSource&RowSet
- spring和JDBC
- 【笔记】《WebGL编程指南》学习-第5章颜色与纹理(2-彩色三角形)
- 安卓开发之Android Studio在drawable目录下添加矢量图vector XML文件
- Singleton模式
- 方法的重载
- RNA-Seq基因组比对工具HISAT2
- Spring JDBC-自增键和行集RowSet
- 纯干货:Linux抓包命令集锦
- Linux安装mysql
- 关于springcloud kafka binder的一个关于consumer group设置的一个bug
- 文本框输入多个IP用逗号隔开js校验方法
- mysql 锁
- javawebday12(枚举 拆装箱 )
- Triple HDU
- C# FTP操作类