Spring Boot JPA One To Many and Many to One With Multiple Tables - spring-boot

I have 5 Tables, A User can have many module/role/plant. user_master is the main table with user details and user_roles is the sub table with module/role/plant details. My doubt is how to write the relationship in Model Class.
user_master
------------
user_id int(10) unsigned
first_name varchar(50)
last_name varchar(50)
mail_id varchar(80)
user_status tinyint(4)
is_deleted tinyint(4)
created_by int(10)
created_date date
modified_by int(10)
modified_date date
user_roles
-----------
user_role_id int(10) unsigned
user_id int(10) unsigned
module_master_id int(10) unsigned
role_master_id int(10) unsigned
plant_master_id int(10) unsigned
module_master:
module_master_id int(10) unsigned
module_code int(10)
module_name varchar(50)
active_flag tinyint(4)
role_master:
role_master_id int(10) unsigned
module_master_id int(10) unsigned
role_code int(10)
role_name varchar(50)
active_flag tinyint(4)
plant_master:
plant_master_id int(10) unsigned
plant_code int(10)
plant_name varchar(50)
active_flag tinyint(4)
The remaining module/role/plant have their own masters, master_id are primary key to master tables.
I just want to write user_master and user_roles model class. One-to-many & many-to-one mapping is required.

For one to many, you can have like below in your entity class:
#OneToMany in user entity
#OneToMany(mappedBy = "user")
private List<Role> roles;
#ManyToOne at roles side
#ManyToOne
#JoinColumn(name = "user")
private User user;
#ManyToMany
#ManyToMany(cascade = {
CascadeType.PERSIST,
CascadeType.MERGE
})
#JoinTable(name = "user_role",
joinColumns = #JoinColumn(name = "user_id"),
inverseJoinColumns = #JoinColumn(name = "role_id")
)
private List<Role> roles= new ArrayList<>();
and role side:
#ManyToMany(mappedBy = "role")
private List<User> users = new ArrayList<>();
here is an example to have many to many with emdeded:
#Entity
public class UserRoleMaster implements Serializable {
#EmbeddedId
private UserRoleMasterId id;
#ManyToOne
#JoinColumn(name = "user_master_id", referencedColumnName = "user_master_id", insertable = false, updatable = false)
private UserMaster userMaster;
#ManyToOne
#JoinColumn(name = "user_roles_id", referencedColumnName = "user_roles_id", insertable = false, updatable = false)
private UserRoles userRoles;
//getter //setter //constructures
#Embeddable
public static class UserRoleMasterId implements Serializable {
#Column(name = "user_master_id")
protected Long userMasterId;
#Column(name = "user_roles_id")
protected Long userRolesId;
//constrcture //getter //setters
}
}

Related

Why OneToMany JPA association is failing while insert statement executes

