Spring JDBC-自增键和行集RowSet

举报
小工匠 发表于 2021/09/10 23:15:41 2021/09/10
【摘要】 概述自增键的使用 Oracle以序列方式产生主键值MySQL以表方式产生主键值 如何规划主键方案自增键小结以行集返回数据 示例 示例源码 概述 Spring JDBC提供了...

概述

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 table
create 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_seq
minvalue 1
maxvalue 999
start with 1
increment by 1
cache 20;

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28

接着我们调整下,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>

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34

业务类

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
 */

@Repository
public 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");
    }

}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50
  • 51
  • 52
  • 53
  • 54
  • 55
  • 56
  • 57
  • 58
  • 59
  • 60
  • 61
  • 62
  • 63
  • 64
  • 65
  • 66
  • 67
  • 68
  • 69
  • 70
  • 71
  • 72
  • 73

单元测试

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");
    }

}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43

日志

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 hierarchy
2017-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 successfully
add Artisan successfully
add Artisan successfully
add Artisan successfully
add Artisan successfully
add Artisan successfully
2017-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 hierarchy
close context successfully

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11

测试结果

这里写图片描述

观察ID,是按照定义的序列生成的ID


MySQL以表方式产生主键值

在MySQL数据库中创建一张用于维护artisan主键的artisan_id表

create table artisan_id(sequence_id int) type = MYISAM;
insert into artisan_id values(0);
  
 
  • 1
  • 2

由于主键维护表的并发访问量很大,最好将其声明为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"/>
  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

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
 */

@Repository
public 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);
    }

}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46
  • 47
  • 48
  • 49
  • 50

单元测试

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");
    }
}

  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44

测试结果:

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 hierarchy
2017-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 successfully
artisan_name:Xiao0
2017-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 hierarchy
close context successfully
  
 
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6

在selectArtisanById查询并返回SqlRowSet的结果集后,数据连接已经断开,但是结果集的数据已经保存在SqlRowSet中。 因此,我们仍然可以访问到SqlRowSet的结果集数据。

值的注意的是,RowSet会一次性装载所有的匹配数据,而不像ResultSet一样,分批次返回一批数据(一批的行数为fetchSize).

所以对于大结果集的数据,使用SQLRowSet会造成很大的内存消耗,不过JdbcTemplate的maxSize属性依然会现在SqlRowSet的返回记录数。


示例源码

代码已托管到Github—> https://github.com/yangshangwei/SpringMaster

文章来源: artisan.blog.csdn.net,作者:小小工匠,版权归原作者所有,如需转载,请联系作者。

原文链接:artisan.blog.csdn.net/article/details/78132969

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。