Sharing a common table with unique values in Hibernate - java

I've been trying to get my head around how I can create a common tag asset library with hibernate, but I can't get it to work.
I want it to look behave something like this:
I want Subscription and Notification to share the same unique tag library.
I have tried with a #ManyToMany annotation. But I don't think that's the way to do it. In my best case scenario I want Hibernate to automatically see if a tag is already present, and then just use that id. But I'm also okay with first creating tags and then linking them. How is the best way to go about this with Hibernate?
Here's my current code:
Subscription.class
#Data
#Entity
public class Subscription {
#Id
#GeneratedValue
#Column(name = "subscription_id")
private Long id;
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(name = "subscription_tag",
joinColumns = { #JoinColumn(name = "subscription_id") },
inverseJoinColumns = { #JoinColumn(name = "tag_id") })
private List<Tags> tags = new ArrayList<>();
private String subscriberString;
}
Tags.class
#Data
#Entity
#Table(
name = "notification_tags",
uniqueConstraints = #UniqueConstraint(columnNames = "tag")
)
public class Tags {
#Id
#GeneratedValue
#Column(name = "tag_id")
private Long id;
private String tag;
}
Notification.class
#Data
#Entity(name = "Notification")
#Table(name = "notification")
#TypeDef(
name = "jsonb-node",
typeClass = JsonNodeStringType.class
)
public class Notification extends NotificationBase {
#Id
#GeneratedValue
#Column(name = "notification_id")
private Long id;
// This is when the object was created
private Date updatedTime = new Date();
private Date timestamp;
private String name;
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(name = "notification_tag",
joinColumns = { #JoinColumn(name = "notification_id") },
inverseJoinColumns = { #JoinColumn(name = "tag_id") })
private List<Tags> tags = new ArrayList<>();
#Type(type = "jsonb-node")
#Column(columnDefinition = "NVARCHAR(4000)")
private JsonNode customJson;
}

I had some relations wrong. You should only have one Class own the relation, but the other class should reference back to that relation.
I also used #JsonManagedReference and #JsonBackReference to avoid circular referencing when Serializing to json.
When I want to search for all Subscriptions with a tag I did not need to search for the tag that is in all Subscriptions, but the other way around, all Subscriptions with a certain tag. See my code below.
SubscriptionRepository.class (to find all Subscription with a certain tag)
public interface SubscriptionRepository extends CrudRepository<Subscription, Long>{
List<Subscription> findAllByTag(Tag tag);
}
Subscription.class
#Data
#Entity
public class Subscription {
#Id
#GeneratedValue
#Column(name = "subscription_id")
private Long id;
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(name = "subscription_tag",
joinColumns = { #JoinColumn(name = "subscription_id") },
inverseJoinColumns = { #JoinColumn(name = "tag_id") })
private Set<Tags> tags = new HashSet<>();
private String subscriberString;
}
Tag.class
#Data
#Entity
#Table(
name = "notification_tags",
uniqueConstraints = #UniqueConstraint(columnNames = "tag")
)
public class Tag {
#Id
#GeneratedValue
#Column(name = "tag_id")
private Long id;
private String tag;
#ManyToMany(mappedBy = "tags", cascade = { CascadeType.PERSIST, CascadeType.MERGE })
#JsonBackReference
private Set<Subscription> subscriptions = new HashSet<>();
#ManyToMany(mappedBy = "tags", cascade = { CascadeType.PERSIST, CascadeType.MERGE })
#JsonBackReference
private Set<Notification> notifications = new HashSet<>();
public Tag() {
}
#Builder
public Tag(String tag) {
this.tag = tag;
}
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
if (!super.equals(o)) return false;
Tag tag1 = (Tag) o;
return Objects.equals(tag, tag1.tag);
}
#Override
public int hashCode() {
return Objects.hash(super.hashCode(), tag);
}
}
Notification.class
#Data
#Entity(name = "Notification")
#Table(name = "notification")
#TypeDef(
name = "jsonb-node",
typeClass = JsonNodeStringType.class
)
public class Notification extends NotificationBase {
#Id
#GeneratedValue
#Column(name = "notification_id")
private Long id;
// This is when the object was created
private Date updatedTime = new Date();
private Date timestamp;
private String name;
#ManyToMany(cascade = CascadeType.ALL)
#JoinTable(name = "notification_tag",
joinColumns = { #JoinColumn(name = "notification_id") },
inverseJoinColumns = { #JoinColumn(name = "tag_id") })
private Set<Tags> tags = new HashSet<>();
#Type(type = "jsonb-node")
#Column(columnDefinition = "NVARCHAR(4000)")
private JsonNode customJson;
}

Related

Hibernate LazyInitializationException

I am trying to get the versions field within my MachineGroup Entity that is ManyToMany relationship. I am trying to fetch it in a custom serializer using ObjectMapper but for some reason I always get the LazyInitializationException - org.hibernate.LazyInitializationException: failed to lazily initialize a collection of role: com.altair.autoTester.entities.machinegroup.MachineGroup.versions, could not initialize proxy - no Session.
MachineGroup
#Entity
#Table(name="machine_groups")
#Getter
#Setter
#AllArgsConstructor(access = AccessLevel.PUBLIC)
#NoArgsConstructor
public class MachineGroup {
#Id
#GeneratedValue(strategy= GenerationType.AUTO, generator = "machine_groups_seq")
#SequenceGenerator(name = "machine_groups_seq", allocationSize = 1, initialValue = 2)
#Column(name = "id")
private long id;
#ManyToMany(cascade = CascadeType.MERGE)
#JoinTable(name = "machine_groups_to_versions",
joinColumns = #JoinColumn(name = "machine_group_id"),
inverseJoinColumns = #JoinColumn(name = "version_id"))
#JsonManagedReference(value="machineGroups-versions")
private Set<Version> versions = new HashSet<>();
#ManyToMany(cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinTable(name = "machine_groups_to_tr_types",
joinColumns = #JoinColumn(name = "machine_group_id"),
inverseJoinColumns = #JoinColumn(name = "tr_type_id"))
private Set<TrType> trTypes = new HashSet<>();
}
Version.java
#Entity
#Table(name="versions")
#Getter
#Setter
public class Version {
#Id
#GeneratedValue(strategy = GenerationType.AUTO, generator = "versions_seq")
#SequenceGenerator(name = "versions_seq", allocationSize = 1)
#Column(name = "id")
private long id;
#Column(name = "name", unique = true)
private String name;
#Column(name = "creation_time")
private Date creationTime;
#Column(name = "exe_file")
#Lob
private Blob exeFile;
#JsonBackReference(value="machineGroups-versions")
#ManyToMany(mappedBy = "versions", cascade = CascadeType.MERGE)
private Set<MachineGroup> machineGroups = new HashSet<>();
public Version(){};
public Version(String name) {
this.name = name;
}
}
MachineGroupSerializer.java
public class MachineGroupSerializer extends StdSerializer<MachineGroup> {
public MachineGroupSerializer() {
this(null);
}
public MachineGroupSerializer(Class<MachineGroup> t) {
super(t);
}
#Transactional
#Override
public void serialize(MachineGroup machineGroup,
JsonGenerator jsonGenerator,
SerializerProvider serializerProvider) throws IOException {
jsonGenerator.writeStartObject();
jsonGenerator.writeNumberField("id",machineGroup.getId());
jsonGenerator.writeObjectField("versions", machineGroup.getVersions().stream().map(Version::getId).collect(Collectors.toSet()));
jsonGenerator.writeObjectField("trTypes", machineGroup.getTrTypes().stream().map(TrType::getId).collect(Collectors.toSet()));
jsonGenerator.writeEndObject();
}
}
TrTypes.java
#Table(name = "tr_types", indexes = {
#Index(name = "tr_types_type_name_uindex", columnList = "type_name", unique = true)
})
#Entity
#AllArgsConstructor(access = AccessLevel.PUBLIC)
#NoArgsConstructor
public class TrType {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", nullable = false)
private Long id;
#Column(name = "type_name", nullable = false)
private String typeName;
}
I put the JsonManagedReference and JsonBackReference so I won't have infinite loop calling one another, not sure if it's causing any issues.
Calling the getTrTypes() function works but has the EAGER fetchType which I do not want to add to the versions collection.
What can cause this lazy exception to occur and how can I prevent it?
Also the function calling the serialzier within my MachineGroupService has the #Transactional annotation, I saw something that is related to the hibernate session that might be close at the serializer level.

How to replace CriteriaBuilder with Spring JPA

I have next classes:
#Entity
#Table
public class Lesson implements ModelEntity {
#Id
#Column(name = "lesson_id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "course_id")
private Course course;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "lesson_type_id")
private LessonType lessonType;
private LocalDate date;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "time_slot_id")
private TimeSlot timeSlot;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "auditorium_id")
private Auditorium auditorium;
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(name = "lesson_teacher", joinColumns = #JoinColumn(name = "lesson_id"), inverseJoinColumns = #JoinColumn(name = "person_id"))
private Set<Teacher> teachers = new HashSet<>();;
#ManyToMany(fetch = FetchType.LAZY)
#JoinTable(name = "lesson_group", joinColumns = #JoinColumn(name = "lesson_id"), inverseJoinColumns = #JoinColumn(name = "group_id"))
private Set<Group> groups = new HashSet<>();
}
#Entity
#Table(name = "groups")
public class Group implements ModelEntity {
#Id
#Column(name = "group_id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#Column(name = "group_name")
private String name;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "faculty_id")
private Faculty faculty;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "group")
private List<Student> students;
}
#Entity
#Table
public class TimeSlot implements ModelEntity {
#Id
#Column(name = "time_slot_id")
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
#Column(name = "time_slot_number")
private Integer number;
#Column(name = "time_slot_name")
private String name;
#Column(name = "time_slot_start")
private LocalTime startTime;
#Column(name = "time_slot_end")
private LocalTime endTime;
}
I wrote method, that find all Groups_id by Date and TimeSlot_id not connected to Lesson with CriteriaBuilder API, it works perfect:
#Override
public Set<Integer> getBusyGroupsId(int lessonId, LocalDate date, int timeSlotId) {
logger.debug("getBusyGroupsId() with agruments {}, {}, {}.", lessonId, date, timeSlotId);
CriteriaBuilder criteriaBuilder = entityManager.getCriteriaBuilder();
CriteriaQuery<Integer> query = criteriaBuilder.createQuery(Integer.class);
Root<Lesson> root = query.from(Lesson.class);
List<Predicate> predicates = new ArrayList<>();
Join<Lesson, TimeSlot> timeSlotJoin = root.join("timeSlot", JoinType.LEFT);
predicates.add(criteriaBuilder.equal(timeSlotJoin.get("id"), timeSlotId));
predicates.add(criteriaBuilder.equal(root.get("date"), date));
if (nonNull(lessonId)) {
predicates.add(criteriaBuilder.notEqual(root.get("id"), lessonId));
}
query.where(predicates.toArray(new Predicate[] {}));
SetJoin<Lesson, Group> joinGroup = root.joinSet("groups");
query.multiselect(joinGroup.get("id"));
TypedQuery<Integer> result = entityManager.createQuery(query);
return result.getResultStream().collect(Collectors.toSet());
}
But after that I think- what about JPA, can it be easier?
I tried something like that, but it doesnt work:
public Set<Integer> findGroupIdByIdNotAndDateEqualsAndTimeSlotIdEquals(Integer lessonId, LocalDate date, Integer timeSlotId);
How to fix it?
Also I stacked with writing method with JPA that should find all Lesson by Group_id and Date(or startDate-endDate) and sort it: first by date, second- by TimeSlot_number.
Can it be written with JPA?
Thanks in advance.
Don't throw stones, I'm just getting to know Spring JPA.

