My problem is that I want to have a many-to-many relationship defined on an entity and use JPA repository to automatically fetch related entities, but I don't want JPA repository to ever save related entities (or records in join table) - I need to do that separately for good reasons.
It seems like Hibernate is saving records into join table even though I tried to disable cascading in every possible way.
Example:
My code looks as follows (I extracted only important parts):
A product can have multiple tags linked via join table product_has_tag, JPA loads tags just fine. All cascading is turned off to not ever try insert any tags or records into product_has_tag via product entity.
@Entity
public class Product {
@ManyToMany(fetch = FetchType.EAGER, cascade = {})
@JoinTable(
name = "product_has_tag",
joinColumns = @JoinColumn(
name = "product_id",
insertable = false,
updatable = false
),
inverseJoinColumns = @JoinColumn(
name = "tag_id",
insertable = false,
updatable = false
)
)
@Cascade(value = {})
protected Set<Tag> tags = new HashSet<>();
}
Entity for join table is defined separately so I am able to insert records into product_has_tag table when I want and in a fashion I want (I need to set value of 'special'). Insertable and Updatable were set to false in a desperate attempt to prevent cascading:
@Entity
public class ProductHasTag {
@Id
@Column(name = "product_id", nullable = false, insertable = false, updatable = false)
protected Long productId;
@Id
@Column(name = "tag_id", nullable = false, insertable = false, updatable = false)
protected Long tagId;
protected String special;
}
And now, in some business logic I want to insert products and then records to product_has_tag - those will be part of the same transaction, but I need them to be inserted manually and not via Hibernate cascading:
@Autowired
TagRepository tagRepository;
@Autowired
ProductRepository productRepository;
@Autowired
ProductHasTagRepository productHasTagRepository;
@Transactional
public void saveItDude(
) {
// get a tag somehow and save it
Tag tag = new Tag();
tag.setName('some tag');
tagRepository.save(tag); // this is ok
// get a product somehow
Product product = productRepository.findById(1);
product.setSomething('hello');
productRepository.save(product); // this is still ok
product.getTags().add(tag); // this triggers Hibernate to cascade and save record into product_has_tag, but I don't want that! I expect 'tags' collection to be basically read-only from db perspective
// create relation manually
ProductHasTag pht = new ProductHasTag();
pht.setProductId(product.getId());
pht.setTagId(tag.getId());
pht.setSpecial('special'); // the whole reason why I am doing this manually
productHasTagRepository.save(pht); // here I am creating record in join table the second time, but I need this instance instead of the automatically generated
}
And now when this method finishes and Hibernate commits the session, I get duplicity error:
Duplicate entry for key 'PRIMARY'] [insert into product_has_tag (product_id,tag_id) values (?,?)];
Hibernate attempted to store record into the join table automatically and hence my custom code caused a duplicity.
Can this be fixed? I really need product object to hold newly added tags, I just don't want them to be saved to db.