Hibernate model with multiple many-to-many relations slow performance when persisting - spring-boot

We have following schema as described on diagram below. There's an Entity with bidirectional many-to-many relations with two other entities(Relation1, Relation2). There's also many-to-many relation between Entity-Relation2 relationship itself and Relation1 entity. When we create an instance of Entity and persist into database, it's working fine, except that operation takes too much time. I'd like to ask, if there are any possible optimizations on Hibernate level, which could boost performance of save operation.
Here's diagram:
Model definitions:
class Entity : AbstractJpaPersistable() {
var name: String? = null
var description: String? = null
#OneToMany(mappedBy = "entity", fetch = FetchType.LAZY, cascade = [CascadeType.PERSIST], orphanRemoval = true)
var entityRelations1: List<EntityToRelation1> = emptyList()
#OneToMany(mappedBy = "entity", fetch = FetchType.LAZY, cascade = [CascadeType.PERSIST], orphanRemoval = true)
var entityRelations2: List<EntityToRelation2> = emptyList()
}
#Entity
class Relation2: AbstractJpaPersistable(){
#OneToMany(mappedBy = "relation2", fetch = FetchType.LAZY, cascade = [CascadeType.PERSIST], orphanRemoval = true)
var entityRelations2: List<EntityToRelation2> = emptyList()
}
#Entity
class Relation1: AbstractJpaPersistable(){
#OneToMany(mappedBy = "relation1", fetch = FetchType.LAZY, cascade = [CascadeType.PERSIST], orphanRemoval = true)
var entityRelations1: List<EntityToRelation1> = emptyList()
#OneToMany(mappedBy = "relation1", fetch = FetchType.LAZY, cascade = [CascadeType.PERSIST], orphanRemoval = true)
var entityRelations2Relations1: List<EntityToRelation2ToRelation1> = emptyList()
}
#Entity
class EntityToRelation2 {
#EmbeddedId
var entityToRelation2Id: EntityToRelation2Id = EntityToRelation2Id()
#ManyToOne(fetch = FetchType.LAZY)
#MapsId("entityId")
#JoinColumn(name = "entity_id", insertable = false, updatable = false)
var entity: Entity? = null
#ManyToOne(fetch = FetchType.LAZY)
#MapsId("relation2Id")
#JoinColumn(name = "relation2_id", insertable = false, updatable = false)
var relation2: Relation2? = null
#OneToMany(mappedBy = "entityToRelation2", fetch = FetchType.LAZY, cascade = [CascadeType.PERSIST], orphanRemoval = true)
var entityRelations2Relations1: List<EntityToRelation2ToRelation1> = emptyList()
}
#Embeddable
class EntityToRelation2Id : Serializable {
#Column(name = "entity_id")
var entityId: Int? = null
#Column(name = "relation2_id")
var relationId: Int? = null
}
#Entity
class EntityToRelation1 {
#EmbeddedId
var entityToRelation1Id = EntityToRelation1Id()
#ManyToOne(fetch = FetchType.LAZY)
#MapsId("entityId")
#JoinColumn(name = "entity_id", insertable = false, updatable = false)
var entity: Entity? = null
#ManyToOne(fetch = FetchType.LAZY)
#MapsId("relation1Id")
#JoinColumn(name = "relation1_id", insertable = false, updatable = false)
var relation1: Relation1? = null
}
#Embeddable
class EntityToRelation1Id : Serializable {
#Column(name = "entity_id")
var entityId: Int? = null
#Column(name = "relation1_id")
var relation1Id: Int? = null
}
#Entity
class EntityToRelation2ToRelation1 {
#EmbeddedId
var entityToRelation2ToRelation1Id = EntityToRelation2ToRelation1Id()
#ManyToOne(fetch = FetchType.LAZY)
#MapsId("entityToRelation2Id")
#JoinColumns(
JoinColumn(name = "entity_id"),
JoinColumn(name = "relation2_id")
)
var entityToRelation2: EntityToRelation2? = null
#ManyToOne(fetch = FetchType.LAZY)
#MapsId("relation1Id")
#JoinColumn(name = "relation1_id", insertable = false, updatable = false)
var relation1: Relation1? = null
}
#Embeddable
class EntityToRelation2ToRelation1Id : Serializable {
var entityToRelation2Id: EntityToRelation2Id? = null
#Column(name = "relation1_id")
var relation1Id: Int? = null
}

