Foreign key is being updated as null - java

I am trying to implement a bi-directional relationship using #OneToMany and #ManyToOne JPA annotation. My foreign key is being updated as NULL which is not correct. I need some input to resolve this.
I have created User and CardInfo class. I am trying to add a relationship where User can have more than one card. When I am trying to persist in database foreign key is being inserted as null.
#Entity
#Table(name = "customer_info")
#Data
#NoArgsConstructor
#AllArgsConstructor
public class User {
#Id
//#GeneratedValue
private String userId;
private String userName;
private Date dateOfBirth;
private boolean primeMember;
#OneToMany(mappedBy = "user", cascade = CascadeType.PERSIST, orphanRemoval = true)
private Set<CardInfo> paymentDetails;
#Data
#AllArgsConstructor
#NoArgsConstructor
#Entity
#Table(name="card_info")
public class CardInfo implements Serializable {
#Id
private String cardNumber;
#Id
private String cardType; // Debit, Credit
private String cardCategory; // Visa, mastercard
private Date expiryDate;
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name="user_id")
#EqualsAndHashCode.Include private User user;
public class DAOImpl {
#Transactional
public String addCustomer(User user) {
// User _user=new User();
// Set<CardInfo> cardData=new HashSet<>();
//
String userId=String.valueOf(Instant.now().toEpochMilli());
user.setUserId(userId);
Session session=sessionFactory.getCurrentSession();
session.persist(user);
return userId;
}
mysql> select * from card_info;
+----------+-------------+--------------+---------------------+---------+
| cardType | cardNumber | cardCategory | expiryDate | user_id |
+----------+-------------+--------------+---------------------+---------+
| CREDIT | 74959454959 | VISA | 2020-04-23 00:00:00 | NULL |
+----------+-------------+--------------+---------------------+---------+
1 row in set (0.00 sec)
user_id column should not be updated as NULL. Please correct me if understanding is not correct.

Although Cascade.PERSIST ensures that CardInfo objects will be persist together with their parent User, it is the responsibility of the application, or the object model to maintain relationships[1].
As the foreign key is in CardInfo, you have to ensure that every CardInfo is associated to the User that you are persisting. A common pattern is to add extra logic to handle both sides of the relationship in the domain object, e.g.:
public class User {
// fields, accessors and mutators
public void addPaymentDetails(CardInfo cardInfo) {
if (paymentDetails == null) {
paymentDetails = new LinkedHashSet<>();
}
if (cardInfo.getUser() != this) {
cardInfo.setUser(this);
}
paymentDetails.add(cardInfo);
}
}
The above code ensures that both sides of the relationship are in sync (i.e., if a user adds a card to its paymental details, then the card info is "owned" by the user).
Finally, while not directly related to your problem, my advice would be to make the relationship between CardInfo and User mandatory and its respective join column NOT NULL so that queries are properly optimised and no CardInfo can exist in the database without an association to its owning User:
#ManyToOne(fetch = FetchType.LAZY, optional = false)
#JoinColumn(name="user_id", nullable = false)

Related

Spring Boot with hibernate: Creating Composite Key and Many-to-many relations

I am trying to create a very simple Spring Boot application for storing sports data.
It has two entities: player and tournament. However, I want to store how each player placed in each tournament.
For this, I created the following ER Diagram. The junction table PlayerPlacement_map contains a relationship-attribute placement for storing how the players place in a tournament:
I have followed this guide on how to map the relationship between Players and Tournament, with Id from those two tables as a composite key in the junction table called PlayerPlacementMap: https://www.baeldung.com/jpa-many-to-many
That has given me the following classes:
Player.java (Tournament.java follows a similar pattern - left out for brevity)
#Entity
#Table(name = "players")
public class Player {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "Id", updatable = false, nullable = false)
private long id;
#Column(name = "Name")
private String name;
#ManyToMany
#JoinTable(
name = "PlayerTournament_map",
joinColumns = #JoinColumn(name = "Player_Id"),
inverseJoinColumns = #JoinColumn(name = "Tournament_Id"))
Set<Tournament> attendedTournaments;
#OneToMany(mappedBy = "player")
Set<PlayerPlacement> placements;
//getters, constructors - left out for brevity
}
PlayerPlacementKey.java (making an embeddable composite key-class)
#Embeddable
public class PlayerPlacementKey implements Serializable {
#Column(name = "Player_Id")
long playerId;
#Column(name = "Tournament_Id")
long tournamentId;
//getters, setters, constructors, equals, hashcode - left out for brevity
}
PlayerPlacement.java (junction table)
#Entity
#Table(name = "PlayerPlacement_map")
public class PlayerPlacement {
#EmbeddedId
PlayerPlacementKey id;
#ManyToOne
#MapsId("PlayerId")
#JoinColumn(name = "Player_Id")
Player player;
#ManyToOne
#MapsId("TournamentId")
#JoinColumn(name = "Tournament_Id")
Tournament tournament;
int placement;
//constructors - left out for brevity
}
I have repositories for player, tournament and playerplacement. Player and tournament repositories are working fine on their own, and I am able to perform CRUD operations through a #RestController against a MSSQL database.
This is the repository for playerplacement:
#Repository
public interface PlayerPlacementRepository extends JpaRepository<PlayerPlacement, PlayerPlacementKey>
{}
For the junction table, I have made a PlayerPlacementController:
#RestController
public class PlayerPlacementController {
#Autowired
private PlayerPlacementRepository playerPlacementRepository;
#PostMapping("/playerplacement")
public PlayerPlacement addPlayerPlacement(#RequestBody PlayerPlacement playerPlacement) {
return playerPlacementRepository.save(playerPlacement);
}
}
Finally, here comes the problem: When I call the /"playerplacement" endpoint, I receive the following error:
org.hibernate.id.IdentifierGenerationException: null id generated for:class com.testproject.learning.model.PlayerPlacement
I think I have migrated the database just fine, but just to be safe, here is my migration for the junction table:
CREATE TABLE PlayerPlacement_map (
PlayerId Bigint NOT NULL,
TournamentId Bigint NOT NULL,
Placement int
CONSTRAINT PK_PlayerPlacement NOT NULL IDENTITY(1,1) PRIMARY KEY
(
PlayerId,
TournamentId
)
FOREIGN KEY (PlayerId) REFERENCES Players (Id),
FOREIGN KEY (TournamentId) REFERENCES Tournaments (Id)
);
I haven't been able to figure out what I am doing wrong. I appreciate all help and pointers that I receive - thanks in advance.
EDIT:
Progress has been made with help of user Alex V., but I hit a new problem as of right now.
I make the following JSON call:
{
"player":
{
"name":"John Winther"
},
"tournament":
{
"name":"Spring Boot Cup 2020"
},
"placement":3
}
I don't set the id (composite key) myself - I guess it should be handled by something of the framework? Anyways, when I make this call in debug mode, I get the following debug variables set in the endpoint:
However, it results in the following error:
java.lang.NullPointerException: Cannot invoke "Object.getClass()" because "o" is null, which probably refers to my equals-method in PlayerPlacementKey.java (?):
#Override
public boolean equals(Object o) {
if (this == o) return true;
if (!(o instanceof PlayerPlacementKey)) return false;
PlayerPlacementKey that = (PlayerPlacementKey) o;
return playerId == that.playerId &&
tournamentId == that.tournamentId;
}
Hope anybody can push me in the right direction one more time.
Your entity has playerId field annotated as #Column(name = "Player_Id"), but database table contains PlayerId column instead of Player_Id. The same situation with tournamentId, player, tournament fields.
#MapsId("TournamentId") has to contain #EmbededId class field name tournamentId instead of TournamentId. It means the field is mapped by embeded id field tournamentId.
#ManyToOne
#MapsId("tournamentId")
#JoinColumn(name = "tournamentId")
Tournament tournament;
The same problem here #MapsId("PlayerId") .

Hibernate: How do I persist order related information

In my application a user places an order and sets the billing address to one of the address mapped with him. Now in future he edits that address.So my order will map to that updated address.
My Order entity
#Entity
public class Orders{
...
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "user_id")
private User user;
#OneToOne
private Address address;
...
}
Address entity
#Entity
#Table(name = "address")
public class Address {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
private String address1;
...
}
Person entity
#Entity
public class Person{
...
#OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
private Set<Address> addresses = new HashSet<>();
...
}
I want my Order entity to have a copy of the Address as it was during the creation of the order.Any changes in the Address by user in his profile, after order creation should have no impact on that Order.
I assume you allow user to pick the address from his address list(Person#address), so when you submit your order it contains the address that is already on database, including the id that creates a relationship, does not create an record:
{
user: {
id: 10,
email: "user#stackoverflow.com"
},
address: {
id: 10,
street: "5th Av"
}
}
If you want to "have a copy of the Address" then you should first update your relationship in Order class like:
#OneToOne(cascade = CascadeType.ALL)
private Address address;
Then send the address without id, that would indicate your repository to create a new entry into database.
Json option:
{
user: {
id: 10,
email: "user#stackoverflow.com"
},
address: {
street: "5th Av", ...
}
}
Or by removing the id on controller:
#PostMapping("/submit-order")
public Order submitOrder( #RequestBody Order order) {
// Remove Order#id to detatch current record and enforce create a new one
order.getAddress().setId(null);
return this.orderRepository.save(order);
}
This way your order has an exclusive copy of address.
ERROR: org.springframework.orm.jpa.JpaSystemException with message "Address was altered from 1 to null"
If you receive this error is because you are removing the id of the entity within the scope of a transaction or session. You should create a copy of the entity out of that scope or use entity manager to detach the entity, here is a example.
#embeddable solution
Another solution would be to use an embeddable object instead, that way you can store the address fields on order table but have them as a composite object:
First you create an order address object with all required fields and mark it with #Embeddable annotation:
#Embeddable
public class AddressOrder {
#Column("street")
private String street;
#Column("postal_code")
private String po;
#Column("city")
private String city;
#Column("country")
private String country;
// Getters and setters
}
Then you use the object on your order table as an attribute and mark it with #Embedded annotation.
#Entity
public class Orders {
#ManyToOne(fetch = FetchType.LAZY)
#JoinColumn(name = "user_id")
private User user;
#Embedded
private AddressOrder address;
// Getters and setters
}
You need to choose the solution according to the database approach you want to use.

Spring data JPA: how to enable cascading delete without a reference to the child in the parent?

Maybe this is an overly simple question, but I am getting an exception when I try to delete a user entity.
The user entity:
#Entity
#Table(name = "users")
public class User
{
#Transient
private static final int SALT_LENGTH = 32;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
#NotNull
private String firstName;
#NotNull
private String lastName;
#Column(unique = true, length = 254)
#NotNull
private String email;
// BCrypt outputs 60 character results.
#Column(length = 60)
private String hashedPassword;
#NotNull
private String salt;
private boolean enabled;
#CreationTimestamp
#Temporal(TemporalType.TIMESTAMP)
#Column(updatable = false)
private Date createdDate;
And I have an entity class which references a user with a foreign key. What I want to happen is that when the user is deleted, any PasswordResetToken objects that reference the user are also deleted. How can I do this?
#Entity
#Table(name = "password_reset_tokens")
public class PasswordResetToken
{
private static final int EXPIRATION_TIME = 1; // In minutes
private static final int RESET_CODE_LENGTH = 10;
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
private String token;
#OneToOne(targetEntity = User.class, fetch = FetchType.EAGER)
#JoinColumn(nullable = false, name = "userId")
private User user;
private Date expirationDate;
The exception I am getting boils down to Cannot delete or update a parent row: a foreign key constraint fails (`heroku_bc5bfe73a752182`.`password_reset_tokens`, CONSTRAINT `FKk3ndxg5xp6v7wd4gjyusp15gq` FOREIGN KEY (`user_id`) REFERENCES `users` (`id`))
I'd like to avoid adding a reference to PasswordResetToken in the parent entity, becaue User shouldn't need to know anything about PasswordResetToken.
It is not possible on JPA level without creating a bidirectional relation. You need to specify cascade type in User class. User should be owner of the relation and it should provide the information on how to deal with related PasswordResetToken.
But if you cannot have a bidirectional relation I would recommend you to setup relation directly in schema generation SQL script.
If you create your schema via SQL script and not via JPA autogeneration (I believe all serious projects must follow this pattern) you can add ON DELETE CASCADE constraint there.
It will look somehow like this:
CREATE TABLE password_reset_tokens (
-- columns declaration here
user_id INT(11) NOT NULL,
CONSTRAINT FK_PASSWORD_RESET_TOKEN_USER_ID
FOREIGN KEY (user_id) REFERENCES users (id)
ON DELETE CASCADE
);
Here is the documentation on how to use DB migration tools with spring boot. And here is the information on how to generate schema script from hibernate (that will simplify the process of writing your own script).
Parent Entity:
#OneToOne
#JoinColumn(name = "id")
private PasswordResetToken passwordResetToken;
Child Entity:
#OneToOne(mappedBy = "PasswordResetToken", cascade = CascadeType.ALL, orphanRemoval = true)
private User user;
If you want the Password entity to be hidden from the client, you can write a custom responses and hide it. Or if you want to ignore it by using #JsonIgnore
If you don't want the reference in the Parent Entity (User), then you have to override the default method Delete() and write your logic to find and delete the PasswordResetToken first and then the User.
You can use Entity listener and Callback method #PreRemove to delete an associated 'Token' before the 'User'.
#EntityListeners(UserListener.class)
#Entity
public class User {
private String name;
}
#Component
public class UserListener {
private static TokenRepository tokenRepository;
#Autowired
public void setTokenRepository(TokenRepository tokenRepository) {
PersonListener.tokenRepository = tokenRepository;
}
#PreRemove
void preRemove(User user) {
tokenRepository.deleteByUser(user);
}
}
where deleteByPerson is very simple method of your 'Token' repository:
public interface TokenRepository extends JpaRepository<Token, Long> {
void deleteByUser(User user);
}
Pay attention on static declaration of tokenRepository - without this Spring could not inject TokenRepository because, as I can understand, UserListener is instantiated by Hybernate (see additional info here).
Also as we can read in the manual,
a callback method must not invoke EntityManager or Query methods!
But in my simple test all works OK.
Working example and test.

Cannot add or update a child row: a foreign key constraint fails - Bidirectional mapping in hibernate

Hi I have a problem with JPA...
DB:
Java classes (irrelevant fields are ommited):
User:
#Entity
public class User{
#Id
private int iduser;
//bi-directional many-to-one association to UserInfo
#OneToMany(mappedBy="user", cascade= {CascadeType.ALL}, fetch=FetchType.EAGER)
private List<UserInfo> userInfos;
}
UserInfo:
#Entity
#Table(name="user_info")
public class UserInfo {
#Id
#Column(name="iduser_info")
private int iduserInfo;
//bi-directional many-to-one association to User
#ManyToOne
private User user;
}
Currently when I try to do this (again I omitted setting irrelevant fields):
User u = new User();
UserInfo info = new UserInfo();
u.addUserInfo(info);
em.persist(u); // save user
I get this error:
Cannot add or update a child row: a foreign key constraint fails (`webstore`.`user_info`, CONSTRAINT `fk_user_info_user` FOREIGN KEY (`user_iduser`) REFERENCES `user` (`iduser`) ON DELETE NO ACTION ON UPDATE NO ACTION)
I have been banging my head all day and I can't figure it out... I have also searched for solutions here but they all suggest that this error shows that I want to enter UserInfo without user_iduser value entered, but even if I add this before persist:
info.setUser(u);
it still doesn't work - is bidirectional mapping even supported with cascading? The desired effect is that User should be inserted and then all the UserInfos in the list after it refering the original User. How can I achieve that?
I don't want to do
SET foreign_key_checks = 0/1;
everytime =(
Try:
#ManyToOne
#JoinColumn(name="user_iduser", nullable=false)
private User user;
Then see if this works. I'm assuming that in user_info table, the user_id field is NOT NULL in your RDBMS. Also, since it's bidirectional, you will have to setXXX on UserInfo and User.
User u = new User();
UserInfo info = new UserInfo();
info.setUser(u);
u.addUserInfo(info);
em.persist(u); // save user
Update: Are you sure you're specifying an ID for both User and UserInfo? It looks like the ID is not set hence there is no reference to link UserInfo to User.
If the ID are AUTO_INCREMENT, then add the following after #Id annotation:
#GeneratedValue(strategy=GenerationType.IDENTITY)
I also had this error, fixed this error by adding #GeneratedValue(strategy = GenerationType.IDENTITY) annotation on top of iduser and you must have foreign keys CASCADE ON DELETE, UPDATE.
this is worked for me in my example ,
i created books and library.
#Entity
#Table(name = "book")
public class Books{
#Id
private int library_id;
private String libraryname;
//add getters and setters bellow
}
#Entity
#Table(name = "library")
public class Book {
#Id
private int book_id;
private String book_title;
#JsonIgnore
#ManyToOne
#JoinColumn(name="library_id" , nullable = false)
#OnDelete(action = OnDeleteAction.CASCADE)
private Library library;
//set getters and setters
}
in controller i used this method
#RequestMapping(value = "/{libraryId}/book", method = RequestMethod.POST, consumes = MediaType.APPLICATION_JSON_VALUE)
public Book createBook(#PathVariable(value = "libraryId") Integer libraryId, #RequestBody Book book) {
List<Book> books = new ArrayList<Book>();
Library author1 = new Library();
Optional<Library> byId = LibraryRepository.findById(libraryId);
Library author = byId.get();
book.setLibrary(author);
Book book1 = bookRepository.save(book);
books.add(book1);
author1.setBooks((List<Book>) books);
return book1;
}

Atypic JPA OneToOne relation

I have a problem using JPA.
I have to tables:
-----------------
| TableA |
|---------------|
| ID: INT |
| ... |
| ESTATUS1: INT |
| ESTATUS2: INT |
-----------------
-----------------
| EstatusTags |
|---------------|
| COD: VARCHAR |---> COD and VALUE are a concatenated PK
| VALUE: INT |
| DESC: VARCHAR |
-----------------
EstatusTags is a table to store sets of pairs [VALUE, DESC], given a COD.
Before I use JPA, I used to query this kind of data in something like this:
SELECT ID, ESTATUS1, ESTATUS2, E1.DESC DESC1, E2.DESC DESC2
FROM TABLEA A
INNER JOIN ESTATUSTAGS E1 ON E1.COD = "a_estatus1"
AND E1.VALUE = A.ESTATUS1
INNER JOIN ESTATUSTAGS E2 ON E2.COD = "a_estatus2"
AND E2.VALUE = A.ESTATUS2
I'm trying to use JPA to model this using two entity classes:
#Entity
#Table(name = "EstatusTags")
public class EstatusTags implements Serializable {
#EmbeddedId
private ValueTagPK id;
#Column(name="VVA_DESC")
private String desc;
#Column(name="VVA_ORDEN")
private Integer orden;
}
#Entity
#Table(name = "TableA")
public class A implements Serializable {
#Column(name="ID")
private String desc;
#OneToOne(???)
private EstatusTag estatus1;
#OneToOne(???)
private EstatusTag estatus2;
}
I have strong doubts in how to model the relations. Can it be done with annotations? There is necesary the JPQL use to fit this structure?
I hope somebody could help me with this.
Thanks a lot.
The problem is that your entity model does not match the table structure.
In your entity model you have a one to one relation ship between A and EstatusTag whereas in your table model you have a relationship of one A and multiple Estatustags (for one value there may exist multiple Etatustags entries)
You overcome the problem that Table A does not have a cod column by adding something like a virtual cod column E1.COD = "a_estatus1" to your SQL Query.
What you can do is you map the value column of to two properties of EstatusTag one time to the composite pk and the other time to a single property in the following way . The simple value is made accessible via property access but marked as not updatable not insertable also the setter does not really work and is made private.
Remark: I don't know if that works with all JPA implementations - Tested with hibernate 4.3.8.
#Entity
#Table(name = "EstatusTags" )
#Access(AccessType.FIELD)
public class EstatusTag implements Serializable{
private #EmbeddedId ValueTagPK id;
#Column(name="VVA_DESC")
private String desc;
#Column(name="VVA_ORDEN")
private Integer orden;
#Column(name="value", updatable=false, insertable=false)
#Access(AccessType.PROPERTY)
public int getValue() {
return id.value;
}
private void setValue(int value) {
// only because otherwise hibernate complains about a missing setter.
}
}
#Entity
#Table(name = "TableA")
public class A implements Serializable{
#Id
#Column(name="ID")
#GeneratedValue(strategy=GenerationType.TABLE)
private int id;
#OneToOne()
#JoinColumn(name="estatus1",referencedColumnName="value")
public EstatusTag estatus1;
#OneToOne()
#JoinColumn(name="estatus2",referencedColumnName="value")
public EstatusTag estatus2;
}

Categories