2

Given two entities Department and Employee forming a one-to-many relationship from Department to Employee.

Since the relationship is quite intuitive, I am leaving out the entity classes.

The following segment of code, simply persists an entity Employee.

public void insert() {
    Employee employee = new Employee();
    employee.setEmployeeName("k");

    Department department = entityManager.find(Department.class, 1L);
    employee.setDepartment(department);
    entityManager.persist(employee);
    entityManager.flush();

    List<Employee> employeeList = department.getEmployeeList();
    employeeList.add(employee);
}

And the following method returns a list of employees associated with a particular department.

public List<Employee> getList() {
    return entityManager.find(Department.class, 1L).getEmployeeList();
}

Both the methods are written in a stateless EJB using CMT (hereby not BMT) named let's say EmployeeService.

A client application invokes these methods in sequence like so,

employeeService.insert();
List<Employee> employeeList = employeeService.getList();

for (Employee e : employeeList) {
    System.out.println(e.getEmployeeId() + " : " + e.getEmployeeName());
}

The sout statement in the foreach loop above displays a newly added Employee entity to List<Employee> in Department with a null employeeId in it given that the line entityManager.flush(); is not present in the very first code snippet.


EntityManager#persist(Object entity) is not guaranteed to generate an id. An id is only guaranteed to be generated at flush time.

What happens is, if entityManager.flush(); is removed/commented, then the entity Employee is added to the list of Employees (List<Employee> employeeList) with a null identifier in it (the primary key column in the underlying database table).

What is the usual way to maintain a bidirectional relationship? Is EntityManager#flush(); always needed every time an entity is to be added to a collection of entities being maintained by the inverse side of the relationship to generate an id associated with a newly persisted entity?

Also, is it always required to manually delete an Employee from List<Employee> (maintained by the inverse side of the relationship - Department) while deleting an Employee entity (using entityManager.remove(employee);)?


EDIT : Entity classes :

Department :

@Entity
@Table(catalog = "testdb", schema = "", uniqueConstraints = {
@UniqueConstraint(columnNames = {"department_id"})})
public class Department implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Basic(optional = false)
    @Column(name = "department_id", nullable = false)
    private Long departmentId;

    @Column(name = "department_name", length = 255)
    private String departmentName;

    @Column(length = 255)
    private String location;
    @OneToMany(mappedBy = "department", fetch = FetchType.LAZY)
    private List<Employee> employeeList = new ArrayList<Employee>(0);

    private static final long serialVersionUID = 1L;
    // Constructors + getters + setters + hashcode() + equals() + toString().
}

Employee :

@Entity
@Table(catalog = "testdb", schema = "", uniqueConstraints = {
    @UniqueConstraint(columnNames = {"employee_id"})})
public class Employee implements Serializable {

    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    @Basic(optional = false)
    @Column(name = "employee_id", nullable = false)
    private Long employeeId;

    @Column(name = "employee_name", length = 255)
    private String employeeName;
    @JoinColumn(name = "department_id", referencedColumnName = "department_id")
    @ManyToOne(fetch = FetchType.LAZY)
    private Department department;

    private static final long serialVersionUID = 1L;

    // Constructors + getters + setters + hashcode() + equals() + toString().
}
Vlad Mihalcea
  • 142,745
  • 71
  • 566
  • 911
