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


105

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

    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を返すだけのように思えます。どうすれば修正できますか?前もって感謝します。

回答:


179

JdbcTemplateで、queryForIntqueryForLongqueryForObjectクエリは、唯一の行が返され実行されたすべてのそのような方法を期待しています。行が1つも取得されない場合、または複数行になる場合は、になりIncorrectResultSizeDataAccessExceptionます。正しい方法はEmptyResultDataAccessException、この例外またはをキャッチしないことですが、使用しているクエリが1行のみを返すようにしてください。それがまったくできない場合は、query代わりにmethodを使用してください。

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.    
}

後述するように、ここでの唯一の欠点は、戻り値の型が複合型である場合、複数のオブジェクトを作成してリストをインスタンス化し、ResultSet.next()不必要に呼び出されることです。ResultSetExtractorこの場合、a を使用する方がはるかに効率的なツールです。
ブレットライアン

3
匿名クラスの定義に括弧がありません-新しいRowMapper ()
Janis Koluzs

私はこれについてブレットと一緒です。ResultSetExtractorはよりクリーンです:)
laher

2
こんにちは@Rakesh、なぜだけreturn nullではないのcatch(EmptyResultDataAccessException exception){ return null; }ですか?
Vishal Zanzrukia 14年

1
おい!queryForObjectを使用しているかどうかを考慮して、なぜ「今はこの例外をキャッチしないのが正しい方法なのか」と質問してもいいですか?queryForObjectの場合に例外をキャッチすることの何が問題になっていますか?ありがとう:)
マイケルストークス

48

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は、複数の行がある場合、または行が返されない場合のすべてのケースを処理できるという追加の利点があります。

更新:数年後、私は共有するいくつかのトリックを持っています。JdbcTemplate次の例が設計されているjava 8ラムダでうまく機能しますが、静的クラスを使用して同じことを非常に簡単に行うことができます。

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

最初に。簡単にするために、2つのプロパティを持つアカウントオブジェクトがあるとします。Account(Long id, String name)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の代わりに、オプション値のとます。

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

21

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

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

私のソリューションは最もエレガントではないかもしれませんが、少なくとも私の作品は機能します。JdbctemplateのオプションではないqueryForObjectListの例を示します。
バイロン

1
ここでの唯一の欠点は、戻り値の型が複合型である場合、複数のオブジェクトを作成してリストをインスタンス化し、ResultSet.next()不必要に呼び出されることです。ResultSetExtractorこの場合、a を使用する方がはるかに効率的なツールです。
ブレットライアン

そして、値を持たないことがオプションであるが、複数を持たない場合はどうなりますか?私はこのパターンを頻繁に持っていますが、この目的のためにSpringでqueryForOptionalObjectを使用したいと考えています。
Guillaume

7

実際に、あなたは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 EmptyResultDataAccessExceptionsize == 0


@アブドルUserMapper implements RowMapper<String>
Brett Ryan

私は、それが最短の構文を与えるとして、ここで最良の答えだと思う
スタン・ソコロフ

DataAccessUtils.singleResult(...)私が探していたものです。Thx
ドレイク

7

データがないときにnullを返すことは、queryForObjectを使用するときに頻繁に行いたいことなので、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);

他の誰かがこれを良いアイデアだと思っているかどうか知りたいです。


1
それはあり、そしてそれは春にあるはずです
ギヨーム

6

わかりました。私はそれを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;
    }

1
なぜこれがそんなに悪いのか、なぜあなたはそれに対して多くの反対投票を受けたのか、「例外の中にプログラムの流れがない」という原理の原理主義者であることはわかりません。printstackトレースをケースを説明するコメントに置き換えるだけで、他には何もしません。
Guillaume

4

Java 8以降を使用するOptionalと、Javaストリームを使用できます。

したがって、JdbcTemplate.queryForList()メソッドを使用し、Streamを作成して、Stream Stream.findFirst()の最初の値または空の値を返すを使用できます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つだけです。


1
素敵できれい。追加のifやラムダはありません。私はそれが好きです。
BeshEater

2

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

MIN(ID_NMB_SRZ)

1

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

SELECT (SELECT <query>) AS value

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


1

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

0

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

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

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

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

作る

    jdbcTemplate.queryForList(sql, String.class)

動作します。jdbcTemplateのタイプが

    org.springframework.jdbc.core.JdbcTemplate

0

queryForObjectの代わりにクエリを使用できます。queryとqueryForObjectの主な違いは、(行マッパーの戻り値の型に基づく)Objectのクエリ戻り値のリストであり、データベースからデータが受信されない場合、queryForObjectは常に単一のオブジェクトのみを期待している場合、そのリストを空にすることができます。 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

IMHO nullが(おそらく)フロントエンドクライアントで送信して解釈するという問題があるため、aを返すのは悪い解決策です。同じエラーが発生し、単にを返すことで解決しましたList<FooObject>。使用しましたJDBCTemplate.query()

フロントエンド(Angular Webクライアント)では、単にリストを調べ、それが空(長さがゼロ)の場合は、レコードが見つからないものとして扱います。


-1

この「EmptyResultDataAccessException」をキャッチするだけです

public Myclass findOne(String id){
    try {
        Myclass m = this.jdbcTemplate.queryForObject(
                "SELECT * FROM tb_t WHERE id = ?",
                new Object[]{id},
                new RowMapper<Myclass>() {
                    public Myclass mapRow(ResultSet rs, int rowNum) throws SQLException {
                        Myclass m = new Myclass();
                        m.setName(rs.getString("name"));
                        return m;
                    }
                });
        return m;
    } catch (EmptyResultDataAccessException e) { // result.size() == 0;
        return null;
    }
}

それからあなたはチェックすることができます:

if(m == null){
    // insert operation.
}else{
    // update operation.
}

queryForObjectの代わりにクエリを使用できます
ABHAY JOHRI

1
通常、このような例外を悪用することは悪い習慣と考えられています。例外は予測可能なプログラムロジックフロー用ではなく、例外的な状況用です。
クリスベイカー
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.