Column 'foreign_key_id' cannot be null - java

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;

Related

Spring Boot - combine nested resources for single API calls

Suppose you have two resources, User and Account. They are stored in separate tables but have a one-to-one relationship, and all API calls should work with them both together. For example a POST request to create a User with an Account would send this data:
{ "name" : "Joe Bloggs", "account" : { "title" : "My Account" }}
to /users rather than have multiple controllers with separate routes like users/1/account. This is because I need the User object to be just one, regardless of how it is stored internally.
Let's say I create these Entity classes
#Table(name = "user")
public class User {
#OneToOne(mappedBy = "user", cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
#NotNull
Account account;
#Column(name = "name")
String name;
}
#Table(name = "account")
public class Account {
#OneToOne(cascade = CascadeType.ALL, optional = false, fetch = FetchType.LAZY)
#JoinColumn(name = "user_id", nullable = false)
#NotNull
User user;
#Column(name = "title")
String title;
}
The problem is when I make that POST request above, it throws an error because user_id is missing, since that's required for the join, but I cannot send the user_id because the User has not yet been created.
Is there a way to create both entities in a single API call?
Since it is a bi-directional relation, and one-to-one is a mandatory in this case, you should persist a user entity and only then persist an account. And one more thing isn't clear here is db schema. What are the pk's of entities? I coukd offer to use user.id as a single identity for both of tables. If so, entities would be as:
User(id, name), Account(user_id, title) and its entities are:
#Table(name = "account")
#Entity
public class Account {
#Id
#Column(name = "user_id", insertable = false, updatable = false)
private Long id;
#OneToOne(mappedBy = "account", fetch = FetchType.LAZY, optional = false)
#MapsId
private User user;
#Column(name = "title")
private String title;
}
#Table(name = "user")
#Entity
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;;
#OneToOne(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
#JoinColumn(name = "id", referencedColumnName = "user_id")
private Account account;
#Column(name = "name")
private String name;
}
at the service layer you must save them consistently:
#Transactional
public void save(User userModel) {
Account account = user.getAccount();
user.setAccount(null);
userRepository.save(user);
account.setUser(user);
accountRepository.save(account);
}
it will be done within a single transaction. But you must save the user first, coz the user_id is a PK of the account table. #MapsId shows that user's id is used as an account's identity
Another case is when account's id is stored in the user table:
User(id, name, account_id), Account(id, title) and entities are:
#Table(name = "account")
#Entity
public class Account {
#Id
#Column(name = "id")
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#OneToOne(cascade = CascadeType.ALL, mappedBy = "account")
private User user;
#Column(name = "title")
private String title;
}
#Table(name = "user")
#Entity
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#Column(name = "account_id", insertable = false, updatable = false)
private Long accountId;
#OneToOne(cascade = CascadeType.ALL, orphanRemoval = true, fetch = FetchType.LAZY)
#JoinColumn(name = "account_id", referencedColumnName = "id", unique = true)
private Account account;
#Column(name = "name")
private String name;
}
in this case an Account entity will be implisitly persisted while User entity saving:
#Transactional
public void save(User userModel) {
userRepository.save(user);
}
will cause an insertion into the both of tables. Since cascade and orphane are declared, for deletion would be enough to set null for the account reference:
user.setAccount(null);
userRepository.save(user);

Saving data on jpa hibernate table gives null on join columns

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);
}

How to get rid of cyclic redundancy while having #ManyToMany relation JPA springboot

