1
0

split PersonEntity/Repo into Rbac and Real Entity/Repo and use in Relation for faster lazy loading (#130)

Co-authored-by: Timotheus Pokorra <timotheus.pokorra@hostsharing.net>
Co-authored-by: Michael Hoennig <michael@hoennig.de>
Reviewed-on: https://dev.hostsharing.net/hostsharing/hs.hsadmin.ng/pulls/130
Reviewed-by: Timotheus Pokorra <timotheus.pokorra@hostsharing.net>
This commit is contained in:
Michael Hoennig
2024-12-05 10:39:25 +01:00
parent ddba946d72
commit ee627cc246
42 changed files with 588 additions and 309 deletions

View File

@ -36,7 +36,7 @@ public interface HsOfficeDebitorRepository extends Repository<HsOfficeDebitorEnt
JOIN HsOfficePartnerEntity partner
ON partner.partnerRel.holder = debitor.debitorRel.anchor
AND partner.partnerRel.type = 'PARTNER' AND debitor.debitorRel.type = 'DEBITOR'
JOIN HsOfficePersonEntity person
JOIN HsOfficePersonRealEntity person
ON person.uuid = partner.partnerRel.holder.uuid
OR person.uuid = debitor.debitorRel.holder.uuid
JOIN HsOfficeContactRealEntity contact

View File

@ -9,7 +9,7 @@ import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficePartne
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficePartnerPatchResource;
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficePartnerResource;
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.HsOfficePartnerRelInsertResource;
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonEntity;
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonRealEntity;
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRealEntity;
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRealRepository;
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationType;
@ -166,8 +166,8 @@ public class HsOfficePartnerController implements HsOfficePartnersApi {
private HsOfficeRelationRealEntity persistPartnerRel(final HsOfficePartnerRelInsertResource resource) {
final var entity = new HsOfficeRelationRealEntity();
entity.setType(HsOfficeRelationType.PARTNER);
entity.setAnchor(ref(HsOfficePersonEntity.class, resource.getAnchorUuid()));
entity.setHolder(ref(HsOfficePersonEntity.class, resource.getHolderUuid()));
entity.setAnchor(ref(HsOfficePersonRealEntity.class, resource.getAnchorUuid()));
entity.setHolder(ref(HsOfficePersonRealEntity.class, resource.getHolderUuid()));
entity.setContact(ref(HsOfficeContactRealEntity.class, resource.getContactUuid()));
em.persist(entity);
return entity;

View File

@ -7,7 +7,7 @@ import lombok.NoArgsConstructor;
import lombok.Setter;
import net.hostsharing.hsadminng.errors.DisplayAs;
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContact;
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonEntity;
import net.hostsharing.hsadminng.hs.office.person.HsOfficePerson;
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRealEntity;
import net.hostsharing.hsadminng.hs.office.relation.HsOfficeRelationRbacEntity;
import net.hostsharing.hsadminng.persistence.BaseEntity;
@ -51,7 +51,7 @@ public class HsOfficePartnerEntity implements Stringifyable, BaseEntity<HsOffice
.withIdProp(HsOfficePartnerEntity::toShortString)
.withProp(p -> ofNullable(p.getPartnerRel())
.map(HsOfficeRelation::getHolder)
.map(HsOfficePersonEntity::toShortString)
.map(HsOfficePerson::toShortString)
.orElse(null))
.withProp(p -> ofNullable(p.getPartnerRel())
.map(HsOfficeRelation::getContact)

View File

@ -20,7 +20,7 @@ public interface HsOfficePartnerRepository extends Repository<HsOfficePartnerEnt
SELECT partner FROM HsOfficePartnerEntity partner
JOIN HsOfficeRelationRealEntity rel ON rel.uuid = partner.partnerRel.uuid
JOIN HsOfficeContactRealEntity contact ON contact.uuid = rel.contact.uuid
JOIN HsOfficePersonEntity person ON person.uuid = rel.holder.uuid
JOIN HsOfficePersonRealEntity person ON person.uuid = rel.holder.uuid
WHERE :name is null
OR partner.details.birthName like concat(cast(:name as text), '%')
OR contact.caption like concat(cast(:name as text), '%')

View File

@ -0,0 +1,79 @@
package net.hostsharing.hsadminng.hs.office.person;
import lombok.AccessLevel;
import lombok.AllArgsConstructor;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.experimental.FieldNameConstants;
import lombok.experimental.SuperBuilder;
import net.hostsharing.hsadminng.errors.DisplayAs;
import net.hostsharing.hsadminng.persistence.BaseEntity;
import net.hostsharing.hsadminng.repr.Stringify;
import net.hostsharing.hsadminng.repr.Stringifyable;
import org.apache.commons.lang3.StringUtils;
import jakarta.persistence.Column;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.Id;
import jakarta.persistence.MappedSuperclass;
import jakarta.persistence.Version;
import java.util.UUID;
import static net.hostsharing.hsadminng.repr.Stringify.stringify;
@MappedSuperclass
@Getter
@Setter
@NoArgsConstructor(access = AccessLevel.PROTECTED)
@AllArgsConstructor(access = AccessLevel.PROTECTED)
@SuperBuilder(toBuilder = true)
@FieldNameConstants
@DisplayAs("Person")
public class HsOfficePerson<T extends HsOfficePerson<?> & BaseEntity<?>> implements BaseEntity<T>, Stringifyable {
private static Stringify<HsOfficePerson> toString = stringify(HsOfficePerson.class, "person")
.withProp(Fields.personType, HsOfficePerson::getPersonType)
.withProp(Fields.tradeName, HsOfficePerson::getTradeName)
.withProp(Fields.salutation, HsOfficePerson::getSalutation)
.withProp(Fields.title, HsOfficePerson::getTitle)
.withProp(Fields.familyName, HsOfficePerson::getFamilyName)
.withProp(Fields.givenName, HsOfficePerson::getGivenName);
@Id
@GeneratedValue
private UUID uuid;
@Version
private int version;
@Column(name = "persontype")
private HsOfficePersonType personType;
@Column(name = "tradename")
private String tradeName;
@Column(name = "salutation")
private String salutation;
@Column(name = "title")
private String title;
@Column(name = "familyname")
private String familyName;
@Column(name = "givenname")
private String givenName;
@Override
public String toString() {
return toString.apply(this);
}
@Override
public String toShortString() {
return personType + " " +
(!StringUtils.isEmpty(tradeName) ? tradeName : (familyName + ", " + givenName));
}
}

View File

@ -27,7 +27,7 @@ public class HsOfficePersonController implements HsOfficePersonsApi {
private StandardMapper mapper;
@Autowired
private HsOfficePersonRepository personRepo;
private HsOfficePersonRbacRepository personRepo;
@Override
@Transactional(readOnly = true)
@ -54,7 +54,7 @@ public class HsOfficePersonController implements HsOfficePersonsApi {
context.define(currentSubject, assumedRoles);
final var entityToSave = mapper.map(body, HsOfficePersonEntity.class);
final var entityToSave = mapper.map(body, HsOfficePersonRbacEntity.class);
final var saved = personRepo.save(entityToSave);

View File

@ -1,103 +0,0 @@
package net.hostsharing.hsadminng.hs.office.person;
import lombok.*;
import lombok.experimental.FieldNameConstants;
import net.hostsharing.hsadminng.errors.DisplayAs;
import net.hostsharing.hsadminng.persistence.BaseEntity;
import net.hostsharing.hsadminng.rbac.generator.RbacView;
import net.hostsharing.hsadminng.rbac.generator.RbacView.SQL;
import net.hostsharing.hsadminng.repr.Stringify;
import net.hostsharing.hsadminng.repr.Stringifyable;
import org.apache.commons.lang3.StringUtils;
import jakarta.persistence.*;
import java.io.IOException;
import java.util.UUID;
import static net.hostsharing.hsadminng.rbac.generator.RbacView.GLOBAL;
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Permission.*;
import static net.hostsharing.hsadminng.rbac.generator.RbacView.RbacSubjectReference.UserRole.CREATOR;
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Role.*;
import static net.hostsharing.hsadminng.rbac.generator.RbacView.rbacViewFor;
import static net.hostsharing.hsadminng.repr.Stringify.stringify;
// TODO.refa: split HsOfficePersonEntity into Real+Rbac-Entity
@Entity
@Table(schema = "hs_office", name = "person_rv")
@Getter
@Setter
@Builder
@NoArgsConstructor
@AllArgsConstructor
@FieldNameConstants
@DisplayAs("Person")
public class HsOfficePersonEntity implements BaseEntity<HsOfficePersonEntity>, Stringifyable {
private static Stringify<HsOfficePersonEntity> toString = stringify(HsOfficePersonEntity.class, "person")
.withProp(Fields.personType, HsOfficePersonEntity::getPersonType)
.withProp(Fields.tradeName, HsOfficePersonEntity::getTradeName)
.withProp(Fields.salutation, HsOfficePersonEntity::getSalutation)
.withProp(Fields.title, HsOfficePersonEntity::getTitle)
.withProp(Fields.familyName, HsOfficePersonEntity::getFamilyName)
.withProp(Fields.givenName, HsOfficePersonEntity::getGivenName);
@Id
@GeneratedValue
private UUID uuid;
@Version
private int version;
@Column(name = "persontype")
private HsOfficePersonType personType;
@Column(name = "tradename")
private String tradeName;
@Column(name = "salutation")
private String salutation;
@Column(name = "title")
private String title;
@Column(name = "familyname")
private String familyName;
@Column(name = "givenname")
private String givenName;
@Override
public String toString() {
return toString.apply(this);
}
@Override
public String toShortString() {
return personType + " " +
(!StringUtils.isEmpty(tradeName) ? tradeName : (familyName + ", " + givenName));
}
public static RbacView rbac() {
return rbacViewFor("person", HsOfficePersonEntity.class)
.withIdentityView(SQL.projection("concat(tradeName, familyName, givenName)"))
.withUpdatableColumns("personType", "title", "salutation", "tradeName", "givenName", "familyName")
.toRole(GLOBAL, GUEST).grantPermission(INSERT)
.createRole(OWNER, (with) -> {
with.permission(DELETE);
with.owningUser(CREATOR);
with.incomingSuperRole(GLOBAL, ADMIN);
})
.createSubRole(ADMIN, (with) -> {
with.permission(UPDATE);
})
.createSubRole(REFERRER, (with) -> {
with.permission(SELECT);
});
}
public static void main(String[] args) throws IOException {
rbac().generateWithBaseFileName("5-hs-office/502-person/5023-hs-office-person-rbac");
}
}

View File

@ -9,9 +9,9 @@ import java.util.Optional;
class HsOfficePersonEntityPatcher implements EntityPatcher<HsOfficePersonPatchResource> {
private final HsOfficePersonEntity entity;
private final HsOfficePersonRbacEntity entity;
HsOfficePersonEntityPatcher(final HsOfficePersonEntity entity) {
HsOfficePersonEntityPatcher(final HsOfficePersonRbacEntity entity) {
this.entity = entity;
}

View File

@ -0,0 +1,52 @@
package net.hostsharing.hsadminng.hs.office.person;
import lombok.*;
import lombok.experimental.FieldNameConstants;
import lombok.experimental.SuperBuilder;
import net.hostsharing.hsadminng.errors.DisplayAs;
import net.hostsharing.hsadminng.rbac.generator.RbacView;
import net.hostsharing.hsadminng.rbac.generator.RbacView.SQL;
import jakarta.persistence.*;
import java.io.IOException;
import static net.hostsharing.hsadminng.rbac.generator.RbacView.GLOBAL;
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Permission.*;
import static net.hostsharing.hsadminng.rbac.generator.RbacView.RbacSubjectReference.UserRole.CREATOR;
import static net.hostsharing.hsadminng.rbac.generator.RbacView.Role.*;
import static net.hostsharing.hsadminng.rbac.generator.RbacView.rbacViewFor;
@Entity
@Table(schema = "hs_office", name = "person_rv")
@Getter
@Setter
@NoArgsConstructor
@SuperBuilder(toBuilder = true)
@FieldNameConstants
@DisplayAs("RbacPerson")
public class HsOfficePersonRbacEntity extends HsOfficePerson<HsOfficePersonRbacEntity> {
public static RbacView rbac() {
return rbacViewFor("person", HsOfficePersonRbacEntity.class)
.withIdentityView(SQL.projection("concat(tradeName, familyName, givenName)"))
.withUpdatableColumns("personType", "title", "salutation", "tradeName", "givenName", "familyName")
.toRole(GLOBAL, GUEST).grantPermission(INSERT)
.createRole(OWNER, (with) -> {
with.permission(DELETE);
with.owningUser(CREATOR);
with.incomingSuperRole(GLOBAL, ADMIN);
})
.createSubRole(ADMIN, (with) -> {
with.permission(UPDATE);
})
.createSubRole(REFERRER, (with) -> {
with.permission(SELECT);
});
}
public static void main(String[] args) throws IOException {
rbac().generateWithBaseFileName("5-hs-office/502-person/5023-hs-office-person-rbac");
}
}

View File

@ -0,0 +1,34 @@
package net.hostsharing.hsadminng.hs.office.person;
import io.micrometer.core.annotation.Timed;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.Repository;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
public interface HsOfficePersonRbacRepository extends Repository<HsOfficePersonRbacEntity, UUID> {
@Timed("app.office.persons.repo.findByUuid.rbac")
Optional<HsOfficePersonRbacEntity> findByUuid(UUID personUuid);
@Query("""
SELECT p FROM HsOfficePersonRbacEntity p
WHERE :name is null
OR p.tradeName like concat(cast(:name as text), '%')
OR p.givenName like concat(cast(:name as text), '%')
OR p.familyName like concat(cast(:name as text), '%')
""")
@Timed("app.office.persons.repo.findPersonByOptionalNameLike.rbac")
List<HsOfficePersonRbacEntity> findPersonByOptionalNameLike(String name);
@Timed("app.office.persons.repo.save.rbac")
HsOfficePersonRbacEntity save(final HsOfficePersonRbacEntity entity);
@Timed("app.office.persons.repo.deleteByUuid.rbac")
int deleteByUuid(final UUID personUuid);
@Timed("app.office.persons.repo.count.rbac")
long count();
}

View File

@ -0,0 +1,23 @@
package net.hostsharing.hsadminng.hs.office.person;
import lombok.Getter;
import lombok.NoArgsConstructor;
import lombok.Setter;
import lombok.experimental.FieldNameConstants;
import lombok.experimental.SuperBuilder;
import net.hostsharing.hsadminng.errors.DisplayAs;
import jakarta.persistence.Entity;
import jakarta.persistence.Table;
@Entity
@Table(schema = "hs_office", name = "person")
@Getter
@Setter
@NoArgsConstructor
@SuperBuilder(toBuilder = true)
@FieldNameConstants
@DisplayAs("RealPerson")
public class HsOfficePersonRealEntity extends HsOfficePerson<HsOfficePersonRealEntity> {
}

View File

@ -0,0 +1,31 @@
package net.hostsharing.hsadminng.hs.office.person;
import io.micrometer.core.annotation.Timed;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.Repository;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
public interface HsOfficePersonRealRepository extends Repository<HsOfficePersonRealEntity, UUID> {
@Timed("app.office.persons.repo.findByUuid.real")
Optional<HsOfficePersonRealEntity> findByUuid(UUID personUuid);
@Query("""
SELECT p FROM HsOfficePersonRealEntity p
WHERE :name is null
OR p.tradeName like concat(cast(:name as text), '%')
OR p.givenName like concat(cast(:name as text), '%')
OR p.familyName like concat(cast(:name as text), '%')
""")
@Timed("app.office.persons.repo.findPersonByOptionalNameLike.real")
List<HsOfficePersonRealEntity> findPersonByOptionalNameLike(String name);
@Timed("app.office.persons.repo.save.real")
HsOfficePersonRealEntity save(final HsOfficePersonRealEntity entity);
@Timed("app.office.persons.repo.count.real")
long count();
}

View File

@ -1,34 +0,0 @@
package net.hostsharing.hsadminng.hs.office.person;
import io.micrometer.core.annotation.Timed;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.Repository;
import java.util.List;
import java.util.Optional;
import java.util.UUID;
public interface HsOfficePersonRepository extends Repository<HsOfficePersonEntity, UUID> {
@Timed("app.office.persons.repo.findByUuid.rbac")
Optional<HsOfficePersonEntity> findByUuid(UUID personUuid);
@Query("""
SELECT p FROM HsOfficePersonEntity p
WHERE :name is null
OR p.tradeName like concat(cast(:name as text), '%')
OR p.givenName like concat(cast(:name as text), '%')
OR p.familyName like concat(cast(:name as text), '%')
""")
@Timed("app.office.persons.repo.findPersonByOptionalNameLike.rbac")
List<HsOfficePersonEntity> findPersonByOptionalNameLike(String name);
@Timed("app.office.persons.repo.save.rbac")
HsOfficePersonEntity save(final HsOfficePersonEntity entity);
@Timed("app.office.persons.repo.deleteByUuid.rbac")
int deleteByUuid(final UUID personUuid);
@Timed("app.office.persons.repo.count.rbac")
long count();
}

View File

@ -4,7 +4,7 @@ import lombok.*;
import lombok.experimental.FieldNameConstants;
import lombok.experimental.SuperBuilder;
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactRealEntity;
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonEntity;
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonRealEntity;
import net.hostsharing.hsadminng.persistence.BaseEntity;
import net.hostsharing.hsadminng.repr.Stringify;
import net.hostsharing.hsadminng.repr.Stringifyable;
@ -45,11 +45,11 @@ public class HsOfficeRelation implements BaseEntity<HsOfficeRelation>, Stringify
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "anchoruuid")
private HsOfficePersonEntity anchor;
private HsOfficePersonRealEntity anchor;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "holderuuid")
private HsOfficePersonEntity holder;
private HsOfficePersonRealEntity holder;
@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "contactuuid")

View File

@ -5,7 +5,7 @@ import net.hostsharing.hsadminng.context.Context;
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactRealRepository;
import net.hostsharing.hsadminng.hs.office.generated.api.v1.api.HsOfficeRelationsApi;
import net.hostsharing.hsadminng.hs.office.generated.api.v1.model.*;
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonRepository;
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonRealRepository;
import net.hostsharing.hsadminng.mapper.StandardMapper;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
@ -34,7 +34,7 @@ public class HsOfficeRelationController implements HsOfficeRelationsApi {
private HsOfficeRelationRbacRepository relationRbacRepo;
@Autowired
private HsOfficePersonRepository holderRepo;
private HsOfficePersonRealRepository personRepo;
@Autowired
private HsOfficeContactRealRepository realContactRepo;
@ -78,10 +78,10 @@ public class HsOfficeRelationController implements HsOfficeRelationsApi {
final var entityToSave = new HsOfficeRelationRbacEntity();
entityToSave.setType(HsOfficeRelationType.valueOf(body.getType()));
entityToSave.setMark(body.getMark());
entityToSave.setAnchor(holderRepo.findByUuid(body.getAnchorUuid()).orElseThrow(
entityToSave.setAnchor(personRepo.findByUuid(body.getAnchorUuid()).orElseThrow(
() -> new NoSuchElementException("cannot find Person by anchorUuid: " + body.getAnchorUuid())
));
entityToSave.setHolder(holderRepo.findByUuid(body.getHolderUuid()).orElseThrow(
entityToSave.setHolder(personRepo.findByUuid(body.getHolderUuid()).orElseThrow(
() -> new NoSuchElementException("cannot find Person by holderUuid: " + body.getHolderUuid())
));
entityToSave.setContact(realContactRepo.findByUuid(body.getContactUuid()).orElseThrow(

View File

@ -6,7 +6,7 @@ import lombok.Setter;
import lombok.experimental.SuperBuilder;
import net.hostsharing.hsadminng.errors.DisplayAs;
import net.hostsharing.hsadminng.hs.office.contact.HsOfficeContactRbacEntity;
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonEntity;
import net.hostsharing.hsadminng.hs.office.person.HsOfficePersonRbacEntity;
import net.hostsharing.hsadminng.rbac.generator.RbacView;
import net.hostsharing.hsadminng.rbac.generator.RbacView.SQL;
@ -52,11 +52,11 @@ public class HsOfficeRelationRbacEntity extends HsOfficeRelation {
.withRestrictedViewOrderBy(SQL.expression(
"(select idName from hs_office.person_iv p where p.uuid = target.holderUuid)"))
.withUpdatableColumns("contactUuid")
.importEntityAlias("anchorPerson", HsOfficePersonEntity.class, usingDefaultCase(),
.importEntityAlias("anchorPerson", HsOfficePersonRbacEntity.class, usingDefaultCase(),
dependsOnColumn("anchorUuid"),
directlyFetchedByDependsOnColumn(),
NOT_NULL)
.importEntityAlias("holderPerson", HsOfficePersonEntity.class, usingDefaultCase(),
.importEntityAlias("holderPerson", HsOfficePersonRbacEntity.class, usingDefaultCase(),
dependsOnColumn("holderUuid"),
directlyFetchedByDependsOnColumn(),
NOT_NULL)