arnaudroger / SimpleFlatMapper

Fast and Easy mapping from database and csv to POJO. A java micro ORM, lightweight alternative to iBatis and Hibernate. Fast Csv Parser and Csv Mapper
http://simpleflatmapper.org
MIT License
437 stars 76 forks source link

Discriminator for 2 association field with same type #661

Closed LukasHaken closed 5 years ago

LukasHaken commented 5 years ago

I need 2 different discriminators for same type.

I wrote failing test with my problem. I have class Foo. Foo has 2 association fields pFirst and pSecond, both fields are same class Parent ( has 2 child (ChildA, ChildB)). Field pFirst has own discriminator column name pFirst_class_id and field pSecond has discriminator column name pSecond_class_id. I have instance of Foo, which pFirst is instance of ChildA and pSecond is instance of ChildB. But it doesn't work for me. For pSecond is used discriminator with column name "pFirst_class_id". Test fail on 3. row in method validateMapper.

` public class DiscriminatorJdbcMapperTest2 {

@Test
public void test2AssFieldWithSameTypeDiscriminatorNoAsm() throws Exception {
    JdbcMapper<Foo> mapper =
            JdbcMapperFactoryHelper.noAsm()
                    .addKeys("id", "pFirst_id", "pSecond_id")
                    .discriminator(Parent.class, "pFirst_class_id", Integer.class, new Consumer<AbstractMapperFactory.DiscriminatorConditionBuilder<ResultSet, Integer, Object>>() {
                                @Override
                                public void accept(AbstractMapperFactory.DiscriminatorConditionBuilder<ResultSet, Integer, Object> builder) {
                                    builder
                                            .when(1, Parent.class)
                                            .when(2, ChildA.class)
                                            .when(3, ChildB.class);
                                }
                            }
                    )
                    .discriminator(Parent.class, "pSecond_class_id", Integer.class, new Consumer<AbstractMapperFactory.DiscriminatorConditionBuilder<ResultSet, Integer, Object>>() {
                        @Override
                        public void accept(AbstractMapperFactory.DiscriminatorConditionBuilder<ResultSet, Integer, Object> builder) {
                            builder
                                .when(1, Parent.class)
                                .when(2, ChildA.class)
                                .when(3, ChildB.class);
                        }
                    }
            )
                    .newMapper(Foo.class);

    validateMapper(mapper);

}

private void validateMapper(JdbcMapper<Foo> mapper) throws Exception {
    List<Foo> is = mapper.forEach(setUpResultSetMock(), new ListCollector<Foo>()).getList();
    assertTrue(is.get(0).pFirst instanceof ChildA);
    assertTrue(is.get(0).pSecond instanceof ChildB);

    assertTrue(is.get(1).pFirst instanceof ChildA);
    assertTrue(is.get(1).pSecond instanceof Parent);
}

private ResultSet setUpResultSetMock() throws SQLException {
    ResultSet rs = mock(ResultSet.class);

    ResultSetMetaData metaData = mock(ResultSetMetaData.class);

    final String[] columns = new String[] { "id", "pFirst_id", "pFirst_class_id", "pFirst_a_string", "pFirst_b_string", "pSecond_id", "pSecond_class_id", "pSecond_a_string", "pSecond_b_string",};

    when(metaData.getColumnCount()).thenReturn(columns.length);
    when(metaData.getColumnLabel(anyInt())).then(new Answer<String>() {
        @Override
        public String answer(InvocationOnMock invocationOnMock) throws Throwable {
            return columns[-1 + (Integer)invocationOnMock.getArguments()[0]];
        }
    });

    when(rs.getMetaData()).thenReturn(metaData);

    final AtomicInteger ai = new AtomicInteger();

    final Object[][] rows = new Object[][]{
            {1, 1, 2, "aString", null, 2, 3, null, "bString"},
            {2, 1, 2, "aString", null, 3, 1, null, null}
    };

    when(rs.next()).then(new Answer<Boolean>() {
        @Override
        public Boolean answer(InvocationOnMock invocationOnMock) throws Throwable {
            return ai.getAndIncrement() < rows.length;
        }
    });
    final Answer<Object> getValue = new Answer<Object>() {
        @Override
        public Object answer(InvocationOnMock invocationOnMock) throws Throwable {
            final Object[] row = rows[ai.get() - 1];
            final Integer col = -1 + (Integer) invocationOnMock.getArguments()[0];
            return (row[col]);
        }
    };

    final Answer<Object> getColumnValue = new Answer<Object>() {
        @Override
        public Object answer(InvocationOnMock invocationOnMock) throws Throwable {
            final Object[] row = rows[ai.get() - 1];
            final String col = (String) invocationOnMock.getArguments()[0];
            return (row[Arrays.asList(columns).indexOf(col)]);
        }
    };

    when(rs.getInt(anyInt())).then(getValue);
    when(rs.getString(anyInt())).then(getValue);
    when(rs.getString(any(String.class))).then(getColumnValue);
    when(rs.getObject(anyInt())).then(getValue);
    when(rs.getObject(anyString(), any(Class.class))).then(getColumnValue);

    return rs;
}

public class Parent {
    int id;

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }
}

public class ChildA extends Parent {
    String aString;

    public String getaString() {
        return aString;
    }

    public void setaString(String aString) {
        this.aString = aString;
    }

}

public class ChildB extends Parent {
    String bString;

    public String getbString() {
        return bString;
    }

    public void setbString(String bString) {
        this.bString = bString;
    }       
}

public class Foo {
    int id;
    Parent pFirst;
    Parent pSecond;
    public int getId() {
        return id;
    }
    public void setId(int id) {
        this.id = id;
    }
    public Parent getpFirst() {
        return pFirst;
    }
    public void setpFirst(Parent pFirst) {
        this.pFirst = pFirst;
    }
    public Parent getpSecond() {
        return pSecond;
    }
    public void setpSecond(Parent pSecond) {
        this.pSecond = pSecond;
    }

}

}

`

Am I doing something wrong?

arnaudroger commented 5 years ago

hey thanks for the bug report. I'll need to have a look, I don't think we can handle that right now. never really thought of that use case.

arnaudroger commented 5 years ago

PS that one might take a while to fix, I don't seen any good solution right now and will need to experiment ...

LukasHaken commented 5 years ago

Hi, thanks for support.

I designed posible solution. It is mainly workaround for me. The solution isn't clearly. So that to be clearly solution, I would have to make big refactoring. My idea is, that every discriminator getter knows, where is discriminator field is in mapping hiearchy. Look at my code and you will surely understand my idea.

arnaudroger commented 5 years ago

my thinking goes along the same line, there is the notion of path of the property, also I'm thinking we could use the prefix of the column name, will need to play about a wee bit. I might just allow both in the end. It's easier for thing like hibernate because they control the sql.

arnaudroger commented 5 years ago

@LukasHaken I have a version kind of ready, needs to figure out the name of some methods as it is rather a lot of discriminator method at the minute ...

arnaudroger commented 5 years ago

@LukasHaken just pushed 7.0.0 with change that should now work for your use case

     JdbcMapper<Foo> mapper =
                JdbcMapperFactoryHelper.noAsm()
                        .addKeys("id", "pFirst_id", "pSecond_id")
                        .discriminator(Parent.class)
                        .onColumn(CaseInsensitiveEndsWithPredicate.of("class_id"), Integer.class)
                        .with(builder ->
                                        builder
                                                .when(1, Parent.class)
                                                .when(2, ChildA.class)
                                                .when(3, ChildB.class)
                        )
                        .newMapper(Foo.class);