resultSet.next()返回false,即使填充了表

时间:2017-02-19 13:46:55

标签: java sqlite jdbc resultset

我有一些函数可以帮助从数据库中检索对象。

public User getUser(int beamID) throws NoSuchUserException {
    return userFromResultSet(getUserResultSet(beamID));
}


private ResultSet getUserResultSet(int beamID) {
    try(Connection conn = dataSource.getConnection()) {

        // queries.getUserByBeamID() returns "SELECT * FROM user WHERE beamID=?"
        PreparedStatement stmt = conn.prepareStatement(queries.getUserByBeamID());

        stmt.setInt(1, beamID);
        System.out.println(stmt.toString());
        return stmt.executeQuery();

    } catch (SQLException e) {
        e.printStackTrace();
        throw new IllegalStateException();
    }
}

private User userFromResultSet(ResultSet resultSet) {
    try {
        boolean next = resultSet.next();  // Debugger tells me this is false.
        if (!next)
            throw new NoSuchUserException();

        User user = new User(this,
             resultSet.getInt("beamID"),
             resultSet.getString("name"),
             resultSet.getInt("points"),
             resultSet.getInt("time")
        );

        if (resultSet.next())
            throw new IllegalStateException("Duplicate user entries exist - database integrity compromised!");

        return user;
    } catch (SQLException e) {
        e.printStackTrace();
        throw new IllegalStateException();
    }
}

奇怪的是,我知道数据 存在有两个原因:

  • 我的程序尝试创建条目(如果它不存在),但是尝试这样做会产生错误,即没有遵循唯一约束

  • 在我的SQLite数据库浏览器中运行查询的工作正常:

The query returns a result, as it should.

我非常怀疑这是未提交数据的问题,因为这是一个基于文件的数据库,使用文本编辑器打开该文件会显示数据中用户名的实例。

2 个答案:

答案 0 :(得分:5)

仔细看看你在这里做了什么:

try (Connection conn = dataSource.getConnection()) {
    PreparedStatement stmt = conn.prepareStatement(queries.getUserByBeamID());

    stmt.setInt(1, beamID);
    System.out.println(stmt.toString());
    return stmt.executeQuery();
} catch (SQLException e) {
    e.printStackTrace();
    throw new IllegalStateException();
}

我认为,在表达式执行完毕后,这是try-with-resources的合同,以保证在try子句中关闭指定的资源。我相信结果集也在try块结束时关闭,因此调用next()返回false,因为没有任何内容。

我编写代码的方法是在User块中填充try POJO,并返回User对象而不是返回结果集:

private User getUserResultSet(int beamID) {
    User user = null;
    try (Connection conn = dataSource.getConnection()) {
        PreparedStatement stmt = conn.prepareStatement(queries.getUserByBeamID());

        stmt.setInt(1, beamID);

        ResultSet rs = stmt.executeQuery();
        rs.next();
        user = new User(this,
            rs.getInt("beamID"),
            rs.getString("name"),
            rs.getInt("points"),
            rs.getInt("time")
        );

    } catch (SQLException e) {
        e.printStackTrace();
        throw new IllegalStateException();
    }

    return user;
}

现在,您关注的问题比以前更好了。如果连接,结果集等出现问题,它将在处理这些事情的实际代码中处理。如果发生异常或其他错误,将返回null用户对象,您应该更新代码以处理这种可能性。

答案 1 :(得分:0)

虽然接受的答案解决了问题的根本原因,但实际的实现存在一些问题。

  • Exceptions被吞下
  • PreparedStatement应该独立于Connection
  • 关闭
  • ResultSet应该独立于PreparedStatementConnection
  • 关闭
  • TYPE_FORWARD_ONLY为空时,它无法处理ResultSets ResultSet.next()抛出的错误
  • 它不会检查结果是否唯一
  • 它没有正确的日志记录

下面是一个更完整的示例:

package com.stackoverflow.questions.Q42327984;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Optional;

import javax.sql.DataSource;

import lombok.Value;
import lombok.extern.log4j.Log4j2;

@SuppressWarnings("javadoc")
public class Answer {

    /**
     * <p>
     * We use lombok's {@link Log4j2} annotation to create a Log4j2 logger, avoid using {@link System#out},
     * {@link System#err} or {@link Throwable#printStackTrace()}
     * </p>
     *
     * @See Lombok's <a href="https://www.projectlombok.org/features/log">log</a> api.
     * @See Log4j2's install <a href="https://logging.apache.org/log4j/2.x/maven-artifacts.html">guide</a>.
     */
    @Log4j2
    static class UserDao {

        final DataSource dataSource;

        final UserQueries queries;

