I'm trying do maintain a code that was developed when I was busy doing other parts of the system and am taking a beat from some entities related to each other.
The scenario is this:
EntityX has one or more EntityA;
EntityA has one or more EntityB;
EntityB has one or more EntityC;
EntityC has one or more EntityD;
My Entities classes, are:
public class EntityX {
#Id
#GeneratedValue(generator = "uuid")
#GenericGenerator(name = "uuid", strategy = "uuid2")
#Column(name = "id", unique = true, insertable = true)
private String id;
#OneToMay(fetch = FetchType.EAGER)
#Fetch(FetchMode.SELECT)
#JoinTable(name = "joinTableX_A",
joinColumns = {#JoinColumn(name = "idX", referencedColumn= "id" ) },
inverseJoinColumns = { #JoinColumn(name = "idA", referencedColumn = "id") })
#Cascade(CascadeType.ALL)
private List<A> aList;
// getters and setters
public class EntityA {
#Id
#GeneratedValue(generator = "uuid")
#GenericGenerator(name = "uuid", strategy = "uuid2")
#Column(name = "id", unique = true, insertable = true)
private String id;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColun(name = "idX")
private X x;
#OneToMay(fetch = FetchType.EAGER)
#Fetch(FetchMode.SELECT)
#JoinTable(name = "joinTableA_B",
joinColumns = {#JoinColumn(name = "idA", referencedColumn= "id" ) },
inverseJoinColumns = { #JoinColumn(name = "idB", referencedColumn = "id") })
#Cascade(CascadeType.ALL)
private List<B> bList;
// getters and setters
public class EntityB {
#Id
#GeneratedValue(generator = "uuid")
#GenericGenerator(name = "uuid", strategy = "uuid2")
#Column(name = "id", unique = true, insertable = true)
private String id;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColun(name = "idA")
private A a;
#OneToMay(fetch = FetchType.EAGER)
#Fetch(FetchMode.SELECT)
#JoinTable(name = "joinTableB_C",
joinColumns = {#JoinColumn(name = "idB", referencedColumn= "id" ) },
inverseJoinColumns = { #JoinColumn(name = "idC", referencedColumn = "id") })
#Cascade(CascadeType.ALL)
private List<C> cList;
// getters and setters
public class EntityC {
#Id
#GeneratedValue(generator = "uuid")
#GenericGenerator(name = "uuid", strategy = "uuid2")
#Column(name = "id", unique = true, insertable = true)
private String id;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColun(name = "idB")
private B b;
#OneToMay(fetch = FetchType.EAGER)
#Fetch(FetchMode.SELECT)
#JoinTable(name = "joinTableC_D",
joinColumns = {#JoinColumn(name = "idC", referencedColumn= "id" ) },
inverseJoinColumns = { #JoinColumn(name = "idD", referencedColumn = "id") })
#Cascade(CascadeType.ALL)
private List<D> dList;
// getters and setters
What I am trying to do is:
When x is deleted, all children a, b, c and d are deleted;
When a is deleted, all children b, c and d are deleted but x is not;
When b is deleted, all children c and d are delete, but x and a are not;
When c is deleted, all children d are deleted, but x, a and b are not.
What am I doing wrong here and how can I get this scenario working?
Try defining your relationships like this, using JPA annotations, you'll even make your code simpler by using the following method:
public class EntityX {
#Id
#GeneratedValue(generator = "uuid")
#GenericGenerator(name = "uuid", strategy = "uuid2")
#Column(name = "id", unique = true, insertable = true)
private String id;
#OneToMay(mappedBy = "x", cascade = CascadeType.ALL)
private List<A> aList;
// getters and setters
}
public class EntityA {
#Id
#GeneratedValue(generator = "uuid")
#GenericGenerator(name = "uuid", strategy = "uuid2")
#Column(name = "id", unique = true, insertable = true)
private String id;
#ManyToOne
#JoinColumn(name = "idX")
private X x;
//and so on
// getters and setters
}
I think its a better Idea to let JPA handle your join column names, unless you have a restriction
Related
Please help me write entities the proper way, so that it can be easily fetched using JPA. I have a DB design as below image:
Table Design Structure
I have created entities
#Entity
#Table(name = "ROLE")
public class Role {
#Id
#GeneratedValue()
#GenericGenerator(name = "uuid2", strategy = "uuid2")
#Column(name = "ROLE_ID")
private UUID roleId;
#Column(name = "ROLE_NAME")
private String roleName;
#ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE })
#JoinTable(name = "ROLE_MODULE_PERMISSION_MAP",
joinColumns = #JoinColumn(name = "ROLE_ID"),
inverseJoinColumns = #JoinColumn(name = "MODULE_ID"))
private List<Module> modules;
}
#Entity
#Table(name = "MODULE")
public class Module {
#Id
#GeneratedValue(generator = "uuid2")
#GenericGenerator(name = "uuid2", strategy = "uuid2")
#Column(name = "MODULE_ID", columnDefinition = "BINARY(16)")
private UUID uuid;
#Column(name = "MODULE_NAME")
private String moduleName;
#ManyToMany(cascade = {CascadeType.PERSIST, CascadeType.MERGE })
#JoinTable(name = "ROLE_MODULE_PERMISSION_MAP",
joinColumns = #JoinColumn(name = "MODULE_ID"),
inverseJoinColumns = #JoinColumn(name = "PERMISSION_ID"))
private List<Permission> permission;
}
#Entity
#Table(name = "PERMISSION")
public class Permission {
#Id
#GeneratedValue(generator = "uuid2")
#GenericGenerator(name = "uuid2", strategy = "uuid2")
#Column(name = "PERMISSION_ID", columnDefinition = "BINARY(16)")
private UUID permissionId;
#Column(name = "PERMISSION_TYPE")
private String permissionType;
#ManyToMany
#JoinTable(name = "ROLE_MODULE_PERMISSION_MAP",
joinColumns = #JoinColumn(name = "PERMISSION_ID"),
inverseJoinColumns = #JoinColumn(name = "ROLE_ID"))
#MapKeyJoinColumn(name="MODULE_ID")
#ElementCollection
private Map<Role, Module> modulePermissions;
}
#Entity
#Table(name = "ROLE_MODULE_PERMISSION_MAP")
public class RoleModulePermissionMap implements Serializable {
#Id
#Column(name = "ROLE_ID", columnDefinition = "BINARY(16)")
private UUID roleId;
#Id
#Column(name = "MODULE_ID", columnDefinition = "BINARY(16)")
private UUID moduleId;
#Id
#Column(name = "PERMISSION_ID", columnDefinition = "BINARY(16)")
private UUID permissionId;
}
I am trying to fetch using:
Role role = roleRepository.findByroleName(roleName)
Where roleRepository is
#Repository
public interface RoleRepository extends JpaRepository<Role, UUID> {
Role findByroleName(String roleName);
}
I want to fetch the Module and Permissions for a specific Role. something like:
{
"roleName": "Development",
"roleAcronym": "DEV",
"permissionGroup": "AdminUser",
"modules": [
{
"moduleName": "Agreement",
"permission": [
{
"permissionName": "CREATE",
"permissionType": "C"
},
{
"permissionName": "UPDATE",
"permissionType": "U"
},
{
"permissionName": "READ",
"permissionType": "R"
}
]
},
{
"moduleName": "Reports",
"permission": [
{
"permissionName": "DELETE",
"permissionType": "C"
},
{
"permissionName": "UPDATE",
"permissionType": "U"
},
{
"permissionName": "READ",
"permissionType": "R"
}
]
}
]
}
I am using Spring Boot Starter JPA - 2.6.2 version.
I have 4 entities: workflowEntity, PhaseEntity, RoundEntity and BlockEntity.
The structure is supposed to be following:
Workflow:
Phase:
Round(optional):
Block
or if there are no rounds, since round_id in workflow_binding table is nullable;
Workflow:
Phase:
Block
I need workflowEntity, when bound with Phase, Block or Round entities, to hold the information of those entitites, but those other entites should not contain any information of their "structural children". e.g I request getAllPhases(), then those Phases that get returned, should not contain information of any Blocks nor Rounds, because unless phases are bound within workflow, they are not tied with Blocks nor Rounds in any way. They can only be tied with Blocks and Rounds in bound workflowEntity.
I have tried to use #JoinTable (commented out examples in workflowEntity, PhaseEntity and RoundEntity), but this returned too much data, that wasnt bound to requested workflow.
Basically I need WorkflowEntity to contain fields such as:
private Map<PhaseEntity, Map<RoundEntity, Set<BlockEntity>>> phaseRoundEntityMap;
private Map<PhaseEntity, Set<BlockEntity>> phaseBlockEntityMap;
#Entity
#Data
#Table(name="workflow")
public class WorkflowEntity {
#Id
#GeneratedValue(generator = "UUID")
#GenericGenerator(name = "UUID", strategy = "org.hibernate.id.UUIDGenerator")
#Column(name = "id")
private UUID id;
#NotBlank
#Size(min = 1, max = 255)
#Column(name= "name")
private String name;
#Column(name="days")
private Integer days;
/*
#OneToMany(cascade = {CascadeType.DETACH, CascadeType.PERSIST, CascadeType.REFRESH, CascadeType.REMOVE}, fetch = FetchType.EAGER)
#JoinTable(name = "workflow_binding",
joinColumns = {#JoinColumn(name = "workflow_id")},
inverseJoinColumns = {#JoinColumn(name = "workflow_phase_id")})
private Set<PhaseEntity> phases;*/
//I need to map these somehow
private Map<PhaseEntity, Map<RoundEntity, Set<BlockEntity>>> phaseRoundEntityMap;
private Map<PhaseEntity, Set<BlockEntity>> phaseBlockEntityMap;
}
#Entity
#Data
#Table(name="workflow_phase")
#NoDuplicatePhases
public class PhaseEntity {
#Id
#GeneratedValue(generator = "UUID")
#GenericGenerator(name = "UUID", strategy = "org.hibernate.id.UUIDGenerator")
#Column(name = "id", updatable = false, nullable = false)
private UUID id;
#NotBlank
#Size(min = 1, max = 255)
private String name;
#Size(max = 1500)
private String description;
private Integer sequenceNumber;
// Phase shouldnt actually contain any information about blocks nor rounds, unless bound with a workflow.
/* Commented out, because includes blocks and rounds not bound to requested workflow
#OneToMany(cascade = {CascadeType.DETACH, CascadeType.PERSIST, CascadeType.REFRESH, CascadeType.REMOVE}, fetch = FetchType.EAGER)
#JoinTable(name = "workflow_binding",
joinColumns = {#JoinColumn(name = "workflow_phase_id")},
inverseJoinColumns = {#JoinColumn(name = "workflow_block_id")})
private Set<BlockEntity> blocks;
#OneToMany(cascade = {CascadeType.DETACH, CascadeType.PERSIST, CascadeType.REFRESH, CascadeType.REMOVE}, fetch = FetchType.EAGER)
#JoinTable(name = "workflow_binding",
joinColumns = {#JoinColumn(name = "workflow_phase_id")},
inverseJoinColumns = {#JoinColumn(name = "workflow_round_id")})
private Set<RoundEntity> rounds;*/
}
#Entity
#Data
#Table(name="workflow_round")
#NoDuplicateRounds
public class RoundEntity {
#Id
#GeneratedValue(generator = "UUID")
#GenericGenerator(name = "UUID", strategy = "org.hibernate.id.UUIDGenerator")
#Column(name = "id", updatable = false, nullable = false)
private UUID id;
#NotBlank
#Size(min = 1, max = 255)
private String name;
private Integer sequenceNumber;
/*Commented out, because includes blocks not bound to requested workflow
#OneToMany(cascade = {CascadeType.DETACH, CascadeType.PERSIST, CascadeType.REFRESH, CascadeType.REMOVE}, fetch = FetchType.EAGER)
#JoinTable(name = "workflow_binding",
joinColumns = {#JoinColumn(name = "workflow_round_id", nullable = false, updatable = false)},
inverseJoinColumns = {#JoinColumn(name = "workflow_block_id", nullable = false, updatable = false)})
private Set<BlockEntity> blocks;*/
}
#Entity
#Data
#Table(name = "workflow_block")
public class BlockEntity {
#Id
#GeneratedValue(generator = "UUID")
#GenericGenerator(name = "UUID", strategy = "org.hibernate.id.UUIDGenerator")
#Column(name = "id", updatable = false, nullable = false)
private UUID id;
#NotBlank
#Size(min = 1, max = 255)
private String name;
#Size(max = 1500)
private String description;
#OneToMany(cascade = {CascadeType.DETACH, CascadeType.PERSIST, CascadeType.REFRESH, CascadeType.REMOVE, CascadeType.MERGE}, orphanRemoval = true, fetch = FetchType.EAGER)
#JoinTable(name = "workflow_assignment_block",
joinColumns = {#JoinColumn(name = "workflow_block_id", nullable = false, updatable = false)},
inverseJoinColumns = {#JoinColumn(name = "workflow_assignment_id")})
private Set<AssignmentEntity> assignments;
private Integer sequenceNumber;
#Column(name = "due_date_day")
private Integer dueDate;
}
Why not create an entity WorkflowBinding?
#Entity
#Table(name = "workflow_binding")
public class WorkflowBinding {
#EmbeddedId
private WorkflowBindingId id;
#ManyToOne(fetch = LAZY)
#JoinColumn(name = "block_id", insertable = false, updatable = false)
private BlockEntity block;
#ManyToOne(fetch = LAZY)
#JoinColumn(name = "round_id", insertable = false, updatable = false)
private RoundEntity round;
#ManyToOne(fetch = LAZY)
#JoinColumn(name = "phase_id", insertable = false, updatable = false)
private PhaseEntity phase;
#ManyToOne(fetch = LAZY)
#JoinColumn(name = "workflow_id", insertable = false, updatable = false)
private WorkflowEntity workflow;
}
#Embeddable
public class WorkflowBindingId {
private UUID blockId;
private UUID roundId;
private UUID phaseId;
private UUID workflowId;
}
On the inverse side you can then do mappings like this:
public class BlockEntity {
#OneToMany(mappedBy = "block")
private Set<WorkflowBinding> bindings;
....
}
I have a LibraryModel class, a LibraryImage class and a LibraryAttribute class. A LibraryModel can have an arbitrary number of LibraryImages and LibraryAttributes.
The error that I get:
org.hibernate.MappingException: Foreign key (FKmbn4xh7xdxv371ao5verqueu3:library_item_attribute [LIBRARY_ITEM_ATTRIBUTE_ID])) must have same number of columns as the referenced primary key (library_item_attribute [LIBRARY_ITEM_ID,LIBRARY_ITEM_ATTRIBUTE_ID])
Here are my annotated Objects:
Library Model:
#Entity
#Table(name = "library_item", uniqueConstraints = {
})
#Inheritance(strategy = InheritanceType.JOINED)
public class LibraryItemModel implements LibraryItem{
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "LIBRARY_ITEM_ID", unique = true, nullable = false)
private Integer libraryItemId;
#Column(name = "ITEM_TITLE", unique = false, nullable = false)
private String itemTitle;
#Column(name = "IS_PARENT", nullable = false)
private Boolean isParent;
#ManyToOne(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumn(name="LIBRARY_ID", nullable = false)
private LibraryModel libraryModel;
#ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinTable(name = "ITEM_LISTING",
joinColumns = {#JoinColumn(name = "PARENT_LIB_ITEM_ID", nullable=false)},
inverseJoinColumns = {#JoinColumn(name="CHILD_LIB_ITEM_ID", nullable = false)})
private Set<LibraryItemModel> itemChildren = new HashSet<>();
#ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL, mappedBy = "itemChildren")
private Set<LibraryItemModel> itemParents = new HashSet<>();
#ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinTable(name = "LIBRARY_ITEM_IMAGE",
joinColumns = { #JoinColumn(name = "LIBRARY_ITEM_ID", nullable=false)},
inverseJoinColumns = { #JoinColumn(name="LIBRARY_IMAGE_ID", nullable = false)})
private Set<LibraryImage> itemImages = new HashSet<>();
#OneToMany(fetch = FetchType.LAZY, mappedBy = "rootLibraryItemModel")
private Set<LibraryModel> libraries = new HashSet<>();
#ManyToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinTable(name = "LIBRARY_ITEM_ATTRIBUTE",
joinColumns = { #JoinColumn(name = "LIBRARY_ITEM_ID", nullable =false)},
inverseJoinColumns = { #JoinColumn(name="LIBRARY_ITEM_ATTRIBUTE_ID", nullable = false)})
private Set<LibraryItemAttribute> libraryItemAttributes = new HashSet<>();
}
LibraryImage:
#Entity
#Table(name = "library_image", uniqueConstraints = {
})
public class LibraryImage {
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "LIBRARY_IMAGE_ID", unique = true, nullable = false)
private Integer libraryImageId;
#Column(name = "IMAGE_LOCATION")
private String imageLocation;
#Column(name = "IMAGE_TITLE")
private String imageTitle;
#Enumerated(EnumType.STRING)
private LibraryImageType imageType;
#ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name="LIBRARY_ITEM_ID", nullable = false)
private LibraryItemModel libraryItemModel;
}
Library Attribute:
#Entity
#Table(name = "library_item_attribute", uniqueConstraints = {
})
public class LibraryItemAttribute {
#Id
#GeneratedValue(strategy = IDENTITY)
#Column(name = "LIBRARY_ITEM_ATTRIBUTE_ID", unique = true, nullable = false)
private Integer libraryItemAttributeId;
#Column(name = "ATTRIBUTE_NAME")
private String attributeName;
#Column(name = "ATTRIBUTE_VALUE")
private String attributeValue;
#ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name="LIBRARY_ITEM_ID", nullable = false)
private LibraryItemModel libraryItemModel;
}
This is really frustrating as the LibraryImage class is mapped without problems and doesn't throw any errors, but the LibraryAttribute class which is annotated in an identical way to the LibraryImage class is throwing this error.
Can someone please have a look and let me know what my problem is?
Found the problem.
In the LibraryItemModel class I defined the Join table with the LibraryItemAttribute to be called "LIBRARY_ITEM_ATTRIBUTE", which is the name of the table of the Library item attributes.
The join table is a different table and should have a different table name.
For the Library Image table above, the image table is called library_image, while the join table is called LIBRARY_ITEM_IMAGE
When I try to insert using this relationship with cascade capability. The system got error:
ERROR: null value in column "b_id" violates not-null constraint
A entity.
#Id
#GeneratedValue(generator = "system-uuid")
#GenericGenerator(name = "system-uuid", strategy = "uuid2")
private String id;
#ManyToOne(cascade = {CascadeType.ALL})
#JoinColumn(name = "b_id")
private B b;
B entity.
#Id
#GeneratedValue(generator = "system-uuid")
#GenericGenerator(name = "system-uuid", strategy = "uuid2")
private String id;
#OneToMany(mappedBy = "b", cascade = { CascadeType.ALL }, fetch = FetchType.EAGER)
private List<C> cs;
C entity.
#GeneratedValue(generator = "system-uuid")
#GenericGenerator(name = "system-uuid", strategy = "uuid2")
private String id;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "b_id", nullable = false, referencedColumnName = "id")
private B b;
Execute.
A a = new A();
B b = new B();
C c = new C();
List<C> cs = new ArrayList<C>();
cs.add(c);
b.setCs(cs);
a.setB(b);
aRepository.save(a);
Your c does not know its b "parent" - try adding c.setB(b) before saving.
There is part of SQL i want to realize in my Custom JPA repository
SELECT * FROM users u
JOIN skills_user sku on sku.user_id = u.id
JOIN specs_user spu on spu.user_id = u.id
GROUP BY u.id
HAVING ANY(sku.dictionary_id in (15,20) or spu.dictionary_id in (15,20))
ORDER BY u.id
I tried this:
//Other predicates
if (filterQuery.getSkills() != null && !filterQuery.getSkills().isEmpty()) {
String[] tmp = filterQuery.getSkills().replaceAll(" ", "").split(",");
List<Integer> ids = new ArrayList<>();
for (String s : tmp) {
ids.add(Integer.parseInt(s));
}
List<Predicate> tmpPredicates = new ArrayList<>();
Join<User, Dictionary> skillJoin = root.join("skillList");
Join<User, Dictionary> specsJoin = root.join("specsList");
for (Integer id : ids) {
tmpPredicates.add(builder.or(builder.equal(skillJoin.get("id"), id), builder.equal(specsJoin.get("id"), id)));
}
predicates.add(builder.and(tmpPredicates.toArray(new Predicate[tmpPredicates.size()])));
}
//Other predicates
But it isn't work correctly.
How can i realise this correctly in JPA custom repository?
there is code of User and Dictionary classes:
#Entity
#SequenceGenerator(name = "user_gen", sequenceName = "users_seq")
#Table(name = "users")
public class User {
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "user_gen")
private Long id;
#Column(name = "login")
private String login;
#Column(name = "password")
private String password;
#Column(name = "name")
private String name;
#Column(name = "surname")
private String surname;
#Column(name = "middlename")
private String middlename;
#Column(name = "academic_group")
private String academicGroup;
#Column(name = "entrance_year")
private int entranceYear;
#Column(name = "avatar_URL")
private String avatarURL;
#Column(name = "salt")
private String salt;
#Enumerated(EnumType.ORDINAL)
#Column(name = "user_group")
private UserGroup group;
#JsonIgnore
#ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinTable(name = "SocialRole_User", joinColumns = {
#JoinColumn(name = "user_id", nullable = false, updatable = false) },
inverseJoinColumns = { #JoinColumn(name = "socialRole_id",
nullable = false, updatable = false) })
private List<SocialRole> socialRoleList;
#JsonIgnore
#ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinTable(name = "specs_user", joinColumns = {
#JoinColumn(name = "user_id", nullable = false, updatable = true)},
inverseJoinColumns = {#JoinColumn(name = "dictionary_id",
nullable = false, updatable = true)})
private List<Dictionary> specsList;
#JsonIgnore
#ManyToMany(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinTable(name = "skills_user", joinColumns = {
#JoinColumn(name = "user_id", nullable = false, updatable = true)},
inverseJoinColumns = {#JoinColumn(name = "dictionary_id",
nullable = false, updatable = true)})
private List<Dictionary> skillList;
#OneToMany(mappedBy = "user", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List<Contacts> contactsList;
//Getters and setters
}
Dictionary:
#Entity
#SequenceGenerator(name = "dictionary_gen", sequenceName = "dictionary_seq")
#Table(name = "dictionary")
public class Dictionary {
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "dictionary_gen")
private Long id;
#Column(name = "dic_name")
private String name;
#Enumerated(EnumType.STRING)
#Column(name = "dic_type")
private DictionaryType type;
// Getters and Setters
}
Have you tried writing the query using JPQL?
SELECT a FROM User a
INNER JOIN a.specsList b
INNER JOIN a.skillList c
GROUP BY a.id
HAVING ANY(b.id in (15,20) OR c.id in (15,20))
ORDER BY a.id;
This JPQL should work the same as your plain SQL.