Consider the following simple hierarchy:
@Entity
@Inheritance(strategy = InheritanceType.JOINED)
@Setter
@Getter
public class Brand {
@Id
Long id;
String name;
}
@Entity
@Setter
@Getter
public class BrandDetail extends Brand {
private String wpCode;
}
Suppose we have a row in brand
table with id 1L
and no corresponding brand_detail
row.
The following:
@Service
public class BrandService {
@Autowired
private BrandRepository brandRepository;
@Autowired
private BrandDetailRepository brandDetailRepository;
public void addBrandDetails(Long brandId, String wpCode) {
// Fetch the existing Brand entity
Brand brand = brandRepository.findById(brandId).orElseThrow(() -> new EntityNotFoundException("Brand not found"));
// Create a BrandDetail entity
BrandDetail brandDetail = new BrandDetail();
brandDetail.setId(brand.getId()); // Set the same ID as the existing Brand
brandDetail.setName(brand.getName()); // Copy the existing fields
brandDetail.setWpCode(wpCode); // Set the new detail
// Save the BrandDetail entity
brandDetailRepository.save(brandDetail);
}
}
fails with:
Hibernate: select b1_0.id,case when b1_1.id is not null then 1 when b1_0.id is not null then 0 end,b1_0.name,b1_1.wp_code from brand b1_0 left join brand_detail b1_1 on b1_0.id=b1_1.id where b1_0.id=?
2025-01-30T16:23:23.680+01:00 TRACE 219372 --- [demo] [ main] .hibernate.orm.jdbc.bind : binding parameter (1:BIGINT) <- [1]
Hibernate: insert into brand (name,id) values (?,?)
2025-01-30T16:23:23.704+01:00 TRACE 219372 --- [demo] [ main] .hibernate.orm.jdbc.bind : binding parameter (1:VARCHAR) <- [test]
2025-01-30T16:23:23.704+01:00 TRACE 219372 --- [demo] [ main] .hibernate.orm.jdbc.bind : binding parameter (2:BIGINT) <- [1]
Hibernate: select b1_0.id,case when b1_1.id is not null then 1 when b1_0.id is not null then 0 end,b1_0.name,b1_1.wp_code from brand b1_0 left join brand_detail b1_1 on b1_0.id=b1_1.id where b1_0.id=?
2025-01-30T16:23:23.709+01:00 TRACE 219372 --- [demo] [ main] .hibernate.orm.jdbc.bind : binding parameter (1:BIGINT) <- [1]
Hibernate: select bd1_0.id,bd1_1.name,bd1_0.wp_code from brand_detail bd1_0 join brand bd1_1 on bd1_0.id=bd1_1.id where bd1_0.id=?
2025-01-30T16:23:23.712+01:00 TRACE 219372 --- [demo] [ main] .hibernate.orm.jdbc.bind : binding parameter (1:BIGINT) <- [1]
Hibernate: insert into brand (name,id) values (?,?)
2025-01-30T16:23:23.714+01:00 TRACE 219372 --- [demo] [ main] .hibernate.orm.jdbc.bind : binding parameter (1:VARCHAR) <- [test]
2025-01-30T16:23:23.714+01:00 TRACE 219372 --- [demo] [ main] .hibernate.orm.jdbc.bind : binding parameter (2:BIGINT) <- [1]
2025-01-30T16:23:23.715+01:00 WARN 219372 --- [demo] [ main] o.h.engine.jdbc.spi.SqlExceptionHelper : SQL Error: -104, SQLState: 23505
2025-01-30T16:23:23.715+01:00 ERROR 219372 --- [demo] [ main] o.h.engine.jdbc.spi.SqlExceptionHelper : integrity constraint violation: unique constraint or index violation ; SYS_PK_10092 table: BRAND
How can I add the details to an existing Brand ?
I've also tried to add to the BrandRepository
the query
@Query("select new com.example.demo.BrandDetail(b.id, b.name) from Brand b where id = :id")
BrandDetail findByBrandId(Long id);
For the sake of completeness here is a simple test:
@SpringBootTest(properties = {
"spring.datasource.url=jdbc:tc:mariadb:10:///test?TC_TMPFS=/testtmpfs:rw",
"spring.jpa.hibernate.ddl-auto=update",
"spring.jpa.show-sql=true",
"logging.level.hibernate.orm.jdbc.bind=trace"
})
class BrandServiceTest {
public static final long BRAND_WITH_DETAILS_ID = 2L;
public static final long BRAND_WITH_NO_DETAILS_ID = 1L;
@Autowired
private BrandRepository brandRepository;
@Autowired
private BrandDetailRepository brandDetailRepository;
@BeforeEach
void setup() {
Brand brand = new Brand();
brand.setId(BRAND_WITH_NO_DETAILS_ID);
brand.setName("test");
brandRepository.saveAndFlush(brand);
BrandDetail brandDetail = new BrandDetail();
brandDetail.setId(BRAND_WITH_DETAILS_ID);
brandDetail.setName("Test with details");
brandDetail.setWpCode("WP2");
brandDetailRepository.saveAndFlush(brandDetail);
}
@Test
void addBrandDetails() {
assertThat(brandRepository.count()).isEqualTo(2);
assertThat(brandDetailRepository.count()).isEqualTo(1);
assertThat(brandRepository.findById(BRAND_WITH_NO_DETAILS_ID).isPresent()).isTrue();
assertThat(brandDetailRepository.findById(BRAND_WITH_NO_DETAILS_ID).isPresent()).isFalse();
BrandDetail brandDetail = brandRepository.findByBrandId(BRAND_WITH_NO_DETAILS_ID);
assertThat(brandDetail.getId()).isEqualTo(BRAND_WITH_NO_DETAILS_ID);
brandDetail.setWpCode("WP1");
brandDetailRepository.saveAndFlush(brandDetail);
assertThat(brandRepository.count()).isEqualTo(2);
assertThat(brandDetailRepository.count()).isEqualTo(2);
}
}