【问题标题】:Java Spring - RowMapper ResultSet - Integer/null valuesJava Spring - RowMapper ResultSet - 整数/空值
【发布时间】:2016-01-20 20:23:26
【问题描述】:

我有一个 Java SE 8 Spring 4.1.6-RELEASE 应用程序,我在其中实现 org.springframework.jdbc.core.RowMapper<T> 接口,我对在其 T mapRow(ResultSet rs, int rowNum) 方法中传递的 java.sql.ResultSet 接口有一些疑问。

当我检查 ResultSet 类时,我看到了一堆方法来取回列值:

╔══════════════╦═════════════════════╦════════════ ══════════════════════════════════════════════════ ══════════════════════════════════════════════════ ══════════════════════════════════════════════ ║ 返回类型 ║ 方法 ║ 返回 (javadoc, se 8) ║ ╠══════════════╬═════════════════════╬════════════ ══════════════════════════════════════════════════ ══════════════════════════════════════════════════ ════════════════════════════════════════⣕═ ║ String ║ getString ║ 列值;如果值为 SQL NULL,则返回值为 null ║ ║ boolean ║ getBoolean ║ 列值;如果值为 SQL NULL,则返回值为 false ║ ║ byte ║ getByte ║ 列值;如果值为 SQL NULL,则返回值为 0 ║ ║ short ║ getShort ║ 列值;如果值为 SQL NULL,则返回值为 0 ║ ║ int ║ getInt ║ 列值;如果值为 SQL NULL,则返回值为 0 ║ ║ long ║ getLong ║ 列值;如果值为 SQL NULL,则返回值为 0 ║ ║ float ║ getFloat ║ 列值;如果值为 SQL NULL,则返回值为 0 ║ ║ double ║ getDouble ║ 列值;如果值为 SQL NULL,则返回值为 0 ║ ║ BigDecimal ║ getBigDecimal ║ 列值;如果值为 SQL NULL,则返回值为 null ║ ║ byte[] ║ getBytes ║ 列值;如果值为 SQL NULL,则返回值为 null ║ ║ Date ║ getDate ║ 列值;如果值为 SQL NULL,则返回值为 null ║ ║ Time ║ getTime ║ 列值;如果值为 SQL NULL,则返回值为 null ║ ║ Timestamp ║ getTimestamp ║ 列值;如果值为 SQL NULL,则返回值为 null ║ ║ InputStream ║ getAsciiStream ║ Java 输入流,将数据库列值作为单字节 ASCII 字符流传递;如果值为 SQL NULL,则返回值为 null ║ ║ Reader ║ getCharacterStream ║ 包含列值的 java.io.Reader 对象;如果值为 SQL NULL,则在 Java 编程语言中返回的值为 null ║ ║ InputStream ║ getBinaryStream ║ Java 输入流,将数据库列值作为未解释的字节流传递;如果值为 SQL NULL,则返回值为 null ║ ║ T ║ getObject ║ 保存列值的类型实例 ║ ╚══════════════╩═════════════════════╩════════════ ══════════════════════════════════════════════════ ══════════════════════════════════════════════════ ══════════════════════════════════════════════

是一般的期望/惯例调用:

rs.getObject("COLUMN_NAME", Boolean.class);

rs.getObject("COLUMN_NAME", Byte.class);

rs.getObject("COLUMN_NAME", Short.class);

rs.getObject("COLUMN_NAME", Integer.class);

rs.getObject("COLUMN_NAME", Long.class);

等等,对于所有的原始类型?对于 SQL NULL 的实例,其他所有内容都返回 null

如果是这样,当类型化的 Object 方法存在时,拥有不同类型的所有方法有什么意义?

另外,每种方法的优缺点是什么?

  1. 使用getInt(String columnLabel)

    Integer resultingActionId = rs.getInt("RESULTING_ACTION_ID");
    if (rs.wasNull) {
        resultingActionId = null
    }
  2. 使用getObject(String columnLabel) 并转换为Integer

    Integer resultingActionId = (Integer) rs.getObject("RESULTING_ACTION_ID");
  3. 使用getObject(String columnLabel, Class type)

    Integer resultingActionId = rs.getObject("RESULTING_ACTION_ID", Integer.class);

