DB에서 단일 문자열 값을 검색하기 위해 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을 반환해야 할 것 같습니다. 이 문제를 어떻게 해결할 수 있습니까? 미리 감사드립니다.
답변
JdbcTemplate와에서, queryForInt
, queryForLong
, queryForObject
쿼리를 실행 이러한 모든 방법을 예상 하는 오직 하나 개의 행을 반환합니다. 행이 없거나 두 개 이상의 행이 있으면 IncorrectResultSizeDataAccessException
. 이제 올바른 방법은이 예외 또는를 포착하지 않는 EmptyResultDataAccessException
것이지만 사용중인 쿼리가 하나의 행만 반환해야합니다. 전혀 불가능하다면 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.
}
답변
당신은 또한 사용할 수 있습니다 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 람다와 훌륭하게 작동하지만 정적 클래스를 사용하여 동일한 결과를 얻을 수 있습니다.
질문은 단순 유형에 관한 것이지만이 예제는 도메인 객체를 추출하는 일반적인 경우에 대한 가이드 역할을합니다.
우선. 단순성을 위해 두 가지 속성이있는 계정 개체가 있다고 가정 해 보겠습니다 Account(Long id, String name)
. RowMapper
이 도메인 개체에 대한을 원할 것 입니다.
private static final RowMapper<Account> MAPPER_ACCOUNT =
(rs, i) -> new Account(rs.getLong("ID"),
rs.getString("NAME"));
이제 매핑하는 방법에서 직접이 매퍼를 사용할 수있다 Account
쿼리에서 도메인 개체를 ( jt
A는 JdbcTemplate
경우).
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);
}
이 정보가 이제 강력한 방식으로 부품을 매우 쉽게 결합하여 도메인을 단순화 할 수 있음을 보여주는 데 도움이되기를 바랍니다.
업데이트 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);
}
답변
제어 흐름에 대한 예외에 의존하고 있기 때문에 좋은 솔루션이 아닙니다. 솔루션에서 예외가 발생하는 것은 정상이며 로그에 예외가있는 것은 정상입니다.
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
원하는대로 자신의 방법을 사용 하고 사용자 지정할 수 있습니다 . 내 제안은 다음과 같이 만드는 것입니다.
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
.
답변
데이터가 없을 때 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);
다른 사람이 이것이 좋은 생각이라고 생각하는지 알고 싶습니다.
답변
좋아, 알아 냈어. 나는 그것을 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;
}
답변
Java 8 이상을 사용하면 Optional
및 Java Streams를 사용할 수 있습니다 .
따라서 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
있으므로 데이터베이스에서 항목이 하나 이상 전송되지 않습니다.