在 Dropwizard 中使用 JDBi3 映射到具有多对多关系的 DTO 类 的问题
Problem mapping to DTO classes with Many-To-Many relations using JDBi3 in Dropwizard
我在 DAO 接口中使用 PostgreSQL 查询通过 JDBi3 映射检索数据时遇到问题。
在我的 Dropwizard 应用程序中,我有 Book DTO class,它与 Author 和 Category DTO classes 具有多对多关系,并且在将查询行映射到 BookDTO class 时遇到问题。以下是 DTO classes 的代码片段:
class BookDTO {
private Long bookId;
// other fields are left for code brevity
private List<Long> authors;
private List<Long> categories;
// empty constructor + constructor with all fields excluding Lists + getters + setters
}
class AuthorDTO {
private Long authorId;
// other fields are left for code brevity
private List<Long> books;
// empty constructor + constructor with all fields excluding List + getters + setters
}
class CategoryDTO {
private Long categoryId;
// other fields are left for code brevity
private List<Long> books;
// empty constructor + constructor with all fields excluding List + getters + setters
}
...并且由于我使用 JDBi3 DAO 接口执行 CRUD 操作,这就是我查询 数据库中所有书籍的方法 看起来像:
@Transaction
@UseRowMapper(BookDTOACMapper.class)
@SqlQuery("SELECT book.book_id AS b_id, book.title, book.price, book.amount, book.is_deleted, author.author_id AS aut_id, category.category_id AS cat_id FROM book " +
"LEFT JOIN author_book ON book.book_id = author_book.book_id " +
"LEFT JOIN author ON author_book.author_id = author.author_id " +
"LEFT JOIN category_book ON book.book_id = category_book.book_id " +
"LEFT JOIN category ON category_book.category_id = category.category_id ORDER BY b_id ASC, aut_id ASC, cat_id ASC")
List<BookDTO> getAllBooks();
...这是 BookDTOACMapper
class 的 map
方法,看起来像:
public class BookDTOACMapper implements RowMapper<BookDTO> {
@Override
public BookDTO map(ResultSet rs, StatementContext ctx) throws SQLException {
final long bookId = rs.getLong("b_id");
// normally retrieving values by using appropriate rs.getXXX() methods
Set<Long> authorIds = new HashSet<>();
Set<Long> categoryIds = new HashSet<>();
long authorId = rs.getLong("aut_id");
if (authorId > 0) {
authorIds.add(authorId);
}
long categoryId = rs.getLong("cat_id");
if (categoryId > 0) {
categoryIds.add(categoryId);
}
while (rs.next()) {
if (rs.getLong("b_id") != bookId) {
break;
} else {
authorId = rs.getLong("aut_id");
if (authorId > 0) { authorIds.add(authorId); }
categoryId = rs.getLong("cat_id");
if (categoryId > 0) { categoryIds.add(categoryId); }
}
}
final List<Long> authorIdsList = new ArrayList<>(authorIds);
final List<Long> categoryIdsList = new ArrayList<>(categoryIds);
return new BookDTO(bookId, title, price, amount, is_deleted, authorIdsList, categoryIdsList);
}
}
我遇到的问题是调用我的 GET 方法时(在资源 class 中定义,它从 BookDAO class 调用 getAllBooks()
方法)displays inconsistent results while the query itself returns proper results.
我在 Whosebug、官方 JDBi3 文档 API 和 Google 上找到的许多问题正在考虑 一对多 关系并使用 @UseRowReducer
注释,其中包含实现 LinkedHashMapRowReducer<TypeOfEntityIdentifier, EntityName>
的 class 但对于这种情况,我找不到实现它的方法。欢迎任何 example/suggestion。 :)
提前谢谢你。
所用工具的版本:
Dropwizard 框架 1.3.8
PostgreSQL 11.7
Java8
评论太长了:
- 这基本上是一个调试问题。为什么?
while (rs.next()) {
if (rs.getLong("b_id") != bookId) {
break;
} else {
while
之后的第一个 if
正在吃掉当前行之后的行(调用行映射器时为当前行的行)。您正在跳过 bookId、authorId 等的处理(将数据放入 Java 对象中)。这就是为什么您得到
inconsistent results while the query itself returns proper results.
因此您需要重新审视您处理数据的方式。我看到两条路径:
- 重新访问处理循环的逻辑以在停止给定
bookId
的处理时存储数据。可以使用可滚动的 ResultSet
s 实现此目的 - 即请求可滚动的 ResultSet
并在 brake;
调用 rs.previous()
之前。在下一次调用行映射器时,处理将从结果集中的正确行开始。
- 利用 SQL/PostgreSQL 的力量并正确地做到这一点:https://dba.stackexchange.com/questions/173831/convert-right-side-of-join-of-many-to-many-into-array 聚合和调整数据库中的数据。数据库是完成这项工作的最佳工具。
也请花点时间查看 https://dba.stackexchange.com/users/3684/erwin-brandstetter 的其他答案。他们在 SQL 和 PostgreSQL.
方面提供了宝贵的见解
As zloster mentioned in his answer I've chosen 2nd option (by this answer 用于多对多关系),这是使用在 List<BookDTO> getAllBooks();
方法上方编辑我的 PostgreSQL 查询 @SqlQuery
注释。查询现在在 SELECT 语句中使用 array_agg
聚合函数将我的结果分组到一个数组中,现在看起来像这样:
@UseRowMapper(BookDTOACMapper.class)
@SqlQuery("SELECT b.book_id AS b_id, b.title, b.price, b.amount, b.is_deleted, ARRAY_AGG(aut.author_id) as aut_ids, ARRAY_AGG(cat.category_id) as cat_ids " +
"FROM book b " +
"LEFT JOIN author_book ON author_book.book_id = b.book_id " +
"LEFT JOIN author aut ON aut.author_id = author_book.author_id " +
"LEFT JOIN category_book ON category_book.book_id = b.book_id " +
"LEFT JOIN category cat ON cat.category_id = category_book.category_id " +
"GROUP BY b_id " +
"ORDER BY b_id ASC")
List<BookDTO> getAllBooks();
因此 BookDTOACMapper
class 的 map(..)
方法必须进行编辑,现在看起来像这样:
@Override
public BookDTO map(ResultSet rs, StatementContext ctx) throws SQLException {
final long bookId = rs.getLong("b_id");
String title = rs.getString("title");
double price = rs.getDouble("price");
int amount = rs.getInt("amount");
boolean is_deleted = rs.getBoolean("is_deleted");
Set<Long> authorIds = new HashSet<>();
Set<Long> categoryIds = new HashSet<>();
/* rs.getArray() retrives java.sql.Array and after it getArray gets
invoked which returns array of Object(s) which are being casted
into array of Long elements */
Long[] autIds = (Long[]) (rs.getArray("aut_ids").getArray());
Long[] catIds = (Long[]) (rs.getArray("cat_ids").getArray());
Collections.addAll(authorIds, autIds);
Collections.addAll(categoryIds, catIds);
final List<Long> authorIdsList = new ArrayList<>(authorIds);
final List<Long> categoryIdsList = new ArrayList<>(categoryIds);
return new BookDTO(bookId, title, price, amount, is_deleted, authorIdsList, categoryIdsList);
}
现在所有结果都是一致的,这里是 pgAdmin4 中的 screenshot 查询。
我在 DAO 接口中使用 PostgreSQL 查询通过 JDBi3 映射检索数据时遇到问题。
在我的 Dropwizard 应用程序中,我有 Book DTO class,它与 Author 和 Category DTO classes 具有多对多关系,并且在将查询行映射到 BookDTO class 时遇到问题。以下是 DTO classes 的代码片段:
class BookDTO {
private Long bookId;
// other fields are left for code brevity
private List<Long> authors;
private List<Long> categories;
// empty constructor + constructor with all fields excluding Lists + getters + setters
}
class AuthorDTO {
private Long authorId;
// other fields are left for code brevity
private List<Long> books;
// empty constructor + constructor with all fields excluding List + getters + setters
}
class CategoryDTO {
private Long categoryId;
// other fields are left for code brevity
private List<Long> books;
// empty constructor + constructor with all fields excluding List + getters + setters
}
...并且由于我使用 JDBi3 DAO 接口执行 CRUD 操作,这就是我查询 数据库中所有书籍的方法 看起来像:
@Transaction
@UseRowMapper(BookDTOACMapper.class)
@SqlQuery("SELECT book.book_id AS b_id, book.title, book.price, book.amount, book.is_deleted, author.author_id AS aut_id, category.category_id AS cat_id FROM book " +
"LEFT JOIN author_book ON book.book_id = author_book.book_id " +
"LEFT JOIN author ON author_book.author_id = author.author_id " +
"LEFT JOIN category_book ON book.book_id = category_book.book_id " +
"LEFT JOIN category ON category_book.category_id = category.category_id ORDER BY b_id ASC, aut_id ASC, cat_id ASC")
List<BookDTO> getAllBooks();
...这是 BookDTOACMapper
class 的 map
方法,看起来像:
public class BookDTOACMapper implements RowMapper<BookDTO> {
@Override
public BookDTO map(ResultSet rs, StatementContext ctx) throws SQLException {
final long bookId = rs.getLong("b_id");
// normally retrieving values by using appropriate rs.getXXX() methods
Set<Long> authorIds = new HashSet<>();
Set<Long> categoryIds = new HashSet<>();
long authorId = rs.getLong("aut_id");
if (authorId > 0) {
authorIds.add(authorId);
}
long categoryId = rs.getLong("cat_id");
if (categoryId > 0) {
categoryIds.add(categoryId);
}
while (rs.next()) {
if (rs.getLong("b_id") != bookId) {
break;
} else {
authorId = rs.getLong("aut_id");
if (authorId > 0) { authorIds.add(authorId); }
categoryId = rs.getLong("cat_id");
if (categoryId > 0) { categoryIds.add(categoryId); }
}
}
final List<Long> authorIdsList = new ArrayList<>(authorIds);
final List<Long> categoryIdsList = new ArrayList<>(categoryIds);
return new BookDTO(bookId, title, price, amount, is_deleted, authorIdsList, categoryIdsList);
}
}
我遇到的问题是调用我的 GET 方法时(在资源 class 中定义,它从 BookDAO class 调用 getAllBooks()
方法)displays inconsistent results while the query itself returns proper results.
我在 Whosebug、官方 JDBi3 文档 API 和 Google 上找到的许多问题正在考虑 一对多 关系并使用 @UseRowReducer
注释,其中包含实现 LinkedHashMapRowReducer<TypeOfEntityIdentifier, EntityName>
的 class 但对于这种情况,我找不到实现它的方法。欢迎任何 example/suggestion。 :)
提前谢谢你。
所用工具的版本:
Dropwizard 框架 1.3.8
PostgreSQL 11.7
Java8
评论太长了:
- 这基本上是一个调试问题。为什么?
while (rs.next()) { if (rs.getLong("b_id") != bookId) { break; } else {
while
之后的第一个 if
正在吃掉当前行之后的行(调用行映射器时为当前行的行)。您正在跳过 bookId、authorId 等的处理(将数据放入 Java 对象中)。这就是为什么您得到
inconsistent results while the query itself returns proper results.
因此您需要重新审视您处理数据的方式。我看到两条路径:
- 重新访问处理循环的逻辑以在停止给定
bookId
的处理时存储数据。可以使用可滚动的ResultSet
s 实现此目的 - 即请求可滚动的ResultSet
并在brake;
调用rs.previous()
之前。在下一次调用行映射器时,处理将从结果集中的正确行开始。 - 利用 SQL/PostgreSQL 的力量并正确地做到这一点:https://dba.stackexchange.com/questions/173831/convert-right-side-of-join-of-many-to-many-into-array 聚合和调整数据库中的数据。数据库是完成这项工作的最佳工具。
- 重新访问处理循环的逻辑以在停止给定
也请花点时间查看 https://dba.stackexchange.com/users/3684/erwin-brandstetter 的其他答案。他们在 SQL 和 PostgreSQL.
方面提供了宝贵的见解As zloster mentioned in his answer I've chosen 2nd option (by this answer 用于多对多关系),这是使用在 List<BookDTO> getAllBooks();
方法上方编辑我的 PostgreSQL 查询 @SqlQuery
注释。查询现在在 SELECT 语句中使用 array_agg
聚合函数将我的结果分组到一个数组中,现在看起来像这样:
@UseRowMapper(BookDTOACMapper.class)
@SqlQuery("SELECT b.book_id AS b_id, b.title, b.price, b.amount, b.is_deleted, ARRAY_AGG(aut.author_id) as aut_ids, ARRAY_AGG(cat.category_id) as cat_ids " +
"FROM book b " +
"LEFT JOIN author_book ON author_book.book_id = b.book_id " +
"LEFT JOIN author aut ON aut.author_id = author_book.author_id " +
"LEFT JOIN category_book ON category_book.book_id = b.book_id " +
"LEFT JOIN category cat ON cat.category_id = category_book.category_id " +
"GROUP BY b_id " +
"ORDER BY b_id ASC")
List<BookDTO> getAllBooks();
因此 BookDTOACMapper
class 的 map(..)
方法必须进行编辑,现在看起来像这样:
@Override
public BookDTO map(ResultSet rs, StatementContext ctx) throws SQLException {
final long bookId = rs.getLong("b_id");
String title = rs.getString("title");
double price = rs.getDouble("price");
int amount = rs.getInt("amount");
boolean is_deleted = rs.getBoolean("is_deleted");
Set<Long> authorIds = new HashSet<>();
Set<Long> categoryIds = new HashSet<>();
/* rs.getArray() retrives java.sql.Array and after it getArray gets
invoked which returns array of Object(s) which are being casted
into array of Long elements */
Long[] autIds = (Long[]) (rs.getArray("aut_ids").getArray());
Long[] catIds = (Long[]) (rs.getArray("cat_ids").getArray());
Collections.addAll(authorIds, autIds);
Collections.addAll(categoryIds, catIds);
final List<Long> authorIdsList = new ArrayList<>(authorIds);
final List<Long> categoryIdsList = new ArrayList<>(categoryIds);
return new BookDTO(bookId, title, price, amount, is_deleted, authorIdsList, categoryIdsList);
}
现在所有结果都是一致的,这里是 pgAdmin4 中的 screenshot 查询。