From 8ae05bd196fdd82b61a541f3ae46ef613f3321d7 Mon Sep 17 00:00:00 2001 From: lipido Date: Fri, 17 Nov 2017 13:33:56 +0100 Subject: [PATCH] Adds Projects and ProjectsTests and ensures robust delete operations of all entities This commit does these things: - Adds Projects and ProjectsTest for basic operations with Project. - Ensures that delete operations preserves the database integrity (i.e. no FK constraint exceptions). We do this in several ways: - By adding CascadeType.REMOVE in @OneToMany where appropriate (e.g: Projects->ProjectAssignment) - By manually setting to NULL orphan entities (e.g: Department->Employee) - By manually removing orphan entities when no @OneToMany is available (e.g: Employee->ProjectAssignment) - Adds CascadeType.PERSIST in Project->ProjectAssignment to automatically persist ProjectAssignments that were added to a given Project. - Refactors tests to avoid repetitive JDBC code that inserts some registers before the test. --- .../siexample/persistence/Departments.java | 2 +- .../siexample/persistence/Employees.java | 20 ++- .../dgpena/siexample/persistence/Project.java | 16 +- .../siexample/persistence/Projects.java | 26 +++ .../persistence/DepartmentsTest.java | 52 +++--- .../siexample/persistence/EmployeesTest.java | 61 ++++++- .../siexample/persistence/ProjectsTest.java | 163 ++++++++++++++++++ 7 files changed, 303 insertions(+), 37 deletions(-) create mode 100644 src/main/java/dgpena/siexample/persistence/Projects.java create mode 100644 src/test/java/dgpena/siexample/persistence/ProjectsTest.java diff --git a/src/main/java/dgpena/siexample/persistence/Departments.java b/src/main/java/dgpena/siexample/persistence/Departments.java index a3b5c2b..cc99f69 100644 --- a/src/main/java/dgpena/siexample/persistence/Departments.java +++ b/src/main/java/dgpena/siexample/persistence/Departments.java @@ -21,7 +21,7 @@ public class Departments { } public void deleteDepartment(Department d) { - // remove first my employees + // unassign employees to this department to preserve DB integrity for (Employee e: d.getEmployees()) { e.setDepartment(null); } diff --git a/src/main/java/dgpena/siexample/persistence/Employees.java b/src/main/java/dgpena/siexample/persistence/Employees.java index 95d24d5..c9eee75 100644 --- a/src/main/java/dgpena/siexample/persistence/Employees.java +++ b/src/main/java/dgpena/siexample/persistence/Employees.java @@ -1,5 +1,7 @@ package dgpena.siexample.persistence; +import java.util.List; + import javax.persistence.EntityManager; public class Employees { @@ -13,5 +15,21 @@ public class Employees { public void addNewEmployee(Employee e) { this.em.persist(e); } - + + public Employee findById(int id) { + return em.find(Employee.class, id); + } + + public void deleteEmployee(Employee e) { + // First remove his/her project assignments (in order to preserve database integrity) + List projectAssignments = this.em.createQuery("SELECT pa FROM ProjectAssignment pa WHERE " + + "pa.employee = :e") + .setParameter("e", e).getResultList(); + + for (ProjectAssignment pa: projectAssignments) { + em.remove(pa); + } + + this.em.remove(e); + } } diff --git a/src/main/java/dgpena/siexample/persistence/Project.java b/src/main/java/dgpena/siexample/persistence/Project.java index 78d6b19..04a8cb8 100644 --- a/src/main/java/dgpena/siexample/persistence/Project.java +++ b/src/main/java/dgpena/siexample/persistence/Project.java @@ -1,8 +1,10 @@ package dgpena.siexample.persistence; +import java.util.Date; import java.util.HashSet; import java.util.Set; +import javax.persistence.CascadeType; import javax.persistence.Entity; import javax.persistence.GeneratedValue; import javax.persistence.GenerationType; @@ -18,7 +20,7 @@ public class Project { private String name; - @OneToMany(mappedBy="project") + @OneToMany(mappedBy="project", cascade = {CascadeType.REMOVE, CascadeType.PERSIST}) private Set projectAssignments = new HashSet<>(); public int getId() { @@ -43,6 +45,15 @@ public class Project { return employees; } + public void addEmployee(Employee e, Date startDate) { + + ProjectAssignment pa = new ProjectAssignment(); + pa.setProject(this); + pa.setStartDate(startDate); + pa.setEmployee(e); + // the ProjectAssignment is automatically persisted due to CascadeType.PERSIST + } + void internalRemoveProjectAssignment(ProjectAssignment projectAssignment) { this.projectAssignments.remove(projectAssignment); @@ -50,7 +61,8 @@ public class Project { void internalAddProjectAssignment(ProjectAssignment projectAssignment) { this.projectAssignments.add(projectAssignment); - } + + } diff --git a/src/main/java/dgpena/siexample/persistence/Projects.java b/src/main/java/dgpena/siexample/persistence/Projects.java new file mode 100644 index 0000000..268d9aa --- /dev/null +++ b/src/main/java/dgpena/siexample/persistence/Projects.java @@ -0,0 +1,26 @@ +package dgpena.siexample.persistence; + +import javax.persistence.EntityManager; + +public class Projects { + + private EntityManager em; + + public Projects(EntityManager em) { + this.em = em; + } + + public void addNewProject(Project p) { + em.persist(p); + } + + public void deleteProject(Project p) { + // ProjectAssingment related instances are automatically removed due to CascadeType.REMOVE + em.remove(p); + } + + public Project findById(int id) { + return em.find(Project.class, id); + } + +} diff --git a/src/test/java/dgpena/siexample/persistence/DepartmentsTest.java b/src/test/java/dgpena/siexample/persistence/DepartmentsTest.java index fc2a628..b436607 100644 --- a/src/test/java/dgpena/siexample/persistence/DepartmentsTest.java +++ b/src/test/java/dgpena/siexample/persistence/DepartmentsTest.java @@ -44,9 +44,7 @@ public class DepartmentsTest extends SQLBasedTest { @Test public void testFindById() throws SQLException { // insert a department previously with JDBC - Statement statement = jdbcConnection.createStatement(); - statement.executeUpdate("INSERT INTO Department(name) values('finanzas')", Statement.RETURN_GENERATED_KEYS); - int deptId = getLastInsertedId(statement); + int deptId = insertADepartment("finanzas"); EntityManager em = emf.createEntityManager(); Departments depts = new Departments(em); @@ -56,18 +54,14 @@ public class DepartmentsTest extends SQLBasedTest { assertEquals("finanzas", d.getName()); } - + @Test public void testFindByIdWithEmployees() throws SQLException { // insert a department previously with JDBC - Statement statement = jdbcConnection.createStatement(); - statement.executeUpdate("INSERT INTO Department(name) values('finanzas')", Statement.RETURN_GENERATED_KEYS); - int deptId = getLastInsertedId(statement); + int deptId = insertADepartment("finanzas"); // insert an employee in this department previously with JDBC - statement = jdbcConnection.createStatement(); - statement.executeUpdate("INSERT INTO Employee(name, department_id) values('pepe', "+deptId+")", Statement.RETURN_GENERATED_KEYS); - int empId = getLastInsertedId(statement); + int empId = insertAnEmployeeInADepartment(deptId); EntityManager em = emf.createEntityManager(); Departments depts = new Departments(em); @@ -78,13 +72,11 @@ public class DepartmentsTest extends SQLBasedTest { assertEquals(empId, departmentEmployees.iterator().next().getId()); } - + @Test public void testUpdateDepartment() throws SQLException { // insert a department previously with JDBC - Statement statement = jdbcConnection.createStatement(); - statement.executeUpdate("INSERT INTO Department(name) values('finanzas')", Statement.RETURN_GENERATED_KEYS); - int deptId = getLastInsertedId(statement); + int deptId = insertADepartment("finanzas"); EntityManager em = emf.createEntityManager(); Departments depts = new Departments(em); @@ -95,7 +87,7 @@ public class DepartmentsTest extends SQLBasedTest { em.getTransaction().commit(); // check in the DB using JDBC - statement = jdbcConnection.createStatement(); + Statement statement = jdbcConnection.createStatement(); ResultSet rs = statement.executeQuery("SELECT * FROM Department d where d.id = "+deptId); rs.next(); assertEquals("contabilidad", rs.getString("name")); @@ -105,9 +97,7 @@ public class DepartmentsTest extends SQLBasedTest { @Test public void testDeleteDepartment() throws SQLException { // insert a department previously with JDBC - Statement statement = jdbcConnection.createStatement(); - statement.executeUpdate("INSERT INTO Department(name) values('finanzas')", Statement.RETURN_GENERATED_KEYS); - int deptId = getLastInsertedId(statement); + int deptId = insertADepartment("finanzas"); EntityManager em = emf.createEntityManager(); Departments depts = new Departments(em); @@ -118,7 +108,7 @@ public class DepartmentsTest extends SQLBasedTest { em.getTransaction().commit(); // check in the DB using JDBC - statement = jdbcConnection.createStatement(); + Statement statement = jdbcConnection.createStatement(); ResultSet rs = statement.executeQuery("SELECT COUNT(*) as total FROM Department d where d.id = "+deptId); rs.next(); assertEquals(0, rs.getInt("total")); @@ -127,14 +117,12 @@ public class DepartmentsTest extends SQLBasedTest { @Test public void testDeleteDepartmentWithEmployees() throws SQLException { // insert a department previously with JDBC - Statement statement = jdbcConnection.createStatement(); - statement.executeUpdate("INSERT INTO Department(name) values('finanzas')", Statement.RETURN_GENERATED_KEYS); - int deptId = getLastInsertedId(statement); + int deptId = insertADepartment("finanzas"); // insert an employee in this department previously with JDBC - statement = jdbcConnection.createStatement(); - statement.executeUpdate("INSERT INTO Employee(name, department_id) values('pepe', "+deptId+")", Statement.RETURN_GENERATED_KEYS); - int empId = getLastInsertedId(statement); + insertAnEmployeeInADepartment(deptId); + + Statement statement; EntityManager em = emf.createEntityManager(); Departments depts = new Departments(em); @@ -151,7 +139,6 @@ public class DepartmentsTest extends SQLBasedTest { assertEquals(0, rs.getInt("total")); } - @Test public void testFindAllDepartments() throws SQLException { // insert a department previously with JDBC @@ -181,5 +168,16 @@ public class DepartmentsTest extends SQLBasedTest { assertTrue(namesToTest.isEmpty()); } - + + private int insertADepartment(String name) throws SQLException { + // insert a department previously with JDBC + Statement statement = jdbcConnection.createStatement(); + statement.executeUpdate("INSERT INTO Department(name) values('"+name+"')", Statement.RETURN_GENERATED_KEYS); + return getLastInsertedId(statement); + } + private int insertAnEmployeeInADepartment(int deptId) throws SQLException { + Statement statement = jdbcConnection.createStatement(); + statement.executeUpdate("INSERT INTO Employee(name, department_id) values('pepe', "+deptId+")", Statement.RETURN_GENERATED_KEYS); + return getLastInsertedId(statement); + } } diff --git a/src/test/java/dgpena/siexample/persistence/EmployeesTest.java b/src/test/java/dgpena/siexample/persistence/EmployeesTest.java index a1a5d31..ec0ba03 100644 --- a/src/test/java/dgpena/siexample/persistence/EmployeesTest.java +++ b/src/test/java/dgpena/siexample/persistence/EmployeesTest.java @@ -19,10 +19,8 @@ public class EmployeesTest extends SQLBasedTest { @Test public void testAddNewEmployee() throws SQLException { // insert a department previously with JDBC - Statement statement = jdbcConnection.createStatement(); - statement.executeUpdate("INSERT INTO Department(name) values('finanzas')", Statement.RETURN_GENERATED_KEYS); - int deptId = getLastInsertedId(statement); - + int deptId = insertADepartment(); + EntityManager em = emf.createEntityManager(); Departments depts = new Departments(em); @@ -47,7 +45,7 @@ public class EmployeesTest extends SQLBasedTest { int employeeId = e.getId(); - statement = jdbcConnection.createStatement(); + Statement statement = jdbcConnection.createStatement(); ResultSet res = statement.executeQuery("SELECT * from Employee where id = "+employeeId); res.next(); assertEquals(deptId, res.getInt("department_id")); @@ -55,5 +53,56 @@ public class EmployeesTest extends SQLBasedTest { } - + + @Test + public void testDeleteEmployeeWithProjectAssignment() throws SQLException { + + // insert an employee with JDBC + int employeeId = insertAnEmployee(); + + // create a project + int projectId = insertAProject(); + + // assign the employee to the project (which should be removed due to CascadeType.REMOVE + // in Project.projectAssignments) + Statement statement = jdbcConnection.createStatement(); + statement.executeUpdate("INSERT INTO ProjectAssignment(project_id, employee_id) values("+projectId+", " + +employeeId+")", Statement + .RETURN_GENERATED_KEYS); + + EntityManager em = emf.createEntityManager(); + + Employees employees = new Employees(em); + Employee e = employees.findById(employeeId); + + em.getTransaction().begin(); + employees.deleteEmployee(e); + em.getTransaction().commit(); + + statement = jdbcConnection.createStatement(); + ResultSet rs = statement.executeQuery("SELECT COUNT(*) as total FROM Employee e where e.id = "+employeeId); + rs.next(); + assertEquals(0, rs.getInt("total")); + } + + + private int insertADepartment() throws SQLException { + Statement statement = jdbcConnection.createStatement(); + statement.executeUpdate("INSERT INTO Department(name) values('finanzas')", Statement.RETURN_GENERATED_KEYS); + return getLastInsertedId(statement); + } + + private int insertAnEmployee() throws SQLException { + Statement statement = jdbcConnection.createStatement(); + statement.executeUpdate("INSERT INTO Employee(name) values('pepe')", Statement.RETURN_GENERATED_KEYS); + return getLastInsertedId(statement); + } + + private int insertAProject() throws SQLException { + Statement statement = jdbcConnection.createStatement(); + statement.executeUpdate("INSERT INTO Project(name) values('our best project')", Statement + .RETURN_GENERATED_KEYS); + return getLastInsertedId(statement); + } + } diff --git a/src/test/java/dgpena/siexample/persistence/ProjectsTest.java b/src/test/java/dgpena/siexample/persistence/ProjectsTest.java new file mode 100644 index 0000000..51028b0 --- /dev/null +++ b/src/test/java/dgpena/siexample/persistence/ProjectsTest.java @@ -0,0 +1,163 @@ +package dgpena.siexample.persistence; + +import static org.junit.Assert.assertEquals; + +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.Date; + +import javax.persistence.EntityManager; + +import org.junit.Test; + +public class ProjectsTest extends SQLBasedTest { + + @Test + public void testAddNewProject() throws SQLException { + EntityManager em = emf.createEntityManager(); + + Projects projects = new Projects(em); + + Project p = new Project(); + p.setName("Our best project"); + + + em.getTransaction().begin(); + + projects.addNewProject(p); + + em.getTransaction().commit(); + + int projectId = p.getId(); + + Statement statement = jdbcConnection.createStatement(); + ResultSet res = statement.executeQuery("SELECT COUNT(*) as total from Project where id = "+projectId); + res.next(); + assertEquals(1, res.getInt("total")); + } + + @Test + public void testAddEmployeeToProject() throws SQLException { + + // insert a project previously with JDBC + int projectId = insertAProject(); + + // insert an employee previously with JDBC + int employeeId = insertAnEmployee(); + + EntityManager em = emf.createEntityManager(); + + Projects projects = new Projects(em); + Project p = projects.findById(projectId); + + Employees employees = new Employees(em); + Employee e = employees.findById(employeeId); + + em.getTransaction().begin(); + + p.addEmployee(e, new Date()); + + em.getTransaction().commit(); + + + // ensure that a ProjectAssignment has been created + Statement statement = jdbcConnection.createStatement(); + ResultSet res = statement.executeQuery("SELECT COUNT(*) as total from ProjectAssignment where project_id = " + + ""+projectId+" and employee_id = "+employeeId); + res.next(); + assertEquals(1, res.getInt("total")); + } + + @Test + public void testCreateProjectAssignment() throws SQLException { + // insert a project previously with JDBC + int projectId = insertAProject(); + + // insert an employee previously with JDBC + int employeeId = insertAnEmployee(); + + EntityManager em = emf.createEntityManager(); + + em.getTransaction().begin(); + + Project p = new Projects(em).findById(projectId); + Employee e = new Employees(em).findById(employeeId); + + ProjectAssignment pa = new ProjectAssignment(); + pa.setProject(p); // pa is automatically added to the Project.projectAssignments collection + pa.setEmployee(e); + pa.setStartDate(new Date()); + // the ProjectAssignment is automatically persisted due to CascadeType.PERSIST present in Project.projectAssignments + + em.getTransaction().commit(); + + // ensure that a ProjectAssignment has been created using JDBC + Statement statement = jdbcConnection.createStatement(); + ResultSet res = statement.executeQuery("SELECT COUNT(*) as total from ProjectAssignment where project_id = " + + ""+projectId+" and employee_id = "+employeeId); + res.next(); + assertEquals(1, res.getInt("total")); + } + + @Test + public void testDeleteProject() throws SQLException { + // insert a project previously with JDBC + int projectId = insertAProject(); + + // insert an employee previously with JDBC + int employeeId = insertAnEmployee(); + + // assign the employee to the project (which should be removed due to CascadeType.REMOVE + // in Project.projectAssignments) + Statement statement = jdbcConnection.createStatement(); + statement.executeUpdate("INSERT INTO ProjectAssignment(project_id, employee_id) values("+projectId+", " + +employeeId+")", Statement + .RETURN_GENERATED_KEYS); + + EntityManager em = emf.createEntityManager(); + Projects projects = new Projects(em); + Project p = projects.findById(projectId); + + em.getTransaction().begin(); + projects.deleteProject(p); + em.getTransaction().commit(); + + // ensure that project doesn't exist using JDBC + statement = jdbcConnection.createStatement(); + ResultSet res = statement.executeQuery("SELECT COUNT(*) as total from Project where id = " + + ""+projectId); + res.next(); + assertEquals(0, res.getInt("total")); + } + + @Test + public void testGetEmployees() throws SQLException { + // insert a project previously with JDBC + int projectId = insertAProject(); + + // insert an employee previously with JDBC + int employeeId = insertAnEmployee(); + + // assign the employee to the project (which should be removed due to CascadeType.REMOVE + // in Project.projectAssignments) + Statement statement = jdbcConnection.createStatement(); + statement.executeUpdate("INSERT INTO ProjectAssignment(project_id, employee_id) values("+projectId+", " + +employeeId+")", Statement + .RETURN_GENERATED_KEYS); + } + + private int insertAProject() throws SQLException { + Statement statement = jdbcConnection.createStatement(); + statement.executeUpdate("INSERT INTO Project(name) values('our best project')", Statement + .RETURN_GENERATED_KEYS); + return getLastInsertedId(statement); + } + + private int insertAnEmployee() throws SQLException { + Statement statement = jdbcConnection.createStatement(); + statement.executeUpdate("INSERT INTO Employee(name) values('pepe')", Statement + .RETURN_GENERATED_KEYS); + return getLastInsertedId(statement); + } +} -- 2.18.1