我有一些函数可以帮助从数据库中检索对象。
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数据库浏览器中运行查询的工作正常:
我非常怀疑这是未提交数据的问题,因为这是一个基于文件的数据库,使用文本编辑器打开该文件会显示数据中用户名的实例。
答案 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
应该独立于PreparedStatement
或Connection
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;
}
}
}