例如,我注意到 org.springframework.jdbc.core.JdbcTemplate 曾经有 queryForLongqueryForInt 等从单行查询中获取单个值的方法并将它们全部替换为类型化的queryForObject 方法。

谢谢!

【问题讨论】:

    标签: java spring jdbc jdbctemplate


    【解决方案1】:

    如果你看看java.sql.ResultSet,你会发现你不需要那么明确。实际上,除非您有一个 typeMapper 用于您的连接,它允许您使用 getObject 方法,否则它将无法工作 (java.sql.ResultSet.getObject)。

    我不知道它是否会对您有所帮助,但我设法找到了自己的 RowMapper,它非常适合我的需求。

    private class ShabaUserMapper implements RowMapper<ShabaUser>
    {
        @Override
        public ShabaUser mapRow( ResultSet rs, int rowNum ) throws SQLException
        {
            Collection<SimpleGrantedAuthority> roles = new ArrayList<SimpleGrantedAuthority>();
    
            String auths = rs.getString( "role" );
    
            roles.add( new SimpleGrantedAuthority( auths ) );
    
            ShabaUser user = new ShabaUser( rs.getString( "username" ), rs.getString( "password" ),
                    rs.getBoolean( "enabled" ), rs.getString( "first_name" ),
                    rs.getString( "last_name" ), rs.getString( "email" ),
                    rs.getString( "date_joined" ), rs.getString( "last_online" ), true, true, true,
                    roles );
    
            // Can be null!
            Integer awesomeness = rs.getInt( "awesomeness" );
            if ( rs.wasNull() )
            {
                awesomeness = null;
            }
    
            user.setAwesomeness( awesomeness );
    
            return user;
        }
    }
    
    private class ShabaUserListExtractor implements ResultSetExtractor<List<ShabaUser>>
    {
        private final ShabaUserMapper rowMapper;
    
        private int                   rowsExpected;
    
        public ShabaUserListExtractor()
        {
            this( new ShabaUserMapper(), 0 );
        }
    
        public ShabaUserListExtractor( ShabaUserMapper rowMapper, int rowsExpected )
        {
            Assert.notNull( rowMapper, "RowMapper is required" );
            this.rowMapper = rowMapper;
            this.rowsExpected = rowsExpected;
        }
    
        @Override
        public List<ShabaUser> extractData( ResultSet rs ) throws SQLException
        {
            HashMap<String, ShabaUser> results = ( this.rowsExpected > 0
                                                                        ? new HashMap<String, ShabaUser>(
                                                                                rowsExpected )
                                                                        : new HashMap<String, ShabaUser>() );
            int rowNum = 0;
            while ( rs.next() )
            {
                ShabaUser user = rowMapper.mapRow( rs, rowNum++ );
    
                if ( results.containsKey( user.getUsername() ) )
                {
                    ShabaUser inUser = results.get( user.getUsername() );
                    ArrayList<GrantedAuthority> combinedAuthorities = new ArrayList<GrantedAuthority>();
    
                    combinedAuthorities.addAll( inUser.getAuthorities() );
                    combinedAuthorities.addAll( user.getAuthorities() );
    
                    results.put( user.getUsername(),
                        createUserDetails( user.getUsername(), user, combinedAuthorities ) );
                } else
                {
                    results.put( user.getUsername(), user );
                }
            }
    
            return new ArrayList<ShabaUser>( results.values() );
        }
    }
    

    我意识到这是很多代码,但希望您能看到这里完成了什么。实际的 RowMapper 实现实际上是为了容纳所有从行信息中提取对象的“肮脏工作”。

    只要您的数据库设置正确并且您在所需列上设置了 NOT NULL,您就永远不会遇到拉出空行的问题。虽然我认为从 ResultSet 检查空响应并没有什么坏处,但如果列应该有值,你仍然会最终抛出异常。

    【讨论】:

    • 在很多情况下我想要一个空整数,主要是如果我有一个可选的外键,这是通过数据库中的一个可为空的整数类型来完成的。我正在查看docs.oracle.com/javase/8/docs/api/java/sql/…,它说“检索此 ResultSet 对象的当前行中指定列的值,如果支持转换,将从列的 SQL 类型转换为请求的 Java 数据类型。如果不支持转换或为该类型指定了 null,则抛出 SQLException。"...
    • ... 我认为这意味着它将尝试从 JDBC 驱动程序结果进行类转换,这应该适用于将数值转换为整数。
    • 在这种情况下,我至少会尝试您的第一个解决方案。 wasNull() 方法在那里,所以我认为这是最佳实践: Integer resultsActionId = rs.getInt("RESULTING_ACTION_ID"); if (rs.wasNull) { resultsActionId = null }
    • 否则你不希望它保持为 0。在整数类型的 SQL_NULL 的情况下,这是如何处理的。
    • 这就是我在示例 1 中拉一个整数以比较在我的问题中处理空值的不同方法;从ResultSet.getInt(String columnLabel) 中提取值并在之后检查ResultSet.wasNull()。我想知道从最佳实践/已知实践/效率/执行代码的角度来看,Integer awesomeness = (Integer) rs.getObject("awesomeness");Integer awesomeness = rs.getObject("awesomeness", Integer.class); 之间的区别是什么。
    【解决方案2】:

    TLDR

    • 如果不允许使用 SQL NULL,则使用 resultSet.getXXX(String columnLabel),或者您只关心返回原始类型(无对象、盒装或数组类型)
    • 如果值可以是SQL NULL,则使用resultSet.getObject(String columnLabel, Class type),但如果类型是盒装类型,请不要拆箱,例如Integer 你可以得到NullPointerException 因为原语不能是null
    • 如果您正在运行 Java 6,请使用 resultSet.getObject(String columnLabel) 进行强制转换

    详情:

    如果列不允许SQL NULL,或者您的代码严格需要或期望原始类型,即没有对象类型(这包括盒装类型,例如Integerprimitive arrays which are actually objects,例如byte[] ) 然后使用具体的get方法:

    // Integer is 0 if value is SQL NULL
    int i = resultSet.getInt(column);
    
    // This is fine but Integer will never be null. This may or may not be what you want
    Integer j = resultSet.getInt(column);
    

    据我所知,这种方法的原因是方便,因为它可以优雅地处理SQL NULL,而您必须拥有自primitives cannot be null 以来的原语,例如为 int 返回 0。

    如果该列确实允许SQL NULL,或者您的代码需要一个对象类型(包括装箱类型或数组),则将resultSet.getObject 与该类型的类一起使用,但在使用此方法时不要取消装箱类型:

    // Integer will be null on SQL NULL but that's OK for boxed types
    Integer i = resultSet.getObject(column, Integer.class);
    
    // Throws NullPointerException on SQL NULL since primitives can't be null
    int unbox = resultSet.getObject(column, Integer.class);
    
    // Integer will be 0 since getInt returns 0 on SQL NULL
    Integer autobox = resultSet.getInt(column);
    

    此方法对于支持但没有特定 get 方法的其他 SQL 类型也很有用,即超出 JDBC 规范 JSR-221 中指定的最小值:

    // No getUUID method but it's supported by Postgres so no need for custom mapping
    UUID uuid = resultSet.getObject(column, UUID.class);
    

    但请注意,此方法直到 Java 7 才添加,因此如果您使用的是 Java 6,则需要强制转换它:

    UUID uuid = (UUID) resultSet.getObject(column);
    

    因此,除非您真的不知道或不关心返回类型,否则没有理由使用它,除非您在风格上更喜欢它,或者您需要在 Java 6 上运行。

    最后,我强烈建议在使用 JDBC 时谨慎使用(或取消装箱)盒装类型,以避免意外 NullPointerException。在您的架构中避免SQL NULL,例如如果可以,默认为 0,如果不确定,请显式检查/转换。

    【讨论】:

      猜你喜欢
      • 2012-11-24
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2020-02-06
      • 2015-10-13
      相关资源
      最近更新 更多