        public UserDao(final DataSource dataSource, final UserQueries queries) {
            this.dataSource = dataSource;
            this.queries = queries;
        }
        /**
         * Find {@link User} by beam id.
         * <p>
         * In most systems not finding a User would not be an "exceptional" condition, we should return {@link Optional}
         * instead of throwing an {@link Exception}
         * </p>
         * <p>
         * 'get' is fine for bean like things, but 'find' is for queries
         * </p>
         * <p>
         * We add "ByBeamId" so we can disambiguate between this method and other queries that might also find
         * {@link User} by a int value.
         * </p>
         *
         * @param beamID the beam ID
         * @return the {@link Optional} {@link User}, null's are the "billion dollar mistake"
         * @throws {@link NoSuchUserException} the no such user exception
         */
        public Optional<User> findUserByBeamId(int beamID) throws NoSuchUserException {

            log.debug("findUserByBeamId({})", beamID);

            final String userByBeamID = this.queries.getUserByBeamID();
            log.debug("findUserByBeamId({}) userByBeamID query: {}", beamID, userByBeamID);

            try (final Connection conn = this.dataSource.getConnection();
                    final PreparedStatement stmt = conn.prepareStatement(userByBeamID)) {

                stmt.setInt(1, beamID);

                /*
                 * ResultSets should be closed properly also
                 */
                try (final ResultSet rs = stmt.executeQuery()) {

                    /*
                     * There are other ways to find if the ResultSet is empty but the method below is safe and
                     * can handle most implementations
                     */
                    int resultSetType = rs.getType();

                    try {

                        boolean scrolledForwardToRow = rs.next();

                        if (!scrolledForwardToRow) {
                            return Optional.empty();
                        }

                    } catch (SQLException e) {

                        /*
                         * ResultSets of TYPE_FORWARD_ONLY can throw a SQLException on ResultSet.next() if the ResultSet
                         * is
                         * empty.
                         */
                        if (ResultSet.TYPE_FORWARD_ONLY == resultSetType) {
                            log.debug(
                                    "findUserByBeamId({}): initial rs.next() call failed but ResultSet is TYPE_FORWARD_ONLY so returning empty.",
                                    userByBeamID, e);
                            return Optional.empty();
                        }

                        log.error("findUserByBeamId({}): initial rs.next() call failed, return empty.", userByBeamID,
                                e);
                        throw e;
                    }

                    /*
                     * User should not have a reference to its DAO, this is a recipe for trouble.
                     *
                     * In general User should be an interface and a static factory method or a builder should be used
                     * for construction.
                     * 'new' should be avoided as it tightly couples a specific implementation of User.
                     *
                     * User user = new User(this,
                     * rs.getInt("beamID"),
                     * rs.getString("name"),
                     * rs.getInt("points"),
                     * rs.getInt("time"));
                     */

                    /*
                     * Assigning to local variables has a couple advantages:
                     * - It makes stepping through the debugger much easier.
                     * - When an exception occurs the line number in the stack trace will only contain one method.
                     *
                     * Note, that the performance impact of local variables is not significant in a method of this
                     * complexity with so many external calls.
                     */

                    final int beamIDFromRs = rs.getInt("beamID");
                    final String name = rs.getString("name");
                    final int points = rs.getInt("points");
                    final int time = rs.getInt("time");

                    final User user = User.of(beamIDFromRs, name, points, time);

                    /*
                     * Before we return our result we need to make sure it was unique.
                     *
                     * There are other ways to find if the ResultSet has no more row but the method below is safe
                     * can handle most implementations.
                     */
                    try {

                        boolean scrolledForward = rs.next();

                        if (!scrolledForward) {
                            return Optional.of(user);
                        }

                    } catch (SQLException se) {

                        /*
                         * ResultSets of TYPE_FORWARD_ONLY can throw a SQLException on ResultSet.next() if the ResultSet
                         * is
                         * empty.
                         */
                        if (ResultSet.TYPE_FORWARD_ONLY == resultSetType) {

                            log.debug(
                                    "findUserByBeamId({}): rs.next() call failed but ResultSet is TYPE_FORWARD_ONLY so returning user: {}.",
                                    userByBeamID, user, se);

                            return Optional.of(user);
                        }

                        log.error("findUserByBeamId({}): rs.next() call failed.", userByBeamID, se);

                        throw se;

                    }

                    log.error("findUserByBeamId({}): results not unique.", userByBeamID);

                    throw new IllegalStateException("findUserByBeamId(" + beamID + ") results were not unique.");
                }

            } catch (SQLException se) {

                log.error("findUserByBeamId({}): failed", userByBeamID, se);

                throw new IllegalStateException("findUserByBeamId(" + beamID + ") failed", se);
            }

        }
        /**
         * Gets the user.
         *
         * @param beamID the beam ID
         * @return the {@link User}
         * @throws NoSuchUserException if the user does not exist.
         * @deprecated use {@link #findUserByBeamId(int)}
         */
        @Deprecated
        public User getUser(final int beamID) throws NoSuchUserException {
            return findUserByBeamId(beamID).orElseThrow(() -> new NoSuchUserException(beamID));
        }

    }

    interface UserQueries {
        String getUserByBeamID();
    }

    /**
     * Use interfaces to avoid tight coupling
     */
    interface User {

        /**
         * static factory method for creating User instances
         *
         * @param beamID the beam ID
         * @param name the name
         * @param points the points
         * @param time the time
         * @return the user
         */
        static User of(final int beamID, final String name, final int points, final int time) {
            return new DefaultUser(beamID, name, points, time);
        }

        /**
         * Gets the beam ID.
         *
         * @return the beam ID
         */
        int getBeamID();

        /**
         * Gets the name.
         *
         * @return the name
         */
        String getName();

        /**
         * Gets the points.
         *
         * @return the points
         */
        int getPoints();

        /**
         * Gets the time.
         *
         * @return the time
         */
        int getTime();
    }

    /**
     * <p>
     * We use lombok's {@link Value} to create a final immutable object with an all args constuctor
     * ({@link #DefaultUser(int, String, int, int)} and proper
     * {@link #toString()}, {@link #equals(Object)} and {@link #hashCode()} methods
     * </p>
     *
     * @See Lombok's <a href="https://www.projectlombok.org/features/Value">value</a> api.
     */
    @Value
    static class DefaultUser implements User {

        final int beamID;

        final String name;

        final int points;

        final int time;
    }

    /**
     * The Class NoSuchUserException.
     */
    @SuppressWarnings("serial")
    public static class NoSuchUserException extends RuntimeException {

        final int beamID;

        public NoSuchUserException(int beamID) {
            this.beamID = beamID;
        }

        /**
         * Gets the beam ID.
         *
         * @return the beam ID
         */
        public int getBeamID() {
            return this.beamID;
        }

    }
}