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.
Related
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.
I have the following class structure
#Entity
#Data // lombok
public class Entity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "entity_details_id", referencedColumnName = "id")
#JsonManagedReference(value = "entityDetails")
private EntityDetails entityDetails;
}
#Entity
#Data
public class EntityDetails {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#OneToOne(mappedBy = "entityDetails")
#JsonBackReference(value = "entityDetails")
private Entity entity;
// Some more fields
#OneToMany(mappedBy = "entityDetails", cascade = CascadeType.ALL, orphanRemoval = true)
#JsonManagedReference(value = "childEntityDetails")
private List<Child> childList;
}
#Entity
#Data
public class Child {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
private Long id;
#Column(name = "description")
private String description;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "entity_details_id")
#JsonBackReference(value = "childEntityDetails)
private EntityDetails entityDetails;
}
Assume I have an Entity object with the following structure:
Entity{
id: null,
entityDetails: {
id: null,
childList: [
{ id: null, description: "ch1" },
{ id: null, description: "ch2" }
]
}
}
When I call entityRepository.save(entity), all the objects are persisted, but the Child table does not have a value for entity_details_id. The other values are correct (I do have Entity.entity_details_id correctly set), but this one does not work. I'm not sure if I'm missing something or if I'm just doing it wrong.
Here are my entity classes.
JobPost.java
#Entity
#Table(name = "job_post")
public class JobPost {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "job_post_id")
private Long jobPostId;
#Column(name = "job_title")
private String jobTitle;
#Column(name = "job_description")
private String jobDescription;
#Column(name = "vacancy")
private int vacancy;
#Column(name = "posted_date")
#JsonFormat(pattern = "yyyy-MM-dd")
private Date postedDate;
#Column(name = "total_applicants")
private int totalApplicants;
}
JobApplication.java
#Entity
#Table(name = "job_application")
public class JobApplication {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "job_application_id")
private Long jobApplicationId;
#Column(name = "job_post_id")
private Long jobPostId;
#Column(name = "applicant_id")
private Long applicantId;
}
Applicant.java
#Entity
#Table(name = "applicant")
public class Applicant {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "applicant_id")
private Long applicantId;
#Column(name = "applicant_name")
private String applicantName;
#Column(name = "applicant_mobile_no")
private String applicantMobileNo;
#Column(name = "applicant_email")
private String applicantEmail;
}
My main goal is to listing the ApplicantList on JobPostId. I am totally new in Spring data JPA. Is JPA mappings are correct?. I don't know which query I should fire in order to fetch the applicantList based on jobPostId.
I would recommend to use JpaMappings and use SpringData instead of using native query.
Steps to follow:
Many-To-Many:
Use JoinTable to directly map JobPost and Applicant instead of creating a separate class.
Link for help:
https://attacomsian.com/blog/spring-data-jpa-many-to-many-mapping
Use SpringData JPA findOne or findById method (depends on spring version). If you use EAGER fetch then it will give you all Applicants associated with the JobPost Id.
One-To-Many
Keep JobApplication class and use OneToMany annotation.
Link for help:
https://attacomsian.com/blog/spring-data-jpa-one-to-many-mapping
Query:
#Query("select a from JobPost j inner join j.jobApplicantList ja inner join ja.applicant a where j.jobPostId=:jobPostId")
List<String> findAllJobApplicants(#Param("jobPostId") Long jobPostId);
I think that you should configure the mappings in such a way.To do this, you only need two entities
JobPost.java
#Entity
#Table(name = "job_post")
public class JobPost {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "job_title")
private String jobTitle;
#Column(name = "job_description")
private String jobDescription;
#Column(name = "vacancy")
private int vacancy;
#Column(name = "posted_date")
#JsonFormat(pattern = "yyyy-MM-dd")
private Date postedDate;
#Column(name = "total_applicants")
private int totalApplicants;
#ManyToMany
#JoinTable(name = "applicant_job_post",
joinColumns = {
#JoinColumn(name = "job_post_id", referencedColumnName = "id")
}, inverseJoinColumns = {
#JoinColumn(name = "applicant_id", referencedColumnName = "id")
})
private Set<Applicant> applicants;
public JobPost() {
}
public void addApplicant(Applicant applicant) {
applicants.add(applicant);
applicant.getJobPosts().add(this);
}
public void removeApplicant(Applicant applicant) {
applicants.remove(applicant);
applicant.getJobPosts().remove(this);
}
}
Applicant.java
#Entity
#Table(name = "applicant")
public class Applicant {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#Column(name = "applicant_name")
private String applicantName;
#Column(name = "applicant_mobile_no")
private String applicantMobileNo;
#Column(name = "applicant_email")
private String applicantEmail;
#ManyToMany(mappedBy = "applicants")
private Set<JobPost> jobPosts;
public Applicant() {
}
public void addJobPost(JobPost jobPost) {
jobPosts.add(jobPost);
jobPost.getApplicants().add(this);
}
public void removeJobPost(JobPost jobPost) {
jobPosts.remove(jobPost);
jobPost.getApplicants().remove(this);
}
}
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;
}
I have two entities which I would like to join through multiple columns. These columns are shared by an #Embeddable object that is shared by both entities. In the example below, Foo can have only one Bar but Bar can have multiple Foos (where AnEmbeddableObject is a unique key for Bar). Here is an example:
#Entity
#Table(name = "foo")
public class Foo {
#Id
#Column(name = "id")
#GeneratedValue(generator = "seqGen")
#SequenceGenerator(name = "seqGen", sequenceName = "FOO_ID_SEQ", allocationSize = 1)
private Long id;
#Embedded
private AnEmbeddableObject anEmbeddableObject;
#ManyToOne(targetEntity = Bar.class, fetch = FetchType.LAZY)
#JoinColumns( {
#JoinColumn(name = "column_1", referencedColumnName = "column_1"),
#JoinColumn(name = "column_2", referencedColumnName = "column_2"),
#JoinColumn(name = "column_3", referencedColumnName = "column_3"),
#JoinColumn(name = "column_4", referencedColumnName = "column_4")
})
private Bar bar;
// ... rest of class
}
And the Bar class:
#Entity
#Table(name = "bar")
public class Bar {
#Id
#Column(name = "id")
#GeneratedValue(generator = "seqGen")
#SequenceGenerator(name = "seqGen", sequenceName = "BAR_ID_SEQ", allocationSize = 1)
private Long id;
#Embedded
private AnEmbeddableObject anEmbeddableObject;
// ... rest of class
}
Finally the AnEmbeddedObject class:
#Embeddable
public class AnEmbeddedObject {
#Column(name = "column_1")
private Long column1;
#Column(name = "column_2")
private Long column2;
#Column(name = "column_3")
private Long column3;
#Column(name = "column_4")
private Long column4;
// ... rest of class
}
Obviously the schema is poorly normalised, it is a restriction that AnEmbeddedObject's fields are repeated in each table.
The problem I have is that I receive this error when I try to start up Hibernate:
org.hibernate.AnnotationException: referencedColumnNames(column_1, column_2, column_3, column_4) of Foo.bar referencing Bar not mapped to a single property
I have tried marking the JoinColumns are not insertable and updatable, but with no luck. Is there a way to express this with Hibernate/JPA annotations?
This worked for me . In my case 2 tables foo and boo have to be joined based on 3 different columns.Please note in my case ,in boo the 3 common columns are not primary key
i.e., one to one mapping based on 3 different columns
#Entity
#Table(name = "foo")
public class foo implements Serializable
{
#Column(name="foocol1")
private String foocol1;
//add getter setter
#Column(name="foocol2")
private String foocol2;
//add getter setter
#Column(name="foocol3")
private String foocol3;
//add getter setter
private Boo boo;
private int id;
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "brsitem_id", updatable = false)
public int getId()
{
return this.id;
}
public void setId(int id)
{
this.id = id;
}
#OneToOne
#JoinColumns(
{
#JoinColumn(updatable=false,insertable=false, name="foocol1", referencedColumnName="boocol1"),
#JoinColumn(updatable=false,insertable=false, name="foocol2", referencedColumnName="boocol2"),
#JoinColumn(updatable=false,insertable=false, name="foocol3", referencedColumnName="boocol3")
}
)
public Boo getBoo()
{
return boo;
}
public void setBoo(Boo boo)
{
this.boo = boo;
}
}
#Entity
#Table(name = "boo")
public class Boo implements Serializable
{
private int id;
#Column(name="boocol1")
private String boocol1;
//add getter setter
#Column(name="boocol2")
private String boocol2;
//add getter setter
#Column(name="boocol3")
private String boocol3;
//add getter setter
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "item_id", updatable = false)
public int getId()
{
return id;
}
public void setId(int id)
{
this.id = id;
}
}
If this doesn't work I'm out of ideas. This way you get the 4 columns in both tables (as Bar owns them and Foo uses them to reference Bar) and the generated IDs in both entities. The set of 4 columns has to be unique in Bar so the many-to-one relation doesn't become a many-to-many.
#Embeddable
public class AnEmbeddedObject
{
#Column(name = "column_1")
private Long column1;
#Column(name = "column_2")
private Long column2;
#Column(name = "column_3")
private Long column3;
#Column(name = "column_4")
private Long column4;
}
#Entity
public class Foo
{
#Id
#Column(name = "id")
#GeneratedValue(generator = "seqGen")
#SequenceGenerator(name = "seqGen", sequenceName = "FOO_ID_SEQ", allocationSize = 1)
private Long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumns({
#JoinColumn(name = "column_1", referencedColumnName = "column_1"),
#JoinColumn(name = "column_2", referencedColumnName = "column_2"),
#JoinColumn(name = "column_3", referencedColumnName = "column_3"),
#JoinColumn(name = "column_4", referencedColumnName = "column_4")
})
private Bar bar;
}
#Entity
#Table(uniqueConstraints = #UniqueConstraint(columnNames = {
"column_1",
"column_2",
"column_3",
"column_4"
}))
public class Bar
{
#Id
#Column(name = "id")
#GeneratedValue(generator = "seqGen")
#SequenceGenerator(name = "seqGen", sequenceName = "BAR_ID_SEQ", allocationSize = 1)
private Long id;
#Embedded
private AnEmbeddedObject anEmbeddedObject;
}
Hibernate is not going to make it easy for you to do what you are trying to do. From the Hibernate documentation:
Note that when using referencedColumnName to a non primary key column, the associated class has to be Serializable. Also note that the referencedColumnName to a non primary key column has to be mapped to a property having a single column (other cases might not work). (emphasis added)
So if you are unwilling to make AnEmbeddableObject the Identifier for Bar then Hibernate is not going to lazily, automatically retrieve Bar for you. You can, of course, still use HQL to write queries that join on AnEmbeddableObject, but you lose automatic fetching and life cycle maintenance if you insist on using a multi-column non-primary key for Bar.