I have two tables which are related to each other, table "user" and "address":
#Entity
#Table(name = "user")
#Data
public class User{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
#NotNull
#Column(unique = true)
private String user_name;
#Column
private String description;
#OneToMany(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true)
protected Set<Address> addresses= new HashSet<>();
}
While in the other table:
#Entity
#Table(name = "address")
#Data
public class Address{
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Integer id;
#ManyToOne
#JoinColumn(name = "user_id")
protected User user;
private String description;
}
I did a post request to create new user with some addresses:
#PostMapping ("/user/create")
public ResponseEntity post(#RequestBody User user) {
userService.saveNewUser(user);
// return
}
In a post request I sent this json:
{
"user_name": "example",
"description": "this is a user description",
"addresses": [
{
"description": "this is a address 1"
},
{
"description": "this is a address 2"
}
]
}
When I insert data I get the "address" table the "user_id" is null, the data are inserted but the relations are not there?
What I'm doing wrong here? Please help!
Update: Let's say I have this method saveNewUser on the service, how to call update Address?
public Oject saveNewUser(User user ) {
//
return jpaRepository.save(user);
}
You have to support all relations manually. Hibernate doesn't do it for you.
So you need to set a user for each address.
public Oject saveNewUser(User user ) {
user.getAddresses().forEach(address -> address.setUser(user));
return jpaRepository.save(user);
}
Related
I have a rest api exposed via #RepositoryRestResource from spring-data-rest-api. When I try to give the json payload from Postman to create a User linked to an Organization, it complains that Column 'organizationId' cannot be null, when I clearly provided it in the json.
{
"firstName": "Test",
"lastName": "User",
"email": "user#example.com",
"phoneNumber": "+12019582790",
"organizationId": "22bf93a5-e620-4aaf-8333-ad67391fb235",
"password": "example123",
"role": "admin",
}
Each user belongs to an organization, so it's a many to one relationship. I want Java to map the Organization that the User belongs to into the User as an Organization object.
User.java:
#Entity
#Table(name="user")
public class User {
#ManyToOne(targetEntity = Organization.class, fetch = FetchType.EAGER)
#JoinColumn(name = "organizationId", nullable = false)
private Organization organization;
}
Organization.java:
#Entity
#Table(name = "organization")
public class Organization {
#Id
#GeneratedValue(generator = "id-generator")
#Type(type = "uuid-char")
#Column(name="organizationId")
private UUID organizationId;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "organization")
private Set<User> users;
}
Any help is greatly appreciated.
This is the appoach I ended up going with. Still would like to know why the #ManyToOne annotation isn't saving the organizationId as a foreign key in the User table by itself.
User.java:
#Type(type = "uuid-char")
#Column
private UUID organizationId;
#ManyToOne
#JoinColumn(name = "organizationId", insertable = false, updatable = false)
private Organization organization;
I have 3 entities: User, Office and Role. The Role and Office already exist in database.
In SQL the User entity references Office with its id and references Role with its name. Example of a record in SQL:
Role table:
1 | USER
Office table:
1 | Office 1
User table:
John | Doe | john.doe#mail.com | Password123 | 1 | USER
For user table:
1 -> reference to Office
USER -> reference to Role
Role entity:
#Data
#Entity
#NoArgsConstructor
#AllArgsConstructor
#Table(name = "t_role")
public class Role {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#Column(name = "name")
private String name;
}
Office entity:
#Data
#Entity
#Table(name = "t_office")
public class Office {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#Column(name = "name")
private String name;
}
User entity:
#Data
#Entity
#Builder
#Table(name = "t_user")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
#Column(name = "first_name")
private String firstName;
#Column(name = "last_name")
private String lastName;
#Column(name = "email")
private String email;
#Column(name = "password")
private String password;
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(name="role", referencedColumnName="name")
private Role role;
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(name = "office_id", referencedColumnName="id")
private Office office;
}
The question is: How can I achieve saving a User without specifying all the Office and Role object in request body, but just the id for Office and name for Role ? Maybe it is needed to use a DTO or a Deserializer ?
Thanks in advance.
I need a request body like this:
{
"firstName": "John",
"lastName": "Doe",
"email": "john.doe#mail.com",
"password": "Password123",
"officeId": 1,
"role": "USER"
}
Instead of a request body like this:
{
"firstName": "John",
"lastName": "Doe",
"email": "john.doe#mail.com",
"password": "Password123",
"office": {
"id": 1,
"name": "Office 1"
},
"role": {
"id": 1,
"name": "USER"
}
}
Yes, you need to use a DTO class for the request body
public class UserDTO {
private String firstName;
private String lastName;
private String email;
private String password;
private String role;
private Integer officeId;
}
and fetch the data from the database for office and role from database and set the data in the user and then save. Ex:
User user = convertUserDTOtoUser(userDTO);
Optional<Office> office = officeRepository.findById(userDTO.getOfficeId());
Role role = roleRepository.findByName(userDTO.getRole());
user.setOffice(office);
user.setRole(role);
userRepository.save(user);
I have these entities:
#Entity
#Data
#NoArgsConstructor
public class Hero {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#NotNull
private String name;
#OneToMany(mappedBy = "character", cascade = CascadeType.ALL, orphanRemoval = true)
#NotEmpty
private List<HeroSkill> heroSkills;
}
#Entity
#Data
#NoArgsConstructor
public class Skill {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#Column(nullable = false)
private String name;
}
#Entity
#Data
#NoArgsConstructor
public class HeroSkill {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#ManyToOne
#JoinColumn(name = "hero_id", referencedColumnName = "id")
#JsonIgnore
private Hero hero;
#ManyToOne
#JoinColumn(name = "skill_id", nullable = false)
private Skill skill;
private int ranks;
}
My HeroService is like this:
#Transactional
public void create(Hero hero) {
heroRepository.save(hero);
}
I am using postman to create a new Hero and this is a sample POST request:
{
"name": "Test",
"heroSkills": [
{
"skill": {
"id": 1
},
"ranks": 4
},
{
"skill": {
"id": 2
},
"ranks": 4
}
]
}
Although a hero is created and two entities on HeroSkill table are also created, the HeroSkill hero_id column is null, so my new heroes are not associated with their skills as they should be.
It works if I modify my service like this:
#Transactional
public void save(Hero hero) {
Hero result = heroRepository.save(hero);
hero.getHeroSkills().stream()
.forEach(it -> {
it.setHero(result);
heroSkillRepository.save(it);
});
}
But I think that I shouldn't have to pass the Hero manually to each HeroSkill. Isn't that the point of adding CascateType.ALL (which includes CascadeType.PERSIST)?
What is the correct approach to that?
Thanks in advance.
There is no reference of Hero(Parent) in HeroSkill(child) that's why JPA can't resolve hero_id and set as null. Use this heroSkill.setHero(hero) to set parent in child.
To save child with parent in bidirectional relation you have to make sync both side.
hero.getHeroSkills().stream()
.forEach(it -> {
it.setHero(hero);
});
Hero result = heroRepository.save(hero);
Hi i am learning Spring JPA using OneToMany and ManyToOne bidirectional relationship, in some example i see OneToMany and ManyToOne relationship when i written in two side, the JPA add a new column as the foreign key column and insert the key value from the Parent table. But when i try mine, the column is always blank. Here is how my code looked like :
Here is my Account.java model :
#Entity
#Table(name = "msAccount")
public class Account {
#Id
#NotBlank(message = "Not Blank")
#Size(min = 0, max = 20)
public String accountId;
#NotBlank(message = "Not Blank")
public String accountName;
#NotBlank(message = "Not Blank")
#Email(message = "Should be the right email")
public String accountEmail;
#NotBlank(message = "Not Blank")
#Size(min = 5, message = "Minimal 5 char")
public String accountAddress;
#NotBlank(message = "Not Blank")
public String town;
#NotBlank(message = "Not Blank")
public String npwp;
#NotBlank(message = "Not Blank")
public String phoneNumber;
public String fax;
public String remarks;
#NotNull
public Date entryTime;
#NotNull
public Boolean active;
#OneToMany(mappedBy="account", cascade = CascadeType.ALL, orphanRemoval = true)
public List<Dealer> dealer;
//getter setter skipped
}
and here is my Dealer.java model :
#Entity
#Table(name = "msDealer")
public class Dealer {
#Id
#NotBlank(message = "Tidak Boleh Kosong")
#Size(min = 0, max = 20)
public String dealerId;
#NotBlank(message = "Tidak Boleh Kosong")
public String dealerName;
#NotBlank(message = "Tidak Boleh Kosong")
#Email(message = "Masukkan Email yang bener")
public String dealerEmail;
#NotBlank(message = "Tidak Boleh Kosong")
#Size(min = 5, message = "Minimal 5 karakter")
public String dealerAddress;
#ManyToOne(fetch = FetchType.LAZY)
public Account account;
//getter setter skipped
}
and here is my Repository :
#Repository
public interface AccountRepository extends JpaRepository<Account, Long> {
}
and here is my Service :
#Service
public class AccountService {
#Autowired
private AccountRepository accountRepository;
public Account save(Account account) {
return accountRepository.save(account);
}
}
and here is my controller :
#RestController
#RequestMapping("/api/account")
public class AccountController {
private final Logger logger = LoggerFactory.getLogger(this.getClass());
private final int ROW_PER_PAGE = 10;
#Autowired
private AccountService accountService;
#PostMapping("/new")
public ResponseEntity<Account> addAccount(#Valid #RequestBody Account account) {
try {
Account newAccount = accountService.save(account);
return ResponseEntity.created(new URI("/api/account/" + newAccount.getAccountId()))
.body(account);
} catch(Exception ex) {
logger.error(ex.getMessage());
return ResponseEntity.status(HttpStatus.BAD_REQUEST).build();
}
}
}
then i post the JSON into my save endpoint :
{
"accountId": "USA001",
"accountName": "string",
"accountEmail": "string",
"accountAddress": "string",
"town": "string",
"npwp": "string",
"phoneNumber": "string",
"fax": "string",
"remarks": "string",
"entryTime": "2020-04-07T15:01:29.404Z",
"active": true,
"dealer": [
{
"dealerId": "MMO001",
"dealerName": "string",
"dealerEmail": "string",
"dealerAddress": "string"
}
]
}
and when i save it the hibernate that showed up in my terminal looked inserting query into that 2 table, but when i check my database table (which is postgresql) i found there is a field "account_account_id" that is null, what did i miss here?
i want the Hibernate run sql like this :
insert into account (account_id, account_name, ...etc)
values ('USA001', 1)
insert into dealer (account_account_id, dealer_name, dealer_id, ...etc)
values ('USA001', 'New dealer 1', 'MMO001')
Here is my UPDATED Model after some try :
my Account.java
I delete cascade = CascadeType.ALL, orphanRemoval = true
#Entity
#Table(name = "msAccount")
public class Account {
#Id
#NotBlank(message = "Tidak Boleh Kosong")
#Size(min = 0, max = 20)
public String accountId;
#NotBlank(message = "Tidak Boleh Kosong")
public String accountName;
#NotBlank(message = "Tidak Boleh Kosong")
#Email(message = "Masukkan Email yang bener")
public String accountEmail;
#NotBlank(message = "Tidak Boleh Kosong")
#Size(min = 5, message = "Minimal 5 karakter")
public String accountAddress;
#NotBlank(message = "Tidak Boleh Kosong")
public String town;
#NotBlank(message = "Tidak Boleh Kosong")
public String npwp;
#NotBlank(message = "Tidak Boleh Kosong")
public String phoneNumber;
public String fax;
public String remarks;
#NotNull
public Date entryTime;
#NotNull
public Boolean active;
#OneToMany(mappedBy="account")
// #JoinColumn(name = "accountId")
public List<Dealer> dealer;
//getter setter skipped
}
and here is my Dealer.java. Added #JoinColumn :
#Entity
#Table(name = "msDealer")
public class Dealer {
#Id
#NotBlank(message = "Tidak Boleh Kosong")
#Size(min = 0, max = 20)
public String dealerId;
#NotBlank(message = "Tidak Boleh Kosong")
public String dealerName;
#NotBlank(message = "Tidak Boleh Kosong")
#Email(message = "Masukkan Email yang bener")
public String dealerEmail;
#NotBlank(message = "Tidak Boleh Kosong")
#Size(min = 5, message = "Minimal 5 karakter")
public String dealerAddress;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "account_id")
public Account account;
//getter setter skipped
}
now the error is getting weird, i got this error when i save the JSON data
> "Unable to find com.api.b2b.Model.Dealer with id MMO001; nested
> exception is javax.persistence.EntityNotFoundException: Unable to find
> com.api.b2b.Model.Dealer with id MMO001"
in some tutorial it worked, but mine is not, what did i do wrong?
here is my github repo : https://github.com/Fly-Away/LearningSpring
You're missing the #JoinColumn on the child side:
#Entity
#Table(name = "ms_dealer")
public class Dealer {
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "account_account_id")
public Account account;
// other fields
}
You have used mappedBy on the parent side, but there is no mapping on the child side. You need to indicate, that the Dealer is the relationship owner - it has the foreign key.
Edit: if you're persisting (not merging) the Account entity, together with its children, you should not pass ids of child entities. (Actually passing any ids upon persist is a code smell and most probably a performance killer.) The json used should look like:
{
"accountName": "string",
"accountEmail": "string",
"accountAddress": "string",
"town": "string",
"npwp": "string",
"phoneNumber": "string",
"fax": "string",
"remarks": "string",
"entryTime": "2020-04-07T15:01:29.404Z",
"active": true,
"dealer": [
{
"dealerName": "string",
"dealerEmail": "string",
"dealerAddress": "string"
}
]
}
Before saving both-side synchronization might also be needed:
account.getDealer().forEach(d -> d.setAccount(account));
Edit:
From Author edits must cascade to child:
#OneToMany(mappedBy = "account", cascade = CascadeType.ALL, orphanRemoval = true)
public List<Dealer> dealer;
You might also add #JsonIgnore over Action or List<Dealer> to avoid stackoverflow on serialization to json.
As you said you are learning, I would like to give you a detailed answer so it will be easy for you to understand. What you are missing here is #JoinColumn.
#JoinColumn could be used on both sides of the relationship. The point here is in physical information duplication (column name) along with not optimized SQL query that will produce some additional UPDATE statements.
According to documentation:
Since many to one are (almost) always the owner side of a bidirectional relationship in the JPA spec, the one to many association is annotated by #OneToMany(mappedBy=...)
Understand by basic code example
#Entity
public class Troop {
#OneToMany(mappedBy="troop")
public Set<Soldier> getSoldiers() {
...
}
#Entity
public class Soldier {
#ManyToOne
#JoinColumn(name="troop_fk")
public Troop getTroop() {
...
}
Troop has a bidirectional one to many relationship with Soldier through the troop property. You don't have to (must not) define any physical mapping in the mappedBy side.
To map a bidirectional one to many, with the one-to-many side as the owning side, you have to remove the mappedBy element and set the many to one #JoinColumn as insertable and updatable to false. This solution is not optimized and will produce some additional UPDATE statements.
#Entity
public class Troop {
#OneToMany
#JoinColumn(name="troop_fk") //we need to duplicate the physical information
public Set<Soldier> getSoldiers() {
...
}
#Entity
public class Soldier {
#ManyToOne
#JoinColumn(name="troop_fk", insertable=false, updatable=false)
public Troop getTroop() {
...
}
Comment below if you have any further questions on the explanation given. :)
To save child with parent in the bidirectional relationship set parent in child entity also to sync both side.
Here set account reference in dealer objects
public Account save(Account account) {
for (Dealer dealer: account.getDealer()) {
dealer.setAccount(account);
}
return accountRepository.save(account);
}
Update:
But if you want to use Unidirectional relation then remove Account relation in Dealer Entity. Remove this portion
#ManyToOne(fetch = FetchType.LAZY)
public Account account;
Then update the relation in Account table.
#OneToMany(cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name = "account_id")
public List<Dealer> dealer;
Here we remove mappedBy because currently we removed mapping in Dealerside and add #JoinColumn to define which column we are using for account refference.
If you have a bidirectional relationship between two entities (here Account and Dealer) you have to decide which side is the owner of said relationship.
By default the One-side is the owner which leads to a Join-Table which is updated when modifying the List.
Since you defined the mappedBy property (#OneToMany(mappedBy = "account")) the Many-side is the owner of the relationship. This means the account column in the msDealer Table will hold the foreign key of the Account and then Join-Table will not be used anymore. The Join-Table is probably a left over from initializing the database before you added the mappedBy definition to the annotation.
Options you have:
Let the Dealer stay the owner and don't use a Join-Table. If you want to observe side effects in the database look at column msDealer.account.
Use the #JoinTable annotation to enforce the usage of such a table
I have the below JSON as input:
{
"type": "Student",
"numOfPeople": "1",
"tenantMembers": [
{
"firstName": "Chris",
"lastName": "C"
}
],
"tenantDetails": {
"firstName": "John",
"lastName": "J",
"email" "xyz#gmail.com"
}
}
I want to use this to do a save:
tenantRepo.save(tenant);
This should save the parent "Tenant" and the children "TenantMembers" and "TenantDetails".
But when I do it does with NULL 'tenant_id's in the children. (If I have foreign keys in the DB gives 'tenant_id' can't be null constraint exception)
My question is: Is this possible in Hibernate?
My models:
Parent class:
#Entity
#Table(name = "tenant")
public class Tenant {
#GeneratedValue
#Id
private Long id;
private String type;
#Column(name = "num_of_people")
private String numOfPeople;
#OneToMany(mappedBy = "tenant", cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private List<TenantMember> tenantMembers;
#OneToOne(mappedBy = "tenant", cascade = CascadeType.ALL)
private TenantDetails tenantDetails;
TenantMember child class:
#Entity
#Table(name = "tenant_member")
public class TenantMember {
#GeneratedValue
#Id
private Long id;
#ManyToOne
#JoinColumn(name = "tenant_id")
private Tenant tenant;
#Column(name = "first_name")
private String firstName;
#Column(name = "last_name")
private String lastName;
TenanatDetails child class:
#Entity
#Table(name="tenant_details")
public class TenantDetails {
#GeneratedValue
#Id
private Long id;
#OneToOne
#JoinColumn(name = "tenant_id")
private Tenant tenant;
#Column(name = "first_name")
private String firstName;
#Column(name = "last_name")
private String lastName;
private String email;
EDIT:
Following up Dragan Bozanovic's suggestion, tried using #JsonIdentityInfo
for the three tables:
#Entity
#Table(name = "tenant")
#JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property="id")
public class Tenant {
#Entity
#Table(name="tenant_details")
#JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property="id")
public class TenantDetails {
#Entity
#Table(name = "tenant_member")
#JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property="id")
public class TenantMember {
and did the following to save:
#RequestMapping(value = "/set", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE)
#ResponseBody
public Tenant test(#RequestBody Tenant tenant) {
Tenant t = new Tenant();
t.setType(tenant.getType());
t.setNumOfPeople(tenant.getNumOfPeople());
tenantRepo.save(t);
tenant.setId(t.getId());
tenant.getTenantDetails().setTenant(tenant);
for(TenantMember member: tenant.getTenantMembers()) {
member.setTenant(tenant);
}
return tenantRepo.save(tenant);
}
Would this be the best approach that is possible?
Hibernate does save the children (hence the constraint violation) because of the cascading options you specified, but it does not save the relationship information (join column value) in your case.
TenantMember and TenantDetails are the owners of the association with Tenant (mappedBy attributes in the association annotations in Tenant).
That means that you have to properly update the tenant field in the TenantMember and TenantDetails instances, because Hibernate ignores inverse side of the association when maintaining the relationship.