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
などのメソッドはすべて、実行されたクエリが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.
}
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
ドメインオブジェクトをマップできます(jt
は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);
}
これが、ドメインをよりシンプルにする強力な方法でパーツを非常に簡単に結合できることを示すのに役立つことを願っています。
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);
}
制御フローの例外に依存しているため、これは良い解決策ではありません。ソリューションでは、例外を取得するのは正常です。例外をログに記録するのは正常です。
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
がありません。
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);
他の誰かがこれが良い考えだと思うかどうか知りたいですか?
わかった。 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;
}
クエリが常に結果を返すように、グループ関数を使用できます。すなわち
MIN(ID_NMB_SRZ)
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;
}
Postgresでは、ほとんどすべての単一値クエリをラップして値またはnullを返すことができます。
SELECT (SELECT <query>) AS value
したがって、呼び出し元の複雑さを回避できます。
Java 8以上を使用すると、 Optional
およびJava Streamsを使用できます。
したがって、単に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つ以下になるようにします。
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); } }
作る
jdbcTemplate.queryForList(sql, String.class)
動作し、jdbcTemplateがタイプであることを確認してください
org.springframework.jdbc.core.JdbcTemplate
私はこれを以前に扱い、春のフォーラムに投稿しました。
受け取ったアドバイスは、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);
パフォーマンスについては明確ではありませんが、機能し、きれいです。
バイロンの場合、これを試すことができます。
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();
}