How to persist nested objects with Hibernate? - java

I have two classes, Customer and ShoppingCart. The java structure of the two classes is the following:
Customer class:
#Entity
#Inheritance(strategy = InheritanceType.JOINED)
public class Customer extends User implements Serializable {
#OneToOne(mappedBy = "owner", cascade=CascadeType.ALL)
private ShoppingCart shoppingCart;
#OneToMany(mappedBy = "customer", fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private List<Purchase> purchases;
public Customer() {
super();
}
public Customer(String username, String email, String password) {
super(username, email, password);
this.shoppingCart = new ShoppingCart();
this.purchases = new ArrayList<>();
}
getters and setters
}
ShoppingCart class:
#Entity
public class ShoppingCart implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer shoppingCartId;
#OneToOne
#JoinColumn(name = "owner_id")
private Customer owner;
#OneToMany(mappedBy = "shoppingCart")
private List<CartItem> items;
public ShoppingCart(Customer owner) {
this.owner = owner;
this.items = new ArrayList<>();
}
public ShoppingCart() {
this.items = new ArrayList<>();
}
getters and setters
}
If needed, this is the User class:
#Entity
#Inheritance(strategy = InheritanceType.JOINED)
public class User implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.SEQUENCE)
private Integer userId;
private String username;
private String email;
private String password;
public User() {
}
public User(String username, String email, String password) {
this.username = username;
this.email = email;
this.password = password;
}
getters and setters
}
I have configured the Repositories classes in this way:
#Repository
public interface CustomerRepository extends CrudRepository<Customer, Integer> {
}
#Repository
public interface UserRepository extends CrudRepository<User, Integer> {
}
#Repository
public interface ShoppingCartRepository extends CrudRepository<ShoppingCart, Integer> {
}
What I want is simple, once I create a Customer, I also want to create a ShoppingCart tuple inside the database. And it actually works fine, the only problem is that the foreign key of the ShoppingCart related with the Customer is set to null. I just have the shopping_cart_id attribute set to an integer value (correctly).
The code I used to test it is the following:
Customer customer = new Customer("stefanosambruna", "ste23s#hotmail.it", "*******");
customerRepository.save(customer);
Now, I may have put some annotations in the wrong places, but I really don't know which ones. Is it related to the constructors? Or to the #JoinColumn and mappedBy configurations? I read all the Q&As about this topic here on StackOverflow and on some other sources, but I didn't find anything 100% useful. Hope to have given all the necessary details.

Related

cannot save to database foreign table new record generate hibernate jpa