Related

#JoinColumn "occurs out of order" when upgrading to spring-boot-3 (Hibernate 6 )

I have the following usage in JoinColumns
#Entity
public class EntityOne{
private String action;
private String type;
#ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST)
#NotFound(action = NotFoundAction.IGNORE)
#JoinColumns({
#JoinColumn(name = "action", referencedColumnName = "action_name", updatable = false, insertable = false),
#JoinColumn(name = "type", referencedColumnName = "type_name", updatable = false, insertable = false)
})
private Entitytwo entitytwo;
}
And
#Entity
public class EntityTwo {
#Id
#Column(name = "type_name")
private String typeName;
#Id
#Column(name = "action_name")
private String actionName;
}
This setup causes hibernate error of
Referenced column '" + column.getName()
+ "' mapped by target property '" + property.getName()
+ "' occurs out of order in the list of '#JoinColumn's
If i change the order inside the #JoinColumns it seems to work, but can stop working at the next time the application starts.
The hibernate comments at the begining of the relevant code states:
// Now we need to line up the properties with the columns in the
// same order they were specified by the #JoinColumn annotations
// this is very tricky because a single property might span
// multiple columns.
// TODO: For now we only consider the first property that matched
// each column, but this means we will reject some mappings
// that could be made to work for a different choice of
// properties (it's also not very deterministic)
And on the relevant code itself:
// we have the first column of a new property
orderedProperties.add( property );
if ( property.getColumnSpan() > 1 ) {
if ( !property.getColumns().get(0).equals( column ) ) {
// the columns have to occur in the right order in the property
throw new AnnotationException("Referenced column '" + column.getName()
+ "' mapped by target property '" + property.getName()
+ "' occurs out of order in the list of '#JoinColumn's");
}
currentProperty = property;
lastPropertyColumnIndex = 1;
}
How should i set the #JoinColumn for it to consistently work?
If the action and type attributes of EntityOne are meant to refer to the corresponding attributes of EntityTwo, they are useless and misleading.
The attribute private Entitytwo entitytwo is enough to design the #ManytoOne relation.
Remove these two attributes and if you need to get the action and type value of the entityTwo linked to an entityOne, simply use entityOne.entitytwo.getAction() (or entityOne.entitytwo.getType()).
I just tried the code you posted in Hibernate 6.1, and I observed no error. Even after permuting various things, still no error. So then to make things harder, I added a third column to the FK and tried permuting things. Still no error.
I now have:
#Entity
public class EntityOne {
#Id #GeneratedValue
Long id;
String action;
String type;
int count;
#ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST)
#NotFound(action = NotFoundAction.IGNORE)
#JoinColumns({
#JoinColumn(name = "count", referencedColumnName = "count", updatable = false, insertable = false),
#JoinColumn(name = "action", referencedColumnName = "action_name", updatable = false, insertable = false),
#JoinColumn(name = "type", referencedColumnName = "type_name", updatable = false, insertable = false),
})
EntityTwo entitytwo;
}
#Entity
public class EntityTwo {
#Id
#Column(name = "type_name")
String typeName;
#Id
#Column(name = "count")
int count;
#Id
#Column(name = "action_name")
String actionName;
}
and the test code:
#DomainModel(annotatedClasses = {EntityOne.class, EntityTwo.class})
#SessionFactory
public class BugTest {
#Test
public void test(SessionFactoryScope scope) {
scope.inTransaction( session -> {
EntityOne entityOne = new EntityOne();
entityOne.action = "go";
entityOne.type = "thing";
EntityTwo entityTwo = new EntityTwo();
entityTwo.actionName = "go";
entityTwo.typeName = "thing";
entityOne.entitytwo = entityTwo;
session.persist( entityOne );
} );
}
}
Perhaps there's something you're not telling us? Like, for example, something to do with the #Id of EntityOne which is missing in your original posted code?
Just in case, also tried this variation:
#Entity
public class EntityOne {
#Id
String action;
#Id
String type;
#Id
int count;
#ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.PERSIST)
#NotFound(action = NotFoundAction.IGNORE)
#JoinColumns({
#JoinColumn(name = "action", referencedColumnName = "action_name", updatable = false, insertable = false),
#JoinColumn(name = "count", referencedColumnName = "count", updatable = false, insertable = false),
#JoinColumn(name = "type", referencedColumnName = "type_name", updatable = false, insertable = false),
})
EntityTwo entitytwo;
}
But still no error.

