Cannot get OneToOne Mapping working with Composite Key. Quarkus + Reactive Hibernate - java

We are migrating our application from Spring Data + Hibernate (it was working) to Quarkus Panache (version v2.16.2) + Reactive Hibernate.
abstract class BaseEntity<ID> : PanacheEntityBase {
open val id: ID? = null
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (other == null || Hibernate.getClass(this) != Hibernate.getClass(other)) return false
other as BaseEntity<*>
return id != null && id == other.id
}
override fun hashCode(): Int {
return javaClass.hashCode()
}
override fun toString(): String {
return "${javaClass.simpleName}<$id>"
}
}
#Embeddable
class AssociatedAccountCompositeId(
#Column(name = "customer_account_id")
val customerAccountId: String,
#Column(name = "promotion_code_id")
val promotionCodeId: Long
) : Serializable {
companion object {
private const val serialVersionUID: Long = 845208461728L
}
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as AssociatedAccountCompositeId
if (customerAccountId != other.customerAccountId) return false
if (promotionCodeId != other.promotionCodeId) return false
return true
}
override fun hashCode(): Int {
return Objects.hash(customerAccountId, promotionCodeId)
}
override fun toString(): String {
return "${javaClass.simpleName}<$customerAccountId><$promotionCodeId>"
}
}
#Entity
#Table(name = "promotion_code")
class PromotionCodeEntity(
#OneToOne(mappedBy = "promotionCode")
var associatedAccount: AssociatedAccountEntity?
) : BaseEntity<Long>() {
#Id
#Column(name = "promotion_code_id")
#SequenceGenerator(name = "promotion_code_sq", sequenceName = "promotion_code_sq", allocationSize = 100)
#GeneratedValue(strategy = SEQUENCE, generator = "promotion_code_sq")
override val id: Long? = null
}
#Entity
#Table(name = "account_assoc")
class AssociatedAccountEntity(
#EmbeddedId
override val id: AssociatedAccountCompositeId,
#OneToOne
#JoinColumn(name = "promotion_code_id", insertable = false, updatable = false)
val promotionCode: PromotionCodeEntity
) : BaseEntity<AssociatedAccountCompositeId?>()
We were migrating the project and different CompositeKey works but here I cannot make it work for few days, and the main difference in OneToOne relationship. Right now I have prepared simplest possible usecase, am getting error:
fun method(customerId: String, dto: PromotionCodeAssignRequestParameters): Uni<List<PromotionCodeEntity>> {
return PromotionCodeEntity.findAll().list()
}
"Error accessing field [private java.lang.String sth.promotion_campaigns_service.enitites.id.AssociatedAccountCompositeId.customerAccountId] by reflection for persistent property [sth.promotion_campaigns_service.enitites.id.AssociatedAccountCompositeId#customerAccountId] : 1",
I tried obvious things like manual open instead of gradle plugin or changing val to var everywhere.
Any help would be appreciated.
EDIT
I am attaching project w example https://github.com/damiankaplon/stack-question

Related

Hibernate #OneToMany removing from Set doesn't trigger constraint validation on update of parent entity