Two entities: Person, Pet.
One Person have multi pets, linked by ownerId. Owner class extends Person class. Person class extends BaseEntity. BaseEntity used to generate unique id value. Using thymeleaf web part to input/insert more pets for one specific owners. Pets do inserted to the h2 database. but the ownerId is null.That's the issue.
Repository code:
public interface OwnerRepository extends JpaRepository<Owner, Long>{}
public interface PetRepository extends JpaRepository<Pet, Long> {}
Full Code: https://github.com/jianheMark/springpetsimplified
BaseEntity code:
#Getter
#Setter
#NoArgsConstructor
#AllArgsConstructor
#MappedSuperclass
public class BaseEntity implements Serializable {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
public boolean isNew() {
return this.id == null;
}
}
PetController partial code:
#GetMapping("/pets/new")
public String initCreationForm(Owner owner, Model model) {
Pet pet = new Pet();
owner.getPets().add(pet);
pet.setOwner(owner);
model.addAttribute("pet", pet);
return VIEWS_PETS_CREATE_OR_UPDATE_FORM;
}
#PostMapping("/pets/new")
public String processCreationForm(Owner owner, Pet pet, BindingResult result, ModelMap model) {
if (StringUtils.hasLength(pet.getName()) && pet.isNew() && owner.getPet(pet.getName(), true) != null){
result.rejectValue("name", "duplicate", "already exists");
}
owner.getPets().add(pet);
if (result.hasErrors()) {
model.put("pet", pet);
return VIEWS_PETS_CREATE_OR_UPDATE_FORM;
} else {
petService.save(pet);
return "redirect:/owners/" + owner.getId();
}
}
Owner partial code:
#Setter
#Getter
#NoArgsConstructor
#Entity
#Table(name = "owners")
public class Owner extends Person {
#Builder
public Owner(Long id, String firstName, String lastName, String address, String city,
String telephone, Set<Pet> pets) {
super(id, firstName, lastName);
this.address = address;
this.city = city;
this.telephone = telephone;
if(pets != null) {
this.pets = pets;
}
}
#Column(name = "address")
private String address;
#Column(name = "city")
private String city;
#Column(name = "telephone")
private String telephone;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "owner")
private Set<Pet> pets = new HashSet<>();
Pet code:
#Setter
#Getter
#NoArgsConstructor
#AllArgsConstructor
#Entity
#Table(name = "pets")
public class Pet extends BaseEntity{
#Builder
public Pet(Long id, String name, Owner owner, LocalDate birthDate) {
super(id);
this.name = name;
this.owner = owner;
this.birthDate = birthDate;
}
#Column(name = "name")
private String name;
#ManyToOne
#JoinColumn(name = "owner_id")
private Owner owner;
#Column(name = "birth_date")
#DateTimeFormat(pattern = "yyyy-MM-dd")
private LocalDate birthDate;
}
The problem:
In Pet Controller you save the pet but you do not set the owner of the pet.
Change your code:
#PostMapping("/pets/new")
public String processCreationForm(Owner owner, Pet pet, BindingResult result, ModelMap model) {
if (StringUtils.hasLength(pet.getName()) && pet.isNew() && owner.getPet(pet.getName(), true) != null){
result.rejectValue("name", "duplicate", "already exists");
}
owner.getPets().add(pet);
if (result.hasErrors()) {
model.put("pet", pet);
return VIEWS_PETS_CREATE_OR_UPDATE_FORM;
} else {
pet.setOwner(owner) // This line here was missing!
petService.save(pet);
return "redirect:/owners/" + owner.getId();
}
}
You also say owner.getPets().add(pet). Still you do not persist this. You do not save this information in the DB, you just do it internally for your code.
You should try debugging and setting breakpoints on your code, so that you check what you save and what you actually receive.
And try delegating all your business logic to the services. So that Controllers are used only for handling what happens on a specific endpoint call.
Good luck.

Not able to get auto generated Id, using JPA

I have two Entities. One is UserEntity and other is TaskEntity.
#Entity
#Table(name="user")
public class UserEntity {
#Id
private String userEmail;
private String password;
#OneToMany(cascade = CascadeType.ALL)
#JoinColumn(name="user_email")
private List<TaskEntity> tasks;
//getter setter for variables
}
#Entity
#Table(name="task")
public class TaskEntity {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Integer id;
private String name;
private String description;
private String statusDate;
private String status;
//getter setter for variables
}
Now I want to create a new task based on userEmail, so this I am doing as follow in DAO class:
#PersistenceContext
EntityManager em;
public Integer addNewTaskByUserEmail(Task task, String userEmail) {
UserEntity userEntity = em.find(UserEntity.class, userEmail);
TaskEntity taskEntity = new TaskEntity();
taskEntity.setName(task.getName());
taskEntity.setDescription(task.getDescription());
taskEntity.setStatus(task.getStatus());
taskEntity.setStatusDate(task.getDate());
userEntity.getTasks().add(taskEntity);
return taskEntity.getId();
}
But in the return statement of I am getting null in service class. How can I return the auto-generated taskId?
A possible issue is you are not saving a task associated with the user. Save the task and then you might be able to get the taskId.
public Integer addNewTaskByUserEmail(Task task, String userEmail) {
UserEntity userEntity = em.find(UserEntity.class, userEmail);
TaskEntity taskEntity = new TaskEntity();
taskEntity.setName(task.getName());
taskEntity.setDescription(task.getDescription());
taskEntity.setStatus(task.getStatus());
taskEntity.setStatusDate(task.getDate());
em.getTransaction().begin();
em.persist(taskEntity);
em.getTransaction().commit();
userEntity.getTasks().add(taskEntity);
return taskEntity.getId();
}
OR
#Autowired TaskRepository taskRepository
public Integer addNewTaskByUserEmail(Task task, String userEmail) {
UserEntity userEntity = em.find(UserEntity.class, userEmail);
TaskEntity taskEntity = new TaskEntity();
taskEntity.setName(task.getName());
taskEntity.setDescription(task.getDescription());
taskEntity.setStatus(task.getStatus());
taskEntity.setStatusDate(task.getDate());
taskEntity = taskRepository.save(taskEntity)
userEntity.getTasks().add(taskEntity);
return taskEntity.getId();
}
Where TaskRepository will be
#Repository
public interface TaskRepository extends JpaRepository<TaskEntity, Integer>
{
}

