JPA and Hibernate One To One Shared Primary Key Uni-directional Mapping in Spring Boot - spring-boot

I want to have one-to-one uni-directional mapping with 2 child entities using shared primary key. Below are model classes
public class Template implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "templatekey")
Integer templateKey;
#Column(name = "templateid", unique = true)
String templateId;
#OneToOne(cascade = CascadeType.ALL, optional = false)
#PrimaryKeyJoinColumn(name = "templatekey", referencedColumnName = "templatekey")
InstantOfferNoEsp instantOfferNoEsp;
#OneToOne(cascade = CascadeType.ALL, optional = false)
#PrimaryKeyJoinColumn(name = "templatekey", referencedColumnName = "templatekey")
Mobile mobile;
//constructor , setter and getters
}
Child 1 :
public class Mobile implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#Column(name = "templatekey")
Integer templateKey;
String mobileNewUrl;
//constructor , setter and getters
}
Child 2:
public class InstantOfferNoEsp {
#Id
#Column(name = "templatekey")
Integer templateKey;
String offerCodeType;
String headerUrl;
//constructor , setter and getters
}
I want templateKey as PK in all tables. and I am calling templateRepository.save(template); to save all entities at once but its not working and getting ids for this class must be manually assigned before calling save() error.
Any suggestions would be of great help. Thank you.

I was able to do what you want with bidirectional #OneToOne like below:
#Entity
public class Mobile {
#Id
Integer templateKey;
#OneToOne
#MapsId
#JoinColumn(name = "templatekey")
Template template;
// ...
}
#Entity
public class InstantOfferNoEsp {
#Id
Integer templateKey;
#OneToOne
#MapsId
#JoinColumn(name = "templatekey")
Template template;
// ...
}
#Entity
public class Template {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "templatekey")
Integer templateKey;
#OneToOne(cascade = CascadeType.ALL, mappedBy = "template", optional = false)
InstantOfferNoEsp instantOfferNoEsp;
#OneToOne(cascade = CascadeType.ALL, mappedBy = "template", optional = false)
Mobile mobile;
// ...
public void setMobile(Mobile mobile)
{
this.mobile = mobile;
this.mobile.setTemplate(this);
}
public void setInstantOfferNoEsp(InstantOfferNoEsp instantOfferNoEsp)
{
this.instantOfferNoEsp = instantOfferNoEsp;
this.instantOfferNoEsp.setTemplate(this);
}
}
and an example of saving:
Mobile mobile = new Mobile();
mobile.setMobileNewUrl("MOB1");
InstantOfferNoEsp instant = new InstantOfferNoEsp();
instant.setOfferCodeType("INST_OFF1");
Template template = new Template();
template.setTemplateId("TMP1");
template.setInstantOffer(instant);
template.setMobile(mobile);
entityManager.persist(template);
P.S. The following mapping works too, but only if we set Template.templateKey manually.
#Entity
public class Template
{
#Id
// #GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "templatekey")
Integer templateKey;
#OneToOne(cascade = CascadeType.ALL, optional = false)
#JoinColumn(name = "templatekey", insertable = false, updatable = false)
InstantOfferNoEsp instantOfferNoEsp;
#OneToOne(cascade = CascadeType.ALL, optional = false)
#JoinColumn(name = "templatekey", insertable = false, updatable = false)
Mobile mobile;
// ...
}
and an example of saving:
Mobile mobile = new Mobile();
mobile.setMobileNewUrl("MOB1");
InstantOfferNoEsp instant = new InstantOfferNoEsp();
instant.setOfferCodeType("INST_OFF1");
Template template = new Template();
template.setTemplateKey(20);
template.setTemplateId("TMP1");
template.setInstantOffer(instant);
template.setMobile(mobile);
entityManager.persist(template);
Also I would suggest your explicitly specify what generation strategy you want to use (do not use GenerationType.AUTO) and use corresponding object wrapper classes instead of primitive types for #Id fields.

Related

Could not write JSON: Infinite recursion