I have the following scenario:
#Entity
#Table(name = "groups_supervisors")
public class SupervisorEntity extends AbstractEntity {
#ManyToOne
#EqualsAndHashCode.Exclude
#JoinColumn(name = "group_id")
KibanaAdGroupEntity kibanaAdGroup;
//Another fields
#Override
public boolean equals(Object o) {
if (this == o)
return true;
if (o == null || getClass() != o.getClass())
return false;
SupervisorEntity that = (SupervisorEntity) o;
if (!Objects.equals(id, that.id))
return false;
if (kibanaAdGroup!=null && !kibanaAdGroup.getId().equals(that.kibanaAdGroup.getId()))
return false;
if (!Objects.equals(name, that.name))
return false;
if (!Objects.equals(updateDate, that.updateDate))
return false;
return Objects.equals(dateAdd, that.dateAdd);
}
#Override
public int hashCode() {
int result = id.hashCode();
result = 31 * result + (kibanaAdGroup != null ? kibanaAdGroup.getId().hashCode() : 0);
result = 31 * result + name.hashCode();
result = 31 * result + updateDate.hashCode();
result = 31 * result + dateAdd.hashCode();
return result;
}
}
#Entity
#Data
#Table(name = "ad_groups")
public class KibanaAdGroupEntity extends AbstractEntity {
#NotNull
#Size(min = 2)
#BatchSize(size = 10)
#ToString.Exclude
#OneToMany(mappedBy = "kibanaAdGroup", cascade = {CascadeType.ALL}, orphanRemoval =true)
Set<SupervisorEntity> supervisors;
//Some other fields
//Destroy releation
public Set<SupervisorEntity> removeSupervisor(SupervisorEntity supervisor) {
supervisors.remove(supervisor);
supervisor.setKibanaAdGroup(null);
return supervisors;
}
}
When I'm trying to perform following
#Transactional
public void test() {
var adGroup = adGroupRepository.findAll().stream().limit(1000).filter(adGr -> adGr.getId() == 142)
.findFirst().get();
var supervisor = adGroup.getSupervisors().iterator().next();
adGroup.removeSupervisor(supervisor);
adGroupRepository.save(adGroup);
}
expected that adGroupRepository.save(adGroup) will perform update cascade on adGroup and constraint #Size(min = 2) on Set<SupervisorEntity> supervisors will trigger an exception.
I tried to debug DefaultFlushEntityEventListener::dirtyCheck , but I see that an old entity and new one one contain identical size of supervisors
Please can any one clarify to me where I've mistaken and how to trigger update/checking of this constraint?
UPD
In logs and DB I see that supervisor was successfully deleted
2022-10-23 10:37:07.022 DEBUG [4b5430e5fcb074b6,4b5430e5fcb074b6] 93041 - [8080-exec-1] org.hibernate.SQL : \n delete \n from\n public.groups_supervisors \n where\n id=?
Hibernate:
delete
from
public.groups_supervisors
where
id=?
For the lazy loaded association mapping , it is suggested to place the validation annotations on the getter rather on the field because of the following reasons mentioned from the doc :
When lazy loaded associations are supposed to be validated it is
recommended to place the constraint on the getter of the association.
Hibernate ORM replaces lazy loaded associations with proxy instances
which get initialized/loaded when requested via the getter. If, in
such a case, the constraint is placed on field level, the actual proxy
instance is used which will lead to validation errors.
So please try :
#Entity
#Data
#Table(name = "ad_groups")
public class KibanaAdGroupEntity extends AbstractEntity {
#BatchSize(size = 10)
#OneToMany(mappedBy = "kibanaAdGroup", cascade = {CascadeType.ALL}, orphanRemoval =true)
Set<SupervisorEntity> supervisors;
#NotNull
#Size(min = 2)
Set<SupervisorEntity> getSupervisors(){
return this.supervisors;
}
}
Seems that I found the problem. This happens due to what type hibernate will inject into Set field. When you perform findAll - Set field will be injected as PersistenSet type by default. This is shared reference between yours business logic side and dirty check mechanism of hibernate. So when you
adGroup.removeSupervisor(supervisor);
adGroupRepository.save(adGroup);
That operation triggers DefaultFlushEntityEventListener::dirtyCheck. But due to final Object[] loadedState = entry.getLoadedState(); keeps shared reference, for hibernate it looks like entity wasn't change state of the entity. That's why no validation error was occurred(but child entity will be deleted as expected by orphan removal)
PS
If you will change any field of the entity validation will be performed as expected

Hibernate #OneToOne does not map id