How can i display values from 2 entites in one form using thymeleaf

I am using Spring Boot, Thymeleaf, Hibernate, and JPA to develop this application. I have mapped a relationship between the two entities I need to retrieve data from and am wondering how to display data from the two tables on the page
Can someone tell me how can I display values from 2 entities in one form using Thymeleaf? I can't find any tutorial, thanks in advance
#Getter
#Setter
#NoArgsConstructor
#Entity
#Table(name = "users")
public class User {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
#OneToOne(cascade = CascadeType.ALL)
private UserDetails userDetails;
public User(String name) {
this.name = name;
}
}
#Data
#NoArgsConstructor
#Entity
#Table(name = "user_details")
public class UserDetails {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String email;
public UserDetails(String email) {
this.email = email;
}
}
A. If the both table is not related to each other then following solution may be work for you.
Create the One Model class.
public class UserAndUsersDetails {
private String name;
private String email;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
}
2.Then call the database and get the result of it
User user = userRepository.get(id);
UserDetails userDetails = userDetailsRepository.get(id);
3.Set the data in userAndUsersDetails class
UserAndUsersDetails userAndUsersDetails = new UserAndUsersDetails()
userAndUsersDetails.setName(user.getName());
userAndUsersDetails.setEmail(userDetails.getEmail());
4.Send the UserAndUsersDetails class object to thymleaf.
B. If both table related to each other then following is best solution for it.
1.Extend the userDetails from user and clone it and donot put id in UserDetails Class
#Data
#NoArgsConstructor
#Entity
#Table(name = "user_details")
public class UserDetails extends User implements Cloneable {
private String email;
public UserDetails(String email) {
this.email = email;
}
public UserDetails clone() {
UserDetails userDetails = new UserDetails();
userDetails.setName(this.getName());
}
}
Get the userDetils reslut
3.Send the object of it to thymelef.

Hibernate one-to-many relationship java.sql.SQLIntegrityConstraintViolationException: Column 'person_id' cannot be null