Hibernate One to many mapping override

I am facing a hibernate problem in updainting the join table in one to many mapping with hibernate. Below are my two entity class and join table entity class.
ArticleCategoryMap.java
#Entity
#Table(name = "ARTICLECATEGORYMAP")
public class ArticleCategoryMap {
private static final long serialVersionUID = -5653708523600543988L;
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
#Column ( name = "id")
Long id;
#ManyToOne(targetEntity = Article.class, fetch = FetchType.EAGER, optional = true, cascade = CascadeType.PERSIST)
#JoinColumn(name = "ARTICLE_ID", nullable = true, insertable = true, updatable = true)
private Article article;
#ManyToOne(targetEntity = Category.class, fetch = FetchType.EAGER, optional = true, cascade = CascadeType.PERSIST)
#JoinColumn(name = "CATEGORY_ID", nullable = true, insertable = true, updatable = true)
private Category category;
//setter and getter
}
Article.java
#Entity
#Table(name = "ARTICLE")
public class Article {
private long id;
private String title;
private String description;
private String keywords;
private String content;
#Id
#GeneratedValue
#Column(name = "ARTICLE_ID")
public long getId() {
return id;
}
//setter and getter
}
Category.java
#Entity
#Table(name = "CATEGORY")
public class Category {
private long id;
private String name;
#OneToMany(cascade = CascadeType.ALL, fetch=FetchType.EAGER)
#JoinTable(
name = "ARTICLECATEGORYMAP",
joinColumns = #JoinColumn(name = "CATEGORY_ID"),
inverseJoinColumns = #JoinColumn(name = "ARTICLE_ID")
)
#CollectionId(
columns = #Column(name="id"),
type=#Type(type="long"),
generator = "sequence"
)
private Collection<Article> articles;
#Id
#GeneratedValue
#Column(name = "CATEGORY_ID")
public long getId() {
return id;
}
#OneToMany(cascade = CascadeType.ALL, fetch=FetchType.EAGER)
#JoinTable(
name = "ARTICLECATEGORYMAP",
joinColumns = #JoinColumn(name = "CATEGORY_ID"),
inverseJoinColumns = #JoinColumn(name = "ARTICLE_ID")
)
#CollectionId(
columns = #Column(name="id"),
type=#Type(type="long"),
generator = "sequence"
)
// setter an getter
}
Now suppose first time I have 2 elements in article table which is mapping to one entry of the category table. so the join table will look something like
Now due to some reason, I want to update the entry where the article entry will map to a new category ID. So the final DB should look like
So My problem Is how can I update this join table.
If you want one to many relationship (1 category have many articles and 1 article to 1 category) you dont need a join table.
The entity classes should look like that:
Category Entity:
Contains a Set of articles:
#Entity
#Table(name = "CATEGORY")
public class Category {
private long id;
private String name;
#OneToMany(mappedBy="category")
private Set<Article> articles;
......
}
Article Entity:
#Entity
#Table(name = "ARTICLE")
public class Article {
#ManyToOne
#JoinColumn(name="id", nullable=false)
private Category category;
private long id;
private String title;
private String description;
private String keywords;
private String content;
.......
}
For more details take a look at hibernate-one-to-many. Hope this helps.
Also move annotation from methods to fields. This:
private long id;
#Id
#GeneratedValue
#Column(name = "CATEGORY_ID")
public long getId() {
return id;
}
Should be:
#Id
#GeneratedValue
#Column(name = "CATEGORY_ID")
private long id;
public long getId() {
return id;
}
Many to many relationship:
At your database you have 3 tables:
CATEGORY
ARTICLE
ARTICLECATEGORYMAP (join table)
For many to many relationship entities would be:
Category Entity:
#Entity
#Table(name = "CATEGORY")
public class Category {
#Id
#GeneratedValue
#Column(name = "CATEGORY_ID")
private long id;
private String name;
#ManyToMany(cascade = { CascadeType.ALL })
#JoinTable(
name = "ARTICLECATEGORYMAP",
joinColumns = { #JoinColumn(name = "CATEGORY_ID") },
inverseJoinColumns = { #JoinColumn(name = "ARTICLE_ID") }
)
Set<Article > articles = new HashSet<>();
.....
}
Article Entity:
#Entity
#Table(name = "ARTICLE")
public class Article {
#Id
#GeneratedValue
#Column(name = "ARTICLE_ID")
private long id;
private String title;
private String description;
private String keywords;
private String content;
#ManyToMany(mappedBy = "articles")
private Set<Category> categories = new HashSet<>();
.......
}
For more info take a look at many-to-many ralationship

Spring data JPA many to many retrieve

I have entities as below. I need to retrieve list of CIDs from CEntity using AEntity's id;
I have to traverse thru AEntity -> ABMapping -> BEntity -> fetch CID from CEntity.
Is there a way to achieve this in JPA or Should I go native query way joining all four tables and get CIDs from CEntity?
Entity A
#Entity
public class AEntity {
#Id
private long id;
#ManyToMany
#JoinTable(name = "ABMapping", joinColumns = #JoinColumn(name = "AEntity_ref", referencedColumnName = "id"), inverseJoinColumns = #JoinColumn(name = "BEntity_ref", referencedColumnName = "id"))
private List<BEntity> bEntities = new ArrayList<>();
}
Entity B
#Entity
public class BEntity {
#Id
private long id;
private CEntity cEntity;
#ManyToMany(mappedBy = "bEntities")
private List<AEntity> aEntities;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "cEntityId")
public CEntity getCEntity() {
return cEntity;
}
}
Entity ABMapping
#Entity
public class ABMapping {
#Id
private long id;
#Column(name="AEntity_ref")
private long ARefId;
#Column(name = "BEntity_ref")
private long BRefId;
}
Entity C
#Entity
public class CEntity {
#Id
private long id;
private String CID;
private List<BEntity> bEntity;
#OneToMany(fetch = FetchType.LAZY, mappedBy = "c", cascade =
CascadeType.ALL)
public List<BEntity> getBEntities() {
return bEntity;
}
#Column(name = "CID_column")
public String getCId() {
return CID;
}
public void setCId(String CID) {
this.CID = CID;
}
}
I went with what #JB Nizet has suggested with.
select distinct c from AEntity a join a.bEntities b join b.cEntity c where a.id = :id