Tiny
  • 27,221
  • 105
  • 339
  • 599
  • What happens when you remove the flush()? – JB Nizet Jun 03 '15 at 21:57
  • If `entityManager.flush();` is removed, then an `Employee` entity is added to `List` in `Deparment` with a `null` id (`employeeId`) - "*If `entityManager.flush();` is removed/commented, then the entity `Employee` is added to the list of `Employee`s (`List employeeList)` with a `null` identifier in it (the primary key column in the underlying database table).*" – Tiny Jun 03 '15 at 22:00
  • Yes, so? Does that crash the app? Does that cause an exception to be thrown? Does that corrupt your database? – JB Nizet Jun 03 '15 at 22:04
  • No but this fetches the list with `null` ids even in a different transaction using `List employeeList = department.getEmployeeList();` (until the application is redeployed). So, it is likely to throw an appropriate exception, if this list is attempted in a place, perhaps by a client application. – Tiny Jun 03 '15 at 22:09
  • No. A transaction A will never use the same entity instance as a concurrent transaction B. Entities are short-lived, non-thread-safe objects, and are not shared between transactions. As soon as the flush occurs, the ID is assigned to the entity anyway, so even if it was shared, it wouldn't have a null ID. – JB Nizet Jun 03 '15 at 22:13
  • I made some changes to the post. – Tiny Jun 03 '15 at 22:28
  • My hunch is that there are mapping issues with the relationship. So I would suggest OP to post the entity classes with the annotation or the xml mapping files – sarahTheButterFly Jun 03 '15 at 23:39
  • @sarahTheButterFly : The entity classes have been added to the post. – Tiny Jun 03 '15 at 23:54
  • @Tiny shouldn't mappedBy = "department" be mappedBy = "department_id''? – sarahTheButterFly Jun 04 '15 at 00:00
  • @sarahTheButterFly : The referencing field in `Employee` is named `department` - `private Department department;`. – Tiny Jun 04 '15 at 00:04
  • @Tiny Yes, you are right. Try adding an employee to department after the department is set on the employee? something like department.getEmployeeList.add(employee). I am more familiar with Hibernate and this is what need to be done in hibernate. – sarahTheButterFly Jun 04 '15 at 00:23
  • @sarahTheButterFly : I already played that game changing the possible ordering of those statements without any success :) (I use Hibernate and EclipseLink) – Tiny Jun 04 '15 at 00:30
  • @Tiny you've probably had a look at this link: http://stackoverflow.com/questions/4275111/correct-use-of-flush-in-jpa-hibernate. Basically what happens is that your code is correct. Persist() does not actually insert Employee to DB. Hence the id is null. After flush() is called it is inserted. Maybe use Cascade parameter so that any change to the collection will be regarded as dirty so Hibernate will insert the new employee? – sarahTheButterFly Jun 04 '15 at 01:45
  • @sarahTheButterFly : `cascade = CascadeType.PERSIST` did not help either. – Tiny Jun 04 '15 at 05:29
  • @Tiny I ran out of ideas :( Sorry if my suggestion wasted your time and I would like to know your last question in the post too! – sarahTheButterFly Jun 04 '15 at 05:31
  • My guess is that either your getter always returns null, or the two calls to insert() and getList() are done in the same transaction, which means that the flush() hasn't happened yet when calling getList(). Make sure to do that from a non-transactional method. – JB Nizet Jun 04 '15 at 06:23

2 Answers2

1

When persisting the Employee, you need to set both sides of the association.

In your Department you should have this method:

public void addEmployee(Employee employee) {
    employees.add(employee);
    employee.setDepartment(this);
}

Make sure you cascade de PERSIST and MERGE events to your children association:

@OneToMany(cascade = CascadeType.ALL, mappedBy = "department", orphanRemoval = true)
private List<Employee> children = new ArrayList<>();

and the persisting logic becomes:

Employee employee = new Employee();
employee.setEmployeeName("k");

Department department = entityManager.find(Department.class, 1L);
department.addEmployee(employee);
Vlad Mihalcea
  • 142,745
  • 71
  • 566
  • 911
0

The answer was in the JB Nizet's last comment below the question :

The two calls to insert() and getList() are done in the same transaction, which means that the flush() hasn't happened yet when calling getList()

There was a huge oversight in test cases I prepared as a quick dirty test.


I chose a singleton EJB (using only an EJB module) marked by @Startup as the application's client for a quick test (@LocalBean without any interface for a pure testing purpose - supported since EJB 3.1/Java EE 6) so that its @PostConstruct method can be invoked as soon as the EJB module is deployed.

The target EJB.

@Stateless
@LocalBean
public class EmployeeService {

    public void insert() {
        // Business logic is excluded for brevity.
    }

    public List<Employee> getList() {
        // Business logic is excluded for brevity.
    }    
}

These methods are invoked by a singleton EJB marked by @Startup (the client).

@Startup
@Singleton
public class Entry {

    @Inject
    private EmployeeService employeeService;

    @PostConstruct
    private void main() {
        employeeService.insert();
        List<Employee> employeeList = employeeService.getList();

        for (Employee e : employeeList) {
            System.out.println(e.getEmployeeId() + " : " + e.getEmployeeName());
        }
    }
}

As such, the client (the singleton EJB) is already in a transaction (using Container Managed Transaction (CMT)) that the target EJB (EmployeeService) has to use, since both the EJBs use,

@TransactionAttribute(TransactionAttributeType.REQUIRED)
@TransactionManagement(TransactionManagementType.CONTAINER)

as default.

If a client is in a transaction, an EJB marked by TransactionAttributeType.REQUIRED uses the same transaction. If the client however, does not start a transaction, the target EJB marked by TransactionAttributeType.REQUIRED creates a new transaction.

Therefore, everything happens in a single transaction, since the client (the singleton EJB) already starts a transaction with the default transaction attribute TransactionAttributeType.REQUIRED which the target EJB must use.


The solution was to either prevent the client from starting a transaction or make the target EJB always create a new transaction whether the client starts a transaction or not using TransactionAttributeType.REQUIRES_NEW.

If a client is in a transaction, an EJB marked by TransactionAttributeType.REQUIRES_NEW suspends that transaction started by the client and creates a new transaction of its own. If the client, nevertheless, does not start a transaction, an EJB marked by TransactionAttributeType.REQUIRES_NEW also creates a new transaction.

In a nutshell, an EJB marked by TransactionAttributeType.REQUIRES_NEW always creates a new transaction whether the transaction is already started by its client or not.


The client can be prevented from starting a transaction using Bean Managed Transaction (BMT) - marking the client (the singleton EJB) by @TransactionManagement(TransactionManagementType.BEAN) such as.

@Startup
@Singleton
@TransactionManagement(TransactionManagementType.BEAN)
public class Entry {
    // ...
}

Which requires explicitly starting and committing a transaction using the javax.transaction.UserTransaction interface (can be injected by the @javax.annotation.Resource annotation). Otherwise, all methods will go without an active database transaction.

Or use @TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED) such as.

@Startup
@Singleton
public class Entry {
    @PostConstruct
    @TransactionAttribute(TransactionAttributeType.NOT_SUPPORTED)
    private void main() {
        //...
    }
}

Methods marked by TransactionAttributeType.NOT_SUPPORTED always go without a database transaction.


Or leave the client untouched (as default) and mark the target EJB by @TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)

@Startup
@Singleton
public class Entry {

    @Inject
    private EmployeeService employeeService;

    @PostConstruct
    private void main() {
        //...    
    }
}

@Stateless
@LocalBean
@TransactionAttribute(TransactionAttributeType.REQUIRES_NEW)
public class EmployeeService {
    //...
}

EJBs (or methods in an EJB) marked by TransactionAttributeType.REQUIRES_NEW always start a new transaction whether or not the associated client already starts a transaction as said previously.


This was a quick dirty test I was trying to make. This is, after all, not a good practice to test an application (if the target EJB (EmployeeService), for example, is a stateful session bean, then injecting it into a singleton EJB is conceptually not appropriate). One should prefer JUnit test cases or something else instead.

I added this answer just for the sake of completeness. I prefer not to accept this answer as it runs into a different problem.

Tiny
  • 27,221
  • 105
  • 339
  • 599