I'm trying to write simple world generator using kotlin, springboot and hibernate, and I have many relations in Entities but one of them not working. Program generate the Countries and cities, but in DB I have a null ID for 'capital'
Entities:
#Entity
data class City(
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
var id:Long? = null,
val name: String,
#OneToMany
#JoinColumn(name="CityId")
val flats: List<Flat>)
#Entity
data class Country(
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
var id:Long? = null,
val name: String,
#OneToOne(fetch = FetchType.EAGER)
val capital: City,
#OneToMany
#JoinColumn(name="CountryId")
val cities: List<City>)
Country generator:
#Component
class CountryGenerator #Autowired constructor(val util: Util) {
fun getRandomCountry(): Country = util.getObj(
Country(null,
gen.country().name(),
CityGenerator(util).getRandomCapital(),
getCities())
) as Country
private fun getCities(): List<City> =
IntStream.range(0, rnd.nextInt(MAX_CITIES_NUMBER!!) + MIN_CITIES_NUMBER!!)
.mapToObj { CityGenerator(util).getRandomCity() }
.toList()
}
City generator:
#Component
class CityGenerator #Autowired constructor(val util: Util) {
fun getRandomCity() = util.getObj(
City(null,
getCityName(),
getListOfFlats())
) as City
fun getRandomCapital() = util.getObj(
City(null,
getCapital(),
getListOfFlats())
) as City
private fun getListOfFlats(): List<Flat> =
IntStream.range(0, rnd.nextInt(MAX_FLATS_NUMBER!!) + MIN_FLATS_NUMBER!!)
.mapToObj { FlatGenerator(util).getFlat() }
.toList()
private fun getCapital() = gen.country().capital()
private fun getCityName(): String = gen.address().city()
}
Any ideas what is wrong with it?
EDIT:
Saving to DB:
#Component
class Util(private val personRepository: PersonRepository,
private val flatRepository: FlatRepository,
private val cityRepository: CityRepository,
private val countryRepository: CountryRepository,
private val worldRepository: WorldRepository) {
fun getObj(obj: Any): Any {
return when (obj) {
is Person -> this.personRepository.save(obj)
is Flat -> flatRepository.save(obj)
is City -> cityRepository.save(obj)
is Country -> countryRepository.save(obj)
is World -> worldRepository.save(obj)
else -> throw IllegalArgumentException("Wrong object");
}
}
EDIT2:
Method Util.getObj() returns correct objects:
Confirm that the method Util.getObj is really saving the new city and returning a city with an id.
Also, if this relationship is required, you should use the property optional = false in the OneToOne annotation:
#OneToOne(fetch = FetchType.EAGER, optional = false)
#JoinColumn(name="capital_id", nullable=false)
val capital: City

Spring Data JPA: Cannot save entity with composite primary key which contains foreign key

Stumped, utterly stumped...
Assume two entities, Parent and Child, with many Child entities to one Parent. Parent's primary key is of type java.util.UUID, and Child's primary key is a composite of the Parent's UUID and a sequence number.
The short of the issue is when I try to save a new Child using childRepository.save(child), I get the following exception:
Caused by: java.lang.IllegalArgumentException: Cannot convert value of
type [com.package.entities.ParentEntity$$_jvst149_0] to required type
[java.util.UUID] for property 'parent': PropertyEditor
[org.springframework.beans.propertyeditors.UUIDEditor] returned
inappropriate value of type
[com.package.entities.ParentEntity_$$_jvst149_0]
Please look at my classes below. The best I can tell I am following the JPA spec correctly, so I'm wondering if this is a bug in Spring Data JPA, perhaps specific to UUID type IDs (similar thing has happened before, see DATAJPA-269)
Note I am using spring-boot-starter-data-jpa 1.4.1.RELEASE
Parent.java:
#Entity
#Table(name = "parent")
public class Parent implements Serializable {
#Id
#GeneratedValue(generator = "uuid")
#GenericGenerator(name = "uuid", strategy = "uuid2")
private UUID id;
//...other fields, getters + setters...
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Parent that = (Parent) o;
return Objects.equals(id, that.id);
}
#Override
public int hashCode() {
return Objects.hash(id);
}
}
Child.java
#Entity
#Table(name = "child")
#IdClass(ChildKey.class)
public class Child implements Serializable {
#Id
#ManyToOne
#JoinColumn(name = "parent_id", referencedColumnName = "id", insertable = false, updatable = false)
private Parent parent;
#Id
private Integer seqNum;
//...other fields, getters + setters...
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
Child that = (Child) o;
return Objects.equals(parent, that.parent) &&
Objects.equals(seqNum, that.seqNum);
}
#Override
public int hashCode() {
return Objects.hash(parent, seqNum);
}
}
ChildKey.class
public class ChildKey implements Serializable {
private UUID parent;
private Integer seqNum;
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
ChildKey that = (ChildKey) o;
return Objects.equals(parent, that.parent) &&
Objects.equals(seqNum, that.seqNum);
}
#Override
public int hashCode() {
return Objects.hash(parent, seqNum);
}
}
ParentRepository.java
#Repository
public interface ParentRepository extends JpaRepository<Parent, UUID> {
}
ChildRepository.java
#Repository
public interface ChildRepository extends CrudRepository<Child, ChildKey> {
}
And finally, the code I execute:
#Transactional
public void createChild(Parent parent) {
// needed to do this to get over "detached entity passed to persist"
parent = parentRepository.getOne(parent.getId());
child = new Child();
child.setParent(parent);
child.setSeqNum(1);
childRepository.save(child);
}
In Many-To-One relationship your child entity has it's own ID, and ID from parent entity is FK not a part of PK.
Example
In the months since I posted this question, I have not found a suitable answer. I unfortunately had to work around the issue by not using #ManyToOne and instead just reference the parent by UUID:
public class Child implements Serializable {
#Id
private UUID parentId;
#Id
private Integer seqNum;
I leave JPA ignorant of the foreign key and just let the database throw an error should i violate reference integrity.
You need to change your ChildKey class:
public class ChildKey implements Serializable {
private Parent parent; // <-- Parent type instead of UUID
private Integer seqNum;
...
UPD: I read JPA spec. and understood that it is incorrect. But it works in my case.

Violation of foreign key constraint JPA

Edit: This code actually works correctly. The problem was un-related and was due to a conflicting Entity which was creating a foreign key constraint and stopping me from inserting into the DataFile table.
I'm having some real trouble with some JPA mappings for a simple #OneToMany mapping.
I'm using EclipseLink and DerbyDB.
#Entity( name = "study2" )
#Access( AccessType.FIELD )
public class Study2 extends EntityBaseItem {
private List<DataFile> datafiles = new ArrayList<DataFile>();
public Study2() { }
#OneToMany( cascade = CascadeType.ALL, orphanRemoval = true )
#JoinColumn( name="STUDY_ID", referencedColumnName = "ID" )
#Access( AccessType.PROPERTY )
public List<DataFile> getDatafiles() {
return this.datafiles;
}
public void setDatafiles( List<DataFile> dfList ) {
this.datafiles = dfList;
}
DataFile.java
#Entity( name = "DataFile" )
public class DataFile extends EntityBaseItem<DataFile> {
private String filename;
private long filesize;
private String fileStatus;
private String fileType;
private String fileSubType;
public DataFile() { }
}
This is my EntityBaseItem.java where the #Id resides:
#MappedSuperclass
public abstract class EntityBaseItem {
#Id
#GeneratedValue( strategy = GenerationType.TABLE )
protected Integer id;
protected EntityBaseItem() {}
public Integer getId() {
return id;
}
public void setId( Integer id ) {
this.id = id;
}
#Override
public int hashCode() {
int hash = 0;
hash += ( this.getId() != null ? this.getId().hashCode() : 0);
return hash;
}
#Override
public boolean equals(Object object) {
if (this == object)
return true;
if (object == null)
return false;
if (getClass() != object.getClass())
return false;
EntityBaseItem other = (EntityBaseItem)object;
if (this.getId() != other.getId() && (this.getId() == null || !this.id.equals(other.id))){
return false;
}
return true;
}
}
The problem is that when I create a Study2 object with some DataFile objects and try to persist it to my DB then I get the error
UPDATE on table 'DATAFILE' caused a violation of foreign key constraint 'DATAFILE_STUDY_ID' for key
If I change the annotation on getDataFiles() and remove the #JoinColumn ( see below ) then the mapping works, however it creates a join table and I'd really rather just have a join column in the DataFile table:
#OneToMany( cascade = CascadeType.ALL, orphanRemoval = true )
#Access( AccessType.PROPERTY )
public List<DataFile> getDatafiles() {
return this.datafiles;
}
I guess it's down to having my #Id in EntityBaseItem as when I removed that and added #Id in the Study2 class then it worked as expected, however there must be some way to keep #Id in the EntityBaseItem and still use a #JoinColumn? I've not had any issues elsewhere in my code, and I have various other mappings which are not as simple as this one.
I know what the error means, however I don't know why it's happening. To me I'd expect my code to work and cascade the DataFiles automatically with a new id for each.
Here is the code that actually causes the error to be thrown:
Study2 testStudy = new Study2();
// set some datafiles etc.
EntityManager em = getEM(); // gives me EntityManager
em.getTransaction().begin();
em.persist( testStudy );
em.getTransaction().commit();
I'd simplified it down to that for testing, throws error on .commit() and then it rolls back the commit.
Change your mappings
public class Study2(){
#OneToMany( cascade = CascadeType.ALL, orphanRemoval = true,mappedBy="study2")
#Access( AccessType.PROPERTY )
public List<DataFile> getDatafiles() {
return this.datafiles;
}
}
Here we say that DataFile is mappedBy "study2" in DataFile class and Study2 has JoinColumn. And the Study2 is inverse side of relationship and will not update the relationship when it gets updated.
Add one field Study2 in DataFile, I have given mapping on field.You can change that
#ManyToOne
#JoinColumn(name="STUDY_ID", referencedColumnName = "ID")
private Study2 study2;
It states that many DataFile are present in one Study2 class

error updating parent-child relationship using Hibernate-JPA

My Hibernate-JPA domain model has these entities:
AttributeType ------< AttributeValue
The relevant Java classes look like this (getters and setters omitted):
#Entity
public class AttributeType {
#Id #GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#Column(unique = true, nullable = false)
private String name;
#OneToMany(mappedBy = "attributeType", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER)
private List<AttributeValue> values = new ArrayList<AttributeValue>();
}
#Entity #Table(uniqueConstraints = #UniqueConstraint(columnNames = {"value", "attribute_type_id"}))
public class AttributeValue {
#Id #GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#ManyToOne(optional = false)
private AttributeType attributeType;
#Column(nullable = false)
private String value;
}
Notice there's a unique constraint on AttributeValue.value and AttributeValue.attributeType, because for an attribute type (e.g. size) we don't want to allow an attribute value (e.g. small) to occur more than once.
If I update an AttributeType by performing the following operations within a single transaction:
delete "small" attribute value from "size" attribute type
add "small" attribute value to "size" attribute type
I get an exception that indicates the unique constraint was violated. This suggests that Hibernate-JPA is performing the insertion of the attribute value before the delete, which seems to invite this kind of problem for no obvious reason.
The class that performs the update of an AttributeType looks like this:
#Transactional(propagation = Propagation.SUPPORTS)
public class SomeService {
private EntityManager entityManager; // set by dependency injection
#Transactional(propagation = Propagation.REQUIRED)
public AttributeType updateAttributeType(AttributeType attributeType) throws Exception {
attributeType = entityManager.merge(attributeType);
entityManager.flush();
entityManager.refresh(attributeType);
return attributeType;
}
}
I could workaround this problem by iterating over the attribute values, figuring out which ones have been updated/deleted/inserted, and performing them in this order instead:
deletes
updates
inserts
But it seems like the ORM should be able to do this for me. I've read that Oracle provides a "deferConstraints" option that causes constraints to be checked only when a transaction has completed. However, I'm using SQL Server, so this won't help me.
You need to use a composite ID instead of a generated ID.
HHH-2801
The problem arises when a new association entity with a generated ID
is added to the collection. The first step, when merging an entity
containing this collection, is to cascade save the new association
entity. The cascade must occur before other changes to the collection.
Because the unique key for this new association entity is the same as
an entity that is already persisted, a ConstraintViolationException is
thrown. This is expected behavior.
Using a new collection (i.e., one-shot delete), as suggested in the
previous comment) also results in a constraint violation, since the
new association entity will be saved on the cascade of the new
collection.
An example of one of the approaches (using a composite ID instead of a generated ID) is illustrated >in manytomanywithassocclass.tar.gz and is checked into Svn.
#Entity
public class AttributeType {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
private Integer id;
#Column(unique = true, nullable = false)
private String name;
#OneToMany(mappedBy = "attributeType", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.EAGER)
private List<AttributeValue> values = new ArrayList<AttributeValue>();
//Getter, Setter...
}
#Entity
#Table (uniqueConstraints = #UniqueConstraint(columnNames = { "value", "attributeType_id" }))
public class AttributeValue{
#EmbeddedId AttributeValueId id;
#MapsId(value= "id")
#ManyToOne(optional = false)
private AttributeType attributeType;
private String value2;
public AttributeValue() {
this.id = new AttributeValueId();
}
public AttributeType getAttributeType() {
return attributeType;
}
public void setAttributeType(AttributeType pAttributeType) {
this.id.setAttributeTypeID(pAttributeType.getId());
this.attributeType = pAttributeType;
}
public String getValue() {
return id.getAttributeValue();
}
public void setValue(String value) {
this.id.setAttributeValue(value);
}
#Embeddable
public static class AttributeValueId implements Serializable {
private Integer id;
private String value;
public AttributeValueId() {
}
public AttributeValueId(Integer pAttributeTypeID, String pAttributeValue) {
this.id = pAttributeTypeID;
this.value = pAttributeValue;
}
public Integer getAttributeTypeID() {
return id;
}
public void setAttributeTypeID(Integer attributeTypeID) {
this.id = attributeTypeID;
}
public String getAttributeValue() {
return value;
}
public void setAttributeValue(String attributeValue) {
this.value = attributeValue;
}
#Override
public int hashCode() {
final int prime = 31;
int result = 1;
result = prime
* result
+ ((id == null) ? 0 : id
.hashCode());
result = prime
* result
+ ((value == null) ? 0 : value.hashCode());
return result;
}
#Override
public boolean equals(Object obj) {
if (this == obj)
return true;
if (obj == null)
return false;
if (getClass() != obj.getClass())
return false;
AttributeValueId other = (AttributeValueId) obj;
if (id == null) {
if (other.id != null)
return false;
} else if (!id.equals(other.id))
return false;
if (value == null) {
if (other.value != null)
return false;
} else if (!value.equals(other.value))
return false;
return true;
}
}
}
See 5.1.2.1. Composite identifier on how to do it with JPA annotation.
See Chapter 8. Component Mapping
See 8.4. Components as composite identifiers
I am not sure if I understand the question as it is getting late, but first thing I would try would be to override AttributeValue's equals method to contain those two unique fields.
In hibernate session there is one queue for the delete and one for the insert. Debug to see if deletes comes before insert.
Look at the merge. Try using update instead.

Categories