Spring Boot JPA - Projection selecting all fields from table when has a collection

I trying to get two fields and a #ElementCollection from entity using projection with interface, but the JPA are selecting all fields from my entity and when i remove the method that get the list of my #ElementCollection the JPA select the only two fields.
My entity class:
#Entity(name = "users")
data class User(
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
var id: Long? = null,
#Column(nullable = false)
var name: String? = null,
#Column(unique = true, nullable = false)
var cpf: String? = null,
#Column(name = "phone_number", nullable = false)
var phoneNumber: String? = null,
#Column(unique = true, nullable = false)
var email: String? = null,
#Column(name = "password_hash")
var passwordHash: String? = null,
#Column(name = "password_hash_recovery")
var passwordHashRecovery: String? = null,
#Column(name = "password_hash_recovery_date")
var passwordHashRecoveryDate: String? = null,
#Column(name = "self_employed", nullable = false)
var selfEmployed: Boolean? = null,
#JoinColumn(name = "user_photo", referencedColumnName = "id")
#OneToOne(fetch = FetchType.LAZY)
var userPhoto: File? = null,
#JoinColumn(name = "id_location", referencedColumnName = "id")
#OneToOne(fetch = FetchType.LAZY)
var location: Location? = null,
#Column(name = "rating_star", nullable = false)
#Enumerated(EnumType.STRING)
var ratingStar: RatingStar = RatingStar.ONE,
#JoinColumn(name = "id_area", referencedColumnName = "id")
#OneToOne(fetch = FetchType.LAZY)
var area: Area? = null,
#OneToMany(mappedBy = "user", cascade = [CascadeType.ALL], fetch = FetchType.LAZY)
var workTimes: List<WorkTime> = arrayListOf(),
#OneToMany(mappedBy = "contractor", cascade = [CascadeType.ALL], fetch = FetchType.LAZY)
var contractorOrders: List<Order>? = arrayListOf(),
#OneToMany(mappedBy = "selfEmployed", cascade = [CascadeType.ALL], fetch = FetchType.LAZY)
var selfEmployedOrders: List<Order>? = arrayListOf(),
) {
#ElementCollection(fetch = FetchType.EAGER)
#Enumerated(EnumType.STRING)
#CollectionTable(
name = "profiles_authorties",
joinColumns = [JoinColumn(name = "user_id", referencedColumnName = "id")],
)
#Column(name = "authority")
private val _authorities: MutableSet<ProfileAuthorities> = HashSet()
init {
_authorities.add(ProfileAuthorities.CLIENT)
}
fun setAsAdmin() =
_authorities.add(ProfileAuthorities.ADMIN)
fun getAuthorities(): Set<ProfileAuthorities> = _authorities
}
My interface for projection:
interface LoginUserProjection {
fun getId(): Long
fun getPasswordHash(): String
fun getAuthorities(): Set<ProfileAuthorities>
}
The result query is:
Hibernate: select user0_.id as id1_12_, user0_.id_area as id_area11_12_, user0_.cpf as cpf2_12_, user0_.email as email3_12_, user0_.id_location as id_loca12_12_, user0_.name as name4_12_, user0_.password_hash as password5_12_, user0_.password_hash_recovery as password6_12_, user0_.password_hash_recovery_date as password7_12_, user0_.phone_number as phone_nu8_12_, user0_.rating_star as rating_s9_12_, user0_.self_employed as self_em10_12_, user0_.user_photo as user_ph13_12_ from users user0_ where user0_.id=?
Hibernate: select authoriti0_.user_id as user_id1_8_0_, authoriti0_.authority as authorit2_8_0_ from profiles_authorties authoriti0_ where authoriti0_.user_id=?
when i remove fun getAuthorities(): Set<ProfileAuthorities> from LoginUserProjection the result is:
Hibernate: select user0_.id as col_0_0_, user0_.password_hash as col_1_0_ from users user0_ where user0_.id=?
My repository method:
#Repository
interface UserRepository : JpaRepository<User, Long> {
fun <T> getUserProjectionById(id: Long, projection: Class<T>): T?
}