JPA / Hibernate delete entity from association table

I have the following hibernate mappings:
Class Folders:
#Entity
#Table(name = "FOLDER")
public class Folders implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "folder_seq")
#Column(name = "ID_FOL")
private Long id;
#Column(name = "NOFOLDER")
private String noFolder;
#LazyCollection(LazyCollectionOption.FALSE)
#OneToMany(cascade = CascadeType.ALL)
#JoinTable(name = "M_DOMAIN_DN", joinColumns = {
#JoinColumn(name = "FK_FOLDER", referencedColumnName = "ID_FOL") }, inverseJoinColumns = {
#JoinColumn(name = "FK_DOMAIN", referencedColumnName = "ID_DOM") })
private List<Domain> domain = new ArrayList<Domain>();
/** GETTER + SETTER **/
}
Class Domain :
#Entity
#Table(name = "DOMAIN")
public class Domain {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "domain_seq")
#Column(name = "ID_DOM")
private Long id;
#Column(name = "CODE")
private String code;
#Column(name = "LABEL")
private String label;
public Long getId() {
/** GETTER + SETTER **/
}
Method that deletes an entity of type Folder:
public void deleteFolder(Long id) {
try {
entityManager.remove(getFolderById(id));
entityManager.flush();
} catch (PersistenceException e) {
}
}
I tried to delete the entity Folder but is not deleted in the database. Deleting manually in the database works fine, looking for help trying to solve this issue.

Categories

Resources