JPA Hibernate - How to save two objects from two entities that are connected - spring

I have two entities: Account and Profile. They are connected with an One To One relationship.
Account Entity:
#Entity
#Table(name = "account")
public class Account {
#Id
#Column(name = "account_id")
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#OneToOne
private Profile profile;
...
}
Profile Enity:
#Entity
#Table(name = "profile")
public class Profile {
#Id
#Column(name = "profile_id")
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#OneToOne(mappedBy = "profile", cascade = CascadeType.ALL)
private Account account;
...
}
The problem is when I try to save in database, a new object from Account and a new object from Profile and connect them. Something like this:
Account account = new Account();
Profile profile = new Profile();
profile.setAccount(account);
account.setProfile(profile);
accountRepository.save(account);
profileRepository.save(profile);
Of course this doesn't work. After searching for a solution I found that I must use persist method and Transactions. However I haven't found how to use them. I tried to use EntityManager and create a persistence.xml file, but spring doesn't find it (I put it in directory: src/main/resources/Meta-INF).
My question is: Is there a simpler way to save both of the objects (without having to create new xml files etc.)? And if there isn't what exactly do I have to do, in order to make it work?
I use spring with hibernate and mysql, in Netbeans with Maven.

I finally solved it !
The problem was that the CascadeType.All property was in the wrong place. It should be in the Account Entity. Also, the profileRepository.save(profile) is not needed because the cascade of the Account manages the save of the profile. I also had some issues when I tried to show (with JSON) an account, with forever recursion, which I solved thanks to this stackoverflow answer (#JsonManagedReference and #JsonBackReference).
So now my code looks like this:
Account Entity:
#Entity
#Table(name = "account")
public class Account {
#Id
#Column(name = "account_id")
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#OneToOne(cascade = CascadeType.ALL)
#JoinColumn(name="profile_id")
#JsonManagedReference
private Profile profile;
...
}
Profile Entity:
#Entity
#Table(name = "profile")
public class Profile {
#Id
#Column(name = "profile_id")
#GeneratedValue(strategy = GenerationType.AUTO)
private Long id;
#OneToOne(mappedBy="profile")
#JsonBackReference
private Account account;
...
}
And for the save in database:
Account account = new Account();
Profile profile = new Profile();
profile.setAccount(account);
account.setProfile(profile);
accountRepository.save(account);

You should change save order. Account entity cannot be created before profile.
Account account = new Account();
Profile profile = new Profile();
profile.setAccount(account);
account.setProfile(profile);
profileRepository.save(profile);
accountRepository.save(account);

Related

Can't delete child entity without deleting parent entity, regardless of CascadeTypes?

I'm trying to connect an entity (User) to entities they create which will be Surveys.
I have two repositories, one UserRepository and one SurveyRepository. I can load Surveys according to which User has them and currently they are all mapped by the User_ID, which is a field on the Survey entity.
However, when I try to remove a Survey, this removes my User whenever I define CascadeType.ALL.
But when I don't use that, I get another error "Caused by: java.sql.SQLIntegrityConstraintViolationException:"
I'm gussing this is all related to the password encryption I'm using, but I am not even trying to delete the User entity, I'm just deleting the Survey, which holds a reference, or an ID to the Survey..
I've tried CascadeType.All on both sides, and I've tried not having any CascadeType at all as well.. If I have it on both sides, this deletes the user whenever I tell my surveyRepository.delete(currentSurvey);
And whenever I don't have it on both sides, I get the exception above..
User Entity:
#Entity
#Table(name = "user")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "user_id")
private Long id;
#NotEmpty
#Email
#Column(unique = true)
private String email;
private String password;
#NotBlank
private String username;
#NotBlank
private String firstName;
#NotBlank
private String lastName;
#NotBlank private String role;
#OneToMany(fetch = FetchType.EAGER)
private Set<Survey> surveys = new HashSet<>();
Survey Entity:
#Entity
#Table(name = "survey")
public class Survey {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name = "survey_id")
private Long id;
private String title, creator, description;
private LocalDate date = LocalDate.now();
#OneToMany(orphanRemoval = true, cascade = CascadeType.ALL, fetch = FetchType.EAGER)
#JoinColumn(name = "survey_id")
#OrderBy("position ASC")
private Set<Question> questions = new HashSet<>();
#ManyToOne(fetch = FetchType.EAGER)
#JoinColumn(name = "user_id")
private User user;
I'm just not sure how I can tell JPA/Hibernate not to touch the User whenever we delete the Survey.
It doesn't matter if I save the User with Survvey or not does it?
Basically I've tried a lot of options and I figure I'm not quite grasping the issue, and I suspect it's about the annotations on the User side, but I still feel as if I should be able to delete the child entity with no problem at all since I am not touching the parent entity?
This is because of EAGER fetch type in User class for surveys.
You delete survey but because it is existed on surveys set in user yet, it wouldn't be deleted actually.
You need to do like this:
// User class
#OneToMany(cascade=CascadeType.ALL, orphanRemoval=true, mappedBy="user")
private Set<Survey> surveys = new HashSet<>();
//Survey class
#ManyToOne
#JoinColumn(name = "user_id")
private User user;

Jpa OneToOne shared primary key half works

I have SpringBoot 2.1.3 and Java 8 application. Building DB with JPA I have 3 table in one to one relationship. Suppose the tables is the follows:
#Entity
#Data //lombok
#Table(name = "users")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private Long id;
#OneToOne(mappedBy = "user", cascade = CascadeType.ALL)
private Address address;
}
And then:
#Entity
#Data
#Table(name = "address")
public class Address {
#Id
#Column(name = "id")
private Long id;
#OneToOne
#MapsId
private User user;
}
That's works.. and it is the best way to do (this exactly example is taken from documentation).
If I start the application the DB is created and if I tried to add entities all works well. The model created follows:
Now I want to add a Country object to my address Entities (for example) and I modified the Entities as follows:
#Entity
#Data
#Table(name = "address")
public class Address {
#Id
#Column(name = "id")
private Long id;
#OneToOne
#MapsId
private User user;
#OneToOne
#MapsId
private Country country;
}
And Country Entities:
#Entity
#Data
#Table(name = "country")
public class Country {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
#Column(name = "id")
private Long id;
#OneToOne(mappedBy = "country", cascade = CascadeType.ALL)
private Address address;
}
The application still starts, the DB is created and the model follows:
But if I try to save a User as follows:
User user = new User();
Address address = new Address();
Country country = new Country();
user.setAddress(address);
address.setUser(user);
address.setCountry(country);
country.setAddress(address);
userRepository.save(user);
I obtain the error:
java.sql.SQLException: Field 'country_id' doesn't have a default value
Anyway I solve the issue removing #MapsId and added #JoinColumn but I would like to understand what's wrong.
P.S.: I'm using MySQL 5.7 with InnoDB dialect (setting on application.properties)
Thanks all
It works only with one #MapsId annotation. Using two is causing that country id is not inserted:
insert into Country (id) values (?)
insert into Users (id) values (?)
insert into Address (user_id) values (?)

