blob: 229423c8080e985c144fbc3907512f0113ff2c58 [file] [log] [blame]
/*
* Copyright (c) 2005, 2021 Oracle and/or its affiliates. All rights reserved.
* Copyright (c) 2005, 2015 SAP. All rights reserved.
*
* This program and the accompanying materials are made available under the
* terms of the Eclipse Public License v. 2.0 which is available at
* http://www.eclipse.org/legal/epl-2.0,
* or the Eclipse Distribution License v. 1.0 which is available at
* http://www.eclipse.org/org/documents/edl-v10.php.
*
* SPDX-License-Identifier: EPL-2.0 OR BSD-3-Clause
*/
// Contributors:
// SAP - initial API and implementation
package org.eclipse.persistence.testing.tests.wdf.jpa1.relation;
import java.io.IOException;
import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.ResultSet;
import java.sql.SQLException;
import java.util.Collections;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
import jakarta.persistence.EntityManager;
import javax.sql.DataSource;
import org.eclipse.persistence.testing.framework.wdf.AbstractBaseTest;
import org.eclipse.persistence.testing.framework.wdf.Bugzilla;
import org.eclipse.persistence.testing.framework.wdf.JPAEnvironment;
import org.eclipse.persistence.testing.models.wdf.jpa1.employee.Bicycle;
import org.eclipse.persistence.testing.models.wdf.jpa1.employee.Department;
import org.eclipse.persistence.testing.models.wdf.jpa1.employee.Employee;
import org.eclipse.persistence.testing.models.wdf.jpa1.employee.Item_Byte;
import org.eclipse.persistence.testing.models.wdf.jpa1.employee.Project;
import org.eclipse.persistence.testing.models.wdf.jpa1.employee.TravelProfile;
import org.eclipse.persistence.testing.models.wdf.jpa1.employee.Vehicle;
import org.eclipse.persistence.testing.tests.wdf.jpa1.JPA1Base;
import org.junit.Before;
import org.junit.Test;
@SuppressWarnings("unchecked")
public class TestBidirectionalManyToMany extends JPA1Base {
private static final int HANS_ID_VALUE = 1;
private static final Integer HANS_ID = HANS_ID_VALUE;
private static final Set<Pair> HANS_SET = new HashSet<Pair>();
private static final int FRED_ID_VALUE = 2;
private static final Integer FRED_ID = FRED_ID_VALUE;
private static final Set<Pair> FRED_SET = new HashSet<Pair>();
private static final Set<Pair> SEED_SET = new HashSet<Pair>();
private static final Project PUHLEN = new Project("G\u00fcrteltiere puhlen");
private static final Project PINSELN = new Project("B\u00e4uche pinseln");
private static final Project FALTEN = new Project("Zitronen falten");
private static final Project ZAEHLEN = new Project("Erbsen z\u00e4hlen");
private static final Project LINKEN = new Project("Eclipse Linken");
@Before
public void initData() throws SQLException {
/*
* 5 Projects:
*/
clearAllTables();
JPAEnvironment env = getEnvironment();
EntityManager em = env.getEntityManager();
try {
env.beginTransaction(em);
Department dep = new Department(17, "diverses");
em.persist(dep);
em.persist(PUHLEN);
em.persist(PINSELN);
em.persist(FALTEN);
em.persist(ZAEHLEN);
em.persist(LINKEN);
Employee hans = new Employee(HANS_ID_VALUE, "Hans", "Wurst", dep);
Set<Project> hansProjects = new HashSet<Project>();
hansProjects.add(PUHLEN);
hansProjects.add(PINSELN);
hansProjects.add(FALTEN);
hans.setProjects(hansProjects);
Employee fred = new Employee(FRED_ID_VALUE, "Fred von", "Jupiter", dep);
Set<Project> fredProjects = new HashSet<Project>();
fredProjects.add(FALTEN);
fredProjects.add(ZAEHLEN);
fred.setProjects(fredProjects);
em.persist(hans);
em.persist(fred);
env.commitTransactionAndClear(em);
HANS_SET.clear();
for (final Project element : hansProjects) {
HANS_SET.add(new Pair(HANS_ID_VALUE, element.getId()));
}
FRED_SET.clear();
for (final Project element : fredProjects) {
FRED_SET.add(new Pair(FRED_ID_VALUE, element.getId()));
}
SEED_SET.clear();
SEED_SET.addAll(HANS_SET);
SEED_SET.addAll(FRED_SET);
} finally {
env.evictAll(em);
closeEntityManager(em);
}
}
@Test
public void testUnchanged() throws SQLException {
JPAEnvironment env = getEnvironment();
EntityManager em = env.getEntityManager();
try {
// 1. do nothing
env.beginTransaction(em);
Employee emp = em.find(Employee.class, HANS_ID);
// do nothing
emp.clearPostUpdate();
env.commitTransactionAndClear(em);
verify(!emp.postUpdateWasCalled(), "postUpdate was called");
checkJoinTable(SEED_SET);
// 2. touch the projects
env.beginTransaction(em);
emp = em.find(Employee.class, HANS_ID);
emp.getProjects().size();
emp.clearPostUpdate();
env.commitTransactionAndClear(em);
verify(!emp.postUpdateWasCalled(), "postUpdate was called");
checkJoinTable(SEED_SET);
// 3. trivial update
env.beginTransaction(em);
emp = em.find(Employee.class, HANS_ID);
Set<Project> projects = emp.getProjects();
emp.setProjects(new HashSet<Project>(projects));
emp.clearPostUpdate();
env.commitTransactionAndClear(em);
verify(!emp.postUpdateWasCalled(), "postUpdate was called");
checkJoinTable(SEED_SET);
} finally {
closeEntityManager(em);
}
}
private void checkJoinTable(final Set<Pair> expected) throws SQLException {
DataSource ds = getEnvironment().getDataSource();
Connection conn = ds.getConnection();
Set<Pair> actual = new HashSet<Pair>();
try {
PreparedStatement pstmt = conn.prepareStatement("SELECT EMP_ID, PROJECT_ID FROM TMP_EMP_PROJECT");
try {
ResultSet rs = pstmt.executeQuery();
try {
while (rs.next()) {
actual.add(new Pair(rs.getInt("EMP_ID"), rs.getInt("PROJECT_ID")));
}
} finally {
rs.close();
}
} finally {
pstmt.close();
}
} finally {
conn.close();
}
if (expected.size() != actual.size()) {
flop("actual set has wrong size " + actual.size() + " expected: " + expected.size());
} else {
verify(true, "");
}
verify(expected.containsAll(actual), "actual set contains some elements missing in the expected set");
verify(actual.containsAll(expected), "expected set contains some elements missing in the actual set");
}
@Test
public void testDeleteEmployee() throws SQLException {
JPAEnvironment env = getEnvironment();
EntityManager em = env.getEntityManager();
try {
env.beginTransaction(em);
Employee emp = em.find(Employee.class, HANS_ID);
verify(emp != null, "employee not found");
em.remove(emp);
env.commitTransactionAndClear(em);
checkJoinTable(FRED_SET);
} finally {
closeEntityManager(em);
}
}
@Test
public void testDeleteNonSharedProject() throws SQLException {
// remove a project that is not shared between employees:
// 1) remove project from set of projects of employee
// 2) remove employee from set of employees of project
// 3) remove the project from the database
JPAEnvironment env = getEnvironment();
EntityManager em = env.getEntityManager();
try {
final int removeId = PUHLEN.getId();
env.beginTransaction(em);
Employee emp = em.find(Employee.class, HANS_ID);
verify(emp != null, "employee not found");
Set<Project> projects = emp.getProjects();
verify(projects != null, "projects are null");
verify(projects.size() == 3, "not exactly 3 projects but " + projects.size());
Iterator<Project> iter = projects.iterator();
while (iter.hasNext()) {
Project project = iter.next();
if (project.getId() == removeId) {
Set<Employee> employeesOfProject = project.getEmployees();
employeesOfProject.remove(emp);
em.remove(project);
iter.remove();
break;
}
}
verify(projects.size() == 2, "no project removed");
emp.clearPostUpdate();
env.commitTransactionAndClear(em);
verify(emp.postUpdateWasCalled(), "post update was not called");
Set<Pair> expected = new HashSet<Pair>(SEED_SET);
expected.remove(new Pair(HANS_ID_VALUE, removeId));
checkJoinTable(expected);
env.beginTransaction(em);
emp = em.find(Employee.class, HANS_ID);
projects = emp.getProjects();
verify(projects.size() == 2, "not exactly 2 projects but " + projects.size());
Object object = em.find(Project.class, removeId);
verify(object == null, "project found");
env.rollbackTransactionAndClear(em);
} finally {
closeEntityManager(em);
}
}
@Test
public void testDeleteSharedProject() throws SQLException {
// remove a project that is shared between employees:
// 1) remove project from set of projects of employee
// 2) remove employee from set of employees of project
// 3) don't remove the project from the database
JPAEnvironment env = getEnvironment();
EntityManager em = env.getEntityManager();
try {
final int REMOVE_ID = FALTEN.getId();
env.beginTransaction(em);
Employee emp = em.find(Employee.class, HANS_ID);
verify(emp != null, "employee not found");
Set<Project> projects = emp.getProjects();
verify(projects != null, "projects are null");
verify(projects.size() == 3, "not exactly 3 projects but " + projects.size());
Iterator<Project> iter = projects.iterator();
while (iter.hasNext()) {
Project project = iter.next();
if (project.getId() == REMOVE_ID) {
Set<Employee> employeesOfProject = project.getEmployees();
employeesOfProject.remove(emp);
iter.remove();
}
}
verify(projects.size() == 2, "no project removed");
emp.clearPostUpdate();
env.commitTransactionAndClear(em);
verify(emp.postUpdateWasCalled(), "post update was not called");
Set<Pair> expected = new HashSet<Pair>(SEED_SET);
expected.remove(new Pair(HANS_ID_VALUE, REMOVE_ID));
checkJoinTable(expected);
env.beginTransaction(em);
emp = em.find(Employee.class, HANS_ID);
projects = emp.getProjects();
verify(projects.size() == 2, "not exactly 2 projects but " + projects.size());
env.rollbackTransactionAndClear(em);
} finally {
closeEntityManager(em);
}
}
@Test
public void testAdd() throws SQLException {
JPAEnvironment env = getEnvironment();
EntityManager em = env.getEntityManager();
try {
env.beginTransaction(em);
final int newId;
Employee emp = em.find(Employee.class, HANS_ID);
verify(emp != null, "employee not found");
Set<Project> projects = emp.getProjects();
Project p6 = new Project("Nasen bohren");
em.persist(p6);
newId = p6.getId();
projects.add(p6);
emp.clearPostUpdate();
env.commitTransactionAndClear(em);
verify(emp.postUpdateWasCalled(), "post update was not called");
Set<Pair> expected = new HashSet<Pair>(SEED_SET);
expected.add(new Pair(HANS_ID_VALUE, newId));
checkJoinTable(expected);
env.beginTransaction(em);
emp = em.find(Employee.class, HANS_ID);
projects = emp.getProjects();
verify(projects.size() == 4, "not exactly 4 projects but " + projects.size());
env.rollbackTransactionAndClear(em);
} finally {
closeEntityManager(em);
}
}
@Test
public void testExchange() throws SQLException {
JPAEnvironment env = getEnvironment();
EntityManager em = env.getEntityManager();
try {
final int newId;
env.beginTransaction(em);
Employee emp = em.find(Employee.class, HANS_ID);
verify(emp != null, "employee not found");
Set<Project> projects = emp.getProjects();
Iterator<Project> iter = projects.iterator();
Project project = iter.next();
int removedId = project.getId();
// there are no managed relationships -> we have to remove the projects on both sides
em.remove(project);
iter.remove();
Project p7 = new Project("Ohren wacklen");
em.persist(p7);
newId = p7.getId();
projects.add(p7);
emp.clearPostUpdate();
env.commitTransactionAndClear(em);
verify(emp.postUpdateWasCalled(), "post update was not called");
Set<Pair> expected = new HashSet<Pair>(SEED_SET);
expected.remove(new Pair(HANS_ID_VALUE, removedId));
//we need to remove also record with Fred (if applicable), because if Fred had associated project, which is now removed,
//he is not associated with the project anymore
expected.remove(new Pair(FRED_ID_VALUE, removedId));
expected.add(new Pair(HANS_ID_VALUE, newId));
checkJoinTable(expected);
env.beginTransaction(em);
emp = em.find(Employee.class, HANS_ID);
projects = emp.getProjects();
verify(projects.size() == 3, "not exactly 3 projects but " + projects.size());
env.rollbackTransactionAndClear(em);
} finally {
env.evictAll(em);
closeEntityManager(em);
}
}
private void verifyEmployees(EntityManager em, int id, int size) {
Project project = em.find(Project.class, id);
verify(project != null, "project not found");
Set<Employee> employees = project.getEmployees();
verify(employees.size() == size, "wrong number of employees: " + employees.size() + " expected: " + size);
}
@Test
public void testGetEmployees() {
JPAEnvironment env = getEnvironment();
EntityManager em = env.getEntityManager();
try {
env.beginTransaction(em);
verifyEmployees(em, LINKEN.getId(), 0);
verifyEmployees(em, PUHLEN.getId(), 1);
verifyEmployees(em, FALTEN.getId(), 2);
env.rollbackTransactionAndClear(em);
} finally {
closeEntityManager(em);
}
}
@Test
public void testCopyProjectsToNew() throws SQLException {
// copy all projects from hans to a new employee with out actually touching them
JPAEnvironment env = getEnvironment();
EntityManager em = env.getEntityManager();
try {
final int newId = 19;
env.beginTransaction(em);
Employee hans = em.find(Employee.class, HANS_ID);
Employee paul = new Employee(19, "Paul", "Knack", null);
paul.setProjects(hans.getProjects());
em.persist(paul);
env.commitTransactionAndClear(em);
Set<Pair> expected = new HashSet<Pair>(SEED_SET);
for (Pair pair : HANS_SET) {
expected.add(new Pair(newId, pair.getProjectId()));
}
checkJoinTable(expected);
env.beginTransaction(em);
paul = em.find(Employee.class, newId);
verify(paul.getProjects().size() == HANS_SET.size(), "Paul has wrong number of projects");
env.rollbackTransactionAndClear(em);
} finally {
closeEntityManager(em);
}
}
@Bugzilla(bugid=300503)
@Test
public void testCopyProjectsToExisting() throws SQLException {
// copy all projects from hans to a new employee with out actually touching them
JPAEnvironment env = getEnvironment();
EntityManager em = env.getEntityManager();
try {
env.beginTransaction(em);
Employee hans = em.find(Employee.class, HANS_ID);
Employee fred = em.find(Employee.class, FRED_ID);
fred.setProjects(hans.getProjects());
env.commitTransactionAndClear(em);
Set<Pair> expected = new HashSet<Pair>(SEED_SET);
expected.removeAll(FRED_SET);
for (Pair pair : HANS_SET) {
expected.add(new Pair(FRED_ID_VALUE, pair.getProjectId()));
}
checkJoinTable(expected);
env.beginTransaction(em);
fred = em.find(Employee.class, FRED_ID);
verify(fred.getProjects().size() == HANS_SET.size(), "Paul has wrong number of projects");
env.rollbackTransactionAndClear(em);
} finally {
closeEntityManager(em);
}
}
@Test
public void testCopyProjectsToExistingTouching() throws SQLException {
// copy all projects from hans to a new employee with out actually touching them
JPAEnvironment env = getEnvironment();
EntityManager em = env.getEntityManager();
try {
env.beginTransaction(em);
Employee hans = em.find(Employee.class, HANS_ID);
Employee fred = em.find(Employee.class, FRED_ID);
hans.getProjects().size();
fred.setProjects(hans.getProjects());
env.commitTransactionAndClear(em);
Set<Pair> expected = new HashSet<Pair>(SEED_SET);
expected.removeAll(FRED_SET);
for (Pair pair : HANS_SET) {
expected.add(new Pair(FRED_ID_VALUE, pair.getProjectId()));
}
checkJoinTable(expected);
env.beginTransaction(em);
fred = em.find(Employee.class, FRED_ID);
verify(fred.getProjects().size() == HANS_SET.size(), "Paul has wrong number of projects");
env.rollbackTransactionAndClear(em);
} finally {
closeEntityManager(em);
}
}
@Test
public void testGuidCollection() throws SQLException {
JPAEnvironment env = getEnvironment();
EntityManager em = env.getEntityManager();
byte[] id1 = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 };
byte[] id2 = new byte[] { 16, 15, 14, 13, 12, 11, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1 };
try {
TravelProfile profile1 = new TravelProfile(id1, true, "neverComeBack");
TravelProfile profile2 = new TravelProfile(id2, true, "flyAway");
Set<TravelProfile> profileSet = new HashSet<TravelProfile>();
profileSet.add(profile1);
profileSet.add(profile2);
Vehicle vehicle = new Vehicle();
Set<Vehicle> vehicleSet = new HashSet<Vehicle>();
vehicleSet.add(vehicle);
profile1.setVehicles(vehicleSet);
profile2.setVehicles(vehicleSet);
vehicle.setProfiles(profileSet);
env.beginTransaction(em);
em.persist(profile1);
em.persist(profile2);
em.persist(vehicle);
env.commitTransactionAndClear(em);
} finally {
closeEntityManager(em);
}
}
@Test
public void testByteItem() {
JPAEnvironment env = getEnvironment();
EntityManager em = env.getEntityManager();
byte[] id1 = new byte[] { 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16 };
try {
env.beginTransaction(em);
Item_Byte item = new Item_Byte(id1, "a", "b", "c");
item.addAttr("key1", "value1");
em.persist(item);
env.commitTransactionAndClear(em);
item = em.find(Item_Byte.class, id1);
item.getAttributes();
} finally {
closeEntityManager(em);
}
}
@Test
public void testCascadeMerge() throws IOException, ClassNotFoundException {
JPAEnvironment env = getEnvironment();
EntityManager em = env.getEntityManager();
try {
env.beginTransaction(em);
em.createQuery("delete from Employee").executeUpdate();
em.createQuery("delete from Vehicle").executeUpdate();
env.commitTransactionAndClear(em);
env.beginTransaction(em);
Bicycle bicycle = new Bicycle();
em.persist(bicycle);
Short bikeId = bicycle.getId();
env.commitTransaction(em);
bicycle = em.find(Bicycle.class, bikeId);
em.clear();
bicycle = AbstractBaseTest.serializeDeserialize(bicycle);
// bicycle should be detached
Employee emp = new Employee(9999, "Robbi", "Tobbi", null);
emp.clearPostPersist();
bicycle.setRiders(Collections.singleton(emp));
env.beginTransaction(em);
Bicycle mergedBike = em.merge(bicycle);
env.commitTransactionAndClear(em);
Employee mergedEmp = mergedBike.getRiders().iterator().next();
verify(mergedEmp.postPersistWasCalled(), "merge did not cascade to employee");
verify(em.find(Employee.class, 9999) != null, "employee not found");
} finally {
closeEntityManager(em);
}
}
private static class Pair {
@Override
public boolean equals(Object obj) {
if (obj instanceof Pair) {
Pair other = (Pair) obj;
return other.empId == empId && other.projectId == projectId;
}
return false;
}
@Override
public int hashCode() {
return empId + 17 * projectId;
}
/**
* @return Returns the empId.
*/
@SuppressWarnings("unused")
public int getEmpId() {
return empId;
}
/**
* @return Returns the projectId.
*/
public int getProjectId() {
return projectId;
}
final int empId;
final int projectId;
Pair(int e, int r) {
empId = e;
projectId = r;
}
}
}