Spring jdbcTemplate OneToMany
Spring jdbcTemplate OneToMany
我收到以下错误,这表明我没有在结果集上调用 next()
,但据我所知(即调试断点在 rs.next()
上停止并按预期填充)。
Servlet.service() for servlet [dispatcherServlet] in context with path
[] threw exception [Request processing failed; nested exception is
org.springframework.jdbc.UncategorizedSQLException:
PreparedStatementCallback; uncategorized SQLException for SQL [SELECT
m.username, m.password, t.name as authority FROM members m JOIN
administrator a ON a.member_id = m.member_id JOIN admin_type t ON
t.admin_type_id = a.admin_type_id WHERE m.username = ?]; SQL state
[24000]; error code [0]; ResultSet not positioned properly, perhaps
you need to call next.; nested exception is
org.postgresql.util.PSQLException: ResultSet not positioned properly,
perhaps you need to call next.] with root cause
org.postgresql.util.PSQLException: ResultSet not positioned properly,
perhaps you need to call next.
代码
@Autowired
NamedParameterJdbcTemplate namedParameterJdbcTemplate;
@Override
public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
String sql = "SELECT m.username, m.password, t.name as authority FROM members m " +
"JOIN administrator a ON a.member_id = m.member_id " +
"JOIN admin_type t ON t.admin_type_id = a.admin_type_id " +
"WHERE m.username = :userName";
SqlParameterSource namedParameters = new MapSqlParameterSource().addValue("userName", userName);
List<User> users = namedParameterJdbcTemplate.query(sql, namedParameters, new UserDetailsRowMapper());
return users.get(0);
}
class UserDetailsRowMapper implements RowMapper<User> {
@Override
public User mapRow(ResultSet rs, int rowNum) throws SQLException {
UserResultSetExtractor extractor = new UserResultSetExtractor();
return extractor.extractData(rs);
}
}
class UserResultSetExtractor implements ResultSetExtractor {
public User extractData(ResultSet rs) throws SQLException, DataAccessException {
Collection<SimpleGrantedAuthority> roles = new ArrayList<>();
while (rs.next()) {
String authority = rs.getString("authority");
roles.add( new SimpleGrantedAuthority(authority));
}
User user = new User(rs.getString("username"), rs.getString("password"), roles);
return user;
}
}
我不确定为什么会收到错误消息。这可能是因为我的逻辑不正确,因为结果集中的每一行都调用了 extractData()
。
问题
我应该如何执行上述操作来填充与authorities
具有一对多关系的User
对象?
+-----------+ +---------------+ +---------------+
| Member | | administrator | | admin_type |
+-----------+ +---------------+ +---------------+
| member_id | | admin_id | | admin_type_id |
| username | | member_id | | name |
| password | | admin_type_id | +---------------+
+-----------+ +---------------+
SQL
SELECT m.username, m.password, t.name as authority FROM members m
JOIN administrator a ON a.member_id = m.member_id
JOIN admin_type t ON t.admin_type_id = a.admin_type_id
WHERE m.username = 'richardmarais';
结果
在您的代码中:
while (rs.next()) {
String authority = rs.getString("authority");
roles.add( new SimpleGrantedAuthority(authority));
}
User user = new User(rs.getString("username"), rs.getString("password"), roles);
return user;
您在 while 循环之外调用 rs.getString
,因此即使没有 rs.next()
也可能是真的。但更重要的是,当您确实跳出循环时,rs.next()
已返回 false,因此您的 get
将不再起作用。你没有提到在哪一行抛出异常,但我会说这是最可能的原因。
您应该在获取权限时随时填充用户信息,例如在第一行。但不要等到你已经处理完所有行。
例如像这样:
public User extractData(ResultSet rs) throws SQLException, DataAccessException {
Collection<SimpleGrantedAuthority> roles = new ArrayList<>();
User user = null;
while (rs.next()) {
if (user == null) {
user = new User(rs.getString("username"), rs.getString("password"), roles);
}
String authority = rs.getString("authority");
roles.add( new SimpleGrantedAuthority(authority));
}
if (user == null) {
throw new MyException("User not found");
}
return user;
}
或者,只需使用像 Hibernate 这样的框架来编写这种样板代码。
您的情况的另一种方法可能是:
@Override
public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
return ofNullable(userName)
.map(u -> namedParameterJdbcTemplate.query(
"SELECT m.username, m.password, t.name as authority " +
"FROM members m " +
"JOIN administrator a ON a.member_id = m.member_id " +
"JOIN admin_type t ON t.admin_type_id = a.admin_type_id " +
"WHERE m.username = :username",
Map.of("username", userName),
UserMapper.userWithRolesResultExtractor)
)
.orElseThrow(() -> new UsernameNotFoundException(
format("Given username: %s does not exist in database", username)));
}
正在另一个 class 中创建以下 mapper
(以重用它):
@UtilityClass
public class UserMapper {
public static final ResultSetExtractor<User> userWithRolesResultExtractor = (resultSet) -> {
User user = null;
while (resultSet.next()) {
if (resultSet.isFirst()) {
user = new User(resultSet.getString("username"),
resultSet.getString("password")
new HashSet<>());
}
user.getRoles().add(new SimpleGrantedAuthority(rs.getString("authority"))));
}
return user;
};
}
正如 Sebastiaan 提到的,有几种 ORM 可以帮助您编写更少的代码,但是性能较差。
PD。在我的示例中,我使用 Set
而不是 List
来避免重复。
我收到以下错误,这表明我没有在结果集上调用 next()
,但据我所知(即调试断点在 rs.next()
上停止并按预期填充)。
Servlet.service() for servlet [dispatcherServlet] in context with path [] threw exception [Request processing failed; nested exception is org.springframework.jdbc.UncategorizedSQLException: PreparedStatementCallback; uncategorized SQLException for SQL [SELECT m.username, m.password, t.name as authority FROM members m JOIN administrator a ON a.member_id = m.member_id JOIN admin_type t ON t.admin_type_id = a.admin_type_id WHERE m.username = ?]; SQL state [24000]; error code [0]; ResultSet not positioned properly, perhaps you need to call next.; nested exception is org.postgresql.util.PSQLException: ResultSet not positioned properly, perhaps you need to call next.] with root cause
org.postgresql.util.PSQLException: ResultSet not positioned properly, perhaps you need to call next.
代码
@Autowired
NamedParameterJdbcTemplate namedParameterJdbcTemplate;
@Override
public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
String sql = "SELECT m.username, m.password, t.name as authority FROM members m " +
"JOIN administrator a ON a.member_id = m.member_id " +
"JOIN admin_type t ON t.admin_type_id = a.admin_type_id " +
"WHERE m.username = :userName";
SqlParameterSource namedParameters = new MapSqlParameterSource().addValue("userName", userName);
List<User> users = namedParameterJdbcTemplate.query(sql, namedParameters, new UserDetailsRowMapper());
return users.get(0);
}
class UserDetailsRowMapper implements RowMapper<User> {
@Override
public User mapRow(ResultSet rs, int rowNum) throws SQLException {
UserResultSetExtractor extractor = new UserResultSetExtractor();
return extractor.extractData(rs);
}
}
class UserResultSetExtractor implements ResultSetExtractor {
public User extractData(ResultSet rs) throws SQLException, DataAccessException {
Collection<SimpleGrantedAuthority> roles = new ArrayList<>();
while (rs.next()) {
String authority = rs.getString("authority");
roles.add( new SimpleGrantedAuthority(authority));
}
User user = new User(rs.getString("username"), rs.getString("password"), roles);
return user;
}
}
我不确定为什么会收到错误消息。这可能是因为我的逻辑不正确,因为结果集中的每一行都调用了 extractData()
。
问题
我应该如何执行上述操作来填充与authorities
具有一对多关系的User
对象?
+-----------+ +---------------+ +---------------+
| Member | | administrator | | admin_type |
+-----------+ +---------------+ +---------------+
| member_id | | admin_id | | admin_type_id |
| username | | member_id | | name |
| password | | admin_type_id | +---------------+
+-----------+ +---------------+
SQL
SELECT m.username, m.password, t.name as authority FROM members m
JOIN administrator a ON a.member_id = m.member_id
JOIN admin_type t ON t.admin_type_id = a.admin_type_id
WHERE m.username = 'richardmarais';
结果
在您的代码中:
while (rs.next()) {
String authority = rs.getString("authority");
roles.add( new SimpleGrantedAuthority(authority));
}
User user = new User(rs.getString("username"), rs.getString("password"), roles);
return user;
您在 while 循环之外调用 rs.getString
,因此即使没有 rs.next()
也可能是真的。但更重要的是,当您确实跳出循环时,rs.next()
已返回 false,因此您的 get
将不再起作用。你没有提到在哪一行抛出异常,但我会说这是最可能的原因。
您应该在获取权限时随时填充用户信息,例如在第一行。但不要等到你已经处理完所有行。
例如像这样:
public User extractData(ResultSet rs) throws SQLException, DataAccessException {
Collection<SimpleGrantedAuthority> roles = new ArrayList<>();
User user = null;
while (rs.next()) {
if (user == null) {
user = new User(rs.getString("username"), rs.getString("password"), roles);
}
String authority = rs.getString("authority");
roles.add( new SimpleGrantedAuthority(authority));
}
if (user == null) {
throw new MyException("User not found");
}
return user;
}
或者,只需使用像 Hibernate 这样的框架来编写这种样板代码。
您的情况的另一种方法可能是:
@Override
public UserDetails loadUserByUsername(String userName) throws UsernameNotFoundException {
return ofNullable(userName)
.map(u -> namedParameterJdbcTemplate.query(
"SELECT m.username, m.password, t.name as authority " +
"FROM members m " +
"JOIN administrator a ON a.member_id = m.member_id " +
"JOIN admin_type t ON t.admin_type_id = a.admin_type_id " +
"WHERE m.username = :username",
Map.of("username", userName),
UserMapper.userWithRolesResultExtractor)
)
.orElseThrow(() -> new UsernameNotFoundException(
format("Given username: %s does not exist in database", username)));
}
正在另一个 class 中创建以下 mapper
(以重用它):
@UtilityClass
public class UserMapper {
public static final ResultSetExtractor<User> userWithRolesResultExtractor = (resultSet) -> {
User user = null;
while (resultSet.next()) {
if (resultSet.isFirst()) {
user = new User(resultSet.getString("username"),
resultSet.getString("password")
new HashSet<>());
}
user.getRoles().add(new SimpleGrantedAuthority(rs.getString("authority"))));
}
return user;
};
}
正如 Sebastiaan 提到的,有几种 ORM 可以帮助您编写更少的代码,但是性能较差。
PD。在我的示例中,我使用 Set
而不是 List
来避免重复。