JPA OneToOne UPDATE instead of INSERT

I am new to Spring/JPA and I am trying to use Database relationship #annotations to simplify my code.
I have two entities, a User and Token entity.
When setToken() is called on the User, I want the Token to overwrite any old tokens associated with the user in the table.
At the moment, the new token (via user.setToken()) is being INSERT rather than UPDATE.
How do I go about achieving this relationship? In "Lame-mans"...A user can only even have one token and can be given another at any time, discarding the old.
There are extra fields in these models that i have truncated for clarity.
#Entity
#Table(name = "Users")
public class User {
#Column(name = "USER_ID")
#Id #GeneratedValue(strategy = GenerationType.IDENTITY)
#JsonInclude(JsonInclude.Include.NON_NULL)
private Long userId;
#OneToOne(fetch = FetchType.LAZY,cascade = CascadeType.ALL)
#JoinColumn(name = "REFRESH_TOKEN_ID")
private RefreshToken refreshToken;
...setters and getters
And the code for a token:
#Entity
#Table(name = "RefreshTokens")
public class RefreshToken {
#Column(name = "REFRESH_TOKEN_ID")
#Id #GeneratedValue(strategy = GenerationType.IDENTITY)
private Long tokenId;
If your RefreshToken has only 1 Field REFRESH_TOKEN_ID which is long. Why you need different table for this. You can just have something like this
public class User {
#Column(name = "USER_ID")
#Id #GeneratedValue(strategy = GenerationType.IDENTITY)
#JsonInclude(JsonInclude.Include.NON_NULL)
private Long userId;
#Column(name = "REFRESH_TOKEN_ID")
private Long refreshToken;
...
When setToken() is called you must be setting any value. If yes thats it. Your logic will work fine,
If no you can always generate a Unique value in Java based on Current time or something else.
or
If you want to continue with same patter use orphanRemoval = true
#OneToOne(fetch = FetchType.LAZY,cascade = CascadeType.ALL, orphanRemoval = true)
#JoinColumn(name = "REFRESH_TOKEN_ID")
private RefreshToken refreshToken;
You should never modify the primary key of a an entity
This is not be possible as you change the id of the token. You need to create a different id in the RefreshToken thats unique and stays the same after save.
If you really do need that - you'd better of delete the entity and create a new one which just copies the old one but with a new primary key.

#ManyToMany with extra column - how to load via Spring Data?

I have many to many relation between User and Event. I need to have extra column in relational table. I did id:
#Entity
public class User {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#OneToMany(mappedBy="user")
private Set<EventUser> eventUsers = new HashSet<>();
//
}
#Entity
public class Event {
#Id
#GeneratedValue(strategy= GenerationType.AUTO)
private long id;
#OneToMany(mappedBy="event")
private Set<EventUser> eventUsers = new HashSet<>();
//
}
#Entity
#Table(name = "Event_User")
public class EventUser {
#Id
#GeneratedValue(strategy= GenerationType.AUTO)
private long id;
private String reaction;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "user_id")
private User user;
#ManyToOne(cascade = CascadeType.ALL)
#JoinColumn(name = "event_id")
private Event event;
//
}
But now... I don't know how to load all events where user has concrete email. Before it I used method:
findByUsersEmail(String email);
Now I can't do this, because Event doesn't have Set users field.
Any ideas ?
What you need here is property-expressions.
Just a quick idea to start:
findByEventUsers_UserEmail(String email);
Note: Dont forget that creating queries by method names is a very limited approach and only used by trivial cases. In any other case, don't be afraid of using the #Query annotation on the method or write JPQL/Criteria API manually.

Spring Data JPA won't handle Set<Enum> correctly

I'm trying out Spring Boot (latest version, using Hibernate 4.3.7) and I have a problem with my User entity. Here it is (most important part of it):
#Entity
#Table("usr")
public class User {
public static enum Role {
UNVERIFIED, BLOCKED, ADMIN
}
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#Column
#ElementCollection
private Set<Role> roles = new HashSet<Role>();
(rest of properties, getters and setters etc)
}
I am also using Spring Boot JPA repositories to save my entities:
#Repository
public interface UserRepository extends JpaRepository<User, Long> {
User findByEmail(String email);
}
The problem is that when I add some Roles to roles set, Hibernate won't save it. It will create reference table, but it will only insert data to User table.
I tried to work this problem out, so I created pure Java + Hibernate project and copied my User class into it. Guess what? It worked!
Fun fact is that when I use pure Hibernate on my second project, created roles table looks different that the one created in my Spring Boot project.
On my clean Hibernate project I have table like:
User_roles:
User_Id bigInt(20)
roles int(11)
While using Spring JPA, I got
user_roles (notice lower case)
User (no "_id" part)
roles
What's going on? What I am doing wrong? Is it related to Spring Boot configuration? Thanks.
The following should match your existing tables.
#Entity
#Table("usr")
public class User {
public static enum Role {
UNVERIFIED, BLOCKED, ADMIN
}
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#ElementCollection
#CollectionTable(name = "User_roles", joinColumns = #JoinColumn(name = "User_Id")
private Set<Role> roles = new HashSet<Role>();
(rest of properties, getters and setters etc)
}
My Solution:
Role.java
public enum Role {
USER, ADMIN
}
User.java
#Entity
#Table("usr")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#ElementCollection(targetClass = Role.class, fetch = FetchType.EAGER)
#JoinTable(name = "user_roles", joinColumns = #JoinColumn(name = "id"))
#Column(name = "roles", nullable = false)
#Enumerated(EnumType.STRING)
private Set roles;
(rest of properties, getters and setters etc)
}

Resources