JPA: update relationship ManyToOne

I have the class BankDataEntity that has a relationship ManyToOne with IbanEntity
#Entity
public class BankDataEntity
#ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#JoinColumns(value = { #JoinColumn(name = "fk_rib", referencedColumnName = "rib", updatable = true),
#JoinColumn(name = "fk_iban", referencedColumnName = "iban", updatable = true) })
private IbanEntity ibanEntity;
}
I want to update the BankDataEntity, so in the below method, i get BankDataEntity by id, then i set the new value in IbanEntity, then i save BankDataEntity.
The problem is that it adds a new IbanEntity in the database instead of modifying the existing one.
public void on(BankDataUpdated event) {
BankDataEntity bankDataEntity = bankDataRepository.findByInternalId(event.internalId);
IbanEntity currentIban = bankDataEntity.getIbanEntity();
IbanEntity newIban = currentIban.iban(event.iban).rib(event.rib);
bankDataRepository.save(bankDataEntity.ibanEntity(newIban));
}

Kotlin spring JPA. lateinit property has not been initialized exception from getting lazy field of JPA entity

I use Spring Boot, Hibernate, Kotlin
In build.gradle.kts:
apply(plugin="org.hibernate.orm")
tasks.withType<org.hibernate.orm.tooling.gradle.EnhanceTask>().configureEach {
options.enableLazyInitialization = true
options.enableDirtyTracking = true
options.enableAssociationManagement = true
}
User entity:
#Entity
#Table(name = "user")
class User(
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
var id: Long = -1,
#Column(name = "user_name", unique = true, nullable = false, length = 20)
var userName: String = "",
#Column(unique = true, nullable = false)
var email: String = "",
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(name = "user_roles", joinColumns = [JoinColumn(name = "user_id")],
inverseJoinColumns = [JoinColumn(name = "role_id")])
var roles: MutableSet<Role> = mutableSetOf()
) {
#OneToOne(fetch = FetchType.LAZY, cascade = [CascadeType.ALL], mappedBy = "user", optional = false)
#LazyToOne(LazyToOneOption.PROXY)
#LazyGroup( "person" )
lateinit var person: Person
....
}
I get it by:
#Transactional
#Component
class UserServiceImpl (private val userRepository: UserRepository){
override fun getUserData(id: Long): Optional<UserView> {
return userRepository.findById(id).map { UserView.build(it, it.person) }
}
...
}
And it.person throws lateinit property has not been initialized exception, but Person was loaded( I see it by hibernate log ). Getting roles works fine.
I have same result without #LazyToOne(LazyToOneOption.PROXY) and #LazyGroup( "person" ).
SOLVED:
#OneToOne(fetch = FetchType.LAZY, cascade = [CascadeType.ALL])
#JoinColumn(name = "person_id", referencedColumnName = "id")
#LazyToOne(LazyToOneOption.PROXY)
#LazyGroup( "person" )
lateinit var person: Person

Spring cascade field not updated

I have an object with the following attribute but when I try to save the object this field is not merging.
How can I solve the issue ?
#ManyToMany(targetEntity = User.class, mappedBy = "userSites", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#NotAudited
private List<IUser> localIt;
userSites:
#BatchSize(size = 20)
#ManyToMany(cascade = { CascadeType.MERGE, CascadeType.PERSIST }, fetch = FetchType.LAZY,targetEntity=Site.class)
#JoinTable(name = "USER_SITE",
joinColumns = { #JoinColumn(name = USER_ID, nullable = false) },
inverseJoinColumns = { #JoinColumn(name = "SITE_ID", nullable = false) })
private Set<ISite> userSites;
Instead of save the attribute :
#ManyToMany(targetEntity = User.class, mappedBy = "userSites", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#NotAudited
private List<IUser> localIt;
I updated the site list of each users by adding the site to register into the concerned field :
#BatchSize(size = 20)
#ManyToMany(cascade = { CascadeType.MERGE, CascadeType.PERSIST }, fetch = FetchType.LAZY,targetEntity=Site.class)
#JoinTable(name = "USER_SITE",
joinColumns = { #JoinColumn(name = USER_ID, nullable = false) },
inverseJoinColumns = { #JoinColumn(name = "SITE_ID", nullable = false) })
private Set<ISite> userSites;

Resources