Thursday, September 30, 2010

JPA Demystified (episode 2) - Persisting entities

Let's go back to the example described in previous post - JPA Demystified (episode 1). We will take a deeper look into cascading of basic operations on entities.

We will stick with MySQL (version 5.1) as the database used for our tests. To be able to create the new records in the database, we have to adjust the table definitions from JPA Demystified (episode 1) a little, to allow usage of MySQL's AUTO_INCREMENT feature (see the end of this post).

Let's start with persisting the entity, for example Employer, with one Employee having one Benefit. This way we should trigger inserts into 3 database tables (each entity is stored in separate table).

We will create the entities for our tests in following manner:
    Employer employer = new Employer();  
    // ... set the employer properties ...  
    Employee employee = new Employee();  
    employee.setEmployer(employer);  
    // ... set the employee properties ...   
    Benefit benefit = new Benefit();  
    benefit.setEmployee(employee);  
    // ... set the benefit properties ...
    employee.setBenefits(Arrays.asList(benefit));
    employer.setEmployees(Arrays.asList(employee));
and try to persist it using following DAO:
public class DefaultEmployerDAO implements EmployerDAO {

    @PersistenceContext
    private EntityManager entityManager;

    public void persist(Employer employer) {
        entityManager.persist(employer);
    }
    ...
}
When you look at the tables definitions, you realize that we will need the primary key value to be set for each entity stored into database. Entity identifiers can be set by hand, if you want the full control over it, or generated automatically, which can be achieved using @GeneratedValue annotation. For example:
    @Id
    @GeneratedValue
    public Long getId() {
        return id;
    }
You have to be aware that default strategy for the primary key generation depends on the JPA provider and database used. For MySQL it can be AUTO_INCREMENT usage (Hibernate) or sequence table (EclipseLink, OpenJPA). As you see using default value isn't as consistent across the JPA providers as we would like it to be. Let's make it more predictable ;)
    @Id
    @GeneratedValue(strategy = GenerationType.IDENTITY)
    public Long getId() {
        return id;
    }
Having the above on each entity identifier will force all three providers to use the MySQL's AUTO_INCREMENT feature for generating primary keys.

You shouldn't smile yet ;) This is not the end of our story ... When you try to run persist method for the Employer now, you will encounter another inconsistency between the JPA providers. Only Hibernate is writing anything into database (into EMPLOYER table only), while both EclipseLink and OpenJPA complaints about the problems with not persisted objects bound to Employer. Why? - because we didn't used the cascade attribute for any of @OneToMany annotations yet, and thus all entities related to the Employer will not be persisted automatically.
We have to change the correlation between entities for Employer
    @OneToMany(mappedBy = "employer", cascade = CascadeType.PERSIST)
    public List getEmployees() {
        return employees;
    }
and for the Employee
    @OneToMany(mappedBy = "employee", cascade = CascadeType.PERSIST)    
    public List getBenefits() {
        return benefits;
    }
and now we can smile watching the records being written into database
-- Hibernate 3.4.0.GA
insert into EMPLOYERS (BUSINESS_NAME) values ('Business Name')
insert into EMPLOYEES (EMPLOYER_ID, FIRST_NAME) values (7, 'Jack')
insert into BENEFITS (EMPLOYEE_ID, END_DATE, name, START_DATE, BENEFIT_TYPE) values (2, '', 'Car for everyone!', '2010-09-26', 'TRANSPORTATION')

-- EclipseLink 2.1.1
INSERT INTO EMPLOYERS (BUSINESS_NAME) VALUES ('Business Name')
SELECT LAST_INSERT_ID()
INSERT INTO EMPLOYEES (FIRST_NAME, EMPLOYER_ID) VALUES ('Jack', 8)
SELECT LAST_INSERT_ID()
INSERT INTO BENEFITS (START_DATE, NAME, END_DATE, BENEFIT_TYPE, EMPLOYEE_ID) VALUES ('2010-09-26', 'Car for everyone!', '', 'TRANSPORTATION', 3)
SELECT LAST_INSERT_ID()

-- OpenJPA 2.0.1
INSERT INTO EMPLOYERS (BUSINESS_NAME) VALUES ('Business Name')
INSERT INTO EMPLOYEES (FIRST_NAME, EMPLOYER_ID) VALUES ('Jack', 9)
INSERT INTO BENEFITS (END_DATE, name, START_DATE, BENEFIT_TYPE, EMPLOYEE_ID) VALUES ('', 'Car for everyone!', '2010-09-26', 'TRANSPORTATION', 4)
As you see in this post, persist will store your entity into database, generating primary keys if requested, and updating the entities with the generated keys. Persisting entities is cascaded according to the cascade attribute of annotations describing correlations between entities. This attribute holds an array of cascade types, if one of the array elements is CascadeType.ALL or CascadeType.PERSIST the cascading will occur.

And as usual - the most boring part for the dessert :)

CREATE TABLE BENEFITS (  
   ID           INT UNSIGNED NOT NULL AUTO_INCREMENT,  
   EMPLOYEE_ID  INT UNSIGNED NOT NULL,  
   BENEFIT_TYPE VARCHAR(64) NOT NULL,  
   START_DATE   DATE,  
   END_DATE     DATE,  
   NAME         VARCHAR(128),   
   CONSTRAINT BENEFITS_PK PRIMARY KEY (ID)  
 );  
   
 CREATE TABLE EMPLOYEES (  
   ID          INT UNSIGNED NOT NULL AUTO_INCREMENT,  
   EMPLOYER_ID INT UNSIGNED NOT NULL,   
   FIRST_NAME  VARCHAR(64),   
   CONSTRAINT EMPLOYEES_PK PRIMARY KEY (ID)  
 );  
   
 CREATE TABLE EMPLOYERS (  
   ID            INT UNSIGNED NOT NULL AUTO_INCREMENT,   
   BUSINESS_NAME VARCHAR(128),   
   CONSTRAINT EMPLOYERS_PK PRIMARY KEY (ID)  
 );     

No comments:

Post a Comment