javahibernatejpapersistencehibernate-cascade

JPA Composite Keys and Cascade


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!


Solution

  • 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!