web-dev-qa-db-ja.com

文字列のJdbctemplateクエリ:EmptyResultDataAccessException:不正な結果サイズ:予期される1、実際の0

Jdbctemplateを使用して、データベースから単一の文字列値を取得しています。これが私の方法です。

    public String test() {
        String cert=null;
        String sql = "select ID_NMB_SRZ from codb_owner.TR_LTM_SLS_RTN 
             where id_str_rt = '999' and ID_NMB_SRZ = '60230009999999'";
        cert = (String) jdbc.queryForObject(sql, String.class); 
        return cert;
    }

私のシナリオでは、クエリにヒットしないようにすることは完全に可能であるため、私の質問は、次のエラーメッセージを回避する方法です。

EmptyResultDataAccessException: Incorrect result size: expected 1, actual 0

例外をスローするのではなく、nullを返すべきだと思われます。どうすれば修正できますか?前もって感謝します。

94
Byron

JdbcTemplateでは、queryForIntqueryForLongqueryForObjectなどのメソッドはすべて、実行されたクエリが1行のみを返すことを想定しています。行が取得されない場合、またはIncorrectResultSizeDataAccessExceptionになる行が複数ある場合。正しい方法は、この例外またはEmptyResultDataAccessExceptionをキャッチすることではありませんが、使用しているクエリが1行のみを返すようにしてください。まったく不可能な場合は、代わりにqueryメソッドを使用してください。

List<String> strLst  = getJdbcTemplate().query(sql,new RowMapper {

  public Object mapRow(ResultSet rs, int rowNum) throws SQLException {
        return rs.getString(1);
  }

});

if ( strLst.isEmpty() ){
  return null;
}else if ( strLst.size() == 1 ) { // list contains exactly 1 element
  return strLst.get(0);
}else{  // list contains more than 1 elements
  //your wish, you can either throw the exception or return 1st element.    
}
159
Rakesh Juyal

ResultSetExtractor の代わりに RowMapper を使用することもできます。どちらも簡単です。唯一の違いは、 ResultSet.next() を呼び出すことです。

public String test() {
    String sql = "select ID_NMB_SRZ from codb_owner.TR_LTM_SLS_RTN "
                 + " where id_str_rt = '999' and ID_NMB_SRZ = '60230009999999'";
    return jdbc.query(sql, new ResultSetExtractor<String>() {
        @Override
        public String extractData(ResultSet rs) throws SQLException,
                                                       DataAccessException {
            return rs.next() ? rs.getString("ID_NMB_SRZ") : null;
        }
    });
}

ResultSetExtractor には、複数の行がある場合や行が返されない場合すべてを処理できるという利点があります。

UPDATE:数年後、共有すべきいくつかのトリックがあります。 JdbcTemplate は、以下の例が設計されているJava 8ラムダで見事に機能しますが、静的クラスを使用して同じことを簡単に実現できます。

問題は単純型に関するものですが、これらの例は、ドメインオブジェクトを抽出する一般的なケースのガイドとして機能します。

最初に。 Account(Long id, String name)を簡単にするために、2つのプロパティを持つアカウントオブジェクトがあると仮定します。このドメインオブジェクトに対してRowMapperが必要になる場合があります。

private static final RowMapper<Account> MAPPER_ACCOUNT =
        (rs, i) -> new Account(rs.getLong("ID"),
                               rs.getString("NAME"));

メソッド内でこのマッパーを直接使用して、クエリからAccountドメインオブジェクトをマップできます(jtJdbcTemplateインスタンスです)。

public List<Account> getAccounts() {
    return jt.query(SELECT_ACCOUNT, MAPPER_ACCOUNT);
}

素晴らしいですが、今は元の問題が必要であり、元のソリューションを使用して RowMapper を使用してマッピングを実行します。

public Account getAccount(long id) {
    return jt.query(
            SELECT_ACCOUNT,
            rs -> rs.next() ? MAPPER_ACCOUNT.mapRow(rs, 1) : null,
            id);
}

素晴らしいですが、これはあなたが繰り返したくなるパターンです。したがって、汎用ファクトリメソッドを作成して、タスク用に新しい ResultSetExtractor を作成できます。

public static <T> ResultSetExtractor singletonExtractor(
        RowMapper<? extends T> mapper) {
    return rs -> rs.next() ? mapper.mapRow(rs, 1) : null;
}