Hi below is my schema definition
CREATE TABLE LOANS (
LOAN_ID NUMBER(9,0) PRIMARY KEY,
CORR_ID VARCHAR(5) NULL
);
CREATE TABLE DV_LOAN_PARTICIPANTS (
LOAN_ID NUMBER(9,0) ,
DVP_PARTICIPANT_NAME VARCHAR(50) NOT NULL,
DVP_PARTICIPANT_TYPE VARCHAR(50) NOT NULL,
PRIMARY KEY ("LOAN_ID", "DVP_PARTICIPANT_NAME")
);
LOANS Entity
#Table(name = "LOANS")
#Entity
public class Loans {
#Id
#Column(name = "LOAN_ID")
private Long loanId;
#OneToMany(cascade = CascadeType.ALL,fetch = FetchType.EAGER)
#JoinColumn(name = "LOAN_ID")
#MapKey(name = "dvpParticipantName")
private Map<String, DVLoanParticipants> dvLoanParticipantsMap;
// getter and setters
}
DV_LOAN_PARTICIPANTS Entity
#Table(name = "DV_LOAN_PARTICIPANTS")
#Entity
public class DVLoanParticipants implements Serializable {
#Id
#Column(name = "LOAN_ID")
private Long loanId;
#Id
#Column(name = "DVP_PARTICIPANT_NAME")
private String dvpParticipantName;
#Column(name = "DVP_PARTICIPANT_TYPE")
private String dvpParticipantType;
// getters and setters
}
Service Class is
DVLoanParticipants dvLoanParticipants = new DVLoanParticipants();
dvLoanParticipants.setLoanId(Long.valueOf("196801758"));
dvLoanParticipants.setDvpParticipantName("VKP");
dvLoanParticipants.setDvpParticipantType("Developer");
Loans loanInsert = new Loans();
loanInsert.setLoanId(Long.valueOf("196801758"));
Map<String,DVLoanParticipants> partyMap = new HashMap<>();
partyMap.put("VKP",dvLoanParticipants);
loanInsert.setDvLoanParticipantsMap(partyMap);
repository.save(loanInsert);
But when i am executing the save i am getting error as
NULL not allowed for column "LOAN_ID"; SQL statement:
insert into dv_loan_participants (dvp_participant_type, loan_id, dvp_participant_name) values (?, ?,
?)
Git Hub Code
https://github.com/vinoykp/spring-jpa/tree/master/spring-boot-hibernate-crud-demo
I had the similar question
Why Value is not getting assigned in JPA for insert statement
What is the issue in association?

Spring Boot 2, Spring 5 JPA: Dealing with multiple OneToMany JoinTables

Not sure the best approach to implementing the CrudRepository for an Entity that has multiple
#OneToMany associations with a #JoinTable
#Entity
#Table(name = "contact", uniqueConstraints = {#UniqueConstraint(columnNames ={"first_name","last_name"})})
#SuppressWarnings("PersistenceUnitPresent")
public class Contact extends Auditable implements Serializable {
#Id
#Column(name = "contact_id")
#GeneratedValue(strategy = GenerationType.SEQUENCE, generator = "contact_generator")
#SequenceGenerator(name = "contact_generator", sequenceName = "contact_seq", allocationSize = 50)
private Long contactId;
#Column(name = "first_name", nullable = false)
private String firstName;
#Column(name = "last_name", nullable = false)
private String lastName;
#Column(name = "middle_name", nullable = true)
private String middleName;
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
#JoinTable(
name = "contact_phone"
)
private List<Phone> phoneNumbers = new ArrayList<>();
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
#JoinTable(name = "contact_email")
private List<EmailAddress> emailAddresses = new ArrayList<>();
public interface ContactRepo extends CrudRepository<Contact, Long> {
List<Contact> findByLastNameContainingIgnoreCase(String lastName);
}
I have the FetchType.LAZY so I don't get the MultipleBagFetchException from 2 cartesian products.
So I know I need to split the 2 joins up which is where I am stuck as to the best solution.
Put in a custom repo and customImpl class that has can access the EntityManager and code out the 2 joins?
I am not crazy and letting Java take care of the cartesian via a Set, nor the one having FetchType.EAGER and dealing with the other with another query??
Generates:
create table contact (
contact_id bigint not null,
create_tm timestamp not null,
created_by varchar(255) not null,
updated_tm timestamp not null,
updated_by varchar(255) not null,
first_name varchar(255) not null,
last_name varchar(255) not null,
middle_name varchar(255),
primary key (contact_id)
)
create table email_address (
email_id bigint not null,
email_addr varchar(255) not null,
email_type varchar(255),
primary_addr boolean default false,
primary key (email_id)
)
create table contact_email (
Contact_contact_id bigint not null,
emailAddresses_email_id bigint not null
)
create table phone (
phone_id bigint not null,
phone_nbr varchar(255) not null,
phone_type varchar(255),
primary_ph boolean default false,
primary key (phone_id)
)
create table contact_phone (
Contact_contact_id bigint not null,
phoneNumbers_phone_id bigint not null
)
The strange think is my JpaDataTests worked find. The find all and findByLastNameContainingIgnoreCase return the phone numbers and email addresses.
However, The Service does not.
#Autowired
private ContactRepo contactRepo;
#Override
public List<Contact> findAllContacts() throws GcaServiceException {
try {
Iterable<Contact> iter = contactRepo.findAll();
return IteratorUtils.toList(iter.iterator());
} catch(DataAccessException e) {
throw new GcaServiceException(e.getMessage());
}
}
#Override
public List<Contact> findByLastName(String lastName) throws GcaServiceException {
try {
return contactRepo.findByLastNameContainingIgnoreCase(lastName);
} catch (DataAccessException e) {
throw new GcaServiceException(e.getMessage());
}
}
[
{
"createTm": "2021-01-11T16:27:19.995011",
"createdBy": "UncleMoose",
"updatedBy": "UncleMoose",
"updateTm": "2021-01-11T16:27:19.995011",
"contactId": 1,
"firstName": "Bruce",
"lastName": "Randall",
"middleName": null,
"phoneNumbers": [],
"emailAddresses": []
},
{
"createTm": "2021-01-11T16:27:19.996009",
"createdBy": "UncleMoose",
"updatedBy": "UncleMoose",
"updateTm": "2021-01-11T16:27:19.996009",
"contactId": 51,
"firstName": "Boss",
"lastName": "Randall",
"middleName": null,
"phoneNumbers": [],
"emailAddresses": []
}
]
Part of the mystery of DataJpaTest vs manual integration testing differences was I decided to look at a map and make sure I wasn't hiking down the wrong Google trail. I turned the H2 console on and found the Join Tables empty even though the insert occurred? However, I notice I was getting different Join Table column names between live and automated testing.
Solution was to explicitly name the Join Table columns. It appears Spring has handled the MultipleBagFetchException issues with multiple OneToMany JoinTable attributes in an Entity.
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
#JoinTable(
name = "contact_phone"
,joinColumns = #JoinColumn(name = "contact_id")
,inverseJoinColumns = #JoinColumn(name = "phone_id")
)
private List<Phone> phoneNumbers = new ArrayList<>();
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
#JoinTable(
name = "contact_email"
,joinColumns = #JoinColumn(name = "contact_id")
,inverseJoinColumns = #JoinColumn(name = "email_id")
)
private List<EmailAddress> emailAddresses = new ArrayList<>();

Hibernate: Find entity from one to many table

I have two tables
CREATE TABLE `heroic_quality`
(
`id` INT NOT NULL AUTO_INCREMENT,
`name` VARCHAR(515) NOT NULL UNIQUE,
PRIMARY KEY (`id`)
);
CREATE TABLE `hero`
(
`id` INT NOT NULL AUTO_INCREMENT,
`name` VARCHAR(515) NOT NULL UNIQUE,
`quality_id` INT DEFAULT NULL,
FOREIGN KEY (`quality_id`) REFERENCES heroic_quality (id),
PRIMARY KEY (`id`)
);
And the objects in hibernate are
#Table(name = "heroic_quality")
#Entity(name = "heroic_quality")
public class HeroicQuality
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
protected long id;
#Column(name = "name", nullable = false, unique = true)
private String name;
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
#JoinColumn(name = "id")
#Fetch(FetchMode.SELECT)
private List<Hero> heroes;
//ommited getters and setters for shortness
}
#Table(name = "hero")
#Entity(name = "hero")
public class Hero
{
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id")
protected long id;
#Column(name = "name", nullable = false, unique = true)
private String name;
//ommited getters and setters for shortness
}
As you see my Hero class doesn't have reference to heroic quality, and I would like to keep it that way.
Also I have a repository
#Repository
public interface HeroicQualityDAO
extends PagingAndSortingRepository<HeroicQuality, Long>
{
Optional<HeroicQuality> findByName(String name);
List<HeroicQuality> findByOrderByIdDesc();
}
What I would like to do is have a method such as
Optional<HeroicQuality> findByHeroName(String heroName)
Such that if given a name of hero from Hero table I will be able to get heroic quality object.
How can I make such a method?
Is there any way I can get heroic quality object without having a reference to it in the hero object?
How can I go about doing that?
Add the following method to HeroicQualityDAO.
Optional<HeroicQuality> findByHeroesName(String heroName);
If you are not happy with the method name, you can do
#Query("Select h from HeroicQuality hq join hq.heros h where h.name = :name")
Optional<HeroicQuality> findByHeroName(String name);

