Wednesday 8 August 2012

JPA Notes


Here are a series of JPA notes that have been useful in the past.

EntityManager Methods

entityManager.persist() - creates a new object and saves it - will throw an EntityExistsException if it exists already - transaction is rolled back.

entityManager.merge() - will either save or update the current object as it is now!  Changes after this won't be saved.  See below - the merge creates a new object which is managed but the object which merge was called on isn't managed

entityManager.getReference() - gets a reference to the object assuming that it exists.  It does a look up against the @Id annotated field in the entity.  The reference will only exist for the duration of the transaction.  Hence get the data into a DTO before the transaction ends.

MyEntity e = new MyEntity();

// scenario 1
// tran starts
em.persist(e);
e.setSomeField(someValue);
// tran ends, and the row for someField is updated in the database

// scenario 2
// tran starts
e = new MyEntity();
em.merge(e);
e.setSomeField(anotherValue);
// tran ends but the row for someField is not updated in the database (you made the changes *after* merging)

// scenario 3
// tran starts
e = new MyEntity();
MyEntity e2 = em.merge(e);
e2.setSomeField(anotherValue);
// tran ends and the row for someField is updated (the changes were made to e2, not e)

JPA Callbacks

There are a number of callback methods in JPA which allow interaction at various points before and after the db save.  These are annotation driven and the names make it pretty clear when they are used.

@PrePersist

@PreUpdate

@PreRemove

@PostLoad

@PostPersist

@PostUpdate

@PostRemove


Annotations

JPA is usually annotation driven.  This provides the information locally to the class and is pretty tidy.  Here are some brief explanations about what the different annotations mean.

@Entity
Flags that an entity should be managed by JPA.  Note Entity exists in JPA and Hibernate so get the import statement right.

@Table
Defines the table that this entity maps to
    @Table(name = "MY_TABLE", schema = "MY_SCHEMA") 

@OneToMany
One of these classes links to many of another class

    @OneToMany(mappedBy = "<java_object_name>", targetEntity = <the_many_class.class>, cascade = CascadeType.ALL, fetch = FetchType.LAZY)

mappedBy shows that the other table is the 'owner' of the foreign key and a @ManyToOne would be expected on that table for a bi-direction relationship.  The mappedBy is only necessary for a bi-directional relationship.  The targetEntity is usually not required because the class variable that this annotation applies to will give this information. If there is only a uni-direction relationship then just a

    @OneToMany
    @JoinColumn(name="column_name")

@ManyToOne
The reverse of the annotation above.  This will allow a bidirection link from the many class back to the one class.
    @ManyToOne(targetEntity = <the_one_class.class>, fetch = FetchType.EAGER, optional = false)
    @JoinColumn(name = "MY_COLUMN")

@ManyToMany
A join that uses a join table. Bi-directional is still possible using the mappedBy as above.  The owning class (Employee in this case) would have this code.

    @ManyToMany
    @JoinTable(name=EMPLOYEE_TO_PROJECT, joinColumns=@JoinColumn(name="EMPLOYEE_ID"), inverseJoinColumns=@JoinColumn(name="PROJECT_ID"))
    private List<ProjectEntity> projects;

The inverted side of this annotation (Project) would have the following,

    @ManyToMany(mappedBy = "projects")
    private List<EmployeeEntity> employees;

@OneToOne
A link between tables on a 1 : 1 basis.  For the table that has the column foreign key use

    @OneToOne
    @JoinColumn(name = "EMPLOYEE_ID")
    private Person person;

on the non-hosting table (ie without the join column) use

    @OneToOne(mappedBy = "person")
    private Employee employee;
 
@Embeddable
A set of 'columns' which can be in a reusable java class but exist in the underlying table.  Address might be an example of this.

@Embedded
An object which is used in this @Entity but is itself @Embeddable.

@MappedSuperClass
Allows class heirarchy in the Java and maps the contents of this class onto the table of the subclass.  EG map a column which exists on every table in a class annotated with @MappedSuperClass and then subclasses will 'inherit' this column into their table. There is still only one table though.  See @Inheritance for table inheritance. If all columns have an OBJECT_ID, CREATED_DATE then these could go in a class with the @MappedSuperClass and all classes could extend this.

@Inheritance
When a class with @Entity extends another class with @Entity then the @Inheritance rules can be set using this annotation.  The 'strategy' can be JOINED (there are two tables which are 'joined' using the sql request), SINGLE_TABLE (there is a single table a bit like Embedded) or TABLE_PER_CLASS (every table has a complete copy of all the data from the whole hierarchy but every table has that)

@OrderBy
Used on a list to order the elements directly out of the database.  The syntax is @OrderBy("<variable name> ASC").  The variable name is the variable in the objects which make up the list so for example List<Person> personList would have a variable from the Person class.

@PersistenceContext

Using the @PersistenceContext allows spring to wire in the JPA EntityManager to be used within the domain structure.

Create a BaseRepository that includes

    public class BaseRepository
    {

        @PersistenceContext
        private EntityManager entityManager;


        /**
         * Gets the entityManager value.
         * 
         * @return the entityManager
         */
        protected EntityManager getEntityManager()
        {
            return this.entityManager;
        }
    }


Spring will then populate this with the entity manager provided the spring configuration has the following line


    <bean class="org.springframework.orm.jpa.support.PersistenceAnnotationBeanPostProcessor" />


A getEntityManager() can be used in the SubClasses to get the entity manager instance.

Query Examples

Get a list of objects from a table,


    public List<MyEntity> getMyEntities()
    {
        final Query query = getEntityManager().createQuery("from MyEntity");
        return query.getResultList();
    }



Get a specific object using a primary key,


    final MyEntity myEntity = getEntityManager().find(MyEntity.class, id);



Use a query with parameters, where name is a provided value and type is an enumeration,


    final Query query = getEntityManager().createQuery("from MyEntity where name = :name and type = :type");
    query.setParameter("name", name);
    query.setParameter("type", MyEntityType.TYPE);
    return query.getResultList();


Creating a query that has a @OneToMany relationship needs a little bit more work.  Here the Company has a list of Products.  It is the products that need to be searched but the companies that need to be returned.  The SELECT only returns the CompanyEntity alias and because this is a @OneToMany the IN(c.products) is used.

    Query query = getEntityManager().createQuery("SELECT c FROM CompanyEntity AS c, IN(c.products) AS p WHERE p.name like :name");
    query.setParameter("name", name);
    return query.getResultList();


No comments:

Post a Comment