I'm new to hibernate, learn doc save persistent object
followed hibernate doc this is person and phone relationship one-to-many
#Entity
#Table(name = "phone")
public class Phone {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
#Column(name = "number")
private String number;
#ManyToOne(fetch = FetchType.LAZY)
private Person person;
//omit setter and getter
}
#Entity
#Table(name = "person")
public class Person {
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
private long id;
private String username;
#OneToMany(cascade = CascadeType.ALL, mappedBy = "person")
private List<Phone> phones = new ArrayList<>();
//omit getter and setter
}
I'm persistent person and add one phone the error be throw
#Test
public void say() {
Person person = new Person();
person.setUsername("aaaa");
Phone phone = new Phone();
phone.setNumber("111");
person.getPhones().add(phone);
personService.save(person);
}
this is Dao persistent
public class PersonDaoImpl implements PersonDao {
#PersistenceContext
private EntityManager entityManager;
#Override
public void save(Person person) {
entityManager.persist(person);
}
Update service code, service just save person
#Service(value = "personService")
public class PersonServiceImpl implements PersonService {
#Autowired
private PersonDao personDao;
#Transactional
#Override
public void save(Person person) {
personDao.save(person);
}
}
error info:
23:35:47.059 [main] DEBUG org.hibernate.engine.spi.ActionQueue - Executing identity-insert immediately
23:35:47.062 [main] DEBUG org.hibernate.SQL -
insert
into
phone
(number, person_id)
values
(?, ?)
23:35:47.297 [main] DEBUG org.hibernate.engine.jdbc.spi.SqlExceptionHelper - could not execute statement [n/a]
java.sql.SQLIntegrityConstraintViolationException: Column 'person_id' cannot be null
Add the #GeneratedValue annotation to specify that the primary key for both entities will be populated outside of your code.
#Entity
#Table(name = "phone")
public class Phone {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private long id;
#Column(name = "number")
private String number;
#JoinColumn("person_id")
#ManyToOne(cascade = CascadeType.ALL, fetch = FetchType.LAZY)
private Person person;
//omit setter and getter
}
public class Person {
#Id
#GeneratedValue(strategy=GenerationType.AUTO)
private long id;
private String username;
#OneToMany(mappedBy = "person")
private List<Phone> phones = new ArrayList<>();
//omit getter and setter
}
Additionally, you need to persist the Person object instead of the Phone object because there is no cascade configured from Phone to Person. If you can't do that, switch the CascadeType on Person to none and put the cascade on the Phone as shown above.
You should also add a #JoinColumn annotation on the Phone entity so hibernate is aware of the foreign key column.
You Missed something. You can try with this.
Person Entity
#Entity
public class Person {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
private String username;
#OneToMany(mappedBy = "person")
private List<Phone> phones = new ArrayList<>();
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
public List<Phone> getPhones() {
return phones;
}
public void setPhones(List<Phone> phones) {
this.phones = phones;
}
//omit getter and setter
}
Phone Entity
#Entity
#Table(name = "phone")
public class Phone {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private long id;
#Column(name = "number")
private String number;
#ManyToOne(cascade = CascadeType.PERSIST)
private Person person;
public String getNumber() {
return number;
}
public void setNumber(String number) {
this.number = number;
}
public Person getPerson() {
return person;
}
public void setPerson(Person person) {
this.person = person;
}
public long getId() {
return id;
}
public void setId(long id) {
this.id = id;
}
//ommit setter and getter
}
Phone Dao
public interface PhoneDao {
public Phone save(Phone phone);
}
PhoneDaoImpl
#Repository
public class PhoneDaoImpl implements PhoneDao {
#PersistenceContext
private EntityManager entityManager;
#Override
public Phone save(Phone phone) {
return entityManager.merge(phone);
}
}
PersonDaoImpl
#Repository
public class PersonDaoImpl implements PersonDao{
#PersistenceContext
private EntityManager entityManager;
#Override
public Person save(Person person) {
return entityManager.merge(person);
}
}
Test Method
#Test
#Transactional
#Commit
public void say()
{
Phone phone = new Phone();
phone.setNumber("jghjkhk");
Person person = new Person();
person.setUsername("7576");
phone.setPerson(person);
Phone pers = phoneDao.save(phone);
Assert.assertNotNull(pers);
}
Try now. It will work.
I think that you need to set the value of the person->id and then also use an getter method to pass the id to your phone object instead of passing the person object
Normally people have hibernate set the id of an entity automatically with a surrogate key.
public class Person {
#Id #GeneratedValue // should pick an appropriate strategy here
private long id;
Since you don't have that you must either add it or set it yourself.
Person p = new Person();
p.setId(1); // hopefully unique
The same goes for phone.
As you are not having any generation type on your #Id and id is the primary key which can not be null so either you have to set value of id or have #GeneratedValue annotation on your id field and set strategy either as Auto or Identity.
You can also have your own sequence generation.
Also, you need to do same for the Phone class.

Spring data unusual query

i have a problem with spring data queries. I want to receive Users (List) where user has given role ( role is in a list ) and assignedOrders of this user (list) is empty or this orders are in given state (State is enum class).
I came up with such query:
List<User> findAllByRoleContainsAndOrdersAssignedStateIsNullOrOrdersAssignedStateEquals(State state,Role role);
but it do not work at all. It is complicated and i've never created such query before. Can you help me with that? Thanks a lot !
EDIT:
Piece of code :
#Entity
public class User implements Persistable {
...........
#Column(name = "ROLES")
#Enumerated(EnumType.STRING)
#ElementCollection(targetClass = Role.class)
private List<Role> role;
..........
#OneToMany(fetch = FetchType.LAZY, mappedBy = "driver")
private List<Order> ordersAssigned = new ArrayList<>();
}
#Entity
public class Order implements Persistable {
...........
#JoinColumn(name = "DRIVER_ID")
#ManyToOne(fetch = FetchType.LAZY,cascade = CascadeType.PERSIST)
private User driver;
#Column(name = "STATE")
#Enumerated(EnumType.STRING)
private State state;
.............
}
Try something like this:
#Entity
public class User {
private String name;
#ElementCollection
#Column(name = "role")
private List<Role> roles = new ArrayList<>();
#OneToMany(mappedBy = "user")
private List<Order> orders = new ArrayList<>();
public User(String name, Role... roles) {
this.name = name;
this.roles.addAll(asList(roles));
}
public enum Role {
ROLE1, ROLE2
}
}
#Entity
public class Order {
private String title;
#ManyToOne
private User user;
private State state;
public enum State {
STATE1, STATE2
}
}
public interface UserRepository extends JpaRepository<User, Long> {
#Query("select distinct u from User u join u.orders o join u.roles r where r = ?1 and (o.state is null or o.state = ?2)")
List<User> getUsers(User.Role role, Order.State state);
}
Working example.
More info is here and here.

Categories