I am getting StackOverflow recursion error when I run query in Postman or Browser .
When i run says:
.w.s.m.s.DefaultHandlerExceptionResolver : Could not write JSON: Infinite recursion (StackOverflowError); nested exception is com.fasterxml.jackson.databind.JsonMappingException: Infinite recursion (StackOverflowError)
Here is the model classes :
#Entity
public class Product {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NotNull
private String title;
#NotNull
private String description;
#NotNull
private double price;
#OneToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "category_id", nullable = false)
private Category category;
private boolean isSealed;
#OneToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "currency_id", nullable = false)
private Currency currency;
#OneToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "user_id", nullable = false)
private User user;
#Nullable
#OneToMany(mappedBy = "product",
cascade = CascadeType.ALL, orphanRemoval = true)
private List<Images> images;
private Date createdDate = new Date();
#OneToOne(fetch = FetchType.LAZY,
cascade = CascadeType.ALL,
mappedBy = "product")
private View view;
#OneToOne(fetch = FetchType.LAZY,cascade=CascadeType.ALL)
#JoinColumn(name="type_id")
private Type type;
private Long viewCount; }
#Entity public class Images{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String imagePath;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "product_id")
private Product product; }
#Entity public class User implements UserDetails, Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NotEmpty
private String fullName;
#NotEmpty
#Email
#Column(unique = true)
private String email;
#NotNull
#Column(unique = true)
private int phoneNumber;
#NotEmpty
#Size(min = 5)
private String password;
private Date createAt = new Date();
#Nullable
private String picPath;
#Nullable
private String token;
#ManyToMany
#JoinTable(name = "user_roles", joinColumns = {#JoinColumn(
name = "user_id")},
inverseJoinColumns = {#JoinColumn(name = "role_id")})
private List<Role> roles;
#OneToOne(fetch = FetchType.LAZY,
cascade = CascadeType.ALL,
mappedBy = "user")
private Product product;
#OneToOne(fetch = FetchType.LAZY,
cascade = CascadeType.ALL,
mappedBy = "user")
private View view; }
#Entity
public class Currency{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String code;
private String currency;
private String region_country;
#OneToOne(mappedBy = "currency", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Product product; }
#Entity
public class Category {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
private String imagePath;
#OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY,
mappedBy = "category")
private Product product;
#OneToMany(mappedBy = "category", fetch = FetchType.LAZY,
cascade = CascadeType.ALL)
private Set<Brand> brands; }
#Entity public class Brand {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
#ManyToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
#JoinColumn(name = "category_id", nullable = false)
private Category category; }
#Entity public class View {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#OneToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "user_id", nullable = false)
private User user;
#OneToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "product_id", nullable = false)
private Product product; }
#Entity public class Type {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
#NotNull
private String name;
#OneToOne(fetch = FetchType.LAZY,
cascade = CascadeType.ALL,
mappedBy = "type")
private Product product; }
#Id
private String role;
#ManyToMany(mappedBy = "roles")
private List<User> users;
}
More than one of your entities have each other in themselves.
For example, Product has an object of User, and User has an object of Product.
To solve this, you have to write
#JsonBackReference(value = "user-product")
private User user;
in the Product class,
and
#JsonManagedReference(value = "user-product")
private Product product;
In the user class.
Do it in every field and for every class that call each other.
Also, Check this out
JPA: Having lists on both ends without infinite loop
You have cycles in your data model. For example, Product holds Images and Images point back to Products.
This works in an object oriented world, because only pointer references are stored in those fields.
When serialized, however, the actual object is written out as json text. Your Product prints the Images object which in turn prints the Product object which again prints the Image object and so on.
You need to decide how you want to represent your json, map your database model into simple plain old java object and use this for serializations. These POJOs are often called View Model or Transport Objects.

JPA one to many, fetch children with specific column value

I have two entities in one to many relationship in my spring-data-jpa project.
Parent entity -
#Entity
#Table(name = "code_group")
public class CodeGroup implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
#NaturalId
#Column(nullable = false)
private String entityId;
#OneToMany
#JoinColumn(name = "codeGroupId", referencedColumnName = "entityId")
private List<SystemCode> systemCodes;
// .. getters setters
}
Child entity -
#Entity
#Table(name = "system_code")
public class SystemCode implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
private Long id;
#Column(nullable = false)
private String codeGroupId;
#Column(nullable = false)
#Enumerated(EnumType.STRING)
private ActiveOrInactive status;
// getters and setters
}
status column is of enum type, it can only have Active or Inactive value.
My existing code works fine. It is fetching code group with associated system codes. I want to filter system code with status='Active'. How to do this?
Try like this:
#OneToMany
#JoinColumn(name = "codeGroupId", referencedColumnName = "entityId")
#Where(clause = "status= 'Active'")
private List<SystemCode> systemCodes;
You can create such methods in your repo:
List<CodeGroup> getAllBySystemCodes_Status(ActiveOrInactive status);
default List<CodeGroup> getAllActive() {
return getAllBySystemCodes_Status(ActiveOrInactive.Active);
}
default List<CodeGroup> getAllInactive() {
return getAllBySystemCodes_Status(ActiveOrInactive.Inactive);
}

Spring JpaRepository manyToMany bidirectional should save instead of update

