Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

multiple one-to-many relations ResultSetExtractor

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?

like image 760
Nimchip Avatar asked Nov 08 '12 18:11

Nimchip


3 Answers

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());
    }
}
like image 119
James Jithin Avatar answered Oct 14 '22 05:10

James Jithin


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.

like image 21
ri5b6 Avatar answered Oct 14 '22 07:10

ri5b6


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);
            }
    }
}
like image 2
tkr Avatar answered Oct 14 '22 05:10

tkr