I am a newbie to the Spring boot (but worked in Laravel). I am facing a problem of cyclic redundancy in #ManyToMany relation. Let's go through the scenario -
What response I ma getting (fetching user's list which has many to many relationships with roles) -
Following is the ER-diagram of associated tables to manage many to many relationship between users and roles table.
User entity class has following code -
#Entity
#Where(clause = "deleted_at IS NULL")
#SQLDelete(sql = "UPDATE users SET deleted_at = CURRENT_TIMESTAMP WHERE id = ?", check = ResultCheckStyle.COUNT)
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
#Table(name = "users")
#JsonIgnoreProperties(
value = {"createdAt", "updatedAt", "deletedAt"}
)
public class User {
#Id
#Column(name = "id", updatable = false, nullable = false)
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#Column(name = "name", nullable = false)
#NotBlank(message = "Name field can not be empty")
private String name;
.....
.....
.....
#ManyToMany(targetEntity = Role.class, fetch = FetchType.EAGER)
#JoinTable(name = "user_roles",joinColumns = #JoinColumn(name = "user_id"),
inverseJoinColumns = #JoinColumn(name = "role_id"))
private Set<Role> roles;
}
And Role entity is as follows -
#Entity
#Table(name = "roles")
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
#SQLDelete(sql = "UPDATE roles SET deleted_at = CURRENT_TIMESTAMP WHERE id = ?", check = ResultCheckStyle.COUNT)
#Where(clause = "deleted_at IS NULL")
#JsonIgnoreProperties(
value = {"createdAt", "updatedAt", "deletedAt"}
)
public class Role {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "id", updatable = false, nullable = false)
private long id;
#Column(name = "title")
#NotBlank(message = "Title field must be not null")
private String title;
......
......
......
#OneToMany(targetEntity = User.class, fetch = FetchType.EAGER)
#JoinTable(name = "user_roles",joinColumns = #JoinColumn(name = "role_id"),
inverseJoinColumns = #JoinColumn(name = "user_id"))
private List<User> users;
}
How to solve this problem? What I am doing wrong here?
Since you are fetching the list directly. You will have to mention the annotation #JsonIgnore everywhere you have mapping specified. By everywhere I don't mean literally everywhere. Just use the annotation and see how it works.
Edit -> Just do it in roles table where you have mapped it to the user table. It will then skip the user mapping while fetching the data.
#JsonIgnore
private List<User> users;
You could annotate users within Role with #JsonBackReference.
Easiest would probably be to annotate the List<T>'s with a #JsonIgnoreProperties annotation to break the cyclic dependencies.
#JsonIgnoreProperties("users")
private List<Role> roles;
#JsonIgnoreProperties("roles")
private List<User> users;

Exposing relationship ID as part of the JSON payload for GET, PUT and POST REST calls

I'm relearning Java, Springboot in a personal project. I have an entity named User, which is managed via the autogenerated scaffolding from jHipster. I also have a UserProfile which is an entity that I created to hold extra data (since I didn't want to muck around with the User object. Now, when I expose REST endpoints for UserProfile, I want the GET calls to include the user_id as part of the JSON, and the PUT/POST calls to accept user_id for the UserProfile, do the association and only then persist. The ORM being used is Hibernate/JPA. What Jackson annotations should I use to make this happen?
My User object is:
public class User {
#ToString.Exclude
#OneToOne(mappedBy = "user", orphanRemoval = true, fetch = FetchType.LAZY)
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
#JsonIgnore
private UserProfile userProfile;
}
and my UserProfile class is:
public class UserProfile {
#ToString.Exclude
#ApiModelProperty(value = "Linking the UserProfile to the base User object that is used for authentication")
#OneToOne(optional = false)
// Note: Not marking with 'NotNull' annotation since we will not want to update the User object when upserting the UerProfile
#JoinColumn(unique = true, nullable = false, insertable = true, updatable = false)
#JsonIgnoreProperties("userProfile")
private User user;
}
My versions are:
spring_boot_version=2.0.8.RELEASE
hibernate_version=5.2.17.Final
Finally, I got this working myself. This SO post helped me solve the issue. And here are my domain model classes with the annotations that worked:
public class User {
#ToString.Exclude
#OneToOne(mappedBy = "user", orphanRemoval = true, fetch = FetchType.LAZY)
#Cache(usage = CacheConcurrencyStrategy.NONSTRICT_READ_WRITE)
#JsonIgnore
private UserProfile userProfile;
}
public class UserProfile {
#ToString.Exclude
#ApiModelProperty(value = "Linking the UserProfile to the base User object that is used for authentication")
#OneToOne(optional = false)
// Note: Not marking with 'NotNull' annotation since we will not want to update the User object when upserting the UerProfile
#JoinColumn(unique = true, nullable = false, insertable = true, updatable = false)
#JsonIgnoreProperties(value = {"login", "password", "firstName", "lastName", "email", "activated", "langKey", "imageUrl", "resetDate"}, ignoreUnknown = true)
private User user;
}

