问题
Let's say I have an object with two different one-to-many relations. Much like:
Customer 1<->M Brands
and Customer 1<->M Orders
And let's say that the my object Customer
has two lists related to those two objects.
I've read this example:
http://forum.springsource.org/showthread.php?50617-rowmapper-with-one-to-many-query
which explains how to do it with a single one-to-many relationship. For your convenience here's the ResultSetExtractor
override:
private class MyObjectExtractor implements ResultSetExtractor{
public Object extractData(ResultSet rs) throws SQLException, DataAccessException {
Map<Integer, MyObject> map = new HashMap<Integer, MyObject>();
MyObject myObject = null;
while (rs.next()) {
Integer id = rs.getInt("ID);
myObject = map.get(id);
if(myObject == null){
String description = rs,getString("Description");
myObject = new MyObject(id, description);
map.put(id, myObject);
}
MyFoo foo = new MyFoo(rs.getString("Foo"), rs.getString("Bar"));
myObject.add(myFoo);
}
return new ArrayList<MyObject>(map.values());;
}
}
I don't think it covers how to work with both. What would be the cleanest approach? Is there a simpler way than to iterate with conditions? Would sets be better off than lists in this case?
回答1:
From your question, I assume that you have three tables; Customer, Brands, Orders. If you want to fetch the Brands and Orders properties of the Customer to your customer object, where there is no relationship between Brands and Orders, what I suggest is to use a UNION query. Something like this:
TBL_CUSTOMER
------------
CUSTOMER_ID
CUSTOMER_ACCOUNT_NO
CUSTOMER_NAME
TBL_CUSTOMER_BRANDS
-------------------
CUSTOMER_BRAND_ID - UK
BRAND_NAME
CUSTOMER_ID - FK
TBL_ORDERS
-------------------
ORDER_ID - UK
CUSTOMER_ID - FK
Query:
SELECT CUS.*, BRANDS.CUSTOMER_BRAND_ID COL_A, BRANDS.BRAND_NAME COL_B, 1 IS_BRAND FROM TBL_CUSTOMER CUS JOIN TBL_CUSTOMER_BRANDS BRANDS ON (CUS.CUSTOMER_ID = BRANDS.CUSTOMER_ID)
UNION ALL
SELECT CUS.*, ORDERS.ORDER_ID, '', 0 IS_BRAND FROM TBL_CUSTOMER CUS JOIN TBL_ORDERS ORDERS ON (CUS.CUSTOMER_ID = ORDERS.CUSTOMER_ID)
Your ResultSetExtractor will become:
private class MyObjectExtractor implements ResultSetExtractor{
public Object extractData(ResultSet rs) throws SQLException, DataAccessException {
Map<Long, Customer> map = new HashMap<Long, Customer>();
while (rs.next()) {
Long id = rs.getLong("CUSTOMER_ID");
Customer customer = map.get(id);
if(customer == null){
customer = new Customer();
customer.setId(id);
customer.setName(rs.getString("CUSTOMER_NAME"));
customer.setAccountNumber(rs.getLong("CUSTOMER_ACCOUNT_NO"));
map.put(id, customer);
}
int type = rs.getInt("IS_BRAND");
if(type == 1) {
List brandList = customer.getBrands();
if(brandsList == null) {
brandsList = new ArrayList<Brand>();
customer.setBrands(brandsList);
}
Brand brand = new Brand();
brand.setId(rs.getLong("COL_A"));
brand.setName(rs.getString("COL_B"));
brandsList.add(brand);
} else if(type == 0) {
List ordersList = customer.getOrders();
if(ordersList == null) {
ordersList = new ArrayList<Order>();
customer.setOrders(ordersList);
}
Order order = new Order();
order.setId(rs.getLong("COL_A"));
ordersList.add(order);
}
}
return new ArrayList<Customer>(map.values());
}
}
回答2:
I think there is no better way than to iterate over all rows, extract the two different objects and add it to a List<Brand>
and List<Order>
within the Customer object.
So you would end up in a customer object:
public class Customer {
private List<Brand> brands;
private List<Order> orders;
....
}
There was an issue on SpringSource regarding a mutliple rowmapper: https://jira.springsource.org/browse/SPR-7698
but there's only one comment linking to a one-to-many resultset extractor: https://github.com/SpringSource/spring-data-jdbc-ext/blob/master/spring-data-jdbc-core/src/main/java/org/springframework/data/jdbc/core/OneToManyResultSetExtractor.java
I think you're doing it right if you really need eager fetching. If you'd need lazy fetching you could load the respective orders and brands on access during runtime. That's how Hibernate and other ORM frameworks do it. It depends on your scenario and what you do with the object.
回答3:
I assume the model described by James Jithin in his answer:
TBL_CUSTOMER
------------
CUSTOMER_ID
CUSTOMER_ACCOUNT_NO
CUSTOMER_NAME
TBL_CUSTOMER_BRANDS
-------------------
CUSTOMER_BRAND_ID - UK
BRAND_NAME
CUSTOMER_ID - FK
TBL_ORDERS
-------------------
ORDER_ID - UK
CUSTOMER_ID - FK
Instead of going for one Query, I would suggest the following three:
SELECT CUS.* FROM TBL_CUSTOMER CUS
SELECT BRANDS.CUSTOMER_ID, BRANDS.CUSTOMER_BRAND_ID, BRANDS.BRAND_NAME FROM TBL_CUSTOMER_BRANDS BRANDS
SELECT ORDERS.CUSTOMER_ID, ORDERS.ORDER_ID FROM TBL_ORDERS ORDERS
Your RowCallbackHandlers would become:
private class CustomerRowCallbackHandler implements RowCallbackHandler {
private final Map<Long, Customer> customerMap;
public BrandRowCallbackHandler(Map<Long, Customer> customerMap) { this.customerMap = customerMap}
public void processRow(ResultSet rs) throws SQLException {
Long id = rs.getLong("CUSTOMER_ID");
Customer customer = map.get(id);
if(customer == null){
customer = new Customer();
customer.setId(id);
customer.setName(rs.getString("CUSTOMER_NAME"));
customer.setAccountNumber(rs.getLong("CUSTOMER_ACCOUNT_NO"));
map.put(id, customer);
}
}
}
private class BrandRowCallbackHandler implements RowCallbackHandler {
private final Map<Long, Customer> customerMap;
public BrandRowCallbackHandler(Map<Long, Customer> customerMap) { this.customerMap = customerMap}
public void processRow(ResultSet rs) throws SQLException {
Long id = rs.getLong("CUSTOMER_ID");
Customer customer = map.get(id);
if(customer != null){
List brandList = customer.getBrands();
if(brandsList == null) {
brandsList = new ArrayList<Brand>();
customer.setBrands(brandsList);
}
Brand brand = new Brand();
brand.setId(rs.getLong("CUSTOMER_BRAND_ID"));
brand.setName(rs.getString("CUSTOMER_BRAND_NAME"));
brandsList.add(brand);
}
}
}
private class OrderRowCallbackHandler implements RowCallbackHandler {
private final Map<Long, Customer> customerMap;
public OrderRowCallbackHandler(Map<Long, Customer> customerMap) { this.customerMap = customerMap}
public void processRow(ResultSet rs) throws SQLException {
Long id = rs.getLong("CUSTOMER_ID");
Customer customer = map.get(id);
if(customer != null){
List ordersList = customer.getOrders();
if(ordersList == null) {
ordersList = new ArrayList<Order>();
customer.setOrders(ordersList);
}
Order order = new Order();
order.setId(rs.getLong("ORDER_ID"));
ordersList.add(order);
}
}
}
回答4:
If I really had to do it, I would prefer RowCallbackHandler over ResultSetExtractor. See RowCallbackHandler api and JDBCTemplate api.
In this case you need to collect the resulting Customers collection yourself in the handler. Sets can help to filter out duplicates.
来源:https://stackoverflow.com/questions/13295600/multiple-one-to-many-relations-resultsetextractor