ResultSetExtractor の作成は簡単になりました。

private static final ResultSetExtractor<Account> EXTRACTOR_ACCOUNT =
        singletonExtractor(MAPPER_ACCOUNT);

public Account getAccount(long id) {
    return jt.query(SELECT_ACCOUNT, EXTRACTOR_ACCOUNT, id);
}

これが、ドメインをよりシンプルにする強力な方法でパーツを非常に簡単に結合できることを示すのに役立つことを願っています。

UPDATE 2:NULLではなく、オプション値の Optional と組み合わせます。

public static <T> ResultSetExtractor<Optional<T>> singletonOptionalExtractor(
        RowMapper<? extends T> mapper) {
    return rs -> rs.next() ? Optional.of(mapper.mapRow(rs, 1)) : Optional.empty();
}

使用すると、次のようになります。

private static final ResultSetExtractor<Optional<Double>> EXTRACTOR_DISCOUNT =
        singletonOptionalExtractor(MAPPER_DISCOUNT);

public double getDiscount(long accountId) {
    return jt.query(SELECT_DISCOUNT, EXTRACTOR_DISCOUNT, accountId)
            .orElse(0.0);
}
37
Brett Ryan

制御フローの例外に依存しているため、これは良い解決策ではありません。ソリューションでは、例外を取得するのは正常です。例外をログに記録するのは正常です。

public String test() {
    String sql = "select ID_NMB_SRZ from codb_owner.TR_LTM_SLS_RTN where id_str_rt = '999' and ID_NMB_SRZ = '60230009999999'";
    List<String> certs = jdbc.queryForList(sql, String.class); 
    if (certs.isEmpty()) {
        return null;
    } else {
        return certs.get(0);
    }
}
19

実際、JdbcTemplateで遊んで、好きなように独自のメソッドをカスタマイズできます。私の提案は、このようなものを作ることです:

public String test() {
    String cert = null;
    String sql = "select ID_NMB_SRZ from codb_owner.TR_LTM_SLS_RTN
        where id_str_rt = '999' and ID_NMB_SRZ = '60230009999999'";
    ArrayList<String> certList = (ArrayList<String>) jdbc.query(
        sql, new RowMapperResultSetExtractor(new UserMapper()));
    cert =  DataAccessUtils.singleResult(certList);

    return cert;
}

元のjdbc.queryForObjectとして機能しますが、throw new EmptyResultDataAccessExceptionの場合はsize == 0がありません。

7
Alex

QueryForObjectを使用する場合、データがないときにnullを返すことがよくあるため、JdbcTemplateを拡張し、以下のようなqueryForNullableObjectメソッドを追加すると便利です。

public class JdbcTemplateExtended extends JdbcTemplate {

    public JdbcTemplateExtended(DataSource datasource){
        super(datasource);
    }

    public <T> T queryForNullableObject(String sql, RowMapper<T> rowMapper) throws DataAccessException {
        List<T> results = query(sql, rowMapper);

        if (results == null || results.isEmpty()) {
            return null;
        }
        else if (results.size() > 1) {
            throw new IncorrectResultSizeDataAccessException(1, results.size());
        }
        else{
            return results.iterator().next();
        }
    }

    public <T> T queryForNullableObject(String sql, Class<T> requiredType) throws DataAccessException {
        return queryForObject(sql, getSingleColumnRowMapper(requiredType));
    }

}

QueryForObjectを使用したのと同じ方法で、コードでこれを使用できるようになりました

String result = queryForNullableObject(queryString, String.class);

他の誰かがこれが良い考えだと思うかどうか知りたいですか?

7
Stewart Evans

わかった。 try catchにラップして、nullを返送しました。

    public String test() {
            String cert=null;
            String sql = "select ID_NMB_SRZ from codb_owner.TR_LTM_SLS_RTN 
                     where id_str_rt = '999' and ID_NMB_SRZ = '60230009999999'";
            try {
                Object o = (String) jdbc.queryForObject(sql, String.class);
                cert = (String) o;
            } catch (EmptyResultDataAccessException e) {
                e.printStackTrace();
            }
            return cert;
    }
5
Byron

クエリが常に結果を返すように、グループ関数を使用できます。すなわち

MIN(ID_NMB_SRZ)
2
DS.

