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 来避免重复。