if got a language table and a system table with a many-to-many relationship:
Language:
#JsonAutoDetect
#Entity
#Table(name = "language")
public class Language implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "language_id", nullable = false)
private int languageId;
#Column(name = "language_name", nullable = false)
private String languageName;
#Column(name = "language_isocode", nullable = false)
private String languageIsoCode;
#ManyToMany(fetch = FetchType.EAGER)
#JoinTable(name = "system_language", joinColumns = {#JoinColumn(name = "language_id", updatable = false)},
inverseJoinColumns = {
#JoinColumn(name = "system_id", updatable = false)}, uniqueConstraints = {
#UniqueConstraint(columnNames = {
"language_id",
"system_id"
})})
private List<System> systems;
public Language() {
}
// GETTER & SETTERS
// ....
}
System
#JsonAutoDetect
#Entity
#Table(name = "system")
public class System implements Serializable {
#Id
#Column(name = "system_id", nullable = false)
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer systemId;
#Column(name = "system_name", nullable = false, unique = true)
private String systemName;
#OneToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "university_id", nullable = false)
private University university;
#JoinColumn(name = "calender_id", nullable = false)
#OneToOne(fetch = FetchType.EAGER)
private Calendar calender;
#OneToMany(mappedBy = "system")
#LazyCollection(LazyCollectionOption.FALSE)
private List<SystemUserRole> systemUserRoleList;
#OneToMany(mappedBy = "system")
#LazyCollection(LazyCollectionOption.FALSE)
private List<Role> roleList;
#OneToOne(mappedBy = "system")
#LazyCollection(LazyCollectionOption.FALSE)
private CsmUserEntity csmUserEntity;
#ManyToMany(mappedBy = "systems")
#LazyCollection(LazyCollectionOption.FALSE)
private List<Language> languages;
public System() {
}
// GETTER & SETTERS
// ....
}
When im writing a first dataset (systemId=1, language_id=20) into the table, everything works fine. But when i try to write a second dataset with the same language_id but with other system_id (systemId=2, language_id=20), then the existing dataset gets updated. But i want to have a new dataset instead. What can i do?
Thanks in advance!

one to many mapping in hibernate for a messaging service

I have two JPA entities: User and Message.
Each Message has one sender and one receiver of type User. And on the other side each User has two sets of type Message: inbox and outbox.
Message:
#Entity
#Table(name = "messages")
public class Message {
#Id
#GeneratedValue(strategy = GenerationType.AUTO, generator = "seq")
#SequenceGenerator(name = "seq", sequenceName = "MESSAGES_SEQ")
#Column(name = "ID", insertable = false, updatable = false)
private int id;
#ManyToOne
#JoinColumn(name = "SENDER")
private User sender;
#ManyToOne
#JoinColumn(name = "RECEIVER")
private User receiver;
private String subject, content;
private Date sdate;
//getters and setters
}
All the properties which not being mapped with an annotation has he same name as the columns in database and are automatically mapped by JPA.
User:
#Entity
#Table(name = "users")
public class User {
#Column(name = "USERNAME")
private String username;
#Column(name = "PASSWORD")
private String pass;
#Column(name = "EMAIL")
private String email;
#Id
#GeneratedValue(strategy = GenerationType.AUTO, generator = "seq")
#SequenceGenerator(name = "seq", sequenceName = "USERS_SEQ")
#Column(name = "ID", insertable = false, updatable = false)
private int id;
#OneToMany(mappedBy = "uploader")
private Set<Book> books;
#OneToMany(mappedBy = "receiver")
private Set<Message> inbox;
#OneToMany(mappedBy = "sender")
private Set<Message> outbox;
//getters and setters
}
The problem is, when I select an User from Oracle database, then the inbox property is empty. How is this caused and how can I solve it?

Hibernate 4 Entity Mapping with Spring

I've got 2 entity classes as follows:
#Entity
#Table(name = "USER_ACCT")
public class UserAccount implements Serializable{
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy=GenerationType.SEQUENCE, generator="USER_ACCT_ID_SEQ")
#SequenceGenerator(name="USER_ACCT_ID_SEQ", sequenceName="USER_ACCT_ID_SEQ")
#Column(name = "USER_ACCT_ID")
protected Long id;
#Basic(optional = false)
#Column(length = 50, unique = true)
private String username;
#Basic(optional = false)
#Column(length = 128, nullable = false)
private String password;
#ManyToMany(targetEntity = UserPermission.class)
#JoinTable(name = "USER_ACCT_PERM",
joinColumns = #JoinColumn(name = "USER_ACCT_ID"),
inverseJoinColumns = #JoinColumn(name = "USER_PERM_ID", referencedColumnName = "USER_PERM_ID"))
private Set<UserPermission> permissions;
// getters and setters...
}
AND
#Entity
#Table(name = "USER_PERM")
public class UserPermission implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy=GenerationType.SEQUENCE, generator="USER_PERM_ID_SEQ")
#SequenceGenerator(name="USER_PERM_ID_SEQ", sequenceName="USER_PERM_ID_SEQ")
#Column(name = "USER_PERM_ID")
protected Long id;
#Column(name = "PERM", unique = true, length = 255, nullable = false)
private String authority;
// getters and setters...
}
Now when I create an object of UserAccount and try to save it using the session factory it gives me the following error:
org.hibernate.TransientObjectException: object references an unsaved transient instance - save the transient instance before flushing: com.rpm.domain.UserPermission
Shouldn't the UserPermission class be saved by itself??
This is the code that is used to persist:
#Transactional
public void addUser(){
Set<UserPermission> permissions = new HashSet<UserPermission>();
permissions.add(new UserPermission("ROLE_ADMIN"););
sessionFactory.getCurrentSession().save(new UserAccount("admin", "d033e22ae348aeb5660fc2140aec35850c4da997", permissions));
}
Not unless you specify the cascade options.

Resources