高并发数据库自增主键分析

时间:2022-09-08 13:56:29

      在一般情况下,在新增领域对象后,都需要获取对应的主键值。使用应用层来维护主键,在一定程度上有利于程序性能的优化和应用移植性的提高。在采用数据库自增主键的方案里,如果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() ) {
           intkey = rs.getInt(); // ②获取对应的表自增主键值
      }

      Spring利用这一技术,提供了一个可以返回新增记录对应主键值的方法: int update(PreparedStatementCreator psc, KeyHolder generatedKeyHolder) ,其中第二个参数类型org.springframework.jdbc.support.KeyHolder,它是一个回调接口,Spring使用它保存新增记录对应的主键,该接口的接口方法描述如下: 

      Number getKey() throws InvalidDataAccessApiUsageException;

      当仅插入一行数据,主键不是复合键且是数字类型时,通过该方法可以直接返回新的主键值。如果是复合主键,或者有多个主键返回时,该方法抛出 InvalidDataAccessApiUsageException。该方法是最常用的方法,因为一般情况下,我们一次仅插入一条数据并且主键字段类型为数字类型; 

      如果是复合主键,则列名和列值构成Map中的一个Entry。如果返回的是多个主键,则抛出InvalidDataAccessApiUsageException异常; 

      Map getKeys() throws InvalidDataAccessApiUsageException;

      如果返回多个主键,即PreparedStatement新增了多条记录,则每一个主键对应一个Map,多个Map构成一个List。

      List getKeyList(): 

      Spring为KeyHolder接口指代了一个通用的实现类GeneratedKeyHolder,该类返回新增记录时的自增长主键值。假设我们希望在新增论坛板块对象后,希望将主键值加载到对象中,则可以按以下代码进行调整:

      public voidaddForum(final Forum forum) {
            final String sql = "INSERT INTO t_forum(forum_name,forum_desc) VALUES(?,?)";
            KeyHolder keyHolder = newGeneratedKeyHolder(); // ①创建一个主键执有者
            getJdbcTemplate().update(newPreparedStatementCreator() {
                  public PreparedStatement createPreparedStatement(Connection conn) throws SQLException {
                        PreparedStatement ps = conn.prepareStatement(sql);
                        ps.setString(1, forum.getForumName());
                        ps.setString(2, forum.getForumDesc());
                        returnps;
                  }
            }, keyHolder);
            forum.setForumId(keyHolder.getKey().intValue()); // ②从主键执有者中获取主键
      }

      这样,在调用addForum(Forum forum)新增forum领域对象后,forum将拥有对应的主键值,方便后继的使用。在JDBC 3.0之前的版本中,PreparedStatement不能绑定主键,如果采用表自增键(如MySQL的auto increment或SQLServer的identity)将给获取正确的主键值带来挑战——因为你必须在插入数据后,马上执行另一条获取新增主键的查询语句。下面给出了不同数据库获取最新自增主键值的查询语句: