I'm developing a Banking system Project Where User Should be able to create an account in any available branch. Branch Creation will be maintained by admin. I had two tables(Entities) like BranchDetails and AccountDetails.
The BranchId Column is the primary key in BranchDetails and Foreign key in AccountDetails. Now When user Creates an account, he will input the Preferred Branch name and the AccountHolder name. I had to Insert this Account in AccountDetails Table which matching the branchId which the user had entered.
How do i achive this. So far i have tried,
BranchDetails.java
#Entity
#Table( name = "branchdetails")
public class BranchDetails {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int branchId;
#Column( name = "branchName")
private String branchName;
#OneToOne(mappedBy = "branchdetails")
private AccountDetails accountDetails;
/getters and setters
}
AccountDetails.java
#Entity
#Table(name = "accountdetails")
public class AccountDetails {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int customerId;
#Column(name = "customerName")
private String customerName;
#OneToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
#JoinColumn(name = "branchId")
private BranchDetails branchdetails;
/getters and setters
}
Controller
#Controller
public class ApiController{
#RequestMapping(value = "/branchcreation", method = RequestMethod.POST
,consumes = {"application/x-www-form-urlencoded"})
#ResponseBody
public String brCreation( BranchDetails br, String branchname){
br.setBranchName(branchname);
branchrepository.save(br);
return "Sucesspage";
}
#RequestMapping(value = "/accountcreation", method = RequestMethod.POST)
#ResponseBody
public String AcCreation(AccountDetails ad,BranchDetails br, String branchname,String customername){
br.setBranchName(branchname);
ad.setCustomerName(customername);
accountrepository.save(ad);
return "Sucesspage";
}
}
Prepare AcCreation method to receive branch id (the branches already exist, so you could send their ids and names to your frontend form's select component) and customer name (provided by the user in the input field):
It would look like this (I changed the name to createAccount, because it sounds naturally, there is no need to use shortcuts in method's name, but in the end it's your choice):
#RequestMapping(value = "/account-creation", method = RequestMethod.POST)
#ResponseBody
public String createAccount(String customerName, Integer branchId){
accountRepository.createAccount(customerName, branchId);
return "Sucesspage";
}
Look at the removed code from the service method.
Details connected with creation on the database should be contained by database access layer, in this case the class AccountRepository and database layer methods should be called by service's methods - in your case we left out the service class).
So you would create Account instance inside its method and then set the branchId field.
Or you could do something like this (you would have to have two separate repositories, one for AccountDetails, second for BranchDetails entities):
#RequestMapping(value = "/account-creation", method = RequestMethod.POST)
#ResponseBody
public String createAccount(String customerName, Integer branchId){
BranchDetails branchDetails = branchRepository.find(branchId);
AccountDetails accountDetails = new AccountDetails();
accountDetails.setCustomerName(customerName);
accountDetails.setBranchDetails(branchDetails);
accountRepository.save(accountDetails);
return "Sucesspage";
}
Related
I have a Hibernate model with id, name and surname. I am using it to get data from the database and then at the GET end-point is this one:
#GetMapping(value = "/contacts", produces = MediaType.APPLICATION_JSON_VALUE)
public List<Contact> allContacts() {
return contactService.findAll();
}
As you can see it returns the Contact object. Actually it is a Hibernate entity.
The problem is that when I use this code
#PostMapping("/contacts")
public Contact createContact(Contact contact) {
return contactService.createContact(contact);
}
it asks not only name and surname but also the id. POST methods should not ask for id since they are not created yet. What should I do so that it doesn't ask for an id?
Edit: Here is the Contact.java class
import lombok.Data;
import javax.persistence.*;
#Entity
#Data
public class Contact {
public Contact() {
}
public Contact(Integer id, String name, String surname) {
this.id = id;
this.name = name;
this.surname = surname;
}
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(columnDefinition = "serial")
private Integer id;
private String name;
private String surname;
}
Define a ContactInput class that only has the attributes you want the user to input and then create some mapping code that creates a valid Contact based on the ContactInput.
You should create ContactDto class
#Data
public ContactDto class {
private String name;
private String surname;
}
In #PostMapping you are gonna get ContactDto from the user. You cannot saved ContactDto into your database. So you need to map ContactDto to Contact. What you can do is simply create ContractMapper class.
public static contactDtoToEntity(ContactDto dto){
Contact dbContact = new Contact();
dbContact.setName(dto.getName());
dbContact.setSurname(dto.getSurname());
return dbContact;
}
Before you saved the contact in your database in service layer, you need to map it and then save. Id is gonna be generated in the database.
I am new to hibernate and trying to implement join annotations of hibernate, but facing this weird issue. As I have attached pojos when I run my controller no output is seen but a new row got populated in user_song_rel table as a foreign key to the song table.
In addition, value of that entry is null(in MySQL screenshot) despite there are entries in the song table corresponding to song_ids.
Please find attached schema of both tables.
Thanks in advance :)
Schema Structure
User Song Mapping
#NamedQueries({
#NamedQuery(
name="findUserSongByUserID",
query="from UserSongRel usr where usr.user_id = :user_id"
)
})
#Entity
#Table(name="user_song_rel")
public class UserSongRel {
public UserSongRel() {
}
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private int usr_id;
private String user_id;
private String song_id;
private String song_src;
private int song_state;
#Temporal(TemporalType.TIMESTAMP)
private Date add_date;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(referencedColumnName = "song_id")
private SongInfo songInfo;
Song
#Entity (name = "song_info")
public class SongInfo {
#Id
private String song_id;
private String s_name;
private String artist;
private String album;
private int duration;
private Date rel_date;
private int popularity;
#OneToMany(mappedBy = "songInfo")
private List<UserSongRel> userSongRelList=new ArrayList<UserSongRel>();
Controller
#RequestMapping(value = "/getUserSongs/{uid}", method = RequestMethod.GET, produces = "application/json")
public ResponseEntity<?> getUserSongs(#PathVariable String uid,
HttpServletResponse response, HttpServletRequest request){
SongInfo usr= songService.getUserSongs(uid);
return ResponseEntity.ok().body(usr);
}
DAO
public SongInfo getUserSongs(String uid) {
Session s = sf.getCurrentSession();
UserSongRel songs=s.get(UserSongRel.class, 1);
List<UserSongRel> songList =new ArrayList<UserSongRel>();
songList.add(songs);
return songList.get(0).getSongInfo();
}
Select query after running controller.
I have a customer object and inside that customer object i have a login object which contains username and password. When i do a POST the request works fine however when i try to do a PUT request it fails. It fails because it says Duplicate entry on the username.
I would like to be able to update the customer details without having to change the username. How can i achieve this.
This is my code :
UserLogin Entity :
#Entity
#Table(name = "Customer",
uniqueConstraints =
{
#UniqueConstraint(columnNames = "email"),
#UniqueConstraint(columnNames = "id"),
#UniqueConstraint(columnNames = "phoneNumber")
}
)
public class Customer implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int customerNumber;
#OneToOne(cascade= CascadeType.ALL)
#JoinColumn(name = "loginCredentialsID")
private UserLogin userlogin;
private String phoneNumber;
private String email;
private String physicalAddress;
private String country;
... getters and setters
}
UserLogin Entity :
#Entity
#Table(name = "UserLogin",
uniqueConstraints =
{
#UniqueConstraint(columnNames = "userName")
})
public class UserLogin implements Serializable, UserDetails {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int loginCredentialsID;
private String username;
private String password;
... getters and setters
}
CustomerService Class :
public Response updatCustomeretails(int id,Customer customer) {
customer.setCustomerNumber(id);
if( customer== null ){
throw new ResourceNotFoundException("Empty", "Missing Data");
}else {
customerRepository.save(customer);
return new Response(" Customer Updated Successfully","Thank you ");
}
When using Sping data JPA to update you should use save which you correctly did when saving on this line customerRepository.save(customer);. However when persisting data to a database in a PUT request JPA uses the keys within your entity mappings to be able to update the proper record.
So in your case you get that error when JPA tries to save a new record rather than an update to an existing record. Your intent is to update but I suspect your keys are missing or they are not properly defined so JPA tries to go and save a new record instead of updating.
So when you do the update(PUT) make sure the object you are passing has the same keys as the one you want to update.
If I have a entity that contains an object of an another class, for example a Book entity that has within it a Publisher entity that is associated as follows:
#ManyToOne
#JoinColumn(name="PUB_CODE", referencedColumnName = "PUB_CODE")
private Publisher pub;
Is this a secure/correct (I saw the correct data in the DB in this example, but not 100% sure if it would work in all cases) approach to post an object that has foreign key association in the database? I don't know if this is safe to do in terms of transaction atomicity or in terms of threading, or if it is efficient. Relevant code below:
Book.java
package app.domain;
/*imports*/
#Entity
public class Book implements Serializable{
/**
*
*/
private static final long serialVersionUID = -6902184723423514234L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#Column(nullable = false, unique=true)
private String bookName;
#Column(nullable = false)
private int pageCount;
#ManyToOne
#JoinColumn(name="PUB_CODE", referencedColumnName="PUB_CODE")
private Publisher pub;
/*public getters and setters*/
}
Publisher.java
package app.domain;
/*imports*/
#Entity
public class Publisher implements Serializable {
private static final long serialVersionUID = 4750079787174869458L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#Column(name="PUB_CODE",nullable = false, unique = true)
private String publisherCode;
#Column(nullable = false)
private String publisherName;
/*public getters and setters*/
}
BookRepo.java
package app.service;
/*imports*/
public interface BookRepo extends JpaRepository<Book, Long>{
#Query("SELECT pb FROM Publisher pb WHERE pb.publisherCode = TRIM(UPPER(:pubCode))")
public Publisher findPublisherByPubCode(#Param("pubCode")String pubCode);
}
BookController.java
package app.controller;
/*imports*/
#RestController
#RequestMapping(value = "/books")
public class BookController {
private BookRepo bookRepo;
#Autowired
public BookController(BookRepo bookRepo) {
this.bookRepo = bookRepo;
}
//The ApiPathParam is for JSONDOC purposes
#RequestMapping(value = "/create", method = RequestMethod.POST)
public List<Book> create(#ApiPathParam(name = "book") #RequestBody Book book, #ApiPathParam(name = "pubCode") #RequestParam("pubCode") String pubCode) {
// Assume exception handling
Publisher pbToAttachToThisBook = bookRepo.findPublisherByPubCode(pubCode);
book.setPub(pbToAttachToThisBook);
bookRepo.save(book);
return bookRepo.findAll();
}
}
Post object body (input into a POST tool):
{
"bookName": "goosebumps",
"id": 0,
"pageCount": 332,
"pub": {
"id": 0,
"publisherCode": "",
"publisherName": "",
"serialVersionUID": 0
},
"serialVersionUID": 0
}
pubCode parameter input provided, also into the POST tool, in the same call as above: 'SC'
After the above code was executed, in the Book table, there was an entry for the book above, with its PUB_CODE foreign key column filled in with 'SC', and the returned List<Book> of the POST controller method that was called showed that the newly added book included the Publisher entity information (such as the full name "Scholastic") for publisher with PUB_CODE='SC' that was already existing in the database.
Thank you.
The technique you posted originally (passing the FK ID, retrieving it manually in your controller, and setting it on the entity explicitly) is valid and secure.
I don't know of a cleaner approach unless you move to HATEOAS principals, which allows for resource link handling: http://projects.spring.io/spring-hateoas/
Sounds like you need to separate/decouple your data layer's domain model from your Rest Resources/ API specs, as they could evolve at a different pace. Also your choice of JPA should not influence the API specs.
Should this feel like something you want to pursue there lots of resources out there including Best Practices for Better RESTful API
Let's say we have two tables, user and domain. User may have one Domain, one Domain may be for many users. So we will do unidirectional many to one:
#Entity
#Table(name = "DOMAIN")
public class Domain implements Serializable
{
#Id
#Column(name = "DOMAIN_ID")
private Integer domainId;
#Column(name = "NAME")
private String domainName;
}
#Entity
#Table(name = "\"USER\"")
public class User implements Serializable
{
#Id
#GeneratedValue(generator = "USER_SEQ")
#GenericGenerator(name = "USER_SEQ", strategy = "sequence", parameters = #Parameter(name = "sequence", value = "SEQ_USER_ID"))
#Column(name = "USER_ID", nullable = false)
private Long userId;
#Column(name = "FIRST_NAME")
private String firstName;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "DOMAIN_ID")
#ForeignKey(name = "DOMAIN_ID")
private Domain domain;
}
Domain table is something unchangable, dictionary. While User is editable table.
I'm creating a basic form where user selects domain in which new user will be created. Lets say that my controller received those data:
Integer domainId = 1;
String firstName = "aaa";
So I'm creating new user:
User newUser = new User();
newUser.setFirstName( firstName );
Now comes my question, should I do this way?
Domain domain = somthingThatWillFetchObjectFromDb.getDomain( domainId );
newUser.setDomain( domain );
//save user
THis will generate additional select, to fetch domain. Of course I can use Integer domainId instead of POJO, but that's not ORM. So once again the question, is this the way it should be done?
Yes, that's what you should do. If you don't want to actually load the domain information from the database, use the strangely named Session.load() method instead of the Session.get() method. If the domain is not already loaded into the session, Session.load() will simply return you an unitialized entity proxy for the domain (just like if you had loaded some entity with a lazy association to the domain), without hitting the database.
That said, if domain is unchangeable, why do you set #Cascade(CascadeType.ALL) on the domain field? This means that every time you're merging or updating a user, the domain will also be merged or updated. And even worse: if you delete a user, the domain will also be deleted (which of course will lead to an exception if other users are referencing the same domain).
Yes. To save a child Entity like User, you need to set the parent Entity i.e. Domain Entity in it.
One otherway is to define bidirectional mapping(OneToMany) in parent Entity ie. Domain, load the Domain, add one or more User object in Domain and save Domain Entity only e.g.
Domain Entity:
#OneToMany(fetch = FetchType.LAZY, mappedBy = "domain")
List<User> users = null;
public List<User> getUsers(){
return this.users;
}
public void setUsers(List<User> user){
this.users = users;
}
public void addUser(User user){
user.setDomain(this);//set the parent entity
if(this.users==null){
this.users = new ArrayList<User>();
}
this.user.add(user);
}
Then to save users:
User user1 = new User();
......
User user2 = new User();
......
Domain domain = loadDomain();//<- use actual method to load the domain
//< add all the users to be saved at once
domain.addUser(user1);
domain.addUser(user2);
//save parent entity i.e. domain
saveDomain(domain);//use actual method to save the entity