Handling a oneToMany relationship in Spring boot JPA

In my database I have a user who can have multiple email addresses. An email address can have only one user. I have following two tables in my database to handle this.
CREATE TABLE IF NOT EXISTS w4a_user (
id INTEGER NOT NULL AUTO_INCREMENT,
login_id VARCHAR(100) NOT NULL UNIQUE,
first_name VARCHAR(100),
last_name VARCHAR(100),
division INTEGER NOT NULL,
created_date TIMESTAMP NOT NULL,
last_active DATE,
PRIMARY KEY (id),
FOREIGN KEY (login_id) REFERENCES w4a_authentication_data (login_id) ON DELETE RESTRICT,
FOREIGN KEY (division) REFERENCES w4a_division (id) ON DELETE RESTRICT
);
CREATE TABLE IF NOT EXISTS w4a_email_address(
email_address VARCHAR(100) NOT NULL,
user_id INTEGER NOT NULL,
is_confirmed BOOLEAN NOT NULL DEFAULT FALSE,
PRIMARY KEY (email_address),
FOREIGN KEY (user_id) REFERENCES w4a_user (id) ON DELETE CASCADE
);
In my Spring boot application, I have following entity classes to handle this.
User.java
#Entity
#Table(name = "w4a_user")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private int id;
#Column(name = "first_name")
#Size(max = 100, message = GlobalConstants.ErrorMessageConstants.ERROR_FIRST_NAME_LENGTH_EXCEEDED)
private String firstName;
#Column(name = "last_name")
#Size(max = 100, message = GlobalConstants.ErrorMessageConstants.ERROR_LAST_NAME_LENGTH_EXCEEDED)
private String lastName;
#Column(name = "created_date")
private Date createdDate;
#Column(name = "last_active")
private Date lastActive;
#ManyToOne
#JoinColumn(name = "division", referencedColumnName = "id")
private Division division;
#OneToMany(mappedBy = "userId", cascade = CascadeType.ALL, orphanRemoval = true)
#Size(min = 1)
private List<ContactNumber> contactNumberList;
#OneToMany(mappedBy = "userId", cascade = CascadeType.ALL, orphanRemoval = true)
#Size(min = 1)
private List<EmailAddress> emailAddresses;
.
.
}
EmailAddress.java
#Entity
#Table(name = "w4a_email_address")
public class EmailAddress {
#Id
#Column(name = "email_address")
#Email(message = GlobalConstants.ErrorMessageConstants.ERROR_EMAIL_INCORRECT_FORMAT,
regexp = GlobalConstants.RegexList.EMAIL_REGEX)
#Size(max = 100, message = GlobalConstants.ErrorMessageConstants.ERROR_EMAIL_LENGTH_EXCEEDED)
private String emailAddress;
#ManyToOne
#JoinColumn(name = "user_id", referencedColumnName = "id")
private User userId;
#Column(name = "is_confirmed")
private Boolean isConfirmed;
.
.
}
I use following method to persist entitites to my database.
#PersistenceContext
private EntityManager em;
#Override
public T createEntity(T entity) {
this.em.unwrap(Session.class).save(entity);
return entity;
}
I set email address list in the user entity and perform above method to create a new user.
The issue I have is when adding a user with an email address already used by an existing user. In this case, the database entry for the email address gets updated with the id of the new user. Instead I want to give an error saying the email address is already in use. What is the best way of handling this?

How #RequestBody works

