Spring MVC can not delete a parent row? - mysql

I started to learn spring mvc. I was doing some practises and i encountered a logical error. In my example I have 2 entity class in spring mvc. Down there my code school_ıd is the primary key. When I try to delete a school from school list I gave an error like this
com.mysql.jdbc.exceptions.jdbc4.MySQLIntegrityConstraintViolationException: Cannot delete or update a parent row: a foreign key constraint fails
Entity school:
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="school_id")
private int schoolId;
#Column(name="school_name")
private String schoolName;
public int getSchoolId() {
return schoolId;
}
public void setSchoolId(int schoolId) {
this.schoolId = schoolId;
}
public String getSchoolName() {
return schoolName;
}
public void setSchoolName(String schoolName) {
this.schoolName = schoolName;
}
Entity Student
#Id
#GeneratedValue(strategy = GenerationType.IDENTITY)
#Column(name="id")
private int id;
#Column(name="first_name")
private String firstName;
#Column(name="last_name")
private String lastName;
#Column(name="email")
private String email;
#OneToOne
#JoinColumn(name="school_id")
private School school;
public School getSchool() {
return school;
}
public void setSchool(School school) {
this.school = school;
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
School Controller delete method:
#GetMapping("/deleteSchool")
public String deleteSchool(#RequestParam("schoolID") int
theSchoolId) {
schoolService.deleteSchool(theSchoolId);
return "redirect:/school/list";
}
Delete Method SchoolDAOImpl:
#Override
public void deleteSchool(int theSchoolId) {
Session currentSession=sessionFactory.getCurrentSession();
Query theQuery=currentSession.createQuery("delete from School where id=:schoolID");
theQuery.setParameter("schoolID", theSchoolId);
theQuery.executeUpdate();
}
Actually i now the problem is i attemp to delete a school, theSchool has at least 1 student because of that i cannot delete a school. For this first i need to delete child in this example child is student after delete parent(school).
But i think my scenario does not avaliable for this. Please help me what should ı do?

You can update Student first:
#Override
public void deleteSchool(int theSchoolId) {
Session currentSession=sessionFactory.getCurrentSession();
Query updateStudentQuery=currentSession.createQuery("Update Student s SET s.school = null WHERE s.school.schoolId = :schoolId");
updateStudentQuery.setParameter("schoolID", theSchoolId);
updateStudentQuery.executeUpdate();
Query theQuery=currentSession.createQuery("delete from School where id=:schoolID");
theQuery.setParameter("schoolID", theSchoolId);
theQuery.executeUpdate();
}
or update using Stundent's DAO and then exec the school's delete query.
I couldnt test so i dont know if it works the "WHERE s.school.schoolId = :schoolId" or you have to make a where with a subquery. If doesn't works, try with a native query:
Query updateStudentQuery=currentSession.createNativeQuery("sql here");
Instead of
Query updateStudentQuery=currentSession.createQuery("Update Student s SET s.school = null WHERE s.school.schoolId = :schoolId");
Also, if you want to make the relation bidirectional, add this to School Entity:
#OneToOne(mappedBy = "school")
private Student student;
By the way, i guess that the relation is #ManyToOne and not #OneToOne, isn't it?

You have to delete the integrity constraint of whatever the DB you are using or you should first delete the child and then parent or you can look the cascade rule of that in hibernate https://thoughts-on-java.org/hibernate-tips-how-to-delete-child-entities/

Related

How to solve HHH000346 Error using hibernate 5 and mysql?

I'm studying restful service and views.
Regarding it, I use mysql and hibernate 5.
My data tables are two and have reference relation.
The problem occurs when I update the primary key.
When I add new one then update existing data in another table (they have reference relation), HHH000346: Error during managed flush occurs.
I already search on google, but I couldn't find the answer.
This is my Entity classes.
#Entity
#Table(name = "users")
#EntityListeners(AuditingEntityListener.class)
public class User {
private long serial;
private String username;
private String password;
public User() {
}
public User(long serial, String username, String password) {
setSerial(serial);
setUsername(username);
setPassword(password);
}
#Column(name = "serial", nullable = false)
#GeneratedValue(strategy = GenerationType.AUTO)
public long getSerial() {
return serial;
}
public void setSerial(long serial) {
this.serial = serial;
}
#Id
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
#Column(name = "password", nullable = false)
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
#Override
public String toString() {
return "serial: " + this.serial + ", username: " + this.username + ", password: " + this.password;
}
}
Entity
#Table(name = "sites")
#EntityListeners(AuditingEntityListener.class)
#IdClass(Site.class)
public class Site implements Serializable{
#ManyToOne
#JoinColumn(name="username",foreignKey=#ForeignKey(name="username"))
private String username;
private String siteURL;
#Id
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
#Id
public String getSiteURL() {
return siteURL;
}
public void setSiteURL(String siteName) {
this.siteURL = siteName;
}
}
And this is class had problem.
public class UserController {
#Autowired
private UserRepository userRepository;
#Autowired
private SiteRepository siteRepository;
private CryptoUtil passwordEncoder = new CryptoUtil();
...
#PutMapping("/users/{username}")
public User updateUser(#PathVariable(value = "username") String username, #Valid #RequestBody User userDetails)
throws ResourceNotFoundException {
User user = userRepository.findById(username)
.orElseThrow(() -> new ResourceNotFoundException("User not found on :: " + username));
List<Site> sites = siteRepository.findByUsername(user.getUsername());
userDetails.setPassword(passwordEncoder.encryptSHA256(userDetails.getPassword()));
final User updateUser = userRepository.save(userDetails);
for (Site site : sites)
{
site.setUsername(userDetails.getUsername());
site = siteRepository.save(site);
}
userRepository.delete(user);
return updateUser;
}
....
}
The for-each statement occurs error.
PLEASE HELP ME
Why did you do this?
#ManyToOne
#JoinColumn(name="username",foreignKey=#ForeignKey(name="username"))
private String username;
It should be:
#ManyToOne
#JoinColumn(name="username",foreignKey=#ForeignKey(name="username"))
private User user;
I'll also suggest you to use the primary key as foreign key.
And you can't have multiple #Id in an entity.

Spring Boot: Saving a one to many json request, foreign key is not saved automatically

I have 2 entities, Role and Resource. A role can have many resources.
#Entity
public class Resource {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
#Column(name="firstname")
private String firstName;
#Column(name="lastname")
private String lastName;
private String email;
#ManyToOne
#JoinColumn(name="roleId", nullable = false)
private Role role;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
public Role getRole() {
return role;
}
public void setRole(Role role) {
this.role = role;
}
}
#Entity
public class Role {
#Id
#GeneratedValue(strategy = GenerationType.AUTO)
private int id;
#Column(name = "rolename")
private String roleName;
#OneToMany(mappedBy = "role", cascade = CascadeType.ALL)
private List<Resource> resources;
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getRoleName() {
return roleName;
}
public void setRoleName(String roleName) {
this.roleName = roleName;
}
public List<Resource> getResources() {
return resources;
}
public void setResources(List<Resource> resources) {
this.resources = resources;
}
}
I'm trying to save a Role object that has a resource in it. This is the body of my json in postman.
{
"roleName" : "Business Analyst",
"resources" : [{
"firstName" : "John",
"lastName" : "Doe",
"email" : "John#Doe.com"
}]
}
http post call in postman:
http://localhost:8080/app/admin/roles/role
Role Controller
#RestController
#RequestMapping(value="/admin/roles")
public class RoleController {
#Autowired
private RoleService roleService;
private static final Logger log = LoggerFactory.getLogger(RoleController.class);
#RequestMapping(value="/role", method = RequestMethod.POST)
public ResponseEntity<?> addRole(#RequestBody Role role, UriComponentsBuilder ucBuilder){
log.info("Adding Role {}" + role);
log.info("Adding Rolename:" + role.getRoleName());
roleService.addRole(role);
HttpHeaders headers = new HttpHeaders();
headers.setLocation(ucBuilder.path("/admin/roles/role/{id}").buildAndExpand(role.getId()).toUri());
return new ResponseEntity<String> (headers,HttpStatus.CREATED);
}
#RequestMapping(value="role", method = RequestMethod.GET)
public ResponseEntity<List<Role>> listAllRoles(){
List<Role> roles = roleService.getAllRoles();
return new ResponseEntity<List<Role>>(roles, HttpStatus.OK);
}
}
RoleRepository
public interface RoleRepository extends CrudRepository<Role, Integer> {
}
RoleService
public interface RoleService {
public void addRole(Role role);
}
RoleServiceImpl
#Service
public class RoleServiceImpl implements RoleService {
#Autowired
private RoleRepository roleRepository;
#Override
public void addRole(Role role) {
roleRepository.save(role);
}
}
Whats happening is, the role Business Analyst gets save in the roleName field of Role table. The id of the said row is auto generated. At the same time, the resource with firstName = John, lastName = Doe and email = John#Doe.com gets save in the Resource table.
However, the role_id is not being saved automatically in the Resource table so now it is null ( the table Resource has the role_id set to nullable ). I was expecting that when I do the json post, the data will be automatically saved in the Role table and also the Resource table. Both of these are happening except that the role_id is not being saved. What did I miss?
Change addRole like below :
public void addRole(Role role) {
for(Resource resource: role.getResources()){
resource.setRole(role);
}
roleRepository.save(role);
}

In Spring Data how to read entity by foreign key value instead of join?

I am using Spring Data and I am in doubt why even after declaring foreign entities as Lazy loaded they are getting eagerly loaded for this method:
findByReportingManager_IdAndAndLevel(Long reporterManagerId, Integer level)
On logs, I can see the query as:
select userhierar0_.id as id1_28_,
userhierar0_.LEVEL as LEVEL5_28_,
userhierar0_.REPORTING_MANAGER_ID as REPORTIN9_28_,
userhierar0_.USER_ID as USER_ID10_28_
from USER_HIERARCHY userhierar0_
left outer join
USER_V3 user1_ on userhierar0_.REPORTING_MANAGER_ID=user1_.id
where user1_.id=? and userhierar0_.CUSTOMER_ID=? and userhierar0_.LEVEL=?
why extra join even if I am passing reporting manager id ?
UserHierarchy Class:
#Entity
#Table(name = "USER_HIERARCHY")
#JsonInclude(JsonInclude.Include.NON_EMPTY)
public class UserHierarchy {
#Id
#GeneratedValue
private Long id;
#ManyToOne(fetch = FetchType.LAZY) // LAZY LOADING
#JoinColumn(name = "USER_ID",referencedColumnName = "ID")
private User user;
#ManyToOne(fetch = FetchType.LAZY) //LAZY LOADING
#JoinColumn(name = "REPORTING_MANAGER_ID", referencedColumnName = "ID")
private User reportingManager;
#Column(name = "LEVEL")
private Integer level;
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public User getUser() {
return user;
}
public void setUser(User user) {
this.user = user;
}
public User getReportingManager() {
return reportingManager;
}
public void setReportingManager(User reportingManager) {
this.reportingManager = reportingManager;
}
public Integer getLevel() {
return level;
}
public void setLevel(Integer level) {
this.level = level;
}
#Override
public String toString() {
return ReflectionToStringBuilder.toStringExclude(this, "user", "reportingManager");
}
User Entity class
#Entity
#Table(name = "USER")
public class User {
#Id
#GeneratedValue
private Long id;
#Column(name = "EMAIL")
private String email;
#Column(name = "CUSTOMER_ID")
private Long customerId;
#Column(name = "STATUS")
private String status;
// Getter and Setter
As per Spring's doc:
At query creation time you already make sure that the parsed property
is a property of the managed domain class.
So does that mean in order to make User object in "managed state" it uses join or I am wrong in the implementation ?
I stumbled across the same problem recently and it seems that there is no solution for this at the moment in Spring Data.
However I've created a ticket for it.
If you go with Criteria API or JPQL for this particular query, then it will work properly.

Adding a calculated field on jpa + jackson

How to add a calculated field to an entity?
DB table
table person {
id number,
first_name varchar,
last_name varchar,
...
}
Java entity
public class person {
BigDecimal id;
String firstName;
String lastName;
...//getters and setters
//what to add here???
public String getFullName() {
return firstName + " " + lastname;
}
}
I tried adding #Transient, but the field is ignored when converting to json.
I tried just leaving the method there, throws an exception that the setter is missing. adding the setter throws another exception that the field does not exist in the DB.
I tried adding #Transient and #JsonPropert, but the field is ignored when converting to json.
I tried adding #Formula, but hibernate (i think) says it is not implemented.
The idea is to have a simple calculated field that is ignored by jpa/hibernate but is used by jackson.
How can I accomplish this?
EDIT
Example full class
#Entity
#Table(name="FDF_PATIENT_COUNTIE")
#JsonIdentityInfo(generator = JSOGGenerator.class)
#JsonIgnoreProperties(ignoreUnknown = true)
#Audited
public class PatientCounty extends FgaBaseClass {
private static final long serialVersionUID = 1425318521043179798L;
private BigDecimal id;
private County FCounties;
private Patient patients;
public PatientCounty() {
}
public PatientCounty(County FCounties, Patient patients) {
this.FCounties = FCounties;
this.patients = patients;
}
#SequenceGenerator(name="generator", sequenceName="FDF_PATIENT_COUNTIE_SEQ")
#Id
#GeneratedValue(strategy=SEQUENCE, generator="generator")
#Column(name="ID", unique=true, nullable=false, precision=22, scale=0)
public BigDecimal getId() {
return this.id;
}
public void setId(BigDecimal id) {
this.id = id;
}
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name="ID_F_COUNTIE")
public County getFCounties() {
return this.FCounties;
}
public void setFCounties(County FCounties) {
this.FCounties = FCounties;
}
#ManyToOne(fetch=FetchType.LAZY)
#JoinColumn(name="ID_FDF_PATIENT")
public Patient getPatients() {
return this.patients;
}
public void setPatients(Patient patients) {
this.patients = patients;
}
}

java.sql.SQLException: Field 'passwordConfirmation' doesn't have a default value

I've been scratching my head over this error for sometime now but can't work it out. I've been using Hibernate 3 to persist User entity. But I am getting java.sql.SQLException: Field 'passwordConfirmation' doesn't have a default value error when I try to do that.
Following is my User entity:
User.java
#Entity
public class User implements Serializable {
private Long id;
private String email;
private String password;
private String passwordConfirmation;
private String captcha;
#Id #GeneratedValue
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
#NotEmpty
#Email
public String getEmail() {
return email;
}
public void setEmail(String email) {
this.email = email;
}
#NotEmpty
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
#NotEmpty
#Transient
public String getPasswordConfirmation() {
return passwordConfirmation;
}
public void setPasswordConfirmation(String passwordConfirmation) {
this.passwordConfirmation = passwordConfirmation;
}
}
Could someone help me understand that why am I getting this error?
passwordConfirmation property in your class is marked as #Transient, although error indicates that column for that field exists in the database schema.
If your database schema was generated automatically by Hibenrate, perhaps it had been done before you added a #Transient annotation. If so, you need to regenerate your schema.
I was mistakenly using a different schema than the one I was generating using Hibernate. Both the schema had passwordConfirmation field in the User entity so got confused.
I've got it working now. Thanks :)