GetJdbcTemplate()。queryForMapは最小サイズの1を想定しているため、nullを返すと、以下のロジックを使用できる場合にEmptyResultDataAccesso fix disを示します

Map<String, String> loginMap =null;
try{
    loginMap = getJdbcTemplate().queryForMap(sql, new Object[] {CustomerLogInInfo.getCustLogInEmail()});
}
catch(EmptyResultDataAccessException ex){
    System.out.println("Exception.......");
    loginMap =null;
}
if(loginMap==null || loginMap.isEmpty()){
    return null;
}
else{
    return loginMap;
}
1

Postgresでは、ほとんどすべての単一値クエリをラップして値またはnullを返すことができます。

SELECT (SELECT <query>) AS value

したがって、呼び出し元の複雑さを回避できます。

1
Rich

Java 8以上を使用すると、 Optional およびJava St​​reamsを使用できます。

したがって、単にJdbcTemplate.queryForList()メソッドを使用し、Streamを作成して Stream.findFirst() を使用すると、Streamの最初の値または空のOptionalが返されます。

public Optional<String> test() {
    String sql = "select ID_NMB_SRZ from codb_owner.TR_LTM_SLS_RTN where id_str_rt = '999' and ID_NMB_SRZ = '60230009999999'";
    return jdbc.queryForList(sql, String.class)
            .stream().findFirst();
}

クエリのパフォーマンスを改善するには、LIMIT 1をクエリに追加して、データベースから転送されるアイテムが1つ以下になるようにします。

0
Samuel Philipp

QueryForObjectの代わりにqueryを使用できます。queryとqueryForObjectの主な違いは、クエリがObjectのリストを返すことです(Row Mapperの戻り値の型に基づく)。 nullでも複数行でもないdbからフェッチし、結果が空の場合、queryForObjectがEmptyResultDataAccessExceptionをスローする場合、クエリを使用してnull結果の場合にEmptyResultDataAccessExceptionの問題を克服するコードを1つ記述しました。

----------


public UserInfo getUserInfo(String username, String password) {
      String sql = "SELECT firstname, lastname,address,city FROM users WHERE id=? and pass=?";
      List<UserInfo> userInfoList = jdbcTemplate.query(sql, new Object[] { username, password },
              new RowMapper<UserInfo>() {
                  public UserInfo mapRow(ResultSet rs, int rowNum) throws SQLException {
                      UserInfo user = new UserInfo();
                      user.setFirstName(rs.getString("firstname"));
                      user.setLastName(rs.getString("lastname"));
                      user.setAddress(rs.getString("address"));
                      user.setCity(rs.getString("city"));

                      return user;
                  }
              });

      if (userInfoList.isEmpty()) {
          return null;
      } else {
          return userInfoList.get(0);
      }
  }
0
ABHAY JOHRI

作る

    jdbcTemplate.queryForList(sql, String.class)

動作し、jdbcTemplateがタイプであることを確認してください

    org.springframework.jdbc.core.JdbcTemplate
0
Dmitry

私はこれを以前に扱い、春のフォーラムに投稿しました。

http://forum.spring.io/forum/spring-projects/data/123129-frustrated-with-emptyresultdataaccessexception

受け取ったアドバイスは、SQlQueryの一種を使用することでした。これは、DBから値を取得しようとしたときに行った例です。

@Component
public class FindID extends MappingSqlQuery<Long> {

        @Autowired
        public void setDataSource(DataSource dataSource) {

                String sql = "Select id from address where id = ?";

                super.setDataSource(dataSource);

                super.declareParameter(new SqlParameter(Types.VARCHAR));

                super.setSql(sql);

                compile();
        }

        @Override
        protected Long mapRow(ResultSet rs, int rowNum) throws SQLException {
                return rs.getLong(1);
        }

DAOでは、次のように呼び出します...

Long id = findID.findObject(id);

パフォーマンスについては明確ではありませんが、機能し、きれいです。

0
grbonk

バイロンの場合、これを試すことができます。

public String test(){
                String sql = "select ID_NMB_SRZ from codb_owner.TR_LTM_SLS_RTN 
                     where id_str_rt = '999' and ID_NMB_SRZ = '60230009999999'";
                List<String> li = jdbcTemplate.queryForList(sql,String.class);
                return li.get(0).toString();
        }
0
Mohan Kumar Dg