How to get more details:
I am doing simple rest post request from Postman chrome extension.
My controller is :
#Controller
#RequestMapping("/theme")
public class ThemeController {
#RequestMapping(value = "/create", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE)
public #ResponseBody
Status addTheme(#RequestBody Theme theme) {
try {
themeServices.addEntity(theme);
return new Status(1, "Theme added Successfully !");
} catch (Exception e) {
// e.printStackTrace();
return new Status(0, e.toString());
}
}
In Theme.java:
#Entity
#Table(name = "theme", uniqueConstraints = { #UniqueConstraint(columnNames = { "theme_id" }) })
#JsonIgnoreProperties({ "hibernateLazyInitializer", "handler" })
#NamedQuery(name = "Theme.findAll", query = "SELECT t FROM Theme t")
public class Theme implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "theme_id")
private long themeId;
private String description;
private String name;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "category_id", nullable=true)
private ThemeCategory themeCategory;
In ThemeCategory.java:
#Entity
#Table(name = "theme_category", uniqueConstraints = { #UniqueConstraint(columnNames = { "category_id" }) })
#JsonIgnoreProperties({ "hibernateLazyInitializer", "handler" })
#NamedQuery(name = "ThemeCategory.findAll", query = "SELECT t FROM ThemeCategory t")
public class ThemeCategory implements Serializable {
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "category_id")
private long categoryId;
private String description;
private String name;
// bi-directional many-to-one association to Theme
// #OneToMany(mappedBy="themeCategory")
#OneToMany(mappedBy = "themeCategory", fetch = FetchType.EAGER)
#Column(nullable = true)
#JsonManagedReference
private Set<Theme> themes;
// bi-directional many-to-one association to ThemeCategory
#ManyToOne
#JoinColumn(name = "parent_category_id", nullable=true)
#JsonBackReference
private ThemeCategory parentThemeCategory;
// bi-directional many-to-one association to ThemeCategory
// #OneToMany(mappedBy="themeCategory")
#OneToMany(mappedBy = "parentThemeCategory", fetch = FetchType.EAGER)
#Column(nullable = true)
#JsonManagedReference
private Set<ThemeCategory> themeCategories;
Theme Category Table:
CREATE TABLE `theme_category` (
`category_id` smallint(5) unsigned NOT NULL AUTO_INCREMENT,
`parent_category_id` smallint(5) unsigned DEFAULT NULL,
`name` varchar(45) NOT NULL,
`description` varchar(1000) DEFAULT NULL ,
`last_update` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`category_id`),
KEY `idx_parent_category_id` (`parent_category_id`),
CONSTRAINT `fk_parent_category_id` FOREIGN KEY (`parent_category_id`) REFERENCES `theme_category` (`category_id`) ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=301 DEFAULT CHARSET=utf8;
Theme Table:
CREATE TABLE `theme` (
`theme_id` smallint(5) unsigned NOT NULL AUTO_INCREMENT,
`name` varchar(45) NOT NULL,
`category_id` smallint(5) unsigned NOT NULL,
`file_path` varchar(200) DEFAULT NULL,
`description` varchar(1000) DEFAULT NULL,
`last_update` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`theme_id`),
KEY `idx_category_id` (`category_id`),
CONSTRAINT `fk_category_id` FOREIGN KEY (`category_id`) REFERENCES `theme_category` (`category_id`) ON UPDATE CASCADE
) ENGINE=InnoDB AUTO_INCREMENT=401 DEFAULT CHARSET=utf8;
I am using Postman extension to do a rest post call:
http://localhost:8080/CustomerRegistration/theme/create
Header params:
Content-Type: application/json
Json Body:
{"description":"theme8","name":"theme8","themeCategory":{"categoryId":302, "themes":[],"parentThemeCategory":{}, "themeCategories":[]}}
And tried around 2 hours with multiple ways of body. But it consistently saying:
The server refused this request because the request entity is in a format not supported
by the requested resource for the requested method.
To analyse, I am not getting any thing else. In Eclipse console also not showing anything regarding the this issue.
What is wrong? Is there any tools to create valid requests.

Resources