How to load only specified attributes in subgraph in an #NamedEntityGraph

I want to load an UserReference object from the database, but from the verifier attribute I want to load only the id, firstName and lastName, so that the userReference would look something like this:
{
"id": 1,
"company": "company1",
"companyContactName": "some name",
"companyPosition": "programmer",
"referenceDate": "02/04/2005",
"verifier": {
"id":1
"firstName": "Jane",
"lastName": "Smith"
"email":null,
"username":null,
"office:null,
"department":null
}
}
I used an entity graph for the UserReference class, but the one I used loads all the information that the user has, including the email, username, office and department.
Is there any way to specify something like EntityGraphType.FETCH to the subgraph, so that it will load only the id, firstName and lastName for the verifier?
This is my UserReferenceRepository:
public interface UserReferenceRepository extends JpaRepository<UserReference, Long>{
#EntityGraph(value = "userReferenceGraph" , type = EntityGraphType.FETCH )
UserReference findOne(Long id);
}
The UserReference class:
#Getter
#Setter
#EqualsAndHashCode (exclude = {"id", "verifier"})
#ToString(exclude = {"id"})
#Entity
#NamedEntityGraphs({
#NamedEntityGraph(
name = "userReferenceGraph",
attributeNodes = {
#NamedAttributeNode(value = "verifier", subgraph = "verifierGraph")
},
subgraphs = {
#NamedSubgraph(
name = "verifierGraph",
type = User.class,
attributeNodes = {
#NamedAttributeNode(value = "id"),
#NamedAttributeNode(value = "firstName"),
#NamedAttributeNode(value = "lastName")})})
})
public class UserReference {
#Id
#GeneratedValue
private Long id;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "user_id", referencedColumnName = "user_id", foreignKey = #ForeignKey (name = "FK_UserReference_UserHRDetails_user_id"))
#JsonIgnore
private UserHRDetails hrDetails;
private String company;
private String companyContactName;
private String companyPosition;
private Date referenceDate;
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "verifier_id")
private User verifier;
}
and User:
#Getter #Setter
#EqualsAndHashCode(exclude = {"id", "department", "company", "authorities", "hrDetails"})
#ToString(exclude = {"password"})
#Entity
#AllArgsConstructor
#Builder
public class User implements Serializable{
private static final long serialVersionUID = 1L;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Access(value = AccessType.PROPERTY)
private Long id;
#Size(max = 50)
#Column(name = "first_name", length = 50)
private String firstName;
#Size(max = 50)
#Column(name = "last_name", length = 50)
private String lastName;
#Column(length = 100, unique = true, nullable = false)
private String email;
#Column(length = 50, unique = true, nullable = false)
private String username;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "department_id")
private Department department;
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "office_id")
private Office office;
}
I suppose you are using Jackson for generating JSON. In this case this is a battle Jackson vs. Entity Graph and former has no chance to win this battle. Entity Graph is just a hint for building SQL query and you can only tell Hibernate to not load some attributes. Hibernate still does not support Entity Graphs when it loads basic Entity fields, see https://hibernate.atlassian.net/browse/HHH-9270. But main problem is that Jackson will call every getter in your Entity during JSON generation and Hibernate will lazy load them not taking into consideration your Entity Graph. I can propose only #JsonIgnore usage, but that may be not as flexible as you need.
I've met the same problem, and i see 2 ways to solve it:
FAST:
You can make some #PostLoad action in your entity and nullate that field you don't need.
#PostLoad
private void postLoad() {
if (verifier != null) {
verifier.email = null;
verifier.office = null;
verifier.department = null;
}
}
CORRECT:
Another way is to protect your entities with converting them to DTOs. Create separate POJOs and convert your User and UserReference to that DTO POJO classes. There you definitely will have more control over your response.

Categories

Resources