JPA Composite Keys and Cascade

303 Views Asked by At

I'd like to use JPA CASCADE to Persist parent and its children at the same time. The parent class (Filter) has a composite key (PKFilter). Aldo, the children class (FilterRule) has its composite key (PKFilterRule). The primary key classes are annotated with @Embeddable and they are referenced as @EmbeddedId.

PKFilter:

@Getter
@Setter
@Embeddable
@NoArgsConstructor
@AllArgsConstructor
@SuppressWarnings("JpaDataSourceORMInspection")
@EqualsAndHashCode
public class PkFilter implements Serializable {

    @Column(name = "CUSTOMER_ID")
    private int customerId;

    @Column(name = "COMPANY_ID")
    private int companyId;

    @Column(name = "FILTER_ID")
    private String filterId;

}

PKFilterRule:

@Getter
@Setter
@Embeddable
@NoArgsConstructor
@AllArgsConstructor
@SuppressWarnings("JpaDataSourceORMInspection")
@EqualsAndHashCode
public class PkFilterRule implements Serializable {

    @Column(name = "CUSTOMER_ID")
    private int customerId;

    @Column(name = "COMPANY_ID")
    private int companyId;

    @Column(name = "FILTER_ID")
    private String filterId;

    @Column(name = "FILTER_RULE")
    private String filterRule;

    @Column(name = "FILTER_KEY")
    private String filterKey;

}

Filter

@Getter
@Setter
@Entity
@EqualsAndHashCode
@Table( name = "IOND_FILTERS")
public class Filter {

    @EmbeddedId
    private PkFilter pkFilter;

    @Column(name = "FILTER_TARGET")
    private String filterTarget;

    @Column(name = "FILTER_STATUS")
    private String filterStatus;

    @OneToMany(mappedBy = "filter", cascade = CascadeType.ALL)
    private List<FilterRule> rules;

}

FilterRule

@Getter
@Setter
@Entity
@EqualsAndHashCode
@Table( name = "IOND_FILTERS_RULES")
public class FilterRule {

    @EmbeddedId
    private PkFilterRule pkFilterRule;

    @MapsId
    @ManyToOne(cascade = CascadeType.PERSIST)
    @JoinColumn(name = "CUSTOMER_ID", referencedColumnName = "CUSTOMER_ID")
    @JoinColumn(name = "COMPANY_ID", referencedColumnName = "COMPANY_ID")
    @JoinColumn(name = "FILTER_ID", referencedColumnName = "FILTER_ID")
    private Filter filter;

}

As a test, we are trying to create a Filter with a FilterItem and then, persist the Filter object:

@RunWith(SpringRunner.class)
@SpringBootTest()
@TestPropertySource(locations = {"classpath:application-test.properties"})
public class ProductsApplicationTests {

    @Autowired
    private FilterRepository filterRepository;

    @Test
    @Transactional
    @Commit
    public void test() {

        var uuid = UUID.randomUUID().toString();

        //Cria um novo Filtro
        var filter = new Filter();
        filter.setPkFilter(
                new PkFilter(208, 210, uuid)
        );

        filter.setFilterTarget("ET");
        filter.setFilterStatus("EN");

        //Adiciona alguns itens
        var filterRule = new FilterRule();
        filterRule.setPkFilterRule(
                new PkFilterRule(208, 210, uuid, "RULE", "KEY")
        );
        filterRule.setFilter(filter);

        filter.setRules(Collections.singletonList(filterRule));

        filterRepository.saveAndFlush(filter);

    }

}

And, as exception, we got:

Caused by: java.lang.IllegalArgumentException: Can not set int field api.products.domain.model.keys.PkFilterRule.companyId to api.products.domain.model.keys.PkFilter
    at java.base/jdk.internal.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException(UnsafeFieldAccessorImpl.java:167)
    at java.base/jdk.internal.reflect.UnsafeFieldAccessorImpl.throwSetIllegalArgumentException(UnsafeFieldAccessorImpl.java:171)
    at java.base/jdk.internal.reflect.UnsafeFieldAccessorImpl.ensureObj(UnsafeFieldAccessorImpl.java:58)
    at java.base/jdk.internal.reflect.UnsafeIntegerFieldAccessorImpl.getInt(UnsafeIntegerFieldAccessorImpl.java:56)
    at java.base/java.lang.reflect.Field.getInt(Field.java:594)
    at org.hibernate.property.access.spi.GetterFieldImpl.get(GetterFieldImpl.java:62)
    ... 97 more

What are we missing? Thanks a lot!

1

There are 1 best solutions below

0
On BEST ANSWER

Found the solution:

Changed PkFilterRule to use PkFilter as below:

@Getter
@Setter
@Embeddable
@NoArgsConstructor
@AllArgsConstructor
@SuppressWarnings("JpaDataSourceORMInspection")
@EqualsAndHashCode
public class PkFilterRule implements Serializable {

    private PkFilter pkFilter; //<-------HERE

    @Column(name = "FILTER_RULE")
    private String filterRule;

    @Column(name = "FILTER_KEY")
    private String filterKey;

}

and then, changed the @MapsID as bellow:

@Getter
@Setter
@Entity
@EqualsAndHashCode
@Table(name = "IOND_FILTERS_RULES")
public class FilterRule {

    @EmbeddedId
    private PkFilterRule pkFilterRule;

    @MapsId("pkFilter") //<------------------------------------ HERE
    @ManyToOne(cascade = CascadeType.PERSIST)
    @JoinColumn(name = "CUSTOMER_ID", referencedColumnName = "CUSTOMER_ID")
    @JoinColumn(name = "COMPANY_ID", referencedColumnName = "COMPANY_ID")
    @JoinColumn(name = "FILTER_ID", referencedColumnName = "FILTER_ID")
    private Filter filter;

}

Last but not least, changed the tests:

@Test
@Transactional
@Commit
public void test() {

    var uuid = UUID.randomUUID().toString();

    //Cria um novo Filtro
    var filter = new Filter();
    filter.setPkFilter(
            new PkFilter(208, 210, uuid)
    );

    filter.setFilterTarget("ET");
    filter.setFilterStatus("EN");

    //Adiciona alguns itens
    var filterRule = new FilterRule();
    filterRule.setPkFilterRule(
            new PkFilterRule(filter.getPkFilter(), //<--------HERE
            "RULE", "KEY")
    );
    filterRule.setFilter(filter);

    filter.setRules(Collections.singletonList(filterRule));

    filterRepository.saveAndFlush(filter);

